Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Unumdorlik sikli
- 3. O'lchash
- 3.1 O'rtacha aldaydi
- 3.2 Kanon yuklama testi: nimani o'lchadik?
- 3.3 Little qonuni bilan tekshirish
- 4. Profil
- 4.1 Kanon profili
- 4.2 Natijani halol o'qish
- 5. Gipoteza: issiq qator
- 5.1 Nima bu
- 5.2 Mikro o'lchov
- 6. Butun tizimda: Amdahl qonuni
- 6.1 Kanon natijasi
- 6.2 Nega: Amdahl qonuni
- 6.3 Unda nega Redis cheklagichi qoldi?
- 7. Boshqa tezlashtirish vositalari
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): autocannon natijasini tekshirish
- 3-mashq (qiyin): qaysi joyni tuzatish kerak
- 10. Loyiha qadami: Redis cheklagichi va bosqich commit'i
- 10.1 RedisCheklagich
- 10.2 Ulash
- 10.3 Test
- 10.4 30-qismdan qolgan xato: Problem title
- 10.5 Versiya va hujjat
- 10.6 Bosqich tekshiruvi
- 10.7 Commit
- 11. Real ishda
- Xulosa
- Manbalar
Backend unumdorligini oshirish: o'lchov, profil, gipoteza va qayta o'lchov
Qisqacha: Unumdorlik ishi bitta sikl: o'lcha → eng sekin joyni top → faqat o'shani tuzat → qayta o'lcha. Kanon shu siklni
vazifalar-apida halol o'tdi va uch saboq oldi. Birinchi yuklama testi API'ni emas, tezlik cheklovini o'lchagan ekan — hamma javob 429. Qisqa yugurishdagi CPU profili asosan ishga tushishni ko'rsatdi. "Issiq qator" gipotezasi mikro o'lchovda tasdiqlandi — Redis cheklagichi 2.4 baravar tez. Lekin butun tizimda farq shovqin ichida qoldi: cheklagich tor joy emas edi. Bu — Amdahl qonuni amalda.
Bu darsda
- Unumdorlik siklini va nega "o'lchovsiz tuzatish" xavfli ekanini tushuntirasiz.
- p50, p95, p99 ni hisoblaysiz va o'rtacha nega aldashini ko'rasiz.
- Yuklama testi aslida nimani o'lchaganini tekshira olasiz.
- Little va Amdahl qonunlari bilan natijani oldindan baholaysiz.
vazifalar-apiga Redis tezlik cheklagichini qo'shib, loyihaning shu bosqichini commit qilasiz.
Oldin bilishingiz kerak: CPU profiling va flamegraph, Sekin so'rovlarni topish, Rate limiting, Observability: metrikalar, trace va xatolarni kuzatish, N+1 muammosi.
O'tgan darslarda ilova to'g'ri to'xtaydigan, o'z sog'lig'ini aytadigan, ichini ko'rsatadigan va tashqi xatolarga chidaydigan bo'ldi. Endi oxirgi savol: u qanchalik tez, va uni qanday tezlashtiramiz — bir haftani behuda o'tkazmasdan?
1. Nega bu kerak?
Dasturchi Sardor "API sekin" degan shikoyatni eshitdi. U kodga qaradi va o'yladi: "Har so'rovda tezlik cheklagichi bazaga yozadi — aniq shu sekinlashtiryapti". Ikki kun ishlab, cheklagichni Redis'ga ko'chirdi. Natija? Foydalanuvchilar farqni sezmadi.
Bu — eng ko'p uchraydigan unumdorlik xatosi: gipotezani o'lchovsiz tuzatish. Sardorning gipotezasi noto'g'ri emas edi — Redis haqiqatan tezroq. Lekin cheklagich so'rov vaqtining kichik bir qismi edi. Kichik qismni ikki baravar tezlashtirish butunni deyarli o'zgartirmaydi.
Bu darsda aynan shu hikoyani kanon o'lchovlari bilan qadamma-qadam ko'ramiz — va kanonning o'zi ham Sardor kabi xulosaga keldi.
O'xshatish: avtobus yo'nalishi. Yo'l 40 daqiqa, shundan 30 daqiqasi bitta chorrahadagi tirbandlik. Avtobusga kuchliroq dvigatel qo'yish (ochiq yo'lda 10 % tezroq) deyarli hech narsa bermaydi. Avval qayerda turib qolganingizni o'lchang.
2. Unumdorlik sikli
flowchart LR
A["1. O'lcha"] --> B["2. Profil"]
B --> C["3. Gipoteza"]
C --> D["4. Mikro o'lchov"]
D --> E["5. Tuzat"]
E --> F["6. Tizimda qayta o'lcha"]
F --> ADiagrammadagi eng ko'p tashlab ketiladigan qadam — oxirgisi. Mikro o'lchovda "2.4× tez" chiqdi — xursand bo'lib, ish tugadi deyish oson. Lekin foydalanuvchi mikro o'lchovni emas, butun so'rovni his qiladi.
3. O'lchash
3.1 O'rtacha aldaydi
Javob vaqtini o'rtacha bilan aytish — eng katta xato. Uning o'rniga persentil (percentile) ishlatiladi. Persentil — barcha javob vaqtlarini o'sish tartibida tizganda, p foizi undan tez yoki teng bo'lgan chegara. Masalan, p95 = 250 ms: 100 so'rovdan 95 tasi 250 ms dan tez yoki teng. Xuddi maktab navbatidagi bo'y kabi: "sinfning 95 foizi 170 sm dan past" — eng baland bir-ikki o'quvchini ham ko'rsatadi.
20 ta so'rovni ko'raylik: ko'pi 12–16 ms, ikkitasi sekin:
const kechikishlar = [12, 15, 14, 13, 16, 15, 14, 13, 250, 15,
14, 16, 13, 15, 14, 12, 15, 14, 13, 900];
function persentil(qiymatlar, p) {
const t = qiymatlar.toSorted((a, b) => a - b);
// "eng yaqin o'rin" usuli
const orin = Math.ceil((p / 100) * t.length) - 1;
return t[Math.max(0, orin)];
}
const ortacha = kechikishlar.reduce((s, x) => s + x, 0)
/ kechikishlar.length;
console.log(`o'rtacha: ${ortacha} ms`);
for (const p of [50, 90, 95, 99]) {
console.log(`p${p}: ${persentil(kechikishlar, p)} ms`);
}Konsolda:
o'rtacha: 70.15 ms
p50: 14 ms
p90: 16 ms
p95: 250 ms
p99: 900 msO'rtacha — 70 ms. Lekin birorta so'rov 70 ms bo'lmagan: 18 tasi 16 ms dan tez, ikkitasi juda sekin. O'rtacha ikkala guruhni ham noto'g'ri tasvirlaydi. Persentil esa aniq: p50 (mediana) — tipik foydalanuvchi 14 ms; p95 va p99 — eng omadsiz foydalanuvchilar. Bir sahifa 20 ta API so'rovi qilsa, deyarli har sahifa ochilishida p95 dagi sekin so'rovlardan biri uchraydi — shuning uchun "dum" (tail latency) muhim. toSorted — nusxa bilan o'zgartirish darsidan: asl massiv buzilmaydi.
3.2 Kanon yuklama testi: nimani o'lchadik?
autocannon — Node'da yozilgan yuklama vositasi: u N ta ulanish ochadi va berilgan vaqt davomida serverga to'xtovsiz so'rov yuboradi. Natijada o'tkazuvchanlik (throughput — soniyasiga bajarilgan so'rovlar, req/s) va kechikish persentillari chiqadi. Vositaning o'zini Yuklama va stress testlari darsida batafsil ko'ramiz.
Kanon jonli serverni (Postgres, haqiqiy rollar, Aziz sessiyasi bilan) autocannon bilan yukladi: GET /v1/vazifalar?limit=20, 20 ulanish, 15 soniya. Har qatorda avval kodlar ustuniga qarang — keyin req/s ga. Kanon chiqishi (sana va ichki yorliqlar qisqartirilgan):
asos | LOG_LEVEL=info | 838 req/s, p50 22 ms, p99 38 ms, kodlar 429×12569, xatolar 0
log warn | LOG_LEVEL=warn | 786 req/s, p50 22 ms, p99 82 ms, kodlar 429×11797, xatolar 0kodlar 429×12569 — hamma javob 429 (Too Many Requests). Birorta ham 200 yo'q! Sabab: 28-qismdagi API cheklovi — bitta foydalanuvchiga daqiqasiga 600 so'rov (Rate limiting). Yuklama testi bitta foydalanuvchi bilan soniyasiga ~800 so'rov yubordi — birinchi soniyadayoq limit tugadi. Test API'ning tezligini emas, 429 javobining tezligini o'lchagan.
Saboq: yuklama testining natijasini o'qishdan oldin status kodlarini tekshiring. Uchta to'g'ri yo'l bor: test muhitida limitni oshirish, ko'p foydalanuvchi bilan yuklash yoki atayin 429 yo'lini o'lchash (bu ham foydali — hujum paytida server 429 ni qanchalik tez qaytaradi). Bularni Yuklama va stress testlari darsida qo'llaymiz.
Ikkinchi qatorga ham qarang: LOG_LEVEL=warn (kam log) — sekinroq chiqdi (786 < 838), p99 esa ikki baravar. Mantiqan kam log tezroq bo'lishi kerak edi. Bu — shovqin: bitta mashina, boshqa jarayonlar bilan band, har rejim bir marta. Bunday farqqa xulosa qurilmaydi.
3.3 Little qonuni bilan tekshirish
Raqamlar o'zaro mosmi? Little qonuni: tizimdagi so'rovlar soni = o'tkazuvchanlik × bitta so'rov vaqti (L = λ × W). autocannon 20 ulanishni doim band ushlaydi (L = 20). Redis cheklagichli yugurishda λ = 1 246 req/s. Demak:
const L = 20; // bir vaqtda ochiq so'rovlar (ulanishlar)
const lam = 1246; // soniyasiga so'rovlar
console.log(`W = ${(L / lam * 1000).toFixed(1)} ms`);Konsolda:
W = 16.1 msautocannon o'sha yugurishda p50 = 15 ms ko'rsatgan — formula bilan mos. Bu oddiy tekshiruv o'lchov to'g'ri yig'ilganini tasdiqlaydi: agar W kutilgandan juda farq qilsa, o'lchovda xato bor (masalan, mijozning o'zi tor joy).
4. Profil
4.1 Kanon profili
Tor joyni taxmin qilmaslik uchun — CPU profil (CPU profiling va flamegraph): node --cpu-prof jarayon vaqtini funksiyalar bo'yicha yozib oladi. Kanon server'ni profil bilan yukladi va natijani paketlar bo'yicha yig'di. Self time — funksiyaning o'zida o'tgan vaqt (u chaqirgan boshqa funksiyalarsiz). Ro'yxatning pastki qismidagi funksiya nomlariga qarang — ular nima ish qilyapti? Kanon chiqishi (qisqartirilgan):
jami namuna vaqti: 14.0 s
paketlar bo'yicha (self time):
52.5 % node ichki
9.7 % (idle)
3.8 % ilova (dist)
3.3 % @nestjs/core
2.8 % pg
2.2 % pino
funksiyalar (self time, top 12):
9.7 % (idle) :0
6.6 % writev :0
4.6 % internalModuleStat :0
3.7 % wrapSafe node:internal/modules/cjs/loader:1819
2.4 % compileSourceTextModule node:internal/modules/esm/utils:317
2.3 % readFileUtf8 :04.2 Natijani halol o'qish
Funksiyalar ro'yxatiga qarang: internalModuleStat, wrapSafe, compileSourceTextModule, readFileUtf8 — bular modul yuklash (fayllarni topish, o'qish, kompilyatsiya). Ya'ni profilning katta qismi so'rovlarga emas, ilovaning ishga tushishiga ketgan. 14 soniyalik yozuvning ~3 soniyasi start (Health check endpoint'lari darsidagi o'lchov: odatda ~2.6 s, band mashinada ko'proq), isitish 2 soniya, qolgani yuklama — va profil o'rtachasi hammasini aralashtirib yubordi.
Kanon yozuvi ham buni tasdiqlaydi: profil skriptida jarayon taymer bilan process.exit qiladi (yuklama vaqti + 6 s). Kanonning bitta profil yugurishi esa buzildi — 129 req/s, p50 117 ms, xatolar 1541. Bizning o'qishimiz: o'sha yugurishda chiqish taymeri yuklama tugamasdan ishlagan yoki profil yozish serverni haddan tashqari sekinlashtirgan; aniq sababi kanonda yozilmagan. Bu raqam xulosalarga kiritilmaydi.
To'g'ri usul: avval serverni isitish, keyin profilni faqat yuklama oynasida yoqish. Node buni node --cpu-prof bilan emas, inspector moduli (Profiler.start/stop) yoki Chrome DevTools orqali qila oladi (CPU profiling darsida ko'rgansiz). Profil bergan yagona foydali signal: pg 2.8 % — baza drayveri sezilarli joyda emas.
Tekshirib ko'ring: Profilda
writev6.6 %. Bu nima bo'lishi mumkin?
Javob
writev — bir nechta bufer'ni bitta tizim chaqiruvi bilan yozish. Serverda bu ikki joydan keladi: HTTP javoblarni soketga yozish va pino loglarini stdout'ga yozish. Har so'rovda log qatori bor (LOG_LEVEL=info) — shuning uchun log hajmini kamaytirish gipotezasi tug'iladi. Lekin «Kanon yuklama testi» bo'limidagi o'lchovda warn tezroq chiqmadi. Gipoteza — tekshirish uchun, xulosa emas.
5. Gipoteza: issiq qator
5.1 Nima bu
Profil aniq javob bermadi. Kanon gipoteza qurdi: tezlik cheklagichi (Rate limiting, 28-qism) har so'rovda Postgres'ga bitta INSERT … ON CONFLICT DO UPDATE qiladi. Bitta foydalanuvchining hamma so'rovlari bitta qatorni yangilaydi. Parallel so'rovlar shu qator qulfi uchun navbatda turadi — bu issiq qator (hot row).
Har yangilash Postgres'da yangi qator versiyasini yaratadi (MVCC darsidagi g'oya). O'z PG 17 bazamizda ko'ramiz. Avval jadval — kanondagidek UNLOGGED:
CREATE UNLOGGED TABLE tezlik_chegaralari (
kalit text NOT NULL PRIMARY KEY,
son int NOT NULL,
tugash timestamptz NOT NULL
);
SELECT relname, relpersistence
FROM pg_class WHERE relname = 'tezlik_chegaralari';CREATE TABLE
relname | relpersistence
--------------------+----------------
tezlik_chegaralari | u
(1 row)relpersistence = u — unlogged: jadval WAL'ga yozilmaydi (tez, lekin server qulasa — bo'shaydi; hisoblagich uchun maqbul). Endi kanon cheklagichining so'rovi, ctid (qatorning jismoniy manzili) bilan:
PREPARE ol(text) AS
INSERT INTO tezlik_chegaralari AS t (kalit, son, tugash)
VALUES ($1, 1, now() + interval '60 seconds')
ON CONFLICT (kalit) DO UPDATE SET
son = CASE WHEN t.tugash <= now() THEN 1 ELSE t.son + 1 END,
tugash = CASE WHEN t.tugash <= now()
THEN now() + interval '60 seconds' ELSE t.tugash END
RETURNING son, ctid;
EXECUTE ol('api:42');
EXECUTE ol('api:42');
EXECUTE ol('api:42');
EXECUTE ol('api:7');PREPARE
son | ctid
-----+-------
1 | (0,1)
(1 row)
INSERT 0 1
son | ctid
-----+-------
2 | (0,2)
(1 row)
INSERT 0 1
son | ctid
-----+-------
3 | (0,3)
(1 row)
INSERT 0 1
son | ctid
-----+-------
1 | (0,4)
(1 row)
INSERT 0 1Biz ishga tushirdik (PostgreSQL 17.6). Bitta foydalanuvchi (api:42) uch so'rov qildi — jadvalda uchta jismoniy qator paydo bo'ldi: (0,1), (0,2), (0,3). Ikkitasi endi "o'lik" (dead tuple) — ularni keyinchalik autovacuum tozalaydi (VACUUM va autovacuum). Har API so'rovi = bitta baza yozuvi + bitta o'lik qator.
Kanon kodidagi izohda "WAL yoziladi" deyilgan — bu jadval uchun aniq emas: UNLOGGED jadval WAL'ga yozmaydi, buni yuqoridagi u ko'rsatdi. Qator qulfi va o'lik qatorlar esa haqiqatan bor.
5.2 Mikro o'lchov
Gipotezani alohida tekshirish uchun kanon uchta cheklagichni bir xil sharoitda o'lchadi: 5 000 chaqiruv, 20 parallel. Mikro o'lchov — butun tizimni emas, faqat bitta qismni (bu yerda cheklagichni) yolg'iz o'lchash. "Bitta kalit" (hamma chaqiruv bitta foydalanuvchidan) va "5000 kalit" (har chaqiruv boshqa foydalanuvchidan) qatorlarini juft-juft solishtiring. Kanon chiqishi:
Postgres bitta kalit | 4594 ops/s | p50 4.06 ms, p99 9.03 ms
Redis bitta kalit | 11147 ops/s | p50 1.52 ms, p99 5.06 ms
xotira bitta kalit | 1244307 ops/s | p50 0.01 ms, p99 0.07 ms
Postgres 5000 kalit | 5062 ops/s | p50 3.72 ms, p99 8.65 ms
Redis 5000 kalit | 12260 ops/s | p50 1.48 ms, p99 2.84 ms
xotira 5000 kalit | 1205023 ops/s | p50 0.01 ms, p99 0.09 ms
tezlik_chegaralari: 5000 qator, 744 kB (har chaqiruv — UPDATE: dead tuple, autovacuum)- Postgres (UPSERT)4 594 ops/s
- Redis (MULTI)11 147 ops/s
Manba: IlmHamroh kanon o'lchovi, 2026-11-03: kanon cheklov.ts, 5 000 chaqiruv, 20 parallel, PostgreSQL 18.6 va Redis 8 — Docker'da, bitta mashina; xotira varianti 1.2 mln ops/s (grafikka sig'maydi); bitta yugurish
Uch xulosa:
- Redis 2.4 baravar tez — gipotezaning bir qismi tasdiqlandi.
- Lekin Postgres'da bitta kalit (4 594) va 5 000 kalit (5 062) deyarli teng. Issiq qatorning qulf raqobati kichik ekan — sekinlik qatordan emas, har chaqiruvdagi tarmoq aylanishidan (ilova → baza → ilova).
- Xotira — millionlab: tarmoq yo'q. Lekin u bitta jarayonda ishlaydi — ikki nusxa bo'lsa, har biri o'z hisobini yuritadi va limit ikki baravar "kengayadi".
6. Butun tizimda: Amdahl qonuni
6.1 Kanon natijasi
Kanon Redis cheklagichini ulab, butun tizimni qayta o'lchadi (kanon chiqishi, sana va ichki yorliqlar qisqartirilgan):
Pg cheklagich | 1169 req/s, p50 16 ms, p99 30 ms, kodlar 429×11687, xatolar 0
Redis cheklagich | 1246 req/s, p50 15 ms, p99 29 ms, kodlar 429×12457, xatolar 0
Pg cheklagich (takror) | 1239 req/s, p50 15 ms, p99 27 ms, kodlar 429×12385, xatolar 0Postgres 1 169 va 1 239, Redis 1 246. Takroriy Postgres yugurishi Redis bilan deyarli teng — farq shovqin ichida. Mikro o'lchovdagi 2.4× tizimda ko'rinmadi.
6.2 Nega: Amdahl qonuni
Amdahl qonuni: agar tizim vaqtining f ulushini s baravar tezlashtirsangiz, umumiy tezlashuv = 1 / ((1 − f) + f / s). Cheklagich so'rov vaqtining qancha qismi bo'lishi mumkinligini sinab ko'ramiz:
const tezlashuv = (f, s) => 1 / ((1 - f) + f / s);
for (const f of [0.05, 0.2, 0.5]) {
const k = tezlashuv(f, 2.4).toFixed(2);
console.log(`ulush ${f * 100} % → ${k}×`);
}Konsolda:
ulush 5 % → 1.03×
ulush 20 % → 1.13×
ulush 50 % → 1.41×Cheklagich so'rov vaqtining 5 % i bo'lsa, uni 2.4 baravar tezlashtirish butunni 3 % ga tezlashtiradi — bitta mashinadagi shovqindan kichik. Taqqoslang: kanonning ikki Postgres yugurishi bir-biridan 6 % farq qildi (1 169 va 1 239). 3 % yaxshilanishni bunday o'lchovda ko'rib bo'lmaydi.
Bu qonunning amaliy ma'nosi: avval ulushni o'lchang. Kichik ulushni qancha tezlashtirsangiz ham, umumiy foyda kichik. 3-mashqda uchta variant orasidan eng foydalisini Amdahl bilan tanlaysiz.
6.3 Unda nega Redis cheklagichi qoldi?
Kanon Redis cheklagichini baribir loyihaga qo'shdi — lekin boshqa sabab bilan, va buni halol yozdi:
- har so'rovda baza yozuvi yo'q — bazaning kuchi haqiqiy ma'lumotga qoladi;
- o'lik qatorlar va autovacuum ishi yo'q (5 000 kalit — 744 kB jadval);
- Redis allaqachon bor (kesh, navbat) — yangi bog'liqlik emas;
- bir nechta nusxada hisob umumiy (xotira cheklagichidan farqli).
Ya'ni qaror "tezlik uchun" emas, "resurs va soddalik uchun". REDIS_URL bo'lmasa — Postgres cheklagichi ishlayveradi.
Tekshirib ko'ring: Kanon Redis cheklagichi tizimni tezlashtirmaganini bilgan holda uni qo'shdi. Bu "o'lchovsiz tuzatish" xatosimi?
Javob
Yo'q. Xato — tuzatishni tezlik uchun qilib, natijani o'lchamaslik. Kanon esa o'lchadi, tezlik foydasi yo'qligini tan oldi va qarorni boshqa, tekshirilgan sabablarga (baza yuki, o'lik qatorlar) asosladi. Sardor ham shunday qilganda, xatosi bo'lmasdi — u faqat "tezlashadi" deb o'ylagan va tekshirmagan.
7. Boshqa tezlashtirish vositalari
Kanonda o'lchanmagan, lekin ish joyida doim uchraydigan vositalar. Hammasiga bir xil qoida: avval profil, keyin tuzatish.
| Vosita | Qachon foyda | Izoh |
|---|---|---|
| N+1 ni bitta so'rovga | ro'yxat har element uchun so'rov qilsa | N+1 muammosi; ko'pincha eng katta yutuq |
Indeks va EXPLAIN |
sekin SQL | Sekin so'rovlarni topish |
| Kesh qatlamlari | bir xil ma'lumot ko'p o'qilsa | HTTP kesh (ETag), Redis (kesh naqshlari), jarayon xotirasi |
| Pool hajmi | so'rovlar ulanish kutsa | Connection pooling; katta pool — har doim ham yaxshi emas |
| JSON serializatsiya | katta javoblar, CPU'da stringify |
fast-json-stringify — sxema bilan 2–5× (hujjat bo'yicha, sinalmagan, 2026-11) |
| Siqish (gzip, brotli) | katta javob, sekin tarmoq | odatda reverse proxy'da (zlib); kanonda ilovada yo'q |
Siqish haqida alohida: kanonda compression middleware yo'q — 25-qismda qaror qilingan va 30-qismda saqlangan: siqishni nginx bajaradi (CPU ishini Node event loop'idan olib tashlash). JSON javoblarimiz kichik (ro'yxat — 20 ta vazifa), siqish foydasi ham kichik.
8. Ko'p uchraydigan xatolar
| Xato | Oqibat | To'g'risi |
|---|---|---|
| O'rtacha vaqtga qarash | sekin "dum" ko'rinmaydi | p50, p95, p99 |
| Status kodlarini tekshirmaslik | 429 yoki 500 ni o'lchaysiz | avval kodlar, keyin req/s |
| Profilni startdan boshlash | modul yuklash profilni to'ldiradi | isitib, keyin profil |
| Faqat mikro o'lchov | tizimda farq yo'q | tizimda qayta o'lchash |
| Kichik ulushni optimallashtirish | Amdahl: deyarli foydasiz | avval ulushni o'lchash |
| Bir marta o'lchab xulosa | shovqinni natija deb o'qish | bir necha yugurish, nisbat |
| Bitta foydalanuvchi bilan yuklama | limit o'lchanadi | ko'p foydalanuvchi yoki test limiti |
9. Mashqlar
Mashqlar kurs/mashqlar/31/30-unumdorlik/ da. 2- va 3-mashq yechimlari Node 24.21.0 da tekshirilgan (biz ishga tushirdik).
1-mashq (oson): bo'shliqlarni to'ldiring
- 100 so'rovning eng sekin 1 tasidan tashqari hammasi tezroq bo'lgan chegara — p
- Kanon yuklama testida hamma javoblarning status kodi —
- L = λ × W formulasi — qonuni
- Kanon mikro o'lchovida Redis cheklagichi Postgres'dan necha baravar tez —
- Kichik ulushni tezlashtirish umumiy foyda bermasligini tushuntiradigan qonun —
Yechim
- 99 — p99: 100 tadan 99 tasi shu qiymatdan tez yoki teng.
- 429 — bitta foydalanuvchining 600/daqiqa limiti tugagan.
- Little — tizimdagi so'rovlar = o'tkazuvchanlik × so'rov vaqti.
- 2.4 — 4 594 ga qarshi 11 147 ops/s (bitta kalit).
- Amdahl — 1 / ((1 − f) + f / s).
2-mashq (o'rta): autocannon natijasini tekshirish
tekshir(natija) funksiyasini yozing. Kirish — autocannon'ga o'xshash obyekt: { reqS, ulanishlar, p50, kodlar } (kodlar — { "200": 9000, "429": 1000 }). Funksiya ogohlantirishlar ro'yxatini qaytarsin. Ikki qoida: (1) 2xx ulushi 95 % dan kam bo'lsa — "2xx atigi N %"; (2) Little bo'yicha kutilgan W (ulanishlar / reqS, ms) p50 dan 2 baravardan ko'p farq qilsa — "Little mos emas". Kanonning ikki natijasini tekshiring. Ishora: Object.entries va filter.
Yechim
function tekshir({ reqS, ulanishlar, p50, kodlar }) {
const ogoh = [];
const jami = Object.values(kodlar).reduce((s, n) => s + n, 0);
const ok = Object.entries(kodlar)
.filter(([kod]) => kod.startsWith("2"))
.reduce((s, [, n]) => s + n, 0);
const ulush = (ok / jami) * 100;
if (ulush < 95) ogoh.push(`2xx atigi ${ulush.toFixed(1)} %`);
const w = (ulanishlar / reqS) * 1000;
const nisbat = Math.max(w, p50) / Math.min(w, p50);
if (nisbat > 2) {
ogoh.push(`Little mos emas: W ${w.toFixed(1)} ms, p50 ${p50} ms`);
}
return ogoh;
}
const kanon = { reqS: 1246, ulanishlar: 20, p50: 15,
kodlar: { 429: 12457 } };
const yaxshi = { reqS: 2224, ulanishlar: 20, p50: 8,
kodlar: { 200: 33000, 404: 360 } };
const shubhali = { reqS: 900, ulanishlar: 50, p50: 5,
kodlar: { 200: 9000 } };
console.log(tekshir(kanon));
console.log(tekshir(yaxshi));
console.log(tekshir(shubhali));Konsolda:
[ '2xx atigi 0.0 %' ]
[]
[ 'Little mos emas: W 55.6 ms, p50 5 ms' ]Kanon natijasi birinchi tekshiruvdan o'tmadi — testni qayta qurish kerak. Uchinchi natijada 50 ulanish bilan 900 req/s bo'lsa, har so'rov ~56 ms bo'lishi kerak edi, autocannon esa 5 ms deydi. Bunday nomuvofiqlik odatda mijoz tomonini (autocannon'ning o'zi CPU'ni to'ldirgan) yoki xato ulanishlarni bildiradi. yaxshi dagi 404 lar — 1 % dan kam, ruxsat etilgan. yaxshi va shubhali — namuna uchun o'ylab topilgan natijalar; faqat kanon — haqiqiy o'lchov.
3-mashq (qiyin): qaysi joyni tuzatish kerak
Profil uch nomzodni ko'rsatdi: JSON serializatsiya (so'rov vaqtining 8 %, 2× tezlashtirish mumkin), cheklagich Postgres → Redis (5 %, 2.4×) va N+1 ni bitta JOIN ga (45 %, 5×). Amdahl qonuni bilan har birining umumiy tezlashuvini hisoblang va eng foydalisidan boshlab tartiblang. Ishora: map va toSorted (nusxa bilan o'zgartirish).
Yechim
const tezlashuv = (f, s) => 1 / ((1 - f) + f / s);
const variantlar = [
{ nom: "JSON serializatsiya", ulush: 0.08, tez: 2 },
{ nom: "cheklagich: Pg → Redis", ulush: 0.05, tez: 2.4 },
{ nom: "N+1 → bitta JOIN", ulush: 0.45, tez: 5 },
];
const tartib = variantlar
.map((v) => ({ nom: v.nom, umumiy: tezlashuv(v.ulush, v.tez) }))
.toSorted((a, b) => b.umumiy - a.umumiy);
for (const v of tartib) {
console.log(`${v.nom}: ${v.umumiy.toFixed(2)}×`);
}Konsolda:
N+1 → bitta JOIN: 1.56×
JSON serializatsiya: 1.04×
cheklagich: Pg → Redis: 1.03×N+1 tuzatishi qolgan ikkitasidan o'n baravardan ko'p foyda beradi — chunki u vaqtning katta ulushida. Cheklagich esa oxirgi o'rinda: «Butun tizimda» bo'limidagi kanon natijasi bilan mos. Real ishda ulushlar taxmin emas, profil va trace'dan olinadi.
10. Loyiha qadami: Redis cheklagichi va bosqich commit'i
10.1 RedisCheklagich
src/umumiy/cheklov.ts — PgCheklagich klassidan keyin. Redis'ning INCR (sonni 1 ga oshirish) va PEXPIRE … NX (muddat — faqat birinchi marta) bitta MULTI ichida — atomik, bitta tarmoq aylanishi:
/** Redis klienti (node-redis) — faqat kerakli metodlar */
type RedisMulti = {
incr(k: string): RedisMulti;
pExpire(k: string, ms: number, rejim?: "NX"): RedisMulti;
pTTL(k: string): RedisMulti;
exec(): Promise<unknown[]>;
};
export type CheklovRedis = {
multi(): RedisMulti;
del(k: string): Promise<unknown>;
};
/**
* 31 #30: Postgres cheklagichi har so'rovda bitta UPSERT — bitta
* foydalanuvchining parallel so'rovlari BITTA qatorni yangilaydi
* ("issiq qator"). REDIS_URL bo'lsa — Redis: INCR + PEXPIRE NX bitta
* MULTI'da (atomik, xotirada). Oyna mantig'i Pg bilan bir xil.
*/
export class RedisCheklagich implements Cheklagich {
constructor(
private readonly r: CheklovRedis,
private readonly prefiks = "vz:cheklov:",
) {}
async ol(kalit: string, limit: number, oynaMs: number)
: Promise<Natija> {
const k = this.prefiks + kalit.slice(0, 300);
const javob = await this.r.multi()
.incr(k).pExpire(k, oynaMs, "NX").pTTL(k).exec();
const [son, , ttl] = javob as [number, unknown, number];
const hozir = Date.now();
return natija(son, limit, hozir + Math.max(0, ttl), hozir);
}
async tozala(kalit: string): Promise<void> {
await this.r.del(this.prefiks + kalit.slice(0, 300));
}
}Uch nozik joy:
NX— muddat faqat kalitda muddat yo'q bo'lsa qo'yiladi. Busiz har so'rov oynani qaytadan 60 soniyaga cho'zardi va faol foydalanuvchi hech qachon tiklanmasdi.CheklovRedisturi — butun node-redis klienti emas, faqat 4 metod. Test uchun soxta obyekt yasash oson, va klient o'zgarsa, faqat shu tur tekshiriladi.- Oyna — qat'iy (fixed window), Postgres variantidagi kabi. Ikki cheklagich bir xil shartnomani bajaradi —
Cheklagichinterfeysi.
Kanonda turlar va konstruktor bir qatorda edi — telefon uchun bo'ldik, mantiq aynan.
10.2 Ulash
src/auth/auth-yarat.ts — import va oxirgi parametr:
import { PgCheklagich, RedisCheklagich } from "../umumiy/cheklov.ts";
import type { CheklovRedis } from "../umumiy/cheklov.ts";
export function authYarat(
pool: pg.Pool, config: Config, sirlar: Sirlar, log: Log,
pochta: Pochta | null = null, redis?: CheklovRedis,
): AuthBogliqliklari {
// …
// 31 #30: REDIS_URL bo'lsa — Redis cheklagichi (issiq qator
// yo'q); aks holda Postgres (28-qism)
cheklagich: redis === undefined
? new PgCheklagich(pool)
: new RedisCheklagich(redis),src/auth/auth.module.ts — fabrika saqlash qatlamidagi Redis klientini uzatadi (u kesh uchun allaqachon bor):
return authYarat(pool, c, sirlar, log, pochta, s.redis);10.3 Test
test/cheklov-redis.test.ts — Postgres cheklagichi testlari bilan bir xil shartnoma. Redis kerak: TEST_REDIS_URL bo'lmasa — o'tkaziladi. Kanon faylidagi uzun qatorlarni telefon uchun bo'ldik, tekshiruvlar aynan:
// cheklov-redis.test.ts — 31-qism #30: RedisCheklagich — Pg
// cheklagichi bilan bir xil shartnoma (qat'iy oyna, limit,
// tiklanish, tozalash). TEST_REDIS_URL kerak.
import { afterAll, test } from "vitest";
import assert from "node:assert/strict";
import { createClient } from "redis";
import { RedisCheklagich } from "../src/umumiy/cheklov.ts";
const URL = process.env["TEST_REDIS_URL"];
if (URL === undefined) {
test.skip("RedisCheklagich — TEST_REDIS_URL yo'q", () => {});
} else {
const r = createClient({ url: URL });
await r.connect();
afterAll(() => r.quit());
test("limit 3: 4-chi rad; tiklanish 1..60 s; oyna tugagach yana "
+ "1 dan; tozala() — darhol", async () => {
const prefiks = `vz:test:cheklov:${String(process.pid)}:`;
const c = new RedisCheklagich(r, prefiks);
await c.tozala("k");
const n = [];
for (let i = 0; i < 4; i++) n.push(await c.ol("k", 3, 60_000));
assert.deepEqual(n.map((x) => [x.son, x.ruxsat, x.qoldi]), [
[1, true, 2], [2, true, 1], [3, true, 0], [4, false, 0],
]);
const tiklanish = n[3]?.tiklanishS ?? 0;
assert.ok(tiklanish >= 1 && tiklanish <= 60);
await c.tozala("k");
assert.equal((await c.ol("k", 3, 60_000)).son, 1);
const q = await c.ol("q", 1, 150);
await new Promise((ok) => setTimeout(ok, 250));
// oyna tugadi — PEXPIRE NX bilan kalit o'chdi
assert.equal((await c.ol("q", 1, 150)).son, 1);
assert.equal(q.son, 1);
await c.tozala("k");
});
}Limit 3 da to'rtinchi so'rov rad (ruxsat: false), tiklanish 1–60 s orasida, tozala() hisobni darhol nolga qaytaradi. Oxirgi qism eng muhimi: 150 ms lik oyna tugagach hisob yana 1 dan boshlandi — demak PEXPIRE NX kalitni o'z vaqtida o'chirdi. Kanon testni Redis 8 bilan yurgizdi; bizda Redis yo'q, test o'tkazildi (↓ … skipped).
10.4 30-qismdan qolgan xato: Problem title
30-qismda 404 javobining title i inglizcha qolgan edi ("Not Found"), API esa o'zbekcha. RFC 9457 about:blank turida title sifatida HTTP holat iborasini beradi, lekin uni mahalliylashtirishga ruxsat beradi ("MAY be localized"). Mashina status va type ga qaraydi, title — odam uchun. src/umumiy/problem.ts:
/**
* 31 (30-qism ochiq xatosi: 404 `title` inglizcha edi). RFC 9457
* §4.2.1: about:blank da title — HTTP holat iborasi, lekin uni
* MAHALLIYLASHTIRISH mumkin ("MAY be localized"). Mashina `status` va
* `type` ga qaraydi (title'ga emas).
*/
export const HOLAT_SARLAVHALARI: Readonly<Record<number, string>> = {
400: "Noto'g'ri so'rov", 401: "Kirish talab qilinadi",
403: "Ruxsat yo'q", 404: "Topilmadi",
405: "Metod ruxsat etilmagan", 406: "Javob formati qo'llanmaydi",
409: "Ziddiyat", 410: "O'chirilgan", 412: "Shart bajarilmadi",
413: "So'rov juda katta", 415: "Tana formati qo'llanmaydi",
422: "Ma'lumot noto'g'ri", 428: "Shart talab qilinadi",
429: "So'rovlar juda ko'p", 500: "Ichki xato",
502: "Tashqi xizmat xatosi", 503: "Xizmat vaqtincha mavjud emas",
504: "Tashqi xizmat javob bermadi",
};problemYasa ichida birinchi qator:
const umumiy = HOLAT_SARLAVHALARI[holat]
?? STATUS_CODES[holat] ?? "Xato";Ro'yxatda yo'q kod — avvalgidek Node'ning inglizcha iborasi. O'zgarish haqida: 25-qismdagi Problem Details darslarida va testlarida "Bad Request", "Not Found", "Method Not Allowed" ko'rgansiz — endi ular "Noto'g'ri so'rov", "Topilmadi", "Metod ruxsat etilmagan". Shuning uchun uchta eski test kutuvi yangilanadi: test/api.test.ts da title: "Noto'g'ri so'rov", test/xato.test.ts da "Topilmadi" va "Metod ruxsat etilmagan". Frontend title ni ko'rsatsa, endi o'zbekcha matn ko'rinadi — status bo'yicha ishlaydigan kod esa o'zgarmaydi.
10.5 Versiya va hujjat
package.json — "version": "4.0.0-alfa.4". API.md ga yangi bo'lim:
## Kuzatuv (31-qism)
- `GET /health/live` — jarayon tirik; `GET /health/ready` — baza javob beradi va yopilish boshlanmagan (SIGTERM dan keyin 503); `GET /health/startup` — ishga tushish tugadi.
- `GET /metrics` — Prometheus (RED: `http_sorovlar_jami{metod,yol,status}`, `http_sorov_davomiyligi_soniya`; yo'l — marshrut shabloni); `METRIKA_TOKEN` bo'lsa Bearer.
- Problem Details `title` — o'zbekcha (RFC 9457 §4.2.1 mahalliylashtirishga ruxsat beradi): `Topilmadi`, `Ma'lumot noto'g'ri` …O'zgarishlar tarixi dagi v4.0.0-alfa qatoriga: "/metrics, /health/startup, graceful drain, Problem title o'zbekcha, AI chidamliligi (retry, circuit breaker, bulkhead), Redis tezlik cheklagichi".
10.6 Bosqich tekshiruvi
Besh darsning hammasi joyida. To'liq tekshiruv:
npm run checkBiz loyiha nusxasida (Node 24.21.0, bazasiz — baza va Redis talab qiladigan testlar o'tkaziladi): tsc --noEmit va eslint --max-warnings=0 xatosiz, nest build muvaffaqiyatli, testlar:
Test Files 42 passed | 24 skipped (66)
Tests 133 passed | 38 skipped (171)Bosqich boshida (feature flag'lar darsidan keyin) xuddi shu sharoitda 128 o'tgan, 37 o'tkazilgan edi: +5 test (kuzatuv 2, chidamlilik 3) va +1 o'tkazilgan (cheklov-redis). Kanon esa to'liq muhitda tekshirdi: PostgreSQL 18.6, Redis, MinIO, Mailpit — Docker'da. Natija: tip, lint, build — 0 xato; Vitest 333 o'tdi, 1 o'tkazildi; migratsiyalar 26, drift 0; jonli API uch saqlash rejimida — status kodlari oldingi bosqich bilan aynan.
Jonli tekshiruv — biz ishga tushirdik (xotira rejimi, METRIKA_TOKEN bilan; sorovId har safar boshqa):
$ curl http://127.0.0.1:5595/yoq-manzil
{"type":"about:blank","title":"Topilmadi","status":404,"detail":"GET /yoq-manzil — bunday manzil yo'q","instance":"/yoq-manzil","sorovId":"3fea25af-116f-4d4f-8e03-72d5747822b4"}
$ curl -X DELETE http://127.0.0.1:5595/v1/teglar
{"type":"about:blank","title":"Metod ruxsat etilmagan","status":405,"detail":"DELETE bu manzilda yo'q","instance":"/v1/teglar","sorovId":"55a82dd3-7eed-4558-91d1-d8afd9e8d65c"}
$ curl -i http://127.0.0.1:5595/metrics
HTTP/1.1 401 Unauthorized10.7 Commit
git add -A
M="feat: graceful drain (readiness 503 + kechikish), /health/startup,"
M+=" Prometheus /metrics, AI chidamliligi (retry, circuit breaker,"
M+=" bulkhead), Redis tezlik cheklagichi; fix: buyruq CLS konteksti"
M+=" va logi stderr'ga, Problem title o'zbekcha"
git commit -m "$M"git add -A dan oldin git status ga qarang: .env, dist/, coverage/ kirmasin. Commit besh darsning hammasini qamraydi: graceful shutdown, health, kuzatuv, chidamlilik va unumdorlik.
11. Real ishda
- Qayerda uchraydi: "API sekin" shikoyati, bulut hisobining o'sishi, Black Friday oldidan sig'imni rejalash. Har holatda birinchi savol — "qayerda o'lchadingiz?".
- Vositalar: autocannon, k6, Artillery (yuklama);
--cpu-prof, Clinic.js, 0x, Chrome DevTools (profil);EXPLAIN ANALYZE,pg_stat_statements(baza); o'tgan darsdagi/metricsva trace'lar (prodakshnda). - Intervyu savollari: "p99 nima va nega o'rtacha emas?", "Unumdorlik muammosini qanday qidirasiz?", "Amdahl qonuni nima deydi?", "Yuklama testi natijasiga qachon ishonmaysiz?"
- Jamoada: optimallashtirish PR'iga "oldin/keyin" o'lchovi, mashina va yugurishlar soni bilan qo'shiladi. O'lchovsiz "tezlashtirdim" — qabul qilinmaydi.
Xulosa
- Sikl: o'lcha → profil → gipoteza → mikro o'lchov → tuzat → tizimda qayta o'lcha.
- O'rtacha aldaydi — p50, p95, p99; Little qonuni o'lchovni tekshiradi.
- Yuklama natijasidan oldin status kodlariga qarang: kanon testi 429 ni o'lchagan.
- Qisqa profil ishga tushishni ko'rsatadi — isitib, keyin profil.
- Amdahl: 5 % ulushni 2.4× tezlashtirish — butun tizimga ~3 %; avval ulushni o'lchang.
- Redis cheklagichi tezlik uchun emas, baza yuki va o'lik qatorlar uchun qoldi — qaror o'lchov bilan asoslandi.
Keyingi dars: Asinxron komponentlarni testlash — kesh muddati, retry kutishi va breaker vaqtini soxta soat bilan millisekundgacha aniq sinaymiz.
Manbalar
- autocannon: https://github.com/mcollina/autocannon (sinalmagan, 2026-11 — kanon ishlatgan)
- Node.js 24 hujjati —
--cpu-prof: https://nodejs.org/docs/latest-v24.x/api/cli.html#--cpu-prof - PostgreSQL 18 hujjati — Unlogged tables (
CREATE TABLE … UNLOGGED): https://www.postgresql.org/docs/18/sql-createtable.html#SQL-CREATETABLE-UNLOGGED - PostgreSQL 18 hujjati — Heap-Only Tuples va MVCC: https://www.postgresql.org/docs/18/storage-hot.html (sinalmagan, 2026-11)
- Redis hujjati —
INCR(rate limiter naqshi),PEXPIRE(NX): https://redis.io/docs/latest/commands/incr/ , https://redis.io/docs/latest/commands/pexpire/ (sinalmagan, 2026-11) - fast-json-stringify: https://github.com/fastify/fast-json-stringify (sinalmagan, 2026-11)
- RFC 9457 — Problem Details,
titlemahalliylashtirish: https://www.rfc-editor.org/rfc/rfc9457.html#section-4.2.1 - Little qonuni va Amdahl qonuni — umumiy manbalar: https://en.wikipedia.org/wiki/Little%27s_law , https://en.wikipedia.org/wiki/Amdahl%27s_law
- Kanon (IlmHamroh, 2026-11-03): yuklama (
autocannon, 20 ulanish, 15 s; PostgreSQL 18.6 va Redis 8 — Docker'da), CPU profil, cheklagich mikro o'lchovi, loyihaning shu bosqichi tekshiruvi (Vitest 333 + 1 skip). SQL bloklari (PostgreSQL 17.6, ICU, UTC), modellar, mashq yechimlari, loyiha nusxasining to'liq tekshiruvi va jonlicurl— biz ishga tushirdik (2026-11-03, Node 24.21.0, NestJS 12.2.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!