Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Liveness va readiness
- 2.1 Farqi
- 2.2 Eng ko'p uchraydigan xato: live da baza
- 3. @nestjs/terminus
- 3.1 O'rnatish va eng kichik endpoint
- 3.2 @HealthCheck() nima qiladi
- 3.3 Indikatorlar
- 4. O'z indikatoringiz
- 4.1 HealthIndicatorService
- 4.2 Osilgan tekshiruv
- 5. 503 va xato filtri — kanon topgan xato
- 6. JS modelida terminus
- 7. Express'da qanday edi → Nest'da qanday
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): degraded holati
- 3-mashq (qiyin): tekshiruv natijasini keshlash
- 10. Loyiha qadami: /health/live va /health/ready
- 10.1 Paket
- 10.2 salomatlik.module.ts
- 10.3 Xato filtri: 503 tarmog'i
- 10.4 Ulash va marshrutlar testi
- 10.5 Test
- 10.6 Tekshiruv
- 11. Real ishda
- Xulosa
- Manbalar
Health check'lar NestJS'da: @nestjs/terminus — /health/live va /health/ready
Qisqacha: Health check — "ilova sog'mi?" savoliga javob beradigan endpoint. Ikki xil savol bor. Liveness (
/health/live) — jarayon tirikmi, qotib qolmaganmi. Readiness (/health/ready) — so'rov qabul qilishga tayyormi (baza javob beryaptimi).@nestjs/terminusbuniHealthCheckService.check([...])va tayyor indikatorlar (xotira, disk, Prisma, TypeORM) bilan qiladi: hammasi yaxshi — 200, birortasidown— 503. Kanon tuzoqni topdi: umumiy xato filtri terminus'ning 503 ini 500 ga aylantirib yuborardi.
Bu darsda
- Liveness va readiness farqini va nega
livebazaga bog'lanmasligini tushuntirasiz. TerminusModule,HealthCheckServiceva@HealthCheck()bilan endpoint yozasiz.HealthIndicatorServicebilan o'z indikatoringizni yasab,up,downvadegradedholatlarini qaytarasiz.- Osilib qolgan tekshiruvni timeout bilan cheklaysiz.
vazifalar-apiga/health/liveva/health/readyqo'shib, xato filtrini 503 uchun tuzatasiz.
Oldin bilishingiz kerak: Exception filter'lar, Prisma bilan NestJS, Hodisalar: @nestjs/event-emitter, Node xotira limitlari.
Oldingi darsda domen hodisalarini qo'shdik. Bu darsda loyiha bosqichining uchinchi qismi: salomatlik tekshiruvi. Bu endpoint'lardan hozir faqat biz foydalanamiz (curl). 33-qismda ular Docker va orkestratorga kerak bo'ladi — o'shanda tayyor turadi.
1. Nega bu kerak?
Serverda vazifalar-api ishlayapti. Bir kuni Postgres qayta ishga tushdi va 30 soniya javob bermadi. Nima bo'ladi? Node jarayoni tirik, port ochiq, curl javob oladi — lekin har GET /v1/vazifalar 500 qaytaradi. Kuzatuvchi dastur "server ishlayapti" deydi, foydalanuvchilar esa xato ko'radi.
Yoki boshqa holat: jarayon tirik, lekin event loop bloklangan — cheksiz sikl. Port ochiq, ammo hech qanday so'rovga javob yo'q.
Bu ikki holatda kerakli harakat har xil:
- Event loop qotdi — jarayonni qayta ishga tushirish kerak. U o'zi tuzalmaydi.
- Baza uzildi — jarayonni qayta ishga tushirish yordam bermaydi. Baza qaytguncha bu nusxaga yangi so'rov yubormaslik kerak.
Shuning uchun ikki xil savol, ikki xil endpoint.
O'xshatish: navbatchi shifokor bemorni ikki savol bilan tekshiradi. "Nafas olyaptimi?" — yo'q bo'lsa, darhol reanimatsiya. "Bugun ishga chiqa oladimi?" — isitmasi bor bo'lsa, uyda qoladi, lekin reanimatsiya kerak emas. Ikkinchi savolga "yo'q" degan javob birinchi savoldagi harakatni talab qilmaydi.
2. Liveness va readiness
2.1 Farqi
Liveness (/health/live) |
Readiness (/health/ready) |
|
|---|---|---|
| Savol | jarayon tirikmi? | so'rov qabul qila oladimi? |
| Nimani tekshiradi | hech narsa — javob berdi | baza, kesh, xotira |
| Yiqilsa orkestrator | jarayonni qayta ishga tushiradi | trafikni boshqa nusxaga yuboradi |
| Tez-tez | har 10–30 s | har 5–10 s |
Liveness (tiriklik tekshiruvi) — jarayon javob berdimi, yetarli. Javob kelmasa yoki uzoq kechiksa, jarayon qotgan.
Readiness (tayyorlik tekshiruvi) — bog'liqliklar javob beryaptimi. Yo'q bo'lsa, nusxa vaqtincha "navbatdan chiqadi", lekin o'ldirilmaydi. Baza qaytgach, tekshiruv yana 200 beradi va trafik qaytadi.
Uchinchi turi ham bor — startup (ishga tushish tekshiruvi): sekin ishga tushadigan ilovaga "hali tayyorlanyapman" deyish imkoni. Kubernetes uni alohida probe sifatida biladi. Bizning loyihada readiness shu vazifani ham bajaradi.
2.2 Eng ko'p uchraydigan xato: live da baza
Tasavvur qiling: /health/live bazani tekshiradi. Baza 30 soniya uzildi. Orkestrator "jarayon o'lgan" deb hamma nusxalarni birdaniga qayta ishga tushiradi. Baza qaytdi — lekin hozir hamma nusxa sovuq startda (NestJS nima va nega darsida o'lchagandik: ~5 s). Natija: 30 soniyalik baza uzilishi o'rniga butun tizim bir daqiqa ishlamadi.
Qoida: live — hech qanday tashqi bog'liqliksiz. Kanon izohida ham shunday yozilgan: "bazaga bog'lamang — baza uzilsa, qayta ishga tushirish yordam bermaydi".
Tekshirib ko'ring: Redis kesh uzildi, lekin ilova keshsiz ham ishlay oladi (sekinroq). Redis tekshiruvini
/health/readyga qo'shish kerakmi?
Javob
Odatda yo'q. Readiness "so'rov qabul qila olamanmi?" degan savol. Kesh yo'q bo'lsa ham ilova javob beradi — demak tayyor. Redis'ni ready'ga qo'shsangiz, kesh uzilganda hamma nusxa trafikdan chiqadi va ilova umuman ishlamay qoladi — keshsiz ishlash imkoniyati behuda ketadi. Kesh holatini alohida monitoring kuzatsin yoki uni "degraded" deb ko'rsating (keyingi bo'limlarda).
3. @nestjs/terminus
3.1 O'rnatish va eng kichik endpoint
npm i --save-exact @nestjs/terminus@12.1.0Biz demo loyihada (NestJS 12.2.0, @nestjs/terminus 12.1.0) controller yozdik:
import { Controller, Get, Module } from "@nestjs/common";
import {
HealthCheck, HealthCheckService, TerminusModule,
} from "@nestjs/terminus";
@Controller("health")
class SalomatlikController {
constructor(private readonly salomatlik: HealthCheckService) {}
@Get("live")
@HealthCheck()
tirik() {
return this.salomatlik.check([]);
}
}
@Module({
imports: [TerminusModule],
controllers: [SalomatlikController],
})
class Ilova {}check([]) — bo'sh ro'yxat: hech narsa tekshirilmaydi. Liveness uchun aynan shu kerak. So'rov (biz ishga tushirdik):
GET /health/live
→ 200 {"status":"ok","info":{},"error":{},"details":{}}Javob formati terminus'ning standarti. To'rt maydon:
| Maydon | Ichida |
|---|---|
status |
"ok", "degraded" yoki "error" |
info |
up va degraded indikatorlar |
error |
down indikatorlar |
details |
hammasi birga |
3.2 @HealthCheck() nima qiladi
Bu dekorator ikkita ish qiladi (paket manbasini ochib ko'rdik). Birinchisi — javobga Cache-Control: no-cache, no-store, must-revalidate sarlavhasini qo'yadi (@Header orqali). Salomatlik javobi hech qachon keshlanmasligi kerak: oraliq proxy eski "ok" ni qaytarsa, tekshiruv ma'nosini yo'qotadi. Ikkinchisi — @nestjs/swagger o'rnatilgan bo'lsa, OpenAPI'ga 200 va 503 javob sxemalarini yozadi (Swagger).
3.3 Indikatorlar
Indikator (health indicator) — bitta bog'liqlikni tekshiradigan funksiya. check() ga ularning ro'yxati beriladi. Terminus tayyor indikatorlar bilan keladi:
| Indikator | Nimani tekshiradi |
|---|---|
MemoryHealthIndicator |
heap yoki RSS chegaradan kichikmi |
DiskHealthIndicator |
disk to'lmaganmi (foiz yoki bayt) |
PrismaHealthIndicator |
Prisma bazasi SELECT 1 ga javob beradimi |
TypeOrm…, Mongoose…, Sequelize…, MikroOrm… |
o'sha ORM ulanishi |
HttpHealthIndicator |
tashqi URL (@nestjs/axios kerak) |
MicroserviceHealthIndicator, GRPCHealthIndicator |
mikroservis transporti |
Biz xotira va diskni sinadik:
() => this.xotira.checkHeap("xotira", 512 * 1024 * 1024),
() => this.disk.checkStorage("disk", {
path: "C:\\", thresholdPercent: 0.999,
}),GET /health/disk
→ 200 {"status":"ok","info":{"disk":{"status":"up"}},"error":{},"details":{"disk":{"status":"up"}}}checkHeap("xotira", 512 MB) — V8 heap'ida ishlatilgan xotira 512 MB dan oshsa down (Node xotira limitlari). Xotira sizib (leak) borayotgan nusxa shunda trafikdan chiqadi. HttpHealthIndicator ni sinamadik — u @nestjs/axios paketini talab qiladi va loyihada yo'q.
4. O'z indikatoringiz
4.1 HealthIndicatorService
Tayyor indikator har doim ham mos kelmaydi. Loyihada ombor besh xil bo'lishi mumkin: xotira, fayl, Postgres, Prisma, kesh. PrismaHealthIndicator faqat Prisma'ni biladi. Bizga "ombor javob beradimi?" degan umumiy savol kerak.
Terminus'ning yangi API'si — HealthIndicatorService. U check(kalit) bilan seans ochadi, seans esa uch holatdan birini qaytaradi: up(...), down(...) yoki degraded(...). Qavs ichidagi obyekt javobga qo'shiladi. Demoda ombor holatini o'zimiz boshqardik:
@Injectable()
class OmborIndikatori {
constructor(private readonly indikator: HealthIndicatorService) {}
async tekshir(kalit: string) {
const s = this.indikator.check(kalit);
if (holat.ombor === "osilgan") await new Promise(() => {});
if (holat.ombor === "down") {
return s.down({ xato: "ulanish uzildi" });
}
if (holat.ombor === "sekin") return s.degraded({ ms: 1800 });
return s.up({ ms: 3 });
}
}/health/ready ga ikki indikator berdik — ombor va xotira — va omborni to'rt holatda so'radik:
GET /health/ready (ombor: up)
→ 200 {"status":"ok","info":{"ombor":{"ms":3,"status":"up"},"xotira":{"status":"up"}},"error":{},"details":{"ombor":{"ms":3,"status":"up"},"xotira":{"status":"up"}}}
GET /health/ready (ombor: sekin)
→ 200 {"status":"degraded","info":{"ombor":{"ms":1800,"status":"degraded"},"xotira":{"status":"up"}},"error":{},"details":{"ombor":{"ms":1800,"status":"degraded"},"xotira":{"status":"up"}}}
GET /health/ready (ombor: down)
ERROR Health Check has failed! {"xotira":{"status":"up"},"ombor":{"xato":"ulanish uzildi","status":"down"}}
→ 503 {"status":"error","info":{"xotira":{"status":"up"}},"error":{"ombor":{"xato":"ulanish uzildi","status":"down"}},"details":{"xotira":{"status":"up"},"ombor":{"xato":"ulanish uzildi","status":"down"}}}
GET /health/ready (ombor: osilgan)
→ TimeoutErrorTo'rt qatorni ketma-ket o'qing:
- up — 200,
status: "ok". Qo'shganmsmaydonimiz javobda. - degraded — 200, lekin
status: "degraded". Bu "ishlayapman, lekin yomon": trafik qabul qilinadi, monitoring esa ogohlantiradi. Kesh uzilgan holat uchun aynan mos. - down — 503,
status: "error", omborerrorga tushdi. Xotira esainfoda qoldi — qaysi qism buzilgani aniq ko'rinadi. Terminus loggaHealth Check has failed!deb yozdi. - osilgan — javob umuman kelmadi. Mijoz 500 ms kutib,
TimeoutErroroldi.
4.2 Osilgan tekshiruv
To'rtinchi holat eng xavfli. Indikator ichidagi await hech qachon tugamasa, check() ham tugamaydi. Terminus o'zi timeout qo'ymaydi. Orkestrator 5 soniyadan keyin o'zi uzadi — lekin har tekshiruv serverda osilgan so'rov va ochiq ulanish qoldiradi. Bazaga ulanish hovuzi (pool) to'lsa, aynan shunday bo'ladi.
Yechim — har indikatorga muddat. Ikki yo'l bor. Birinchisi — Promise.race bilan qo'lda (loyihada shunday). Ikkinchisi — terminus 12 ning attempt(...).withTimeout(ms) zanjiri:
urinish(kalit: string) {
return this.indikator
.check(kalit)
.attempt(async ({ signal }) => {
// kut — 1000 ms li ish; signal bekor qilinsa, u ham to'xtaydi
await kut(1000, signal);
return { ms: 1000 };
})
.withTimeout(200);
}GET /health/urinish (attempt + withTimeout(200))
ERROR Health Check has failed! {"ombor":{"message":"timeout of 200ms exceeded","responseTime":211,"status":"down"}}
→ 503 {"status":"error","info":{},"error":{"ombor":{"message":"timeout of 200ms exceeded","responseTime":211,"status":"down"}},"details":{"ombor":{"message":"timeout of 200ms exceeded","responseTime":211,"status":"down"}}}200 ms dan keyin indikator down bo'ldi, message va responseTime ni terminus o'zi qo'shdi (211 — o'lchangan ms, sizda boshqacha bo'ladi). signal — AbortController signali: muddat tugaganda u bekor qilinadi, tekshiruv ichidagi ishni to'xtatish mumkin. Zanjirda yana cacheFor(ms) bor — natijani bir muddat keshlash (3-mashqda o'zimiz yozamiz).
Ikkinchi yo'l chiroyliroq. Kanon esa birinchisini tanlagan: withTimeout terminus 12 da paydo bo'lgan va kanon yozilgan paytda bizning ombor indikatori allaqachon Promise.race bilan yozilgan edi. Ikkalasi ham to'g'ri. Tayyor indikatorlarning eski timeout sozlamasi esa paket ichida @deprecated deb belgilangan — "o'rniga .withTimeout(ms) ni zanjirlang".
Tekshirib ko'ring: Readiness indikatorining muddati 2 soniya, orkestrator esa tekshiruvni 1 soniyadan keyin uzadi. Nima bo'ladi?
Javob
Baza sekinlashsa (masalan 1.5 s), orkestrator 1 soniyada "javob yo'q" deb hisoblaydi, garchi indikator hali up qaytarishi mumkin bo'lsa ham. Bundan ham yomoni — serverda tekshiruv yana bir soniya ishlaydi, hech kim natijani kutmaydi. Qoida: indikator muddati orkestrator muddatidan qisqa bo'lsin. Shunda server o'zi aniq down va sababini qaytaradi.
5. 503 va xato filtri — kanon topgan xato
Terminus down holatida ServiceUnavailableException tashlaydi — Nest'ning 503 li HttpException i. Nest'ning sukut filtri uni to'g'ri qaytaradi: 503 va terminus formati.
Lekin vazifalar-api da o'zimizning global filtr bor (Exception filter'lar). U hamma xatoni Problem Details'ga aylantiradi. Qoidasi: IlovaXatosi — o'z statusi bilan; 500 dan kichik HttpException — statusi bilan; qolgan hammasi — "kutilmagan" 500. 503 esa 500 dan kichik emas. Natija: terminus "tayyor emas" dedi, filtr esa uni "server buzildi" ga aylantirdi.
Demoda shunday filtr bilan tekshirdik:
@Catch()
class HammasiFiltr implements ExceptionFilter {
catch(_x: unknown, host: ArgumentsHost) {
host.switchToHttp().getResponse<Response>().status(500)
.json({ status: 500, detail: "Kutilmagan xato" });
}
}umumiy @Catch() filtri bilan: GET /health/ready (down)
ERROR Health Check has failed! {"xotira":{"status":"up"},"ombor":{"xato":"ulanish uzildi","status":"down"}}
→ 500 {"status":500,"detail":"Kutilmagan xato"}Ikki zarar bor. Orkestrator uchun 500 ham "tayyor emas" (200 emas) — bu yerda omad. Lekin monitoring 500 ni "dasturchi xatosi" deb ko'radi va navbatchini tunda uyg'otadi. Bundan ham yomoni — tekshiruvlar ma'lumoti yo'qoldi: qaysi bog'liqlik buzilgani javobda yo'q. Loyiha qadamida filtrga 503 uchun alohida tarmoq qo'shamiz va shu xatoni test bilan qotiramiz.
flowchart LR
T["terminus: ombor down"] --> X["ServiceUnavailableException 503"]
X --> F{"Problem filtri"}
F -->|"eski: 500 emasmi?"| K["500 Kutilmagan xato"]
F -->|"yangi: 503 tarmog'i"| P["503 + tekshiruvlar"]Diagrammadagi ikki yo'lni solishtiring: bitta if farqi — javob butunlay boshqa ma'noga ega.
6. JS modelida terminus
Terminus'ning asosiy ishi — indikatorlarni parallel yurgizish, har biriga muddat qo'yish va natijani to'rt maydonga yig'ish. Paketsiz modelda yozamiz — blok brauzerda ishlaydi:
const kut = (ms, qiymat) =>
new Promise((ok) => setTimeout(() => ok(qiymat), ms));
function muddat(ms) {
let t;
const p = new Promise((_, rad) => {
const xato = new Error(`${ms} ms ichida javob yo'q`);
t = setTimeout(() => rad(xato), ms);
});
return { p, bekor: () => clearTimeout(t) };
}
async function tekshir(tekshiruvlar, kutishMs) {
const info = {};
const error = {};
const details = {};
const ishlar = Object.entries(tekshiruvlar).map(async ([k, fn]) => {
const m = muddat(kutishMs);
try {
const q = await Promise.race([fn(), m.p]);
details[k] = info[k] = { ...q, status: "up" };
} catch (e) {
details[k] = error[k] = { xato: e.message, status: "down" };
} finally {
m.bekor();
}
});
await Promise.all(ishlar); // hammasi parallel
const ok = Object.keys(error).length === 0;
const tana = { status: ok ? "ok" : "error", info, error, details };
return `${ok ? 200 : 503} ${JSON.stringify(tana)}`;
}
console.log(await tekshir({}, 50)); // live
console.log(await tekshir({ ombor: () => kut(5, { ms: 5 }) }, 50));
console.log(await tekshir({
ombor: () => Promise.reject(new Error("ulanish uzildi")),
}, 50));
const osilgan = () => new Promise(() => {});
console.log(await tekshir({ ombor: osilgan }, 50));Konsolda:
200 {"status":"ok","info":{},"error":{},"details":{}}
200 {"status":"ok","info":{"ombor":{"ms":5,"status":"up"}},"error":{},"details":{"ombor":{"ms":5,"status":"up"}}}
503 {"status":"error","info":{},"error":{"ombor":{"xato":"ulanish uzildi","status":"down"}},"details":{"ombor":{"xato":"ulanish uzildi","status":"down"}}}
503 {"status":"error","info":{},"error":{"ombor":{"xato":"50 ms ichida javob yo'q","status":"down"}},"details":{"ombor":{"xato":"50 ms ichida javob yo'q","status":"down"}}}Birinchi uch qator Nest chiqishi bilan bir xil shaklda. To'rtinchisi esa Nest'dagi TimeoutError dan yaxshiroq: osilgan tekshiruv o'rniga aniq 503 va sabab. Loyihadagi OmborIndikatori aynan shu Promise.race naqshini ishlatadi. finally dagi bekor() muhim: usiz har tekshiruvdan keyin keraksiz taymer 50 ms osilib turadi.
Soddalashtirilgan joyi: degraded holati yo'q (2-mashqda qo'shamiz) va xato logga yozilmaydi.
7. Express'da qanday edi → Nest'da qanday
25–27-qismlardagi vazifalar-api da alohida health endpoint yo'q edi. Express'da yozganimizda u oddiy marshrut bo'lardi: app.get("/health/ready", ...) ichida await ombor.soni({}) va try/catch. Nest'da terminus beradigan narsa:
| Express (qo'lda) | Nest + terminus | |
|---|---|---|
| Javob formati | o'zingiz o'ylab topasiz | standart: status/info/error/details |
| Bir nechta tekshiruv | Promise.all qo'lda |
check([...]) |
| Tayyor tekshiruvlar | yo'q | xotira, disk, ORM'lar |
| Kesh sarlavhasi | unutish oson | @HealthCheck() o'zi |
Nima yutdik: format standart — monitoring vositalari uni taniydi; bir qatorda yangi indikator qo'shiladi. Nima yo'qotdik: yana bitta prod bog'liqlik (jami 22 ta) va terminus'ning 503 xatosini filtrimiz bilan kelishtirish ishi.
8. Ko'p uchraydigan xatolar
| Xato | Nima bo'ladi | Tuzatish |
|---|---|---|
live bazani tekshiradi |
baza uzilsa hamma nusxa qayta ishga tushadi | live — tekshiruvsiz |
| Indikatorda muddat yo'q | so'rov osiladi | Promise.race yoki withTimeout |
| Umumiy filtr 503 ni 500 qiladi | monitoring yolg'on, sabab yo'qoladi | filtrda 503 tarmog'i |
/health ga auth guard tushdi |
orkestrator 401 oladi | guard'dan ozod qiling |
/health versiyalandi (/v1/health) |
orkestrator sozlamasi API versiyasiga bog'lanadi | VERSION_NEUTRAL |
| Har tekshiruvda og'ir so'rov | baza probe'lardan charchaydi | yengil so'rov, kesh |
To'rtinchi qatorni 30-qismning auth darslarida uchratamiz: global guard yoqilganda kanon aynan shu xatoni topgan.
9. Mashqlar
Mashqlar kurs/mashqlar/30/41-salomatlik/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Jarayon tirikligini tekshiradigan endpoint —
- Baza uzilganda qaysi endpoint 503 qaytarishi kerak? —
- Terminus
downholatida qaytaradigan status kod — - "Ishlayapman, lekin sekin" holati —
@HealthCheck()qo'yadigan sarlavha —
Yechim
- /health/live — tekshiruvsiz,
check([]). - /health/ready — ombor va xotira; yiqilsa trafik boshqa nusxaga ketadi.
- 503 — Service Unavailable; filtrimiz uni saqlashi kerak.
- degraded — 200 bilan qaytadi,
status: "degraded". - Cache-Control —
no-cache, no-store, must-revalidate.
2-mashq (o'rta): degraded holati
Model ketma-ket ishlaydigan soddaroq tekshir(tekshiruvlar, sekinMs) funksiyasini yozing. Har tekshiruv vaqtini o'lchang (performance.now()). sekinMs dan uzoq bo'lsa — indikator degraded, aks holda up, xato bo'lsa — down. Umumiy holat: birorta down — 503 va "error"; down yo'q, lekin degraded bor — 200 va "degraded"; aks holda 200 va "ok". Soddalik uchun muddat va details yo'q. Ishora: ichma-ich ternary yoki if.
Yechim
const kut = (ms, qiymat) =>
new Promise((ok) => setTimeout(() => ok(qiymat), ms));
async function tekshir(tekshiruvlar, sekinMs) {
const info = {};
const error = {};
let degraded = false;
for (const [k, fn] of Object.entries(tekshiruvlar)) {
const t = performance.now();
try {
await fn();
const ms = performance.now() - t;
const status = ms > sekinMs ? "degraded" : "up";
if (status === "degraded") degraded = true;
info[k] = { status };
} catch (e) {
error[k] = { xato: e.message, status: "down" };
}
}
const bor = Object.keys(error).length > 0;
const status = bor ? "error" : degraded ? "degraded" : "ok";
const tana = JSON.stringify({ status, info, error });
return `${bor ? 503 : 200} ${tana}`;
}
console.log(await tekshir({ ombor: () => kut(5) }, 30));
console.log(await tekshir({ ombor: () => kut(60) }, 30));
console.log(await tekshir({
ombor: () => kut(60),
kesh: () => Promise.reject(new Error("Redis uzildi")),
}, 30));Konsolda:
200 {"status":"ok","info":{"ombor":{"status":"up"}},"error":{}}
200 {"status":"degraded","info":{"ombor":{"status":"degraded"}},"error":{}}
503 {"status":"error","info":{"ombor":{"status":"degraded"}},"error":{"kesh":{"xato":"Redis uzildi","status":"down"}}}Uchinchi qatorda down degraded dan ustun: umumiy holat "error". Terminus'da ham degraded indikator info ga tushadi (demo chiqishidagidek). ms 60 ga yaqin, lekin sekin kompyuterda 5 ms li ham 30 dan oshishi mumkin — bunday chegaralarni zaxira bilan tanlang.
3-mashq (qiyin): tekshiruv natijasini keshlash
Orkestrator har soniyada /health/ready ni so'raydi. 10 ta nusxa bo'lsa — soniyada 10 ta baza so'rovi, faqat "tayyormisan?" uchun. keshli(fn, ttl, soat) funksiyasini yozing: u fn ni o'raydi va natijani ttl ms davomida saqlaydi. soat — vaqtni qaytaradigan funksiya (test uchun soxta soat). 0, 1000, 2000, 4999, 5000, 6000, 11000 ms dagi 7 tekshiruvda, ttl = 5000 bo'lsa, baza necha marta so'ralishini chiqaring. Ishora: closure ichida { natija, tugash } saqlang; t < tugash — kesh yaroqli.
Yechim
// Natijani ttl ms davomida keshlaydigan tekshiruv
function keshli(fn, ttl, soat) {
let saqlangan = null; // { natija, tugash }
return async () => {
const t = soat();
if (saqlangan && t < saqlangan.tugash) return saqlangan.natija;
const natija = await fn();
saqlangan = { natija, tugash: t + ttl };
return natija;
};
}
let vaqt = 0;
let bazaSorovi = 0;
const ombor = keshli(async () => {
bazaSorovi++;
return { status: "up" };
}, 5000, () => vaqt);
for (const t of [0, 1000, 2000, 4999, 5000, 6000, 11000]) {
vaqt = t;
await ombor(); // orkestrator har soniya so'raydi
}
console.log(`7 ta tekshiruv, bazaga ${bazaSorovi} ta so'rov`);Konsolda:
7 ta tekshiruv, bazaga 3 ta so'rovSo'rovlar 0, 5000 va 11000 da ketdi. 4999 — kesh hali yaroqli (4999 < 5000), 5000 — muddat tugadi. Terminus'dagi cacheFor(ms) shu g'oya. Narxi: baza uzilganini 5 soniyagacha kech bilasiz. Readiness uchun 1–5 soniya kesh odatda qabul qilinadi; liveness'ni esa keshlash shart emas — u baribir hech narsa tekshirmaydi.
10. Loyiha qadami: /health/live va /health/ready
10.1 Paket
npm i --save-exact @nestjs/terminus@12.1.010.2 salomatlik.module.ts
Yangi fayl src/salomatlik/salomatlik.module.ts — modul, controller va o'z indikatorimiz bitta faylda (kichik, bir-biriga bog'liq):
// salomatlik.module.ts — orkestrator (Docker healthcheck, Kubernetes
// probe, 33–34-qism) uchun endpoint'lar — @nestjs/terminus.
// GET /health/live — jarayon tirikmi. Tekshiruvsiz: javob berdi —
// yetarli. Bazaga bog'lamang: baza uzilsa, qayta ishga tushirish
// yordam bermaydi
// GET /health/ready — so'rov qabul qilishga tayyormi: ombor (baza)
// 2 s ichida javob beradi, heap 512 MB dan kam. Yo'q bo'lsa — 503
// Versiyasiz (VERSION_NEUTRAL): /v1 ga bog'liq emas
import {
Controller, Get, Inject, Injectable, Module, VERSION_NEUTRAL,
} from "@nestjs/common";
import {
HealthCheck, HealthCheckService, HealthIndicatorService,
MemoryHealthIndicator, TerminusModule,
} from "@nestjs/terminus";
import type {
HealthCheckResult, HealthIndicatorResult,
} from "@nestjs/terminus";
import { VAZIFA_OMBORI } from "../tokenlar.ts";
import type { VazifaOmbori } from "../vazifalar/ombor.ts";
/**
* O'z indikatorimiz: ombor turi muhim emas (xotira, fayl, Postgres,
* Prisma, kesh) — VazifaOmbori.soni() javob beradimi
*/
@Injectable()
export class OmborIndikatori {
constructor(
private readonly indikator: HealthIndicatorService,
@Inject(VAZIFA_OMBORI) private readonly ombor: VazifaOmbori,
) {}
async tekshir<K extends string>(
kalit: K,
kutishMs = 2000,
): Promise<HealthIndicatorResult<K>> {
const seans = this.indikator.check(kalit);
const t = performance.now();
try {
await Promise.race([
this.ombor.soni({}),
new Promise((_, rad) => {
setTimeout(() => {
const ms = String(kutishMs);
rad(new Error(`${ms} ms ichida javob yo'q`));
}, kutishMs).unref();
}),
]);
return seans.up({ ms: Math.round(performance.now() - t) });
} catch (x) {
return seans.down({
xato: x instanceof Error ? x.message : String(x),
});
}
}
}
@Controller({ path: "health", version: VERSION_NEUTRAL })
export class SalomatlikController {
constructor(
private readonly salomatlik: HealthCheckService,
private readonly ombor: OmborIndikatori,
private readonly xotira: MemoryHealthIndicator,
) {}
@Get("live")
@HealthCheck()
tirik(): Promise<HealthCheckResult> {
return this.salomatlik.check([]);
}
@Get("ready")
@HealthCheck()
tayyor(): Promise<HealthCheckResult> {
return this.salomatlik.check([
() => this.ombor.tekshir("ombor"),
() => this.xotira.checkHeap("xotira", 512 * 1024 * 1024),
]);
}
}
@Module({
imports: [TerminusModule],
controllers: [SalomatlikController],
providers: [OmborIndikatori],
})
export class SalomatlikModule {}Faylning to'rt qarori:
VAZIFA_OMBORItokeni — indikator omborning qaysi turi ekanini bilmaydi (Custom provider'lar). Xotira rejimida ham, Postgres'da ham bir xil ishlaydi.soni({})— eng arzon so'rovlardan biri..unref()— taymer jarayonni tirik ushlamasin (timers chuqur). Ombor 3 ms da javob bersa ham taymer 2 soniya turadi;unrefbo'lmasa, test tugagandan keyin jarayon 2 soniya kutardi.VERSION_NEUTRAL— API/v1bilan versiyalangan, lekin health yo'li versiyasiz:/health/ready. Orkestrator sozlamasi API versiyasiga bog'lanmasin. Yo'l nomlari inglizcha — bu Docker va Kubernetes konventsiyasi.TerminusModule—HealthCheckService,HealthIndicatorServicevaMemoryHealthIndicatorni beradi. Uni faqat shu modul import qiladi.
Kanon faylida qatorlar uzun edi — telefon uchun bo'ldik, izohlar qayta taqsimlandi, kod ma'nosi aynan.
10.3 Xato filtri: 503 tarmog'i
src/umumiy/problem.filter.ts — importlarga import type { Problem } from "./problem.ts"; qo'shing. "Kutilmagan" 500 tarmog'idan oldin yangi tarmoq:
// 503 — "tayyor emas" (masalan @nestjs/terminus /health/ready):
// kutilgan holat — status va tekshiruv natijalari saqlanadi
// (kutilmagan 500 EMAS)
if (xato instanceof HttpException && xato.getStatus() === 503) {
const tekshiruvlar = xato.getResponse();
joriyLog(this.log).warn(
{ tekshiruvlar }, "xizmat tayyor emas (503)",
);
const p = problemYasa(503, {
...umumiy, detail: "Xizmat vaqtincha tayyor emas",
});
problemYubor(res, { ...p, tekshiruvlar } as Problem);
return;
}Uch narsa: status 503 saqlanadi; terminus natijasi Problem Details'ning tekshiruvlar kengaytmasiga tushadi (Problem Details kengaytma maydonlarga ruxsat beradi); log darajasi — warn, error emas: bu dasturchi xatosi emas, kutilgan holat. Kanonda shu tarmoq ikki qator bilan yozilgan; biz uni o'qishga qulay bo'lishi uchun tekshiruvlar va p o'zgaruvchilariga ajratdik — xulq aynan bir xil.
10.4 Ulash va marshrutlar testi
src/app.module.ts — import { SalomatlikModule } from "./salomatlik/salomatlik.module.ts"; va imports da VazifalarModule dan oldin SalomatlikModule,.
test/marshrutlar.test.ts — butun marshrutlar jadvalini tekshiradi (Reflector va DiscoveryService darsidan). Yangi ikki yo'l jadvalga qo'shilmasa, test yiqiladi. "/eksportlar/:id/fayl": "GET, HEAD", qatoridan keyin:
"/health/live": "GET, HEAD",
"/health/ready": "GET, HEAD",10.5 Test
test/hodisa.test.ts ga ikkinchi server qo'shiladi — ombori ataylab buzilgan:
class BuzuqOmbor extends XotiraOmbori {
override soni(): Promise<number> {
return Promise.reject(new Error("ulanish uzildi"));
}
}
let i: Ishlayotgan;
let buzuq: Ishlayotgan;
beforeAll(async () => {
i = await ishga({
port: 0,
bogliqliklar: { ombor: new XotiraOmbori() },
});
buzuq = await ishga({
port: 0,
bogliqliklar: { ombor: new BuzuqOmbor() },
});
});
afterAll(async () => {
for (const x of [i, buzuq]) {
await yop(x.server, 1000);
await x.app.close(); // @Interval taymerlari to'xtasin
}
});Va yangi test (avvalgi @Interval testidan oldin):
test("salomatlik: /health/live va /health/ready — 200; ombor javob "
+ "bermasa ready 503 (Problem Details + tekshiruvlar), live "
+ "baribir 200", async () => {
const live = await fetch(`${i.manzil}/health/live`);
expect([live.status, await live.json()]).toEqual([
200, { status: "ok", info: {}, error: {}, details: {} },
]);
const ready = (await (
await fetch(`${i.manzil}/health/ready`)
).json()) as {
status: string;
info: { ombor: { status: string } };
};
expect(ready.status).toBe("ok");
expect(ready.info.ombor.status).toBe("up");
const yomon = await fetch(`${buzuq.manzil}/health/ready`);
expect(yomon.status).toBe(503);
expect(yomon.headers.get("content-type")).toMatch(/problem\+json/);
const p = (await yomon.json()) as {
status: number;
tekshiruvlar: {
error: { ombor: { status: string; xato: string } };
};
};
expect(p.tekshiruvlar.error.ombor).toEqual({
status: "down", xato: "ulanish uzildi",
});
expect((await fetch(`${buzuq.manzil}/health/live`)).status)
.toBe(200);
});Oxirgi qator darsning asosiy qoidasini qotiradi: ombor buzilgan serverda ham live 200. Fayl boshidagi izohni ham yangilang: "salomatlik endpoint'lari, rejalashtirilgan tozalash (@Interval)".
10.6 Tekshiruv
npm run tip
npm run lint
npm testKanon nusxasida (Node 24.21.0, bazasiz):
> tsc --noEmit
> eslint . --max-warnings=0
Test Files 20 passed | 5 skipped (25)
Tests 79 passed | 6 skipped (85)Endi kanon topgan xatoni o'z ko'zingiz bilan ko'ring: filtrdagi 503 tarmog'ini vaqtincha olib tashlab, testni qayta yurgizdik:
❯ test/hodisa.test.ts (2 tests | 1 failed) 428ms
× salomatlik: /health/live va /health/ready — 200; ombor javob bermasa ready 503 (Problem Details + tekshiruvlar), live baribir 200 154ms
✓ @Interval: eksport-tozalash ro'yxatda; muddati o'tgan ish tozalanadi va 404 27ms
AssertionError: expected 500 to be 503 // Object.is equalityTarjimasi: "500 ning 503 bo'lishi kutilgan edi (Object.is tengligi)". Aynan «503 va xato filtri» bo'limidagi demo: filtr 503 ni 500 ga aylantirdi. Tarmoqni qaytaring — test yashil.
Kanonning jonli tekshiruvi (Prisma rejimi) — sarlavhalardan keraklilarini qoldirdik:
$ curl /health/ready
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Content-Type: application/json; charset=utf-8
{"status":"ok","info":{"ombor":{"ms":4,"status":"up"},"xotira":{"status":"up"}},"error":{},"details":{"ombor":{"ms":4,"status":"up"},"xotira":{"status":"up"}}}Prisma bilan ham ombor 4 ms da javob berdi. Cache-Control — @HealthCheck() ishi.
Hali commit qilmang — oxirgi qism, SSE oqimi, keyingi darsda.
11. Real ishda
- Docker va Kubernetes:
HEALTHCHECK(Dockerfile),livenessProbevareadinessProbe(Kubernetes) aynan shu endpoint'larni so'raydi. 33–34-qismlarda ularni ulaymiz; endpoint'lar tayyor. - Load balancer: nginx, AWS ALB, Yandex Cloud balanser ham "sog'lom nusxa"ni health endpoint orqali aniqlaydi.
- Monitoring: Uptime Kuma, Grafana kabi vositalar
/health/readyni kuzatib,degradedyokierrorda xabar beradi. - Intervyu savollari: "Liveness va readiness farqi nima?", "Liveness'da bazani tekshirish nega xavfli?", "Health endpoint'ga auth kerakmi?" (javob: yo'q, lekin ichki ma'lumotni — versiya, xato matni — ochiq bermang).
Xulosa
- Liveness — "tirikmisan?" (tekshiruvsiz); readiness — "so'rov qabul qila olasanmi?" (baza, xotira).
TerminusModule+HealthCheckService.check([...])+@HealthCheck()— formatstatus/info/error/details, kesh sarlavhasi bilan.- O'z indikatori —
HealthIndicatorService.check(kalit)→up,down,degraded;degraded200 bilan qaytadi. - Terminus o'zi muddat qo'ymaydi:
Promise.raceyokiattempt().withTimeout(ms). - Umumiy xato filtri 503 ni 500 ga aylantirmasin — loyihada alohida tarmoq va test.
/health/*versiyasiz (VERSION_NEUTRAL), yo'l nomlari inglizcha.
Keyingi dars: Server-Sent Events NestJS'da — @Sse() va Observable bilan vazifa hodisalarini brauzerga jonli oqim qilib yuboramiz va loyiha bosqichini commit qilamiz.
Manbalar
- NestJS hujjati — Health checks (Terminus): https://docs.nestjs.com/recipes/terminus
@nestjs/terminusmanba kodi (12.1.0): https://github.com/nestjs/terminus- Kubernetes — Liveness, Readiness and Startup Probes: https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/
- RFC 9457 — Problem Details (kengaytma maydonlari): https://www.rfc-editor.org/rfc/rfc9457
- Kanon (IlmHamroh, 2026-10-28):
/health/live,/health/ready, filtr topilmasi (503 → 500), jonli tekshiruv Prisma rejimida. Demo (indikatorlar,degraded, osilgan tekshiruv,withTimeout, umumiy filtr) va loyiha nusxasidagi tekshiruv, filtrsiz test chiqishi — biz ishga tushirdik (2026-11-02, Node 24.21.0, NestJS 12.2.0,@nestjs/terminus12.1.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!