Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. Qayta urinish: attempts va backoff
- 2.1 Sozlama
- 2.2 Eksponensial jadval
- 2.3 Doimiy xato: UnrecoverableError
- 3. Sur'at: concurrency va rate limiter
- 3.1 Ikkalasi bir narsa emas
- 3.2 Kanon o'lchovi
- 4. Worker o'lsa: stalled ishlar
- 4.1 Qulf va "yurak urishi"
- 4.2 Kanon tajribasi
- 4.3 Yolg'on stalled
- 5. Takror va tozalik
- 5.1 Idempotent jobId
- 5.2 removeOnComplete va removeOnFail
- 6. Loyiha qadami: navbat.ts (2-qism) — ishchi()
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): chegaralangan eksponensial kutish
- 3-mashq (qiyin): qayta urinish simulyatori
- 10. Real ishda
- Xulosa
- Manbalar
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 —
attemptsvabackoff(eksponensial: 2, 4, 8 … soniya), doimiy xato uchunUnrecoverableError— darhol to'xtaydi. Tezlik nazorati —concurrency(bir worker bir vaqtda nechta ish) valimiter(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
attemptsvabackoffbilan qayta urinish jadvalini hisoblaysiz.- Vaqtinchalik va doimiy xatoni ajratib,
UnrecoverableErrorishlatasiz. concurrencyva rate limiter farqini kanon o'lchovida ko'rasiz.- Worker o'lganda "stalled" ish qanday tiklanishini va nega u ikki marta boshlanishini tushunasiz.
jobIdvaremoveOnComplete/Failbilan 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 connectionsqaytardi — 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
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:
// 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:
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 sBirinchi 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):
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'yichadurationms ichida ko'pi bilanmaxta 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:
const w = new Worker("limit", () => kut(100), {
connection, prefix: P, concurrency: 10,
limiter: { max: 10, duration: 1000 },
});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 msNima 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:
// 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:
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 bajaradi4.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:
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'lsinO'qiymiz:
navbatda: 1— ikkinchiaddikkala safar ham bir xilidqaytardi, 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: 3bo'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:
- CPU og'ir ishni sandboxed processor ga yoki worker_threads ga chiqaring.
- Ish uzoq bo'lsa,
lockDurationni ish vaqtidan katta qiling. - Ishni qismlarga bo'ling.
Tekshirib ko'ring: Nega
maxStalledCountsukutda 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:
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):
/** 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 };concurrencysukutda 5 (BullMQ sukuti 1) — bildirishnomalar kirish-chiqish ishi.limiter— faqat kerak joyda: SMS worker'iga provayder limiti bilan beriladi (SMS provayderlar).stalledIntervalvamaxStalledCount— BullMQ sukuti bilan bir xil, lekin kodda aniq yozilgan: kim o'qisa, ko'radi.UnrecoverableErrormodul orqali eksport — ishlovchilarbullmqni 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
- Zaharli ish. Hujumchi worker'ni yiqitadigan ma'lumot yuboradi (ulkan rasm). Worker o'ladi → stalled → boshqa worker oladi → u ham o'ladi.
maxStalledCount: 1bu zanjirni ikkinchi marta to'xtatadi. 0 ga yoki katta songa qo'ymang. - Qayta urinishni qurolga aylantirish. Har urinish tashqi API'ga so'rov bo'lsa,
attempts: 100bilan bitta ish 100 ta so'rov — provayder sizni bloklaydi.attemptsni chegaralang, doimiy xatolarniUnrecoverableErrorqiling. - 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: 2000li eksponensial kutishda 3-urinishdan oldingi kutish — ms- Qayta urinmasdan darhol
failedqiladigan 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 —
bullmqdan. - stalled —
waitingga 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
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:
1000, 2000, 4000, 8000, 10000, 10000, 10000
jami kutish: 45 sBullMQ'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
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:
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:
failedishlar 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
attemptssukutda 1;backoff: exponential—2^(n−1) × delay, kanon sozlamasi 5 urinish / 30 s; ko'p ishdajitter.- Doimiy xato —
UnrecoverableError: darholfailed,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
- BullMQ — Retrying failing jobs (
attempts,backoff, jitter): https://docs.bullmq.io/guide/retrying-failing-jobs - BullMQ — Stop retrying jobs (
UnrecoverableError): https://docs.bullmq.io/patterns/stop-retrying-jobs - BullMQ — Concurrency, Rate limiting: https://docs.bullmq.io/guide/workers/concurrency, https://docs.bullmq.io/guide/rate-limiting
- BullMQ — Stalled jobs: https://docs.bullmq.io/guide/workers/stalled-jobs
- BullMQ — Auto-removal of jobs: https://docs.bullmq.io/guide/queues/auto-removal-of-jobs
- bullmq 6.3.19 manba kodi (
backoffs.jsformulasi,worker.jssukut qiymatlari,moveStalledJobsToWaitmatni) — paket ichidan, 2026-10-28 da o'qildi - Kanon tajribasi (IlmHamroh, 2026-10-27): bullmq 6.3.19, Redis 8.10.2 (Docker), Node 24.21.0, Windows 11. Nazariy hisob va mashq yechimlari — biz ishga tushirdik (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!