IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime44/69-dars18 daqiqa
Mundarija (21)

Nega navbat kerak: og'ir ishni so'rovdan ajratish

Qisqacha: Rasmni kichraytirish, email yuborish, hisobot yasash kabi og'ir ishni HTTP so'rov ichida qilsangiz, foydalanuvchi kutadi va server bir vaqtda kelgan so'rovlar ostida bo'g'iladi. Navbat (queue) bu ishni ikkiga bo'ladi: so'rov faqat "shu ishni qil" degan yozuvni navbatga qo'yadi va darhol 202 qaytaradi, alohida jarayon — worker — ishlarni o'z sur'atida bajaradi. Kanonda 20 parallel so'rovning javob vaqti mediana 593 ms dan 42 ms ga tushdi. Narxi: ish endi "kamida bir marta" bajariladi, shuning uchun ishlovchi idempotent bo'lishi shart.

Bu darsda

  • Sinxron va asinxron ish farqini o'lchov bilan ko'rasiz.
  • Producer, navbat va consumer (worker) rollarini ajratasiz.
  • 202 Accepted qachon to'g'ri javob ekanini bilasiz.
  • Yukni tekislash (load leveling) nima ekanini hisoblab ko'rasiz.
  • Nega navbat "kamida bir marta" (at-least-once) yetkazadi va bunga qanday tayyorlanishni tushunasiz.

Oldin bilishingiz kerak: Event loop'ni bloklash, Status kodlarni to'g'ri tanlash, SKIP LOCKED bilan DB-asosli navbat, Pub/Sub va Redis Streams, Rasmlarni qayta ishlash: sharp.

Oldingi darsda tayyor realtime xizmatlari bilan realtime bo'limini yopdik. Endi yangi bo'lim — fon vazifalari: navbat, BullMQ, cron, boshqa brokerlar, outbox va uzoq jarayonlar. Bu dars nazariy poydevor, keyingi darsdan BullMQ bilan kod yozamiz.

1. Nega bu kerak?

Malika vazifaga 4K rasm biriktirdi. Server undan uchta o'lcham yasashi kerak: katta (1280 px), o'rta (640) va kichik (160) — sharp darsidagidek. Keyin vazifa a'zolariga email ketadi (Email yuborish), Telegram'ga eslatma, webhook (Chiquvchi webhook'lar).

Hammasini POST ishlovchisi ichida qilsak nima bo'ladi?

  1. Foydalanuvchi kutadi. Rasm, email va webhook'ning eng sekini qancha bo'lsa, javob ham shuncha kech keladi.
  2. Tashqi xizmat yiqilsa — so'rov ham yiqiladi. SMTP server 5 soniya javob bermasa, Malika "Xato" ko'radi, garchi rasm saqlangan bo'lsa ham.
  3. Bir vaqtda 20 kishi yuklasa — hammasi sekinlashadi. CPU bitta, ishlar bir-birini kutadi.
  4. Server qayta ishga tushsa — yarim ish yo'qoladi. Javob qaytgan, lekin email hali ketmagan edi.

Navbat to'rttalasini hal qiladi. O'xshatish: oshxonada ofitsiant buyurtmani yozib oladi va darhol "qabul qilindi" deydi. Ovqatni oshpaz o'z sur'atida pishiradi. Ofitsiant oshpaz yonida turib, har ovqat tayyor bo'lishini kutmaydi — aks holda keyingi mijozga borolmaydi.

2. Sinxron va asinxron ish

Sinxron (so'rov ichida) Asinxron (navbat orqali)
Javob qachon ish tugagach ish yozib qo'yilgach
Javob kodi 200 / 201 202 Accepted
Tashqi xizmat yiqilsa so'rov yiqiladi ish keyinroq qayta uriniladi
Ko'p so'rov kelganda hammasi sekinlashadi navbat uzayadi, javob tez
Natijani bilish javobda keyinroq: holat so'rovi, SSE, bildirishnoma

202 Accepted — "so'rovingiz qabul qilindi, lekin hali bajarilmadi" (Status kodlarni to'g'ri tanlash). Javobga odatda ish identifikatorini qo'yasiz, mijoz keyin holatini so'raydi yoki natija SSE orqali keladi.

Asinxron yo'l vaqt bo'yicha shunday ko'rinadi:

sequenceDiagram
  participant B as Brauzer
  participant A as API
  participant Q as Navbat
  participant W as Worker
  B->>A: POST /vazifalar/7/rasm
  A->>Q: ish yoz (faylId 42)
  A-->>B: 202 Accepted, ish id
  Note over B: foydalanuvchi ishlashda davom etadi
  Q->>W: ish beriladi
  Note over W: rasm o'lchamlari yasaladi
  W->>Q: bajardim (ack)
  W-->>B: natija tayyor (SSE yoki bildirishnoma)

Ketma-ketlikka qarang: 202 javobi ish boshlanishidan oldin qaytdi. Brauzer natijani alohida yo'l bilan oladi.

Har ishni ham navbatga qo'yish shart emas. Qoida oddiy: foydalanuvchi javobni ko'rishi uchun natija kerakmi? Vazifa yaratish — kerak (yangi id), sinxron qiling. Rasm o'lchamlari, email, hisobot PDF — keyinroq bo'lsa ham bo'ladi, navbatga.

Tekshirib ko'ring: Parolni tiklash havolasini emailga yuborish — sinxronmi yoki navbatmi?

Javob

Navbat. Foydalanuvchiga "Agar bu email ro'yxatda bo'lsa, xat yuborildi" degan javob darhol qaytadi. SMTP sekin bo'lsa ham u kutmaydi. Ustiga, javob vaqti emailning bor-yo'qligiga bog'liq bo'lmay qoladi — hujumchi javob tezligidan qaysi email ro'yxatda ekanini bilib ololmaydi (28-qismda shu mavzuga qaytamiz).

3. Kanon o'lchovi: 593 ms va 42 ms

Kanon ikkita marshrut yasadi. Biri rasmni so'rov ichida qayta ishlaydi, ikkinchisi faqat navbatga yozadi. Keyin har biriga 20 ta parallel POST yubordi. Kod kanon tajribasidan, telefon uchun qatorlarni bo'ldik (q.add — keyingi darsning mavzusi, hozir "navbatga yoz" deb o'qing):

js
import express from "express";
import sharp from "sharp";
import { Queue } from "bullmq";

// 3840×2160 lik namunaviy rasm
const foto = await sharp({
  create: { width: 3840, height: 2160, channels: 3,
    background: "#468" },
}).jpeg().toBuffer();

// uch o'lcham, WebP
const ishla = () => Promise.all([1280, 640, 160].map((w) =>
  sharp(foto).resize(w).webp().toBuffer()));

const app = express();
const q = new Queue("rasm", { connection, prefix: "sinov" });

app.post("/sinxron", async (_q, res) => {
  await ishla();
  res.json({ ok: true });
});

app.post("/navbat", async (_q, res) => {
  const j = await q.add("rasm", {});
  res.status(202).json({ id: j.id });
});

Natija (Node 24.21.0, bullmq 6.3.19, sharp 0.35.5, Redis 8.10.2 — Docker'da, i5-12500H). Chiqishda ikki xil raqam bor. Mediana — o'rtadagi foydalanuvchi qancha kutgani. p95 — 100 ta so'rovdan eng sekin 5 tasini hisobga olmaganda qolganlarning eng sekini, ya'ni "deyarli eng yomon holat". Ikkala qatorni solishtiring:

text
sinxron  javob vaqti mediana 593 ms, p95 957 ms
navbat   javob vaqti mediana 42 ms, p95 42 ms
navbatdagi 20 ish worker'da (concurrency 2): 2234 ms — foydalanuvchi kutmadi, yuk tekislandi

Nima bo'ldi? Uchta raqamni o'qiymiz:

  • 593 ms → 42 ms. Navbatga yozish — Redis'ga bitta kichik yozuv. Rasm qayta ishlanmadi, javob 14 baravar tez.
  • p95 957 ms. Sinxron yo'lda eng sekin so'rovlar navbatning oxirida qolganlar: ular boshqalarning rasmini kutdi. Navbat yo'lida p95 va mediana bir xil — hamma bir xil tez.
  • 2234 ms. Ish yo'qolmadi. Worker 20 ta rasmni ikkitadan bajarib, 2.2 soniyada tugatdi. Foydalanuvchi buni kutmadi.

E'tibor bering: umumiy ish kamaymadi, hatto ko'paydi (2.2 s). Navbat ishni tezlashtirmaydi — u ishni foydalanuvchi kutadigan yo'ldan olib chiqadi.

O'lchov bir marta, band mashinada olingan. Sizning kompyuteringizda raqamlar boshqacha bo'ladi, nisbat esa shunga yaqin.

4. Producer, navbat, consumer

Uch rol bor:

  • Producer (ishlab chiqaruvchi) — ishni navbatga qo'yadi. Bizda — Express marshruti.
  • Navbat (queue) — ishlar ro'yxati, alohida saqlanadi (Redis, Postgres, broker). Server qayta ishga tushsa ham yo'qolmaydi.
  • Consumer (iste'molchi) yoki worker — navbatdan ish olib bajaradi. Odatda alohida jarayon, kerak bo'lsa bir nechta nusxa.
flowchart LR
  B[Brauzer] -->|POST /vazifalar/7/rasm| A[API - producer]
  A -->|ish yoziladi| Q[(Navbat)]
  A -->|202 Accepted| B
  Q -->|ish olinadi| W1[Worker 1]
  Q -->|ish olinadi| W2[Worker 2]
  W1 -->|bajardim - ack| Q
  W2 -->|bajardim - ack| Q
  W1 --> S[sharp, SMTP, webhook]
  W2 --> S

Diagrammaga qarang: API va worker'lar bir-birini bilmaydi, ular faqat navbat orqali gaplashadi. Shuning uchun:

  • worker'lar sonini API'dan alohida oshirish mumkin;
  • worker yiqilsa, API ishlashda davom etadi — ishlar navbatda kutadi;
  • worker'ni boshqa mashinaga, hatto boshqa tilga ko'chirish mumkin.

Siz bu naqshni ikki marta ko'rgansiz. SKIP LOCKED bilan DB-asosli navbat darsida navbat Postgres jadvali edi, worker'lar FOR UPDATE SKIP LOCKED bilan ish olardi. Redis Streams darsida consumer group xabarlarni ishchilarga bo'lib bergan edi. Fon vazifalari kutubxonalari (BullMQ, pg-boss) aynan shu g'oyalar ustiga qurilgan: qayta urinish, kechiktirish, prioritet va kuzatuvni tayyor beradi.

5. Model: nega javob tezlashadi

Raqamlarni o'zimiz hisoblaylik. Soddalashtirilgan model: 20 ta so'rov bir vaqtda keldi, har ish 100 ms CPU oladi, CPU bitta. Navbatga yozishni 1 ms deb olamiz:

js
// 20 ta so'rov bir vaqtda keldi; har ish 100 ms CPU oladi.
const SOROV = 20;
const ISH_MS = 100;

// Sinxron: server ishni so'rov ichida qiladi — CPU bitta,
// ishlar ketma-ket bajariladi, har javob o'z ishi tugaganda.
const sinxron = Array.from({ length: SOROV },
  (_, i) => (i + 1) * ISH_MS);

// Navbat: so'rov faqat ishni yozib qo'yadi (~1 ms) va 202 qaytaradi.
const navbat = Array.from({ length: SOROV }, (_, i) => i + 1);

const mediana = (xs) => [...xs].sort((a, b) => a - b)[xs.length / 2];
console.log("sinxron: mediana", mediana(sinxron), "ms, eng sekin",
  Math.max(...sinxron), "ms");
console.log("navbat:  mediana", mediana(navbat), "ms, eng sekin",
  Math.max(...navbat), "ms");

Konsolda:

text
sinxron: mediana 1100 ms, eng sekin 2000 ms
navbat:  mediana 11 ms, eng sekin 20 ms

Sinxron yo'lda 20-so'rov 19 ta boshqa ishni kutdi — 2 soniya. Kanonda bu 957 ms chiqdi. Sabab: sharp rasmni bir nechta thread'da qayta ishlaydi va CPU bitta emas, 16 ta. Sharp ichidagi libvips kutubxonasining o'z thread hovuzi (pool — oldindan tayyorlab qo'yilgan ishchi oqimlar to'plami) bor. Lekin shakl bir xil: sinxron yo'lda oxirgi foydalanuvchi hammaning ishini kutadi.

CPU'ni bloklaydigan ish (Event loop'ni bloklash) bo'lsa, vaziyat bundan ham yomon: rasm qayta ishlanayotganda server hatto GET /health ga ham javob bermaydi. Worker alohida jarayonda bo'lsa, API'ning event loop'i bo'sh qoladi.

6. Yukni tekislash

Trafik tekis kelmaydi. Ertalab 9:00 da hamma ishga kelib vazifa yaratadi, tunda — hech kim. Sinxron serverni eng katta cho'qqiga mo'ljallab qurishga to'g'ri keladi. Navbatli tizimda cho'qqi navbatda "yotadi", worker'lar uni o'z sur'atida eritadi. Bu yukni tekislash (load leveling).

20 ta ish bir vaqtda navbatga tushdi, worker bir vaqtda 2 tasini bajaradi (concurrency 2), har ish 100 ms:

js
// Navbatdagi 20 ish; worker bir vaqtda 2 tasini bajaradi.
function worker(ishlar, parallel, ishMs) {
  const bosh = Array(parallel).fill(0); // har "qo'l" qachon bo'shaydi
  return ishlar.map((kelgan) => {
    bosh.sort((a, b) => a - b);
    const start = Math.max(bosh[0], kelgan);
    bosh[0] = start + ishMs;
    return bosh[0];
  });
}

const ishlar = Array(20).fill(0); // hammasi 0-ms da keldi
const tugash = worker(ishlar, 2, 100);
console.log("oxirgi ish tugadi:", Math.max(...tugash), "ms");

for (const t of [0, 250, 500, 750, 1000]) {
  const qolgan = tugash.filter((x) => x > t).length;
  console.log(`t=${t} ms: navbatda/ishda ${qolgan} ta`);
}

Konsolda:

text
oxirgi ish tugadi: 1000 ms
t=0 ms: navbatda/ishda 20 ta
t=250 ms: navbatda/ishda 16 ta
t=500 ms: navbatda/ishda 10 ta
t=750 ms: navbatda/ishda 6 ta
t=1000 ms: navbatda/ishda 0 ta

Navbat uzunligi — tizimning eng muhim ko'rsatkichi. U kichrayib borsa, worker'lar ulguryapti. Doim o'ssa — worker kam yoki ish sekinlashgan: worker qo'shasiz yoki concurrency'ni oshirasiz. BullMQ'da bu getJobCounts() va Bull Board panelida ko'rinadi.

Ikkinchi tomoni: concurrency — himoya ham. Worker bir vaqtda 2 tadan ortiq rasm olmaydi, demak 200 ta so'rov kelsa ham server xotirasi 2 ta rasmga yetsa bas. Sinxron yo'lda 200 ta so'rov — 200 ta rasm bir vaqtda xotirada.

Tekshirib ko'ring: Worker'lar soni 2 dan 4 ga oshdi. Yuqoridagi misolda oxirgi ish qachon tugaydi?

Javob

500 ms da. 20 ta ish, har "qadamda" 4 tasi, 5 qadam × 100 ms. worker(ishlar, 4, 100) bilan tekshiring. Real hayotda chiziqli emas: CPU yadrolari, xotira va tashqi xizmat limitlari tugagach, worker qo'shish yordam bermaydi.

7. Kamida bir marta: at-least-once

7.1 Worker ish o'rtasida o'lsa

Navbat ishni worker'ga berdi. Worker email yubordi va "bajardim" deyishdan oldin jarayon o'ldi (deploy, xotira tugashi, elektr). Navbat nima qilishi kerak?

  • Ishni o'chirib yuborsa — email ketmagan bo'lishi mumkin edi. Ish yo'qoladi. Bu at-most-once (ko'pi bilan bir marta).
  • Ishni qayta bersa — email ikki marta ketishi mumkin. Lekin ish yo'qolmaydi. Bu at-least-once (kamida bir marta).

Ishonchli navbatlar ikkinchisini tanlaydi: ish faqat worker tasdiqlagandan (ack, acknowledgement) keyin o'chiriladi. Tasdiq kelmasa — ish boshqa worker'ga qayta beriladi. Redis Streams'dagi XACK va XAUTOCLAIM (Pub/Sub va Redis Streams) aynan shu edi.

js
// Navbat xabarni worker "bajardim" (ack) demaguncha o'chirmaydi.
const navbat = [
  { id: "x-1", vazifaId: 7 },
  { id: "x-2", vazifaId: 8 },
];
const yuborilgan = [];

function ishla(xabar, olibQolsinmi) {
  yuborilgan.push(`email: vazifa ${xabar.vazifaId}`);
  if (olibQolsinmi) throw new Error("worker o'ldi (ack'dan oldin)");
}

let urinish = 0;
while (navbat.length > 0) {
  const xabar = navbat[0];
  urinish++;
  try {
    ishla(xabar, urinish === 1); // birinchi urinishda o'ladi
    navbat.shift(); // ack — endi o'chirsa bo'ladi
  } catch (e) {
    console.log(xabar.id, "->", e.message, "— xabar navbatda qoldi");
  }
}
console.log(yuborilgan);

Konsolda:

text
x-1 -> worker o'ldi (ack'dan oldin) — xabar navbatda qoldi
[ 'email: vazifa 7', 'email: vazifa 7', 'email: vazifa 8' ]

Ish yo'qolmadi — lekin 7-vazifa haqidagi email ikki marta ketdi. Bu nazariya emas: kanon BullMQ worker'ini ish o'rtasida SIGKILL bilan o'ldirdi va ish boshqa worker'da qaytadan boshlandi (BullMQ ishonchlilik darsida xom chiqishi bilan ko'ramiz).

7.2 Exactly-once — nega yo'q

"Aniq bir marta" (exactly-once) yetkazishni tarmoq orqali kafolatlab bo'lmaydi: worker email yubordi va ack yuborayotganda tarmoq uzildi — navbat buni "ish bajarilmadi" holatidan ajrata olmaydi. Amalda qilinadigan narsa — at-least-once yetkazish + idempotent ishlovchi. Natija "aniq bir marta ta'sir" kabi ko'rinadi.

Idempotent ishlovchi — bir xabarni ikki marta olsa ham ta'siri bir marta bo'ladigan ishlovchi. Usullar:

Usul Misol
Natija tabiatan idempotent UPDATE … SET holat = 'yuborildi' — ikki marta ham shu
Bajarilganlar ro'yxati xabar id si jadvalda bor bo'lsa — o'tkazib yuborish
Tashqi xizmatning idempotentlik kaliti Idempotency-Key (Idempotency-Key)
Navbatda takrorni rad etish bir xil jobId ikki marta qo'shilmaydi (keyingi darslar)

Ikkinchi usulni 3-mashqda yozasiz. To'liq versiyasi — "inbox" jadvali — Transactional outbox darsida.

Diqqat: "Bizda worker hech qachon o'lmaydi" — xato taxmin. Har deploy eski worker jarayonini to'xtatadi. To'g'ri to'xtatish (Graceful shutdown) xavfni kamaytiradi, lekin yo'qotmaydi: SIGKILL, xotira tugashi va elektr uzilishi ham bor.

8. Navbatga nima qo'yiladi

Navbatdagi ish — kichik ko'rsatma, ma'lumotning o'zi emas.

Yaxshi Yomon
{ faylId: 42 } rasmning 5 MB baytlari
{ vazifaId: 7, muddat: "2026-11-01T09:00:00Z" } butun vazifa obyekti va a'zolar ro'yxati
{ bildirishnomaId: 5, kanal: "email" } tayyor HTML xat va SMTP paroli

Sabablari:

  • Navbat Redis'da yashaydi — xotira qimmat va cheklangan (Redis persistence va operatsiya).
  • Worker ishni bajarganda ma'lumot o'zgargan bo'lishi mumkin. Vazifa o'chirilgan bo'lsa, vazifaId bo'yicha bazadan o'qigan worker buni ko'radi. Ishga qotib qolgan nusxa esa eski ma'lumot bilan email yuboradi.
  • Navbatdagi ma'lumot loglar va boshqaruv panelida ko'rinadi. Sir (token, parol) u yerga tushmasin.

9. Hujumchi nigohi

Navbat — API orqasidagi eshik. Hujumchi nima qiladi:

  1. Navbatni to'ldiradi. Har so'rov bitta og'ir ish qo'shsa, 10 000 ta so'rov — 10 000 ta ish, worker'lar soatlab band. Himoya — rate limiting API tomonda va foydalanuvchi bo'yicha ish soni limiti.
  2. Og'ir ish yuboradi. 50 megapikselli rasm worker xotirasini tugatadi. Himoya — producer tomonda tekshiruv (hajm, o'lcham) va worker'da limitlar.
  3. Ish ma'lumotiga ishonishdan foydalanadi. Worker { foydalanuvchiId } ni tekshirmasdan ishlatsa va u mijozdan kelgan bo'lsa — boshqa odam nomidan ish bajariladi. Himoya — producer ma'lumotni sessiyadan oladi (28-qism), worker esa bazadan qayta tekshiradi.
  4. Panelga kiradi. Navbat boshqaruv paneli ishlarni o'chirish va qayta yurgizishga imkon beradi. Himoya — panel faqat admin uchun, auth ortida (BullMQ ilg'or imkoniyatlari).

10. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Javob tez, lekin email ketmayapti worker ishga tushirilmagan worker — alohida jarayon, uni ham ishga tushiring va kuzating
Email ikki marta ketdi at-least-once, ishlovchi idempotent emas xabar id si bo'yicha takrorni tekshirish
Navbat doim o'syapti worker kam yoki ish sekin navbat uzunligini kuzating, worker qo'shing
Worker eski ma'lumot bilan ishladi ishga butun obyekt qo'yilgan ishga faqat id, ma'lumotni worker bazadan o'qisin
Foydalanuvchi natijani bilmaydi 202 dan keyin hech narsa yo'q holat so'rovi yoki SSE/bildirishnoma
Hamma narsa navbatda sinxron bo'lishi kerak bo'lgan ish ham navbatga foydalanuvchiga darhol kerak natija — sinxron

11. Mashqlar

Mashqlar kurs/mashqlar/29/44-navbat/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • Ishni navbatga qo'yadigan tomon —
  • Navbatdan ish olib bajaradigan jarayon —
  • "Qabul qilindi, lekin hali bajarilmadi" status kodi —
  • Ish faqat tasdiqdan keyin o'chirilsa, yetkazish kafolati — at--once
  • Bir xabarni ikki marta olsa ham ta'siri bir marta bo'ladigan ishlovchi —
Yechim
  • producer — bizda Express marshruti.
  • worker (consumer) — odatda alohida jarayon.
  • 202 — Accepted; javobga ish id si qo'yiladi.
  • least — at-least-once: yo'qolmaydi, lekin takrorlanishi mumkin.
  • idempotent — takror xabar ta'sirni oshirmaydi.

2-mashq (o'rta): kim qancha kutdi

Ishlar navbatga turli vaqtda keldi: [0, 0, 0, 0, 0, 50, 50, 400] (ms). Worker'da ikkita "qo'l" (concurrency 2), har ish 100 ms. tugashlar(vaqtlar, parallel, ishMs) funksiyasini yozing: har ish uchun { kelgan, kutdi, tugadi } qaytarsin (kutdi — navbatda bekor turgan vaqt). Oxirida eng uzoq kutishni chiqaring. Ishora: darsdagi worker funksiyasi deyarli tayyor — har "qo'l" qachon bo'shashini saqlang.

Yechim
js
const kelish = [0, 0, 0, 0, 0, 50, 50, 400];

function tugashlar(vaqtlar, parallel, ishMs) {
  const bosh = Array(parallel).fill(0);
  return vaqtlar.map((t) => {
    bosh.sort((a, b) => a - b);
    const start = Math.max(bosh[0], t);
    bosh[0] = start + ishMs;
    return { kelgan: t, kutdi: start - t, tugadi: bosh[0] };
  });
}

const natija = tugashlar(kelish, 2, 100);
for (const x of natija) console.log(x);
const engKop = Math.max(...natija.map((x) => x.kutdi));
console.log("eng uzoq kutish:", engKop, "ms");

Konsolda:

text
{ kelgan: 0, kutdi: 0, tugadi: 100 }
{ kelgan: 0, kutdi: 0, tugadi: 100 }
{ kelgan: 0, kutdi: 100, tugadi: 200 }
{ kelgan: 0, kutdi: 100, tugadi: 200 }
{ kelgan: 0, kutdi: 200, tugadi: 300 }
{ kelgan: 50, kutdi: 150, tugadi: 300 }
{ kelgan: 50, kutdi: 250, tugadi: 400 }
{ kelgan: 400, kutdi: 0, tugadi: 500 }
eng uzoq kutish: 250 ms

50-ms da kelgan ikkinchi ish 250 ms kutdi: undan oldin 6 ta ish turgan edi. 400-ms dagisi esa kutmadi — cho'qqi o'tib, navbat bo'shagan edi. Bu "kutish vaqti" (queue latency) — navbat uzunligidan keyin ikkinchi muhim ko'rsatkich. Ish turlari uchun "eng ko'pi bilan qancha kutsa bo'ladi" chegarasini qo'yib, oshsa — ogohlantirish bering.

3-mashq (qiyin): idempotent iste'molchi

Navbat at-least-once ishlaydi va h-1 xabarini uch marta yetkazdi. iste(xabar) funksiyasini yozing: xabar birinchi marta kelsa — emaillar ga vazifaId ni qo'shib, "bajarildi" qaytarsin; takror kelsa — hech narsa qilmay "takror — o'tkazildi" qaytarsin. Ma'lumot:

js
const yetkazildi = [
  { id: "h-1", vazifaId: 7 },
  { id: "h-2", vazifaId: 8 },
  { id: "h-1", vazifaId: 7 }, // qayta yetkazildi
  { id: "h-1", vazifaId: 7 }, // yana
];
Yechim
js
const yetkazildi = [
  { id: "h-1", vazifaId: 7 },
  { id: "h-2", vazifaId: 8 },
  { id: "h-1", vazifaId: 7 }, // qayta yetkazildi
  { id: "h-1", vazifaId: 7 }, // yana
];

const ishlangan = new Set();
const emaillar = [];

function iste(xabar) {
  if (ishlangan.has(xabar.id)) return "takror — o'tkazildi";
  emaillar.push(xabar.vazifaId);
  ishlangan.add(xabar.id);
  return "bajarildi";
}

for (const x of yetkazildi) console.log(x.id, iste(x));
console.log("emaillar:", emaillar);

Konsolda:

text
h-1 bajarildi
h-2 bajarildi
h-1 takror — o'tkazildi
h-1 takror — o'tkazildi
emaillar: [ 7, 8 ]

Bu o'quv versiyasining ikki zaif joyi bor. Birinchisi — Set jarayon xotirasida: worker qayta ishga tushsa, u bo'sh, takrorlar yana o'tadi. Ikkinchisi — emaillar.push va ishlangan.add orasida jarayon o'lsa, email ketgan, lekin belgilanmagan. Haqiqiy yechim — belgini bazada saqlash va amal bilan bitta tranzaksiyada yozish. Kanon buni "inbox" jadvali bilan qildi: bir hodisa 3 marta keldi, biznes amali 1 marta bajarildi (Transactional outbox).

12. Loyiha qadami: kanon tajribasi, loyihaga hali kirmaydi

Bu darsda vazifalar-api ga kod qo'shilmaydi. Kanon navbatni avval o'lchov bilan asosladi (yuqoridagi 593 → 42 ms) va keyingi darsda modullar/src/navbat/navbat.ts modulini yozadi: vazifa muddati eslatmalari, bildirishnoma yetkazish va rejali ishlar uchun uchta BullMQ navbati.

Loyihada navbatga tushadigan ishlar ro'yxati shu bo'limda shakllanadi:

Ish Qayerdan Dars
Vazifa muddati eslatmasi vazifa yaratilganda/muddat o'zgarganda BullMQ asoslari
Bildirishnoma yetkazish (email, Telegram, push) bildirishnoma markazi BullMQ asoslari
Yetim fayllarni tozalash, outbox tozalash har kuni 03:30 Cron va rejali vazifalar
Domen hodisalarini tarqatish outbox relay Transactional outbox

O'zingiz sinab ko'ring: darsdagi worker funksiyasida parallel ni 1, 2, 4, 8 qilib, oxirgi ish tugash vaqtini jadvalga yozing. Keyin har ish 100 emas, 10–200 ms oralig'ida tasodifiy bo'lsin — navbat uzunligi qanday o'zgaradi?

13. Real ishda

  • Deyarli har backend'da fon vazifalari bor: email, SMS, push, rasm va video qayta ishlash, hisobot, eksport, webhook, to'lovlarni tekshirish, ma'lumot sinxronlash.
  • "Javob tez, ish keyin" — katta tizimlarning asosiy naqshi. Video yuklaganingizda platforma "qayta ishlanmoqda" deydi — bu navbat.
  • Navbat — tizimni qismlarga ajratish vositasi: API va worker'lar alohida deploy qilinadi, alohida masshtablanadi, biri yiqilsa ikkinchisi ishlaydi.
  • Intervyuda: "Nega navbat kerak?", "At-least-once va exactly-once farqi?", "Idempotent consumer qanday yoziladi?", "202 qachon qaytariladi?"

Xulosa

  • Og'ir yoki tashqi xizmatga bog'liq ishni so'rovdan ajrating: producer navbatga yozadi, worker bajaradi, API 202 qaytaradi.
  • Kanon: 20 parallel so'rov — sinxron mediana 593 ms (p95 957), navbat 42 ms; worker 20 ishni 2234 ms da tugatdi.
  • Navbat ishni tezlashtirmaydi — foydalanuvchi kutadigan yo'ldan olib chiqadi va cho'qqini tekislaydi.
  • Navbat uzunligi va kutish vaqti — asosiy ko'rsatkichlar.
  • Ishonchli navbat — at-least-once: ack'siz ish qayta beriladi. Exactly-once o'rniga — idempotent ishlovchi.
  • Ishga faqat kichik ko'rsatma (id) qo'ying, ma'lumot va sirni emas.

Keyingi dars: BullMQ asoslari — Redis ustidagi navbat: Queue.add, Worker, ish holatlari, QueueEvents va alohida worker jarayoni; vazifalar uchun eslatma navbati.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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