IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime46/69-dars17 daqiqa
Mundarija (26)

BullMQ ishonchlilik: qayta urinish, concurrency va osilib qolgan ishlar

Qisqacha: Fon vazifasi albatta yiqiladi: SMTP band, tarmoq uzildi, worker deploy paytida o'ldi. BullMQ bunga uchta vosita beradi. Qayta urinish — attempts va backoff (eksponensial: 2, 4, 8 … soniya), doimiy xato uchun UnrecoverableError — darhol to'xtaydi. Tezlik nazorati — concurrency (bir worker bir vaqtda nechta ish) va limiter (soniyasiga nechta). Osilib qolgan ishni tiklash — worker o'lsa, ish "stalled" bo'lib boshqa worker'ga qaytadi; kanonda 2.1 soniyada. Natija: ish yo'qolmaydi, lekin ikki marta boshlanishi mumkin — ishlovchi idempotent bo'lsin.

Bu darsda

  • attempts va backoff bilan qayta urinish jadvalini hisoblaysiz.
  • Vaqtinchalik va doimiy xatoni ajratib, UnrecoverableError ishlatasiz.
  • concurrency va rate limiter farqini kanon o'lchovida ko'rasiz.
  • Worker o'lganda "stalled" ish qanday tiklanishini va nega u ikki marta boshlanishini tushunasiz.
  • jobId va removeOnComplete/Fail bilan navbatni toza va takrorsiz saqlaysiz.

Oldin bilishingiz kerak: BullMQ asoslari, Nega navbat kerak, Rate limiting, Distributed lock va rate limit Redis'da.

Oldingi darsda navbat, worker va ish holatlarini ko'rdik. Endi ishlar yiqilganda nima bo'lishini boshqaramiz.

1. Nega bu kerak?

Bildirishnoma worker'i Malikaga email yuboradi. Bir kunda nimalar bo'lishi mumkin:

  • SMTP server 421 Too many connections qaytardi — vaqtinchalik xato, bir daqiqadan keyin o'tadi.
  • Foydalanuvchi akkauntini o'chirgan — doimiy xato, 100 marta urinsa ham o'tmaydi.
  • 5 000 ta eslatma bir vaqtda navbatga tushdi, SMTP provayder esa soniyasiga 10 tadan ko'p qabul qilmaydi.
  • Deploy paytida worker ish o'rtasida o'ldirildi.

Har biriga alohida javob kerak: qayta urinish, darhol to'xtash, sur'atni cheklash, osilgan ishni qaytarish. SKIP LOCKED darsida bularni o'zimiz yozgan edik (urinish, keyingi_urinish = now() + 2^n, qorovul). BullMQ'da ular sozlama.

2. Qayta urinish: attempts va backoff

2.1 Sozlama

js
await q.add("email", { bildirishnomaId: 5 }, {
  attempts: 5,
  backoff: { type: "exponential", delay: 2000 },
});
  • attempts: 5 — jami 5 urinish (birinchisi + 4 ta qayta). Sukutda 1 — ya'ni qayta urinish yo'q. Buni bilmaslik — eng ko'p uchraydigan xato.
  • backoff — urinishlar orasidagi kutish. fixed — har safar bir xil, exponential — har safar ikki baravar.

Processor xato otsa va urinish qolgan bo'lsa, ish failed ga emas, delayed ga o'tadi va kutish tugagach yana waiting ga qaytadi (oldingi darsdagi holatlar diagrammasi).

2.2 Eksponensial jadval

BullMQ manba kodidagi formula (bullmq 6.3.19, backoffs.js, jitter'siz): Math.round(2 ** (urinish - 1) * delay). Shuni hisoblaymiz:

js
// BullMQ "exponential" strategiyasi (jitter'siz)
const eksp = (delay) => (urinish) =>
  Math.round(2 ** (urinish - 1) * delay);

function jadval(attempts, kutish) {
  let soat = 0;
  const qatorlar = [];
  for (let u = 1; u <= attempts; u++) {
    qatorlar.push(`${u}-urinish: ${soat / 1000} s`);
    if (u < attempts) soat += kutish(u);
  }
  return qatorlar;
}

console.log(jadval(5, eksp(2000)).join(" | "));
console.log(jadval(3, eksp(100)).join(" | "));

Konsolda:

text
1-urinish: 0 s | 2-urinish: 2 s | 3-urinish: 6 s | 4-urinish: 14 s | 5-urinish: 30 s
1-urinish: 0 s | 2-urinish: 0.1 s | 3-urinish: 0.3 s

Birinchi qator — kanon modulining sukut sozlamasi (attempts: 5, delay: 2000): 30 soniya ichida 5 urinish. Ikkinchisi — kanon testining sozlamasi (attempts: 3, delay: 100): uchinchi urinish 300 ms dan keyin. Test ham aynan buni tekshiradi: "vaqtincha" ish 3-urinishda o'tdi va umumiy vaqt ≥ 300 ms.

Nega eksponensial? Tashqi xizmat yiqilgan bo'lsa, minglab worker uni har soniyada urib, tiklanishiga xalaqit beradi. Kutish o'sib borsa, bosim kamayadi. Bu timers va timers/promises chuqur darsidagi eksponensial kutish (exponential backoff) g'oyasining o'zi.

Jitter. 1 000 ta ish bir vaqtda yiqilsa, ularning hammasi aynan 2 s dan keyin yana bir vaqtda uradi. BullMQ 6 da backoff: { type: "exponential", delay: 2000, jitter: 0.5 } — kutishga tasodifiy siljish qo'shadi (manba kodida: [maks × (1 − jitter), maks] oralig'ida). Kanon jitter ishlatmagan; ko'p ishli tizimda qo'shishni tavsiya qilamiz.

2.3 Doimiy xato: UnrecoverableError

Foydalanuvchi o'chirilgan bo'lsa, 5 marta urinish — 30 soniya behuda ish va 5 ta xato logi. Processor UnrecoverableError otsa, BullMQ qolgan urinishlarni bekor qilib, ishni darhol failed qiladi. Kanon testi uchala holatni tekshirdi (qisqartirib, formatlab):

ts
const w = ishchi<{ k: string }>(NAVBAT.tizim, u, (j) => {
  const s = (chaqiruv.get(j.data.k) ?? 0) + 1;
  chaqiruv.set(j.data.k, s);
  if (j.data.k === "doimiy") {
    throw new UnrecoverableError("foydalanuvchi o'chirilgan");
  }
  if (j.data.k === "vaqtincha" && s < 3) throw new Error("SMTP 421");
  return Promise.resolve(`ok ${String(s)}`);
}, { prefiks: PREF, concurrency: 2 });

const opts = {
  attempts: 3, backoff: { type: "exponential" as const, delay: 100 },
};
// "oddiy", "vaqtincha", "doimiy" — uchta ish
// ...
assert.deepEqual(holatlar, ["completed", "completed", "failed"]);
assert.ok(Date.now() - t0 >= 300, "backoff 100 + 200 ms");
assert.deepEqual(chaqiruvlar, [1, 3, 1]);
assert.equal(cj?.failedReason, "foydalanuvchi o'chirilgan");
assert.equal(cj.attemptsMade, 1);

Kanon qaydiga ko'ra natija: "vaqtincha" 3-urinishda o'tdi (backoff ≥ 300 ms), UnrecoverableError → darhol failed, attemptsMade 1. Bu test Redis bilan yuradi; kanon to'liq paketni 82/82 o'tkazdi, biz Redis'siz yurgizganimizda to'plam o'tkazib yuborildi (REDIS_URL yo'q).

Qaysi xato qaysi turga kiradi:

Vaqtinchalik — qayta urin Doimiy — UnrecoverableError
tarmoq timeout, ECONNRESET foydalanuvchi/vazifa topilmadi
HTTP 429, 502, 503, 504 HTTP 400, 401, 403, 404, 422
SMTP 4xx (421, 450) SMTP 5xx (550 mailbox not found)
Postgres deadlock, serialization failure ma'lumot validatsiyadan o'tmadi

Ikkinchi ustundagilarni qayta urinish — vaqtni yeydi va provayder oldida obro'ni tushiradi (masalan, yo'q manzilga qayta-qayta xat — Email yetkazib berish).

3. Sur'at: concurrency va rate limiter

3.1 Ikkalasi bir narsa emas

  • concurrency — bitta worker jarayoni bir vaqtda nechta ishni bajaradi. Sukut — 1. Kirish-chiqish ishlari (HTTP, SMTP, bazaga yozish) uchun 5–50 normal; CPU ishlari (sharp) uchun yadrolar sonidan oshirmang.
  • limiter: { max, duration } — butun navbat bo'yicha duration ms ichida ko'pi bilan max ta ish. Bu cheklov Redis'da saqlanadi — 10 ta worker jarayoni bo'lsa ham umumiy. Tashqi provayder limiti (soniyasiga 10 ta SMS) shu bilan hurmat qilinadi.

3.2 Kanon o'lchovi

30 ta ish, har biri 100 ms kutadi. Kanon uch sozlamani sinadi; chiqishda har qatorning oxiridagi umumiy vaqtni solishtiring:

js
const w = new Worker("limit", () => kut(100), {
  connection, prefix: P, concurrency: 10,
  limiter: { max: 10, duration: 1000 },
});
text
concurrency 1                  30 ish × 100 ms: 3722 ms
concurrency 10                 30 ish × 100 ms: 422 ms
concurrency 10 + limiter 10/s  30 ish × 100 ms: 2238 ms

Nima bo'ldi? Concurrency 10 ish vaqtini qariyb 9 baravar qisqartirdi, limiter esa uni qaytadan 2 soniyadan oshirdi. Bu raqamlar to'g'rimi? Nazariy eng kam vaqtni hisoblaylik va kanon bilan solishtiramiz:

js
// 30 ish × 100 ms: nazariy eng kam vaqt
function nazariy(ishlar, ishMs, concurrency, limiter) {
  const parallel = Math.ceil(ishlar / concurrency) * ishMs;
  if (!limiter) return parallel;
  // har `duration` oynada ko'pi bilan `max` ta ish boshlanadi
  const oynalar = Math.ceil(ishlar / limiter.max);
  return (oynalar - 1) * limiter.duration + ishMs;
}

console.log("concurrency 1 :", nazariy(30, 100, 1), "ms");
console.log("concurrency 10:", nazariy(30, 100, 10), "ms");
console.log("+ limiter 10/s:",
  nazariy(30, 100, 10, { max: 10, duration: 1000 }), "ms");

Konsolda:

text
concurrency 1 : 3000 ms
concurrency 10: 300 ms
+ limiter 10/s: 2100 ms
Sozlama Nazariya Kanon Ortiqcha
concurrency 1 3000 3722 +722 ms — har ish uchun Redis bilan 2–3 aylanish
concurrency 10 300 422 +122 ms
+ limiter 10/s 2100 2238 +138 ms

Ikki xulosa. Birinchisi — concurrency kirish-chiqish ishlarini deyarli chiziqli tezlashtirdi (3.7 s → 0.4 s). Ikkinchisi — limiter concurrency'dan ustun: worker 10 tani bir vaqtda bajarishga tayyor bo'lsa ham, soniyasiga 10 tadan ortiq olmadi. Har ishga ~24 ms (concurrency 1 da) — BullMQ'ning o'z narxi: ish olish, qulf, natijani yozish. Juda kichik ishlarni (1 ms) navbatga bittadan qo'yish shuning uchun samarasiz — ularni partiyaga yig'ing.

4. Worker o'lsa: stalled ishlar

4.1 Qulf va "yurak urishi"

Worker ish olganda Redis'da shu ishga qulf qo'yadi — muddati lockDuration (sukut 30 s). Ish davom etayotganda worker qulfni muntazam yangilab turadi (har lockDuration / 2 da). Bu davriy yangilash — Realtime'ni masshtablash darsidagi heartbeat ("men tirikman" signali) g'oyasi. Bu Distributed lock darsidagi muddatli qulfning o'zi.

Worker o'lsa, qulf yangilanmaydi va muddati tugaydi. Boshqa worker'lar har stalledInterval da (sukut 30 s) active ro'yxatini tekshiradi: qulfi yo'q ish — stalled (osilib qolgan). Uni waiting ga qaytaradi va stalledCounter ni oshiradi. Agar ish maxStalledCount dan (sukut 1) ko'p marta osilib qolsa — failed, sababi "job stalled more than allowable limit" (manba kodidagi matn).

sequenceDiagram
  participant R as Redis
  participant A as Worker A
  participant B as Worker B
  A->>R: ishni oldi, qulf 2 s
  A->>A: ishlayapti
  Note over A: SIGKILL - jarayon o'ldi
  B->>R: har 1 s - active ro'yxatini tekshir
  R-->>B: qulfi tugagan ish bor
  B->>R: stalled - waiting ga qaytar, stalledCounter 1
  B->>R: ishni oldi
  B->>B: ishni BOSHIDAN bajaradi

4.2 Kanon tajribasi

Kanon worker'ni alohida jarayonda ishga tushirdi (lockDuration: 2000, stalledInterval: 1000 — sinov tez bo'lsin deb), ish boshlangach jarayonni SIGKILL bilan o'ldirdi va ikkinchi worker kutdi. Undan oldin bir xil jobId bilan ikki marta add qildi. Chiqishda uch narsaga qarang: navbatda soni, millisekundlar va attemptsMade / stalledCounter:

text
bir xil jobId ikki marta add: buyurtma-77-chek / buyurtma-77-chek, navbatda: 1
2136 ms dan keyin: stalled: buyurtma-77-chek; qayta olindi (attemptsMade 0, stalledCounter 1) — at-least-once: ish IKKI MARTA boshlandi → ishlovchi idempotent bo'lsin

O'qiymiz:

  • navbatda: 1 — ikkinchi add ikkala safar ham bir xil id qaytardi, lekin yangi ish yaratmadi.
  • 2136 ms — qulf 2 s da tugadi, stalled tekshiruvi keyingi soniyada topdi. Sukut sozlamalarda bu 30–60 s bo'ladi.
  • attemptsMade 0 — stalled urinish hisoblanmaydi. attempts: 3 bo'lsa ham, worker o'limi urinishni "yemaydi". Hisoblovchi — stalledCounter.
  • "IKKI MARTA boshlandi" — birinchi worker ishning bir qismini bajargan bo'lishi mumkin (email ketgan, chek yaratilgan). Ikkinchisi boshidan boshlaydi. Bu Nega navbat kerak darsidagi at-least-once'ning BullMQ'dagi ko'rinishi.

4.3 Yolg'on stalled

Stalled faqat worker o'lganda emas, event loop bloklanganda ham chiqadi. Processor ichida 40 soniya sinxron hisob ketsa, worker qulfni yangilay olmaydi (Event loop'ni bloklash) — Redis uni o'lgan deb hisoblaydi, ish ikkinchi worker'da boshlanadi, birinchisi esa ham davom etadi. Natija — bir ish ikki joyda parallel.

Davosi:

Tekshirib ko'ring: Nega maxStalledCount sukutda 0 emas, 1?

Javob

0 bo'lsa, bitta deploy paytida o'ldirilgan worker'ning ishi darhol failed bo'ladi — ish yo'qoladi. 1 — bitta "baxtsiz hodisa"ni kechiradi. Ikkinchi marta ham osilib qolsa, muammo ishning o'zida bo'lishi ehtimoli katta (masalan, xotirani tugatib worker'ni yiqitadigan "zaharli" ish) — uni cheksiz qaytarmaslik kerak.

5. Takror va tozalik

5.1 Idempotent jobId

Bir xil jobId li ish navbatda turgan bo'lsa, add yangi ish yaratmaydi. Bu — producer tomonidagi takrorni to'xtatish: foydalanuvchi "To'lash" tugmasini ikki marta bosdi, API ikki marta add qildi — chek bitta.

jobId ma'nodan yasaladi: buyurtma-77-chek, eslatma-7-<muddat>, b-5-email. Tasodifiy UUID bersangiz — takror to'xtamaydi. Yana ikki qoida bor. Birinchisi: jobId da : belgisi bo'lmasin. Sababi — BullMQ Redis kalitlarida uni ajratuvchi sifatida ishlatadi. Ikkinchisi: jobId sof butun son ham bo'lmasin ("77" emas, "buyurtma-77"): BullMQ o'zining avtomatik raqamlari bilan aralashib ketmasligi uchun rad etadi. Ikkala qoida bullmq 6.3.19 manba kodida xato bilan tekshiriladi ("Custom Id cannot contain :", "Custom Id cannot be integers").

Lekin jobId worker'dagi takrorni (stalled) to'xtatmaydi — u navbatga kirishdagi takror uchun. Worker tomonida idempotentlik — ishlovchining o'zida (Transactional outbox darsidagi inbox).

5.2 removeOnComplete va removeOnFail

Sukutda BullMQ tugagan ishlarni o'chirmaydi: kuniga 100 000 ish — bir haftada 700 000 ta hash Redis xotirasida. Kanon sozlamasi:

ts
const SUKUT = {
  attempts: 5,
  backoff: { type: "exponential", delay: 2000 },
  removeOnComplete: { age: 24 * 3600, count: 1000 },
  removeOnFail: { age: 7 * 24 * 3600 },
} as const;
  • Muvaffaqiyatlilar — 1 kun yoki oxirgi 1000 tasi (qaysi biri kichik).
  • Yiqilganlar — 7 kun: ularni tahlil qilish, qo'lda qayta yurgizish kerak bo'ladi.

Paket ichidagi hujjatga ko'ra tozalash "best-effort": alohida taymer yo'q, eski ishlar keyingi ish tugaganda o'chiriladi. Navbat bir kun jim tursa, eski ishlar ham turaveradi.

Yana bir nuqta: ish o'chirilgach, uning jobId si bo'shaydi. 5.1 dagi "takror to'xtaydi" kafolati faqat ish Redis'da turgan muddat uchun.

6. Loyiha qadami: navbat.ts (2-qism) — ishchi()

Kanon worker yaratishni bitta funksiyaga o'radi (formatlab):

ts
/** Worker: doimiy xato (masalan foydalanuvchi o'chirilgan) —
 * `UnrecoverableError` — qayta urinilmaydi. */
export function ishchi<T>(
  nomi: string,
  ulanishOpt: ConnectionOptions,
  ishlov: Processor<T>,
  o: {
    concurrency?: number;
    limiter?: { max: number; duration: number };
    prefiks?: string;
  } = {},
): Worker<T> {
  return new Worker<T>(nomi, ishlov, {
    connection: ulanishOpt,
    prefix: o.prefiks ?? "vz",
    concurrency: o.concurrency ?? 5,
    ...(o.limiter ? { limiter: o.limiter } : {}),
    stalledInterval: 30_000,
    maxStalledCount: 1,
  });
}

export { UnrecoverableError, type Job };
  • concurrency sukutda 5 (BullMQ sukuti 1) — bildirishnomalar kirish-chiqish ishi.
  • limiter — faqat kerak joyda: SMS worker'iga provayder limiti bilan beriladi (SMS provayderlar).
  • stalledInterval va maxStalledCount — BullMQ sukuti bilan bir xil, lekin kodda aniq yozilgan: kim o'qisa, ko'radi.
  • UnrecoverableError modul orqali eksport — ishlovchilar bullmq ni to'g'ridan import qilmaydi.

Tugatish: worker jarayoni SIGTERM olganda await w.close() chaqiradi — joriy ishlar tugashini kutib yopiladi. To'liq tartib — Graceful shutdown to'liq darsida. Bu darsda commit qilmang — modul Cron darsida tugaydi.

7. Hujumchi nigohi

  1. Zaharli ish. Hujumchi worker'ni yiqitadigan ma'lumot yuboradi (ulkan rasm). Worker o'ladi → stalled → boshqa worker oladi → u ham o'ladi. maxStalledCount: 1 bu zanjirni ikkinchi marta to'xtatadi. 0 ga yoki katta songa qo'ymang.
  2. Qayta urinishni qurolga aylantirish. Har urinish tashqi API'ga so'rov bo'lsa, attempts: 100 bilan bitta ish 100 ta so'rov — provayder sizni bloklaydi. attempts ni chegaralang, doimiy xatolarni UnrecoverableError qiling.
  3. Limiter'siz yuborish. 10 000 ta SMS bir zumda — provayder limitidan oshish va pul. Limiter — moliyaviy himoya ham.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Ish bir marta yiqilib, qayta urinilmadi attempts sukutda 1 attempts va backoff bering
404 xato 5 marta qayta urinildi doimiy xato oddiy Error UnrecoverableError
Email ikki marta ketdi stalled → ish ikki marta boshlandi idempotent ishlovchi
Uzoq ishlar "stalled" bo'lib, parallel ikki marta event loop bloklangan, qulf yangilanmadi sandboxed processor, lockDuration
Provayder 429 qaytaryapti limiter yo'q limiter: { max, duration }
Redis xotirasi o'syapti removeOnComplete yo'q yoshi va soni bo'yicha tozalash
Takror ish tushdi jobId tasodifiy ma'nodan yasalgan jobId

9. Mashqlar

Mashqlar kurs/mashqlar/29/46-ishonchlilik/ da.

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

  • Jami urinishlar soni sozlamasi —
  • delay: 2000 li eksponensial kutishda 3-urinishdan oldingi kutish — ms
  • Qayta urinmasdan darhol failed qiladigan xato klassi —
  • Qulfi tugagan, worker'i o'lgan ish —
  • Butun navbat bo'yicha soniyasiga ishlar sonini cheklaydigan sozlama —
Yechim
  • attempts — sukut 1.
  • 4000 — 2 ** (2 - 1) * 2000: 2-urinish yiqildi, 2 ta urinish qilingan.
  • UnrecoverableError — bullmq dan.
  • stalled — waiting ga qaytariladi, stalledCounter++.
  • limiter — { max, duration }, Redis'da umumiy.

2-mashq (o'rta): chegaralangan eksponensial kutish

Eksponensial kutish tez o'sadi: 20-urinishda 2^19 × 1000 ms — 6 kun. Ko'p tizim kutishga yuqori chegara qo'yadi. kutishlar(attempts, delay, maks) funksiyasini yozing: urinishlar orasidagi kutishlar massivini qaytarsin (attempts − 1 ta), har biri maks dan oshmasin. kutishlar(8, 1000, 10_000) va jami kutishni soniyada chiqaring.

Yechim
js
function kutishlar(attempts, delay, maks) {
  const natija = [];
  for (let u = 1; u < attempts; u++) {
    natija.push(Math.min(2 ** (u - 1) * delay, maks));
  }
  return natija;
}

const k = kutishlar(8, 1000, 10_000);
console.log(k.join(", "));
console.log("jami kutish:", k.reduce((a, b) => a + b, 0) / 1000, "s");

Konsolda:

text
1000, 2000, 4000, 8000, 10000, 10000, 10000
jami kutish: 45 s

BullMQ'da bunday strategiya Worker sozlamasidagi settings.backoffStrategy (o'z funksiyangiz) bilan beriladi, ishda esa backoff: { type: "custom" } (hujjat bo'yicha; kanon sinamagan). Ko'p hollarda eksponensial + kichik attempts yetarli.

3-mashq (qiyin): qayta urinish simulyatori

yurit(ishlovchi, { attempts, delay }) yozing. U ishlovchini urinish raqami bilan chaqirsin; xato bo'lsa — virtual soatni eksponensial kutishga oshirib, qayta urinsin. UnrecoverableError — darhol to'xtasin. Har xatoni <soat> ms: <N>-urinish xato — <xabar> deb chiqaring va oxirgi holatni qaytaring. Uch holatni sinang: 3-urinishda o'tadigan, doimiy xato, hech qachon o'tmaydigan (attempts: 3, delay: 100).

Yechim
js
class UnrecoverableError extends Error {
  name = "UnrecoverableError";
}

function yurit(ishlovchi, { attempts, delay }) {
  let soat = 0;
  for (let u = 1; u <= attempts; u++) {
    try {
      const r = ishlovchi(u);
      return `completed (${u}-urinish, ${soat} ms): ${r}`;
    } catch (e) {
      console.log(`  ${soat} ms: ${u}-urinish xato — ${e.message}`);
      if (e instanceof UnrecoverableError) {
        return `failed darhol (attemptsMade ${u})`;
      }
      soat += 2 ** (u - 1) * delay;
    }
  }
  return `failed (${attempts} urinish tugadi)`;
}

const opts = { attempts: 3, delay: 100 };
console.log(yurit((u) => {
  if (u < 3) throw new Error("SMTP 421");
  return "yuborildi";
}, opts));
console.log(yurit(() => {
  throw new UnrecoverableError("foydalanuvchi o'chirilgan");
}, opts));
console.log(yurit(() => { throw new Error("SMTP 421"); }, opts));

Konsolda:

text
  0 ms: 1-urinish xato — SMTP 421
  100 ms: 2-urinish xato — SMTP 421
completed (3-urinish, 300 ms): yuborildi
  0 ms: 1-urinish xato — foydalanuvchi o'chirilgan
failed darhol (attemptsMade 1)
  0 ms: 1-urinish xato — SMTP 421
  100 ms: 2-urinish xato — SMTP 421
  300 ms: 3-urinish xato — SMTP 421
failed (3 urinish tugadi)

Birinchi holat kanon testidagi "vaqtincha" ishning aynan nusxasi: 3-urinish 300 ms da. Ikkinchisi — attemptsMade 1, kanondagidek. Haqiqiy BullMQ'da kutish paytida ish delayed holatida Redis'da turadi — worker bo'sh, boshqa ishlarni oladi. Bizning simulyatorda esa vaqt virtual.

10. Real ishda

  • Har tashqi integratsiya (email, SMS, to'lov tekshiruvi, webhook) — attempts + backoff + doimiy xatolar ro'yxati. Bu kod review'da birinchi tekshiriladigan narsa.
  • Provayder limitlari (Telegram, SMS, email API) — limiter bilan; limit oshsa provayder 429 beradi va bloklashi mumkin (Bot prodakshnda).
  • Yiqilgan ishlar — "dead letter" ro'yxati: failed ishlar kuzatiladi, sababi tuzatilgach qo'lda qayta yurgiziladi (job.retry()), Bull Board panelida bir tugma (ilg'or imkoniyatlar).
  • Intervyuda: "Exponential backoff nima va nega jitter?", "Stalled job nima?", "Concurrency va rate limit farqi?", "Ish ikki marta bajarilsa nima qilasiz?"

Xulosa

  • attempts sukutda 1; backoff: exponential — 2^(n−1) × delay, kanon sozlamasi 5 urinish / 30 s; ko'p ishda jitter.
  • Doimiy xato — UnrecoverableError: darhol failed, attemptsMade 1.
  • Kanon: 30 × 100 ms — concurrency 1: 3722 ms, 10: 422 ms, limiter 10/s bilan 2238 ms; limiter umumiy va concurrency'dan ustun.
  • Worker o'lsa: qulf tugaydi → stalled → boshqa worker boshidan bajaradi (kanon 2136 ms, stalledCounter 1) — at-least-once.
  • Bloklangan event loop ham yolg'on stalled beradi.
  • jobId — navbatga kirishdagi takrorni to'xtatadi (navbatda: 1); removeOnComplete/Fail — Redis'ni toza tutadi.

Keyingi dars: BullMQ ilg'or imkoniyatlari — ota va bola ishlar (flow), prioritet, progress, sandboxed processor, metrikalar va Bull Board paneli.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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