IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime42/69-dars15 daqiqa
Mundarija (30)

Chat amaliyoti: xabar saqlash, yetkazildi, o'qildi va oflayn bildirishnoma

Qisqacha: Chat xabari uch bosqichdan o'tadi: server saqladi (ack — "yuborildi"), qabul qiluvchining qurilmasi oldi (uning ack'i — "yetkazildi"), qabul qiluvchi ko'rdi (alohida hodisa — "o'qildi"). Avval bazaga yozing, keyin tarqating: ulanish uzilsa ham xabar yo'qolmaydi. Qabul qiluvchi oflayn bo'lsa — push yoki Telegram orqali bildirishnoma. "Yozmoqda…" — volatile va siyraklashtirilgan (throttle) hodisa. Tarix — keyset sahifalash, server limiti bilan.

Bu darsda

  • Xabarlar jadvalini PostgreSQL'da loyihalaysiz: idempotent yuborish, matn limiti va suhbat indeksi bilan.
  • Yuborildi, yetkazildi va o'qildi holatlarini Socket.IO ack'lari bilan qurasiz.
  • Oflayn qabul qiluvchi uchun bildirishnoma zaxirasini (fallback) ulaysiz.
  • "Yozmoqda…" hodisasini siyraklashtirasiz.
  • Tarixni keyset bilan sahifalaysiz va chat xavfsizligini tekshirasiz.

Oldin bilishingiz kerak: Realtime'ni masshtablash, Socket.IO, Bildirishnoma tizimi arxitekturasi, Keyset pagination va katta OFFSET, Idempotency-Key.

Oldingi to'rt darsda realtime'ning qurilish bloklarini yig'dik. Bugun ularni bitta amaliy vazifada birlashtiramiz: ikki kishi o'rtasidagi chat.

1. Nega bu kerak?

Aziz Dilnozaga yozdi: "Salom, hisobot tayyormi?". Uning ekranida nima ko'rinishi kerak?

  • bitta belgi — server xabarni oldi va saqladi;
  • ikki belgi — Dilnozaning telefoni xabarni oldi;
  • ko'k belgilar — Dilnoza o'qidi.

Dilnoza telefonni o'chirib qo'ygan bo'lsa-chi? Xabar yo'qolmasligi va Dilnoza bildirishnoma olishi kerak. Aziz ikki marta bosib yuborsa yoki internet uzilib, ilova qayta yuborsa — xabar ikki marta chiqmasligi kerak.

O'xshatish: buyurtma xati. Pochta qabul qildi (kvitansiya), adresatga yetdi (imzo), adresat ochdi (javob). Har bosqich — alohida tasdiq, alohida joydan.

2. Avval saqlash: xabarlar jadvali

2.1 Sxema

Asosiy qoida: avval bazaga yozing, keyin tarqating. Aks holda server xabarni Dilnozaga yuborib, saqlashdan oldin qulasa — u bor, lekin tarixda yo'q. Teskarisi xavfsiz: saqlandi, tarqatish yetmadi — Dilnoza ilovani ochganda tarixdan oladi.

Biz o'z PostgreSQL 17 bazamizda sinadik (kanonda chat xotirada edi — pastdagi «Loyiha qadami»):

sql
CREATE TABLE xabarlar (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  kimdan     integer NOT NULL,
  kimga      integer NOT NULL,
  mijoz_id   uuid NOT NULL,
  matn       text NOT NULL
             CHECK (char_length(matn) BETWEEN 1 AND 2000),
  yaratildi  timestamptz NOT NULL DEFAULT now(),
  yetkazildi timestamptz,
  oqildi     timestamptz,
  UNIQUE (kimdan, mijoz_id)
);
CREATE INDEX xabarlar_suhbat
  ON xabarlar (least(kimdan, kimga), greatest(kimdan, kimga), id);
text
CREATE TABLE
CREATE INDEX

Ustunlar bo'yicha:

  • mijoz_id — ilova xabarni yaratganda o'zi beradigan UUID. Qayta yuborishda xuddi shu ID ketadi. UNIQUE (kimdan, mijoz_id) takrorni bazada to'xtatadi.
  • matn 1–2 000 belgi — kanon serveri ham matnni 2 000 belgiga qirqadi. Bazadagi CHECK — oxirgi himoya qatlami.
  • yetkazildi, oqildi — vaqt yoki NULL. Holat alohida ustunda emas: vaqtlarning o'zi holatni aytadi.
  • Ifoda indeksi least/greatest — suhbat yo'nalishsiz: (1→2) va (2→1) xabarlari bitta suhbat. least(1,2)=1, greatest(1,2)=2 — ikkala yo'nalishda ham bir xil kalit (Partial, expression va covering indekslar).

2.2 Idempotent yuborish

sql
INSERT INTO xabarlar (kimdan, kimga, mijoz_id, matn)
VALUES (1, 2, 'a0000000-0000-4000-8000-000000000001',
        'Salom, hisobot tayyormi?')
ON CONFLICT (kimdan, mijoz_id) DO NOTHING
RETURNING id;
text
 id
----
  1
(1 row)

INSERT 0 1

Internet uzildi, ilova javobni olmadi va xuddi shu xabarni qayta yubordi:

sql
INSERT INTO xabarlar (kimdan, kimga, mijoz_id, matn)
VALUES (1, 2, 'a0000000-0000-4000-8000-000000000001',
        'Salom, hisobot tayyormi?')
ON CONFLICT (kimdan, mijoz_id) DO NOTHING
RETURNING id;
text
 id
----
(0 rows)

INSERT 0 0

Ikkinchi yozuv yo'q — xabar bir marta. 0 rows bo'lsa server mavjud xabarni mijoz_id bo'yicha topib, o'sha id ni ack'da qaytaradi. Bu Idempotency-Key darsidagi g'oyaning chatdagi shakli.

Bitta nozik joy: rad etilgan INSERT ham sequence'dan raqam oldi. Keyingi xabar id si 2 emas, 3 bo'ladi — pastdagi natijada ko'rasiz. id lar teshikli bo'lishi normal (Primary key tanlovi); ularga "nechta xabar" deb tayanmang.

2.3 Matn limiti bazada

sql
\set VERBOSITY terse
INSERT INTO xabarlar (kimdan, kimga, mijoz_id, matn)
VALUES (1, 2, gen_random_uuid(), repeat('x', 2001));
text
ERROR:  new row for relation "xabarlar" violates check constraint "xabarlar_matn_check"

Bu urinish ham sequence'dan bitta raqam oldi. Bu xato foydalanuvchiga chiqmasligi kerak — server matnni oldinroq tekshiradi. CHECK esa server kodida xato bo'lsa ham bazani himoya qiladi.

3. Uch holat: yuborildi, yetkazildi, o'qildi

3.1 Oqim

sequenceDiagram
  participant A as Aziz
  participant S as Server
  participant D as Dilnoza
  A->>S: yoz (mijoz_id, matn)
  Note over S: INSERT xabarlar
  S-->>A: ack id (yuborildi)
  S->>D: xabar (timeout 1 s)
  D-->>S: ack (oldim)
  Note over S: yetkazildi = now()
  S->>A: yetkazildi #id
  D->>S: oqidim #id
  Note over S: oqildi = now()
  S->>A: oqildi #id

Diagrammaga qarang: uch xil tasdiq uch xil joydan keladi. Birinchisi — Aziz emitWithAck ning javobi. Ikkinchisi — serverning Dilnozaga timeout(1000).emit ining ack'i. Uchinchisi — Dilnoza ekranda xabarni ko'rgach yuboradigan alohida hodisa.

Xuddi shuni jadvalda:

Belgi Kim yuboradi Server nima qiladi Bazada
bitta belgi (yuborildi) server → Aziz (ack) INSERT, keyin ack yangi qator
ikki belgi (yetkazildi) Dilnoza ilovasi → server (ack) Azizga yetkazildi yetkazildi = now()
ko'k (o'qildi) Dilnoza → server (oqidim) Azizga oqildi oqildi = now()

3.2 Kanon serveri

Kanon chatni Socket.IO 4.8.4 da xotirada qurdi (formatlangan; matnni 2 000 ga qirqish va "faqat qabul qiluvchi o'qiydi" qoidasi aynan):

js
so.on("yoz", (x, ack) => {
  const m = {
    id: keyingi++, kimdan: so.data.fId, kimga: x.kimga,
    matn: String(x.matn).slice(0, 2000), vaqt: Date.now(),
    yetkazildi: false, oqildi: false,
  };
  xabarlar.push(m);
  ack({ id: m.id }); // ack — server SAQLADI (yuborildi)
  s.io.to(`f:${x.kimga}`).timeout(1000).emit("xabar", m,
    (err, javoblar) => {
      if (!err && javoblar.length) {
        m.yetkazildi = true;
        s.io.to(`f:${m.kimdan}`).emit("yetkazildi", m.id);
      }
    });
});
so.on("oqidim", (id) => {
  const m = xabarlar.find(
    (y) => y.id === id && y.kimga === so.data.fId);
  if (m) {
    m.oqildi = true;
    s.io.to(`f:${m.kimdan}`).emit("oqildi", id);
  }
});

Kanon natijasi (Aziz — 1, Dilnoza — 2). Qatorda hodisalar qaysi tartibda yozilganiga qarang:

text
yozmoqda: 1 → dil oldi #1 → server saqladi #1 → yetkazildi #1 → o'qildi #1

E'tibor bering: "dil oldi #1" "server saqladi #1" dan oldin yozilgan. Server ack ni chaqirib, darhol Dilnozaga yubordi — Dilnozaning mijozi Azizning await idan tezroq ishladi. Tarmoqda hodisalar tartibi kafolatlanmagan: mijoz holatni har doim "faqat oldinga" yangilashi kerak.

3.3 Holat faqat oldinga yuradi

js
// Xabar holati faqat oldinga yuradi: yuborildi → yetkazildi → o'qildi
const TARTIB = ["kutilmoqda", "yuborildi", "yetkazildi", "o'qildi"];

function holatniYangila(joriy, yangi) {
  const oldinga = TARTIB.indexOf(yangi) > TARTIB.indexOf(joriy);
  return oldinga ? yangi : joriy;
}

// tarmoqda hodisalar aralash tartibda keldi
const kelgan = ["yuborildi", "o'qildi", "yetkazildi"];
let holat = "kutilmoqda";
for (const h of kelgan) {
  holat = holatniYangila(holat, h);
  console.log(`${h.padEnd(10)} → ${holat}`);
}

Konsolda:

text
yuborildi  → yuborildi
o'qildi    → o'qildi
yetkazildi → o'qildi

Kechikib kelgan "yetkazildi" "o'qildi" ni orqaga qaytarmadi. Bu kichik funksiya — Optimistic concurrency dagi "eski yangilanish yangisini bosib ketmasin" g'oyasining mijozdagi varianti.

Tekshirib ko'ring: Nega serverga "yetkazildi" ni Dilnozaning ack'i emas, emit muvaffaqiyatli bajarilgani belgilamaydi?

Javob

emit faqat xabarni navbatga qo'yadi — u tarmoqqa ketdimi, Dilnozaning ilovasi uni qayta ishladimi, server bilmaydi (WebSocket protokoli va ws darsidagi bufferedAmount). Yagona ishonchli dalil — qabul qiluvchining o'zi yuborgan javob (ack). Kanonda timeout(1000) bilan: bir soniyada javob bo'lmasa, "yetkazildi" qo'yilmaydi.

3.4 O'qildi — faqat qabul qiluvchidan

Kanon oqidim ishlovchisida y.kimga === so.data.fId sharti bor. Usiz Aziz o'zi yuborgan xabarni "o'qildi" deb belgilashi — yoki begona suhbatdagi xabar ID sini taxmin qilib, uni belgilashi mumkin edi. Bazada xuddi shu shart WHERE da:

sql
UPDATE xabarlar SET oqildi = now()
WHERE id = 1 AND kimga = 1 AND oqildi IS NULL;

UPDATE xabarlar SET oqildi = now()
WHERE id = 1 AND kimga = 2 AND oqildi IS NULL;
text
UPDATE 0
UPDATE 1

Birinchisi — Aziz (1) o'z xabarini belgilashga urindi: 0 qator. Ikkinchisi — Dilnoza (2): 1 qator. oqildi IS NULL — takroriy hodisa vaqtni qayta yozmaydi. Egalik so'rovning o'zida tekshiriladi, alohida SELECT bilan emas (RBAC va resurs egaligi, 28-qism).

Amalda ilova bitta-bitta emas, "shu xabargacha hammasi o'qildi" deb yuboradi: WHERE kimga = $men AND kimdan = $u AND id <= $oxirgi AND oqildi IS NULL.

4. Oflayn qabul qiluvchi

4.1 Kanon tajribasi

Dilnoza ulanishni yopdi, Aziz yana yozdi:

text
oflayn xabar #2: yetkazildi=false → push/Telegram fallback (bildirishnoma markazi) kerak; onlayn: 1

Server timeout(1000).emit qildi — xonada hech kim yo'q, javob yo'q, yetkazildi false qoldi. Presence ro'yxatida faqat Aziz.

4.2 Zaxira kanal

Xabar bazada — yo'qolmaydi. Lekin Dilnoza bilishi kerak. Bu yerda Bildirishnoma tizimi arxitekturasi darsidagi markaz ishga tushadi: "realtime yetmadi" → push (Push bildirishnomalar) yoki Telegram. Qoidalar:

  • darhol emas — 10–30 soniya kuting: Dilnoza boshqa oynada o'qib qo'yishi mumkin;
  • birlashtirib — 5 ta xabar uchun bitta "Aziz: 5 ta yangi xabar";
  • sukut soatlari va foydalanuvchi sozlamalari — markazda.

Bu ish so'rov ichida emas, fonda — navbatda bajariladi (Nega navbat kerak). Dilnoza qayta ulanganda ilova o'zi tarixni so'raydi va hamma yetkazilmagan xabarlarni "yetkazildi" qiladi.

5. "Yozmoqda…"

5.1 volatile

Kanon "yozmoqda" hodisasini volatile bilan yuboradi:

js
so.on("yozmoqda", (kimga) =>
  so.to(`f:${kimga}`).volatile.emit("yozmoqda", so.data.fId));

Bu hodisa yetmasa — muhim emas, bir soniyadan keyin keyingisi keladi. Navbatga qo'yib keyin yetkazish ma'nosiz: 10 soniya kechikkan "yozmoqda…" yolg'on. WebSocket protokoli va ws darsidagi "tashlash" siyosati.

5.2 Siyraklashtirish

Har tugma bosilishida hodisa yubormang — 60 belgilik gap 60 hodisa. Mijoz uni siyraklashtiradi (throttle): masalan, 2 soniyada ko'pi bilan bitta. Qabul qiluvchi esa "yozmoqda…" ni 3 soniyadan keyin o'zi o'chiradi, agar yangisi kelmasa. 2-mashqda siyraklashtirishni yozasiz.

6. Tarix: keyset sahifalash

6.1 Kanon

Dilnoza suhbatni ochdi — oxirgi xabarlar kerak, yuqoriga aylantirsa — oldingilari. Kanon serveri (formatlangan):

js
so.on("tarix", ({ bilan, oldin, limit }, ack) =>
  ack(xabarlar
    .filter((m) => [m.kimdan, m.kimga].includes(bilan)
      && (oldin ? m.id < oldin : true))
    .slice(-Math.min(limit, 50))));
text
tarix keyset: 1-sahifa 50 (#73–#122), 2-sahifa 50 (#23–#72); limit 500 so'ralsa ham 50

Mijoz limit: 500 so'radi — server 50 berdi. Chegara serverda bo'lishi shart: aks holda bitta so'rov butun tarixni (va server xotirasini) oladi (DoS va resurs tugashidan himoya).

Kanon filtri faqat bilan ni tekshiradi, chunki tajribada ikki foydalanuvchi bor edi. Haqiqiy serverda ikkala ishtirokchi tekshiriladi — aks holda foydalanuvchi 3 "bilan: 2" deb Dilnozaning hamma suhbatlarini oladi. 3-mashqda to'g'ri shaklini yozasiz.

6.2 SQL'da

Bazamizdagi suhbatga yana 121 ta xabar qo'shamiz va oxirgi sahifani olamiz. Natijada eng katta id ga qarang — u nechchi bo'ldi va nega:

sql
INSERT INTO xabarlar (kimdan, kimga, mijoz_id, matn)
SELECT 1 + i % 2, 2 - i % 2, gen_random_uuid(), 'm' || i
FROM generate_series(1, 121) AS i;

SELECT id, kimdan, matn
FROM xabarlar
WHERE least(kimdan, kimga) = 1 AND greatest(kimdan, kimga) = 2
ORDER BY id DESC
LIMIT 3;
text
INSERT 0 121
 id  | kimdan | matn
-----+--------+------
 124 |      2 | m121
 123 |      1 | m120
 122 |      2 | m119
(3 rows)

Nima bo'ldi? Oxirgi id — 124, 122 emas: yuqoridagi rad etilgan qayta yuborish 2 ni, CHECK xatosi esa 3 ni "yeb" qo'ygan. Keyingi sahifa — eng kichik ko'rilgan id dan oldingilar (namuna uchun LIMIT 3, ilovada 50):

sql
SELECT id, matn
FROM xabarlar
WHERE least(kimdan, kimga) = 1 AND greatest(kimdan, kimga) = 2
  AND id < 120
ORDER BY id DESC
LIMIT 3;
text
 id  | matn
-----+------
 119 | m116
 118 | m115
 117 | m114
(3 rows)

OFFSET yo'q. So'rov tezligi sahifa raqamiga bog'liq emas. Yangi xabar kelsa ham sahifalar siljimaydi (Keyset pagination va katta OFFSET). WHERE dagi least/greatest ifodasi indeks ifodasi bilan aynan bir xil — shundagina planner xabarlar_suhbat indeksini ishlata oladi.

7. Hujumchi nigohi

Hujum Himoya Qayerda
begona suhbat tarixini so'rash ikkala ishtirokchi socket.data.fId bilan tekshiriladi server
boshqaning xabarini "o'qildi" qilish WHERE kimga = $men kanon, SQL
10 MB matn 2 000 belgi: server + CHECK kanon, SQL
kimdan ni mijoz yuborishi kimdan faqat sessiyadan kanon
spam: sekundiga 100 xabar foydalanuvchi bo'yicha rate limit Rate limiting
matnda <script> ekranda textContent mijoz
limit: 1000000 server chegarasi 50 kanon

Chat — XSS (begona foydalanuvchi matni ichidagi skript boshqa odamning brauzerida ishga tushishi) uchun eng qulay joy: bir foydalanuvchining matni boshqasining ekranida chiqadi. Mijoz matnni hech qachon innerHTML bilan qo'ymasin; havolalarni ajratsa — xavfsiz kutubxona bilan.

8. Ko'p uchraydigan xatolar

Xato Natijasi To'g'risi
avval tarqatish, keyin saqlash tarixda yo'q xabarlar avval INSERT, keyin emit
mijoz_id siz qayta yuborish ikki marta chiqqan xabar UNIQUE (kimdan, mijoz_id)
holatni oxirgi kelgan hodisa bilan almashtirish "o'qildi" → "yetkazildi" orqaga faqat oldinga
har tugmaga "yozmoqda" sekundiga o'nlab hodisa throttle, volatile
oflaynga darhol push ikki joyda bir xabar, bezovta kutib, birlashtirib
OFFSET bilan tarix sekin, sahifa siljiydi keyset (id < $oldin)
server limitisiz tarix bitta so'rov hammasini oladi Math.min(limit, 50)

9. Mashqlar

Mashqlar kurs/mashqlar/29/42-chat/ da.

1-mashq (oson): qaysi tasdiq qayerdan

Har holatga javob bering: u qaysi hodisadan keladi va bazada qaysi ustunga ta'sir qiladi?

  1. Azizning ekranida bitta belgi.
  2. Ikki belgi.
  3. Ko'k belgilar.
  4. Dilnoza oflayn, 30 soniyadan keyin telefonga push.
Yechim
  1. Azizning emitWithAck("yoz") javobi — server INSERT qildi; yangi qator, yetkazildi va oqildi — NULL.
  2. Server Dilnozaga yuborgan xabar ning ack'i — yetkazildi = now().
  3. Dilnozaning oqidim hodisasi — oqildi = now() (faqat kimga = 2 bo'lsa).
  4. yetkazildi hali NULL — fon vazifasi buni ko'rib bildirishnoma markaziga topshiradi. Bazada o'zgarish yo'q (bildirishnoma o'z jadvalida).

2-mashq (o'rta): "yozmoqda" siyraklashtirish

yozmoqdaYuborish(vaqtlar, oraliq) funksiyasini yozing: tugma bosilgan vaqtlar (ms) massividan qaysi paytlarda hodisa yuborilishini qaytarsin — oxirgi yuborilgandan beri kamida oraliq ms o'tgan bo'lsa. Bosishlar: [0, 120, 250, 900, 1100, 3400, 3500, 3650], oraliq 2 000.

Yechim
js
// tugma bosilgan vaqtlar (ms)
const bosishlar = [0, 120, 250, 900, 1100, 3400, 3500, 3650];

function yozmoqdaYuborish(vaqtlar, oraliq) {
  const yuborilgan = [];
  let oxirgi = -Infinity;
  for (const t of vaqtlar) {
    if (t - oxirgi >= oraliq) {
      yuborilgan.push(t);
      oxirgi = t;
    }
  }
  return yuborilgan;
}

const y = yozmoqdaYuborish(bosishlar, 2000);
console.log(y.join(", "));
console.log(`${bosishlar.length} bosish → ${y.length} hodisa`);

Konsolda:

text
0, 3400
8 bosish → 2 hodisa

8 bosish — 2 hodisa. Qabul qiluvchi "yozmoqda…" ni 3 soniya ko'rsatadi: 0 dagi hodisa 3 000 gacha, 3 400 dagisi 6 400 gacha. 3 000–3 400 oralig'ida qisqa uzilish bo'ladi — shuning uchun ko'rsatish muddati odatda siyraklashtirish oralig'idan biroz uzunroq olinadi.

3-mashq (qiyin): xavfsiz tarix

Kanon tarix ishlovchisining xavfsiz nusxasini yozing: tarix(bilan, men, { oldin, limit }) faqat men va bilan o'rtasidagi xabarlarni (ikkala yo'nalish) qaytarsin, limit 1–50 oralig'iga keltirilsin (yo'q bo'lsa 50). 122 ta xabar: id 1–122, toqlari Aziz (1) → Dilnoza (2), juftlari teskari. Uch sahifani limit: 500, keyin oldin bilan oling va har birining soni va id oralig'ini chiqaring.

Yechim
js
// 122 ta xabar: 1–122, hammasi Aziz (1) va Dilnoza (2) orasida
const xabarlar = Array.from({ length: 122 }, (_, i) => ({
  id: i + 1, kimdan: i % 2 ? 2 : 1, kimga: i % 2 ? 1 : 2,
}));

function tarix(bilan, men, { oldin, limit }) {
  // server chegarasi: 1–50
  const n = Math.min(Math.max(limit ?? 50, 1), 50);
  const suhbat = xabarlar.filter((m) =>
    (m.kimdan === men && m.kimga === bilan) ||
    (m.kimdan === bilan && m.kimga === men));
  const oldingi = suhbat.filter((m) => !oldin || m.id < oldin);
  return oldingi.slice(-n);
}

const s1 = tarix(2, 1, { limit: 500 });
const s2 = tarix(2, 1, { oldin: s1[0].id, limit: 50 });
const s3 = tarix(2, 1, { oldin: s2[0].id, limit: 50 });
for (const [i, s] of [s1, s2, s3].entries()) {
  const oraliq = `#${s[0].id}–#${s.at(-1).id}`;
  console.log(`${i + 1}-sahifa: ${s.length} (${oraliq})`);
}

Konsolda:

text
1-sahifa: 50 (#73–#122)
2-sahifa: 50 (#23–#72)
3-sahifa: 22 (#1–#22)

Birinchi ikki sahifa kanon natijasi bilan bir xil. men serverda socket.data.fId dan olinadi — mijoz faqat bilan, oldin va limit ni aytadi. Uchinchi foydalanuvchi tarix(2, 3, …) so'rasa, bo'sh massiv oladi. Math.max(…, 1) — limit: 0 yoki manfiy son slice(-0) orqali hamma xabarni qaytarmasin.

10. Loyiha qadami: kanon tajribasi

Chat vazifalar-api ga kirmaydi — kanon qarori: bu naqsh 38-qismdagi jamoa loyihasi uchun. Darsdagi Socket.IO natijalari — kanon o'lchovidan (IlmHamroh, 2026-10; socket.io 4.8.4, Redis 8.10.2, 2026-10-27). Kanonda xabarlar xotirada saqlangan; SQL sxemasi va so'rovlari — bizniki, o'z PostgreSQL 17 bazamizda ishga tushirdik (2026-10-28). Mashqlar ham biz ishga tushirdik.

vazifalar dan bu darsga ulanadigan joy — oflayn zaxira: realtime yetmasa bildirishnoma markazi. U Bildirishnoma tizimi arxitekturasi darsidagi modulda.

11. Real ishda

  • Telegram, WhatsApp — aynan shu uch holat (yuborildi, yetkazildi, o'qildi) va mijoz yaratgan xabar ID si (qayta yuborishda takror bo'lmasin).
  • Slack tarixni kursor bilan sahifalaydi; API hujjatida cursor va limit chegarasi bor.
  • Katta chatlarda xabarlar saqlash uchun Cassandra/ScyllaDB kabi bazalar ishlatiladi; o'rta loyiha uchun PostgreSQL bemalol yetadi.
  • Intervyuda: "Chat tizimini loyihalang" — system design savollarining eng mashhurlaridan biri: saqlash, yetkazish holatlari, oflayn, masshtab.

Xulosa

  • Avval saqlang, keyin tarqating; mijoz_id + UNIQUE — qayta yuborishda takror yo'q.
  • Uch tasdiq: server ack (yuborildi), qabul qiluvchi ack (yetkazildi), oqidim hodisasi (o'qildi); holat faqat oldinga.
  • "O'qildi" faqat qabul qiluvchidan: shart so'rovning o'zida (WHERE kimga = $men).
  • Oflayn — xabar bazada qoladi, bildirishnoma markazi kutib va birlashtirib push yuboradi.
  • "Yozmoqda…" — volatile va throttle; tarix — keyset, server limiti 50.

Keyingi dars: Tayyor realtime xizmatlari va CRDT — Centrifugo, Pusher, Ably kabi xizmatlar qachon o'z serveringizdan yaxshi va ikki kishi bir matnni oflayn tahrirlaganda Yjs ziddiyatni qanday hal qiladi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!