Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Avval saqlash: xabarlar jadvali
- 2.1 Sxema
- 2.2 Idempotent yuborish
- 2.3 Matn limiti bazada
- 3. Uch holat: yuborildi, yetkazildi, o'qildi
- 3.1 Oqim
- 3.2 Kanon serveri
- 3.3 Holat faqat oldinga yuradi
- 3.4 O'qildi — faqat qabul qiluvchidan
- 4. Oflayn qabul qiluvchi
- 4.1 Kanon tajribasi
- 4.2 Zaxira kanal
- 5. "Yozmoqda…"
- 5.1 volatile
- 5.2 Siyraklashtirish
- 6. Tarix: keyset sahifalash
- 6.1 Kanon
- 6.2 SQL'da
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi tasdiq qayerdan
- 2-mashq (o'rta): "yozmoqda" siyraklashtirish
- 3-mashq (qiyin): xavfsiz tarix
- 10. Loyiha qadami: kanon tajribasi
- 11. Real ishda
- Xulosa
- Manbalar
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…" —
volatileva 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»):
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);CREATE TABLE
CREATE INDEXUstunlar 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.matn1–2 000 belgi — kanon serveri ham matnni 2 000 belgiga qirqadi. BazadagiCHECK— oxirgi himoya qatlami.yetkazildi,oqildi— vaqt yokiNULL. 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
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; id
----
1
(1 row)
INSERT 0 1Internet uzildi, ilova javobni olmadi va xuddi shu xabarni qayta yubordi:
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; id
----
(0 rows)
INSERT 0 0Ikkinchi 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
\set VERBOSITY terse
INSERT INTO xabarlar (kimdan, kimga, mijoz_id, matn)
VALUES (1, 2, gen_random_uuid(), repeat('x', 2001));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 #idDiagrammaga 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):
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:
yozmoqda: 1 → dil oldi #1 → server saqladi #1 → yetkazildi #1 → o'qildi #1E'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
// 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:
yuborildi → yuborildi
o'qildi → o'qildi
yetkazildi → o'qildiKechikib 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,
emitmuvaffaqiyatli 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:
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;UPDATE 0
UPDATE 1Birinchisi — 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:
oflayn xabar #2: yetkazildi=false → push/Telegram fallback (bildirishnoma markazi) kerak; onlayn: 1Server 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:
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):
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))));tarix keyset: 1-sahifa 50 (#73–#122), 2-sahifa 50 (#23–#72); limit 500 so'ralsa ham 50Mijoz 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:
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;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):
SELECT id, matn
FROM xabarlar
WHERE least(kimdan, kimga) = 1 AND greatest(kimdan, kimga) = 2
AND id < 120
ORDER BY id DESC
LIMIT 3; 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?
- Azizning ekranida bitta belgi.
- Ikki belgi.
- Ko'k belgilar.
- Dilnoza oflayn, 30 soniyadan keyin telefonga push.
Yechim
- Azizning
emitWithAck("yoz")javobi — serverINSERTqildi; yangi qator,yetkazildivaoqildi—NULL. - Server Dilnozaga yuborgan
xabarning ack'i —yetkazildi = now(). - Dilnozaning
oqidimhodisasi —oqildi = now()(faqatkimga = 2bo'lsa). yetkazildihaliNULL— 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
// 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:
0, 3400
8 bosish → 2 hodisa8 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
// 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:
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
cursorvalimitchegarasi 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),
oqidimhodisasi (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…" —
volatileva 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
- Socket.IO, Acknowledgements va
volatile: https://socket.io/docs/v4/emitting-events/ - PostgreSQL 17,
INSERT … ON CONFLICT: https://www.postgresql.org/docs/17/sql-insert.html - PostgreSQL 17, ifoda indekslari: https://www.postgresql.org/docs/17/indexes-expressional.html
- Kanon tajribasi (IlmHamroh, 2026-10-27): socket.io 4.8.4, Redis 8.10.2, Node 24.21.0. SQL — biz ishga tushirdik, PostgreSQL 17 (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!