Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Hovuz qanday ishlaydi
- 2.1 Ulanishning hayoti
- 2.2 Hisoblagichlar
- 2.3 Sozlamalar
- 3. Hovuz hajmi: qancha?
- 3.1 Kanon o'lchovi
- 3.2 Nazariy hisob
- 3.3 Formula
- 4. release() unutish: pool leak
- 4.1 Kanon tajribasi
- 4.2 To'g'ri naqsh: try/finally
- 5. max_connections va ko'p instans
- 5.1 101-ulanish
- 5.2 Ko'p instans hisobi
- 6. Hovuz xatolari va boshqa sozlamalar
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): instanslar uchun max
- 3-mashq (qiyin): leak'ni ushlang
- 9. Node qadami: hovuz sozlamalari vazifalar-api da
- 9.1 Sozlamalar — config.ts
- 9.2 Hovuz — omborYarat
- 9.3 Yopish — jarayon.ts
- 9.4 Test
- 10. Real ishda
- Xulosa
- Manbalar
Connection pooling: hovuz hajmi, release unutish va max_connections
Qisqacha: Connection pooling — Postgres'ga bir nechta ulanishni ochib qo'yib, so'rovlarga navbat bilan qayta ishlatish. Kanon o'lchovi: yangi ulanish 12.7 ms, hovuzdan — 1.1 ms. Hovuz hajmi (
max) ko'p bo'lsa yaxshi emas: qisqa so'rovlarda 10 dan keyin foyda kam, Postgres esa sukut bo'yicha atigi 100 ta ulanish beradi — 101-chisi53300 sorry, too many clients alreadybilan rad etiladi. Eng xavfli xato —release()ni unutish: kanonda 3 ta qaytarilmagan ulanishdan keyin 4-so'rovtimeout exceeded when trying to connectbilan yiqildi,pool.end()esa abadiy osilib qoldi.
Bu darsda
- Ulanish nega qimmat ekanini va hovuz buni qanday hal qilishini tushuntirasiz.
max,idleTimeoutMillis,connectionTimeoutMillisni o'z ilovangiz uchun tanlaysiz.- Hovuz hajmini kanon o'lchovi va formula asosida asoslaysiz.
release()unutilishini (pool leak) taniysiz vatry/finallybilan oldini olasiz.- Ko'p server instansi
max_connectionsni qanday tugatishini hisoblaysiz.
Oldin bilishingiz kerak: node-postgres (pg) asoslari, postgres.js va boshqa driverlar, cluster va ko'p yadroli masshtablash.
1. Nega bu kerak?
node-postgres asoslari darsida Pool ni "taksi parki" deb atadik va "tafsiloti keyin" dedik. Endi tafsilot.
Postgres har ulanish uchun serverda alohida jarayon (process) ishga tushiradi. Ulanish ochish — TCP qo'l berib ko'rishish, autentifikatsiya, yangi jarayon, uning xotirasi. Kanon buni o'lchadi: 50 marta "ulan → SELECT 1 → uzil" va 50 marta tayyor ulanishdan SELECT 1:
bitta so'rov: har safar yangi ulanish 12.7 ms, pool'dan 1.12 ms11 baravar farq. Sekundiga 200 so'rov keladigan API har so'rovda yangi ulanish ochsa, faqat ulanishga 2.5 soniya protsessor vaqti ketadi — ishning o'zidan oldin.
Ikkinchi muammo — chegara. Postgres jarayonlari xotira oladi, shuning uchun serverda ulanishlar soni cheklangan: max_connections, sukut — 100. Hovuz bu ikkalasini hal qiladi: kam sonli ulanishni ochib qo'yadi va ularni minglab so'rovlar orasida bo'lishadi.
O'xshatish: oshxonadagi qozonlar. Har buyurtma uchun yangi qozon sotib olinmaydi (qimmat, sekin), lekin cheksiz qozon ham qo'yib bo'lmaydi (plita joyi cheklangan). To'g'ri son — plitadagi o'choqlar soniga qarab.
2. Hovuz qanday ishlaydi
2.1 Ulanishning hayoti
stateDiagram-v2
[*] --> band: yangi ochildi (connect)
bosh --> band: so'rov oldi
band --> bosh: release()
bosh --> [*]: idleTimeoutMillis o'tdi
band --> [*]: release(true) yoki xatoDiagrammada ulanish ikki holatda: band (so'rov bajaryapti yoki client qo'lda) va bo'sh (hovuzda kutyapti). Bo'sh turgani uzoq qolsa — yopiladi. Hovuzga yangi so'rov kelganda: bo'sh ulanish bo'lsa — beriladi; bo'lmasa va jami max dan kam bo'lsa — yangisi ochiladi; max ga yetgan bo'lsa — so'rov navbatda kutadi.
2.2 Hisoblagichlar
pg hovuzi uchta sonni ko'rsatadi: totalCount (jami ochiq), idleCount (bo'sh), waitingCount (navbatda kutayotgan so'rovlar). max: 2 li hovuzga to'rtta 100 ms lik so'rov berib, ularni kuzatamiz:
import pg from "pg";
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
max: 2,
idleTimeoutMillis: 1000,
connectionTimeoutMillis: 2000,
});
const holat = () =>
`jami ${pool.totalCount}, bo'sh ${pool.idleCount}, ` +
`kutyapti ${pool.waitingCount}`;
pool.on("connect", () => console.log("yangi ulanish ochildi"));
const ishlar = [1, 2, 3, 4].map((n) =>
pool.query("SELECT $1::int AS n, pg_sleep(0.1)", [n]),
);
console.log("darhol:", holat());
await Promise.all(ishlar);
console.log("tugagach:", holat());
await new Promise((r) => setTimeout(r, 1500));
console.log("1,5 s dan keyin:", holat());
await pool.end();Biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, pg 8.23.1, Windows 11):
darhol: jami 2, bo'sh 0, kutyapti 2
yangi ulanish ochildi
yangi ulanish ochildi
tugagach: jami 2, bo'sh 2, kutyapti 0
1,5 s dan keyin: jami 0, bo'sh 0, kutyapti 0Qatorma-qator:
- darhol — 4 ta so'rov keldi: 2 tasi ulanish oldi (ular hali ochilyapti), 2 tasi navbatda.
totalCountochilayotgan ulanishlarni ham sanaydi. - yangi ulanish ochildi ×2 —
"connect"hodisasi (EventEmitter asoslari) faqat ikki marta: uchinchi va to'rtinchi so'rov yangi ulanish ochmadi, birinchi ikkitasi bo'shashini kutdi. - tugagach — ikkala ulanish bo'sh, hovuzda turibdi.
- 1,5 s dan keyin —
idleTimeoutMillis: 1000o'tdi, bo'sh ulanishlar yopildi. Keyingi so'rov kelsa — qaytadan ochiladi.
2.3 Sozlamalar
| Sozlama | pg sukut qiymati |
Nima |
|---|---|---|
max |
10 | eng ko'p ulanish |
idleTimeoutMillis |
10 000 | bo'sh ulanish qancha yashaydi; 0 — abadiy |
connectionTimeoutMillis |
0 (cheksiz!) | ulanish olishni qancha kutadi |
maxUses |
cheksiz | ulanish N ta so'rovdan keyin almashtiriladi |
Uchinchi qatorga e'tibor bering: sukut bo'yicha so'rov ulanishni cheksiz kutadi. Hovuz to'lib qolsa (leak yoki juda sekin so'rovlar) — HTTP so'rovlar javobsiz osilib qoladi, foydalanuvchi aylanayotgan belgini ko'radi. Production'da doim chegara qo'ying (masalan 2–5 soniya) — shunda ilova tez "baqiradi" va siz uni log'da ko'rasiz.
Tekshirib ko'ring:
max: 2li hovuzga 4 ta so'rov kelganda nechta ulanish ochiladi? Nega 4 ta emas?
Javob
2 ta. max — qat'iy chegara: hovuz undan ortiq ochmaydi, ortiqcha so'rovlar navbatda kutadi va bo'shagan ulanishni oladi. Biz ishga tushirgan chiqishda "connect" hodisasi aynan ikki marta chiqdi.
3. Hovuz hajmi: qancha?
3.1 Kanon o'lchovi
Kanon 400 ta parallel so'rovni turli max bilan bajardi (har biri 3 marta, mediana). Ikki xil so'rov: qisqa (indeks bo'yicha bitta qator) va 20 ms kutadigan (pg_sleep(0.02) — sekin disk yoki boshqa tranzaksiyani kutish taqlidi).
400 parallel (qisqa): max 1: 561 ms | max 2: 280 ms | max 5: 149 ms | max 10: 84 ms | max 20: 52 ms | max 50: 39 ms
400 parallel (20ms): max 1: 9322 ms | max 2: 4720 ms | max 5: 1890 ms | max 10: 935 ms | max 20: 457 ms | max 50: 200 ms| max | qisqa so'rov |
|---|---|
| 1 | 561 |
| 2 | 280 |
| 5 | 149 |
| 10 | 84 |
| 20 | 52 |
| 50 | 39 |
Manba: IlmHamroh kanon o'lchovi t44, 2026-10-24: Node 24.21.0, pg 8.23.1, PostgreSQL 18.6 (Docker, 16 CPU), Windows 11, i5-12500H; 3 marta mediana
Chiziqqa qarang: 1 → 10 ulanishda vaqt 7 baravar kamaydi (561 → 84 ms). 10 → 50 da — atigi 2 baravar (84 → 39), ulanish esa 5 baravar ko'p. Egri chiziq "tirsak" hosil qiladi — undan keyin har yangi ulanish kam foyda beradi.
20 ms li so'rovlarda esa boshqa manzara: vaqt deyarli to'g'ri proporsional kamaydi. Nega — o'zimiz hisoblaymiz.
3.2 Nazariy hisob
Agar har so'rov aniq 20 ms davom etsa va max ta ulanish parallel ishlasa, 400 so'rov 400 / max "to'lqin"da bajariladi:
const olchov = {
1: 9322, 2: 4720, 5: 1890, 10: 935, 20: 457, 50: 200,
};
for (const [max, kanon] of Object.entries(olchov)) {
const nazariy = Math.ceil(400 / max) * 20;
const ortiqcha = Math.round((kanon / nazariy - 1) * 100);
console.log(
`max ${max}: nazariy ${nazariy}, kanon ${kanon} (+${ortiqcha}%)`,
);
}Konsolda:
max 1: nazariy 8000, kanon 9322 (+17%)
max 2: nazariy 4000, kanon 4720 (+18%)
max 5: nazariy 1600, kanon 1890 (+18%)
max 10: nazariy 800, kanon 935 (+17%)
max 20: nazariy 400, kanon 457 (+14%)
max 50: nazariy 160, kanon 200 (+25%)Kanon raqamlari nazariyaga yaqin: 1 dan 20 gacha har doim +14–18% (tarmoq aylanishi va pg_sleep aniqligi). Faqat 50 da ortiqcha +25% ga o'sdi — 50 ta ulanishni boshqarishning o'z narxi bor. Demak pg_sleep kabi kutish so'rovlarida ulanishlar bir-biriga xalaqit bermaydi: Postgres jarayoni uxlayapti, protsessor bo'sh. Qisqa so'rovlarda esa ulanishlar protsessor uchun raqobatlashadi va qo'shimcha ulanish yordam bermaydi.
3.3 Formula
PostgreSQL wiki'sidagi mashhur boshlang'ich formula ("Number Of Database Connections"):
ulanishlar ≈ (yadrolar × 2) + effektiv disklar soni4 yadroli server, SSD → taxminan 9–10. Bu — butun baza uchun faol ulanishlar soni, bitta ilova uchun emas. G'oyasi: yadrodan ancha ko'p faol jarayon bo'lsa, ular navbat bilan protsessor oladi va har biri sekinlashadi; "× 2" — jarayonlar ba'zan disk yoki tarmoqni kutishini hisobga oladi.
Amaliy tartib:
- Formuladan boshlang (masalan
max: 10—pgsukutining o'zi). - Yuklama testi bilan o'lchang:
waitingCountva so'rov vaqtini kuzating. - So'rovlar ko'p kutsa (sekin tashqi I/O, qulflar) — kattalashtiring; protsessorga og'ir bo'lsa — kattalashtirish yordam bermaydi, so'rovni tezlashtiring (EXPLAIN ANALYZE'ni o'qish).
Maslahat: Kanon chizig'idagi "tirsak" 10 atrofida chiqdi — konteynerda 16 ta protsessor bor edi. Kichik VPS'da (2 yadro) tirsak ancha oldin keladi. O'z serveringizda o'lchang.
4. release() unutish: pool leak
4.1 Kanon tajribasi
pool.query() ulanishni o'zi qaytaradi. Lekin tranzaksiya uchun ulanishni qo'lda olamiz: const client = await pool.connect() — va qaytarish bizning vazifamiz. Unutilsa — pool leak (hovuz sizishi, xotira sizishi kabi): ulanish abadiy "band" bo'lib qoladi.
const p = new pg.Pool({
connectionString: url,
max: 3,
connectionTimeoutMillis: 1000,
});
for (let i = 0; i < 3; i++) {
const k = await p.connect();
await k.query("SELECT 1");
// k.release() — unutildi
}
await p.query("SELECT 1"); // 4-so'rovKanon chiqishi:
leak'dan keyin: { total: 3, idle: 0, waiting: 0 }
4-so'rov: 1007 ms dan keyin — timeout exceeded when trying to connectUchta ulanish ochiq, bittasi ham bo'sh emas. To'rtinchi so'rov navbatga turdi va connectionTimeoutMillis (1000 ms) dan keyin xato oldi: "ulanishga urinishda kutish vaqti tugadi". Xato matni chalg'itadi — "ulanish" deydi, lekin baza sog'lom; muammo sizning kodingizda. connectionTimeoutMillis sukut 0 bo'lganda esa bu so'rov abadiy kutardi.
Kanonning ikkinchi topilmasi: qaytarilmagan client bor paytda pool.end() ham abadiy kutadi. Birinchi urinishda skript 400 soniyadan ortiq osilib qoldi va qo'lda to'xtatildi. Kanon skripti endi qaytarilmagan ulanishlarni release(true) bilan yopadi (true — "bu ulanishni hovuzga qaytarma, yop").
4.2 To'g'ri naqsh: try/finally
const client = await pool.connect();
try {
await client.query("SELECT 1");
} finally {
client.release(); // xato bo'lsa ham qaytadi
}Kanon chiqishi:
try/finally release bilan 10 marta: { total: 1, idle: 1 }10 marta oldi-qaytardi — doim bitta ulanish ishlatildi. finally bloki try ichida xato tashlansa ham bajariladi — shuning uchun leak bo'lmaydi.
Diqqat:
release()ni ikki marta chaqirish ham xato:pgRelease called on client which has already been released to the pool.deb xato tashlaydi. Qoida: bittaconnect()— bittarelease(),finallyichida.
Tekshirib ko'ring: API'da har HTTP so'rovda
pool.connect()bor, lekin xato bo'lgan yo'ldarelease()chaqirilmaydi.max: 10. Ilova qachon "osiladi"?
Javob
10-xatoli so'rovdan keyin: har xato bitta ulanishni yo'qotadi. 10 tasi yo'qolgach, hovuzda bo'sh ulanish qolmaydi va hamma so'rovlar navbatda kutadi — sog'lomlari ham. Ilova bir necha soat yaxshi ishlab, keyin to'satdan "o'lishi" — pool leak'ning tipik belgisi.
5. max_connections va ko'p instans
5.1 101-ulanish
Kanon 120 ta alohida Client ochishga urindi:
server: { max: '100', zaxira: '3', hozir: '9' } | konteyner CPU: 16 (Docker VM)
ochildi 100; 101-ulanish: 53300 sorry, too many clients already53300 — too_many_connections: "kechirasiz, mijozlar juda ko'p". zaxira: '3' — superuser_reserved_connections: oxirgi 3 ta joy faqat superuser uchun (administrator to'lib qolgan bazaga baribir kira olishi uchun). Kanon postgres superuser bilan ulangani uchun ularni ham oldi; oddiy foydalanuvchi bilan chegara undan ham oldin keladi. (hozir: '9' — pg_stat_activity dagi hamma jarayonlar, Postgres'ning o'z fon jarayonlari ham; ular max_connections ga kirmaydi.)
5.2 Ko'p instans hisobi
Bitta ilova uchun max: 10 — yaxshi. Lekin ilova bitta nusxada ishlamaydi:
- cluster — har yadroga bitta Node jarayoni, har birida o'z hovuzi;
- bir nechta server (yuklama balanslovchi ortida);
- deploy paytida eski va yangi versiya bir vaqtda ishlaydi.
const serverlar = 3;
const jarayonlar = 4; // cluster: har serverda 4 ta Node
const max = 10;
const deploy = 2; // yangilashda eski + yangi
const jami = serverlar * jarayonlar * max * deploy;
console.log("eng ko'p ulanish:", jami); // eng ko'p ulanish: 240
console.log("sig'adimi:", jami <= 100 - 3); // sig'adimi: false240 > 97 — deploy paytida yangi jarayonlar 53300 oladi. Bu xato hisob qog'ozda emas, aynan eng xavfli paytda — yangilash paytida chiqadi.
Uch yo'l:
- Har jarayonning
maxini kamaytirish:97 / (3 × 4 × 2) ≈ 4. max_connectionsni oshirish — har ulanish xotira oladi va §3 dagi raqobat ortadi; cheksiz emas.- Ilova va Postgres orasiga tashqi pooler qo'yish — yuzlab mijoz ulanishini o'nlab server ulanishiga aylantiradi. Bu — PgBouncer va tashqi pooler'lar darsi. Kanon bu hisobni faqat qog'ozda qildi — ko'p instans sinalmagan.
6. Hovuz xatolari va boshqa sozlamalar
pool.on("error", …)—pghujjati ogohlantiradi: hovuzdagi bo'sh ulanish uzilsa (masalan Postgres qayta ishga tushdi), hovuz"error"hodisasini chiqaradi. Tinglovchi bo'lmasa — Node jarayoni yiqiladi (EventEmitter chuqur —"error"qoidasi). Hech bo'lmasa log yozadigan tinglovchi qo'ying. Kanon keyinroq qo'shni tuzoqni topdi: hovuzdan olingan (band) client'ning ulanishi uzilsa, xatopool.on("error")ga bormaydi — client'ning o'z"error"hodisasiga chiqadi va tinglovchisiz jarayon yiqiladi. Bu Tranzaksiya asoslari mavzusi (yechim —client.on("error", …)).idleTimeoutMillis— kanon:300bilan 10 ta ulanish 600 ms dan keyin 0 ga tushdi (oldin 10 ulanish, 600 ms dan keyin 0). Kichik qiymat — bo'sh paytda bazani bo'shatadi, lekin keyingi so'rov yana 12.7 ms to'laydi.allowExitOnIdle: true— bo'sh hovuz skriptning tugashiga xalaqit bermasin (skript va testlar uchun).statement_timeout— bitta so'rov chegarasi (Postgres sozlamasi;pgdastatement_timeoutopsiyasi). Hovuz hajmi qanchalik yaxshi bo'lmasin, bitta 30 soniyalik so'rov ulanishni 30 soniya band qiladi.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Bir necha soatdan keyin hamma so'rov osiladi | release() unutilgan (leak) |
try { … } finally { client.release() } |
timeout exceeded when trying to connect |
hovuz to'la: leak yoki sekin so'rovlar | leak'ni qidiring; waitingCount |
53300 sorry, too many clients already |
instanslar × max > max_connections |
max ni kamaytiring yoki pooler |
Skript pool.end() da osiladi |
qaytarilmagan client | hamma connect() ga release() |
| Jarayon kutilmaganda yiqiladi | pool.on("error") yo'q |
tinglovchi qo'shing |
max: 100 qo'yildi — tezlashmadi |
protsessor raqobati | o'lchang; so'rovni tezlashtiring |
8. Mashqlar
Mashqlar kurs/mashqlar/26/44-pool/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
pghovuzining sukutmaxi —- Bo'sh ulanishni yopish vaqti —
- Ulanishni kutish chegarasining sukut qiymati — (cheksiz)
- Ulanishlar ko'pligi xatosining SQLSTATE kodi —
- Kanon: yangi ulanish 12.7 ms, hovuzdan — ms
Yechim
- 10 — formula bo'yicha 4 yadroli serverga yaqin.
- idleTimeoutMillis — sukut 10 000 ms.
- 0 — shuning uchun production'da
connectionTimeoutMillisni aniq yozing. - 53300 —
too_many_connections. - 1.1 (aniqrog'i 1.12) — 11 baravar farq.
2-mashq (o'rta): instanslar uchun max
maxHisobla({ maxConnections, zaxira, serverlar, jarayonlar, deploy, boshqalar }) funksiyasini yozing: har Node jarayoni uchun xavfsiz max ni qaytarsin (boshqalar — psql, migratsiya, monitoring uchun qoldiriladigan ulanishlar). Natija butun son, kamida 1 bo'lsin — 1 dan kam chiqsa, xato tashlasin ("pooler kerak"). Sinang: 100, 3, 3, 4, 2, 5 va 100, 3, 10, 8, 2, 5.
Yechim
function maxHisobla(s) {
const bosh = s.maxConnections - s.zaxira - s.boshqalar;
const nusxalar = s.serverlar * s.jarayonlar * s.deploy;
const max = Math.floor(bosh / nusxalar);
if (max < 1) {
throw new Error(
`${nusxalar} ta jarayonga ${bosh} ulanish yetmaydi` +
" — pooler kerak",
);
}
return max;
}
const asos = {
maxConnections: 100, zaxira: 3, boshqalar: 5, deploy: 2,
};
const kichik = { ...asos, serverlar: 3, jarayonlar: 4 };
console.log(maxHisobla(kichik)); // 3
try {
maxHisobla({ ...asos, serverlar: 10, jarayonlar: 8 });
} catch (e) {
console.log(e.message);
}Konsolda:
3
160 ta jarayonga 92 ulanish yetmaydi — pooler kerakMath.floor — pastga yaxlitlash: 92 / 24 = 3.83 → 3 (4 desak, 96 > 92). Ikkinchi holatda 160 ta jarayon — har biriga bittadan ham yetmaydi. Bu raqamlar — PgBouncer darsiga to'g'ridan-to'g'ri ko'prik.
3-mashq (qiyin): leak'ni ushlang
pg hovuzini o'rab oluvchi kuzatuvchi(pool, ms) funksiyasini yozing: pool.on("acquire") (client berilganda) va pool.on("release") hodisalari bilan har client qachon olinganini yozib borsin; ms dan ko'p qaytarilmagan client bo'lsa — console.log("leak shubhasi: N ta client M ms dan beri"). Tekshirish: max: 3, bitta client oling va qaytarmang, bittasini olib qaytaring; 300 ms dan keyin tekshiring. Oxirida qaytarilmaganini release(true) bilan yoping (aks holda pool.end() osiladi — kanon topilmasi).
Yechim
import pg from "pg";
function kuzatuvchi(pool, ms) {
const olingan = new Map();
pool.on("acquire", (client) => olingan.set(client, Date.now()));
pool.on("release", (_err, client) => olingan.delete(client));
return () => {
const hozir = Date.now();
const eski = [...olingan.values()].filter((t) => hozir - t > ms);
if (eski.length > 0) {
console.log(
`leak shubhasi: ${eski.length} ta client ${ms} ms dan beri`,
);
} else {
console.log("leak yo'q");
}
};
}
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
max: 3,
});
const tekshir = kuzatuvchi(pool, 200);
const unutilgan = await pool.connect();
const yaxshi = await pool.connect();
yaxshi.release();
await new Promise((r) => setTimeout(r, 300));
tekshir();
unutilgan.release(true);
tekshir();
await pool.end();Biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, pg 8.23.1):
leak shubhasi: 1 ta client 200 ms dan beri
leak yo'q"release" hodisasi release(true) da ham chiqadi — shuning uchun ikkinchi tekshiruv toza. Map kaliti — client obyektining o'zi. Production'da bunday kuzatuvni setInterval bilan ishga tushirib, log'ga yozadilar; waitingCount o'sishi ham ogohlantirish belgisi.
9. Node qadami: hovuz sozlamalari vazifalar-api da
Avval tajriba: pg-tajriba papkasida «Hisoblagichlar» bo'limidagi misolni max: 2 va max: 4 bilan ishga tushiring — "connect" hodisasi necha marta chiqadi? Keyin «release() unutish» tajribasini connectionTimeoutMillis siz takrorlang va 5 soniyadan keyin Ctrl+C bilan to'xtating — sukut qiymat nimaga olib kelishini o'z ko'zingiz bilan ko'ring.
Endi loyiha. PgOmbori tayyor (node-postgres asoslari, Parametrli so'rovlar), lekin server uni hali ishlatmaydi. Bu darsda uchta narsa qo'shamiz: sozlamalar, hovuzni yaratish va server to'xtaganda hovuzni yopish.
9.1 Sozlamalar — config.ts
25-qismdagi Zod sxemasiga (Env validatsiyasi) yangi maydonlar (kanon kodi, qisqartirilgan):
SAQLASH: z.enum(["xotira", "fayl", "postgres"]).default("xotira"),
DATABASE_URL: z.url({ protocol: /^postgres(ql)?$/ }).optional(),
// auth (28-qism) gacha: API qaysi foydalanuvchining vazifalari bilan
EGASI_ID: z.coerce.number().int().positive().default(1),
// bitta jarayon uchun; N instans × PG_POOL_MAX < max_connections
PG_POOL_MAX: z.coerce.number().int().min(1).max(100).default(10),
// bitta so'rov chegarasi (ms)
PG_STATEMENT_TIMEOUT: z.coerce.number().int().min(0)
.default(5000),Va qoida: SAQLASH=postgres bo'lsa, DATABASE_URL majburiy (.refine(...)). PG_POOL_MAX sukut 10 — «Hovuz hajmi» bo'limidagi tirsak va pg sukuti. Izohdagi formula — «Ko'p instans hisobi» bo'limidan. .env.example ga ham shu kalitlar qo'shiladi; DATABASE_URL — faqat lokal trust namunasi, izohda "serverda parol URL-kodlangan bo'lsin" degan eslatma bilan.
9.2 Hovuz — omborYarat
src/ishga-tushir.ts dagi omborYarat endi ombor bilan birga uni yopish funksiyasini ham qaytaradi. Postgres tarmog'i (kanon kodi):
case "postgres": {
const pool = pgPool(config.DATABASE_URL, {
max: config.PG_POOL_MAX,
idleTimeoutMillis: 30_000,
// ulanish 5 s ichida berilmasa — xato (leak darhol ko'rinadi)
connectionTimeoutMillis: 5_000,
statement_timeout: config.PG_STATEMENT_TIMEOUT,
});
// bo'sh ulanish uzilsa — 'error'; tinglovchisiz jarayon yiqiladi
pool.on("error", () => {});
// Fail-fast: baza yoki foydalanuvchi yo'q — ishga tushmaymiz
const { rows } = await pool.query<{ n: number }>(
"SELECT count(*)::int AS n FROM foydalanuvchilar WHERE id = $1",
[config.EGASI_ID],
);
if (rows[0]?.n !== 1) {
await pool.end();
const id = String(config.EGASI_ID);
throw new Error(`EGASI_ID=${id} foydalanuvchilar jadvalida yo'q`);
}
return {
ombor: new PgOmbori(pool, config.EGASI_ID),
yop: () => pool.end(),
};
}Har sozlama shu darsning bir bo'limiga mos:
| Kanon sozlamasi | Qaysi bo'limdan |
|---|---|
max: PG_POOL_MAX (10) |
«Hovuz hajmi: qancha?» |
connectionTimeoutMillis: 5_000 |
«Sozlamalar» — sukut 0 (cheksiz) xavfli |
idleTimeoutMillis: 30_000 |
«Sozlamalar» |
statement_timeout |
«Hovuz xatolari va boshqa sozlamalar» |
pool.on("error") |
o'sha bo'lim |
Fail-fast ("tez yiqil") — server ishga tushayotganda bazani bir marta tekshiradi. Baza yo'q yoki EGASI_ID noto'g'ri bo'lsa, server umuman ishga tushmaydi va sababini aytadi. Yarim ishlaydigan server birinchi so'rovda 500 qaytarishidan ko'ra bu yaxshi.
Kanon tinglovchisi bo'sh (() => {}) — xato yutib yuboriladi. Bu minimal himoya: jarayon yiqilmaydi, hovuz buzilgan ulanishni o'zi chiqarib tashlaydi. Production'da bu yerga log yozuvi qo'shish to'g'ri bo'lardi — aks holda ulanish uzilishlari izsiz qoladi.
9.3 Yopish — jarayon.ts
25-qismda server SIGTERM'da yangi ulanishlarni qabul qilmay, boshlangan so'rovlarni tugatib to'xtardi (Process darajasidagi xatolar). Endi undan keyin yana bitta qadam — oxirida:
const ozVaqtida = await yop(server, kutish);
await oxirida().catch((x: unknown) => {
log.fatal("yopishda xato", x);
});server.ts oxirida sifatida saqlash.yop ni beradi, ya'ni pool.end(). Tartib muhim: avval HTTP server (so'rovlar tugaydi), keyin hovuz. Teskarisi bo'lsa, oxirgi so'rovlar yopilgan hovuzga urilib, xato oladi.
9.4 Test
Postgres testlaridan biri aynan shu kodni tekshiradi:
const c = configYukla({
SAQLASH: "postgres", DATABASE_URL: URL, EGASI_ID: "999",
});
await assert.rejects(omborYarat(c), /EGASI_ID=999/);
assert.throws(
() => configYukla({ SAQLASH: "postgres" }), /DATABASE_URL/,
);Birinchisi — fail-fast (999-foydalanuvchi yo'q), ikkinchisi — DATABASE_URL siz config yuklanmaydi. Biz PostgreSQL 17.6 da ishga tushirdik — o'tdi (12/12). Kanon jonli serverni ham tekshirdi: 9 ta so'rovdan keyin pg_stat_activity da bitta vazifalar-api ulanishi, holati idle. Bu application_name ning foydasi: baza tomonida ulanish kimniki ekani ko'rinadi.
Commit — PgBouncer darsida, holat 44 bilan birga.
10. Real ishda
- Har backend freymvorki ichida hovuz bor: Prisma (
connection_limit), TypeORM, Sequelize, Knex — nomi boshqa, g'oya bir xil. Sozlamasini bilmasdan deploy qilish — 53300 ga yo'l. - Monitoring: Grafana panellarida
pg_stat_activitydagi ulanishlar soni va holati (active,idle,idle in transaction) — kundalik ko'rsatkich. - Serverless (Vercel, AWS Lambda) — har funksiya nusxasi o'z hovuzini ochadi; yuzlab nusxa bo'lsa, faqat tashqi pooler qutqaradi (PgBouncer).
- Intervyuda: "Hovuz hajmini qanday tanlaysiz?", "Pool leak nima va uni qanday topasiz?", "Nega
max: 1000yomon g'oya?"
Xulosa
- Ulanish qimmat (kanon: 12.7 ms va hovuzdan 1.1 ms) va cheklangan (
max_connectionssukut 100). - Hovuz:
max— chegara, ortiqchasi navbatda;idleTimeoutMillis— bo'shlarni yopadi;connectionTimeoutMillis— sukut 0, doim qo'ying. - Hajm: qisqa so'rovlarda "tirsak" ~10 (kanon); kutadigan so'rovlarda ko'proq foyda; formula — yadrolar × 2 + disklar.
pool.connect()→try/finally release(); unutilsa —timeout exceeded…va osilib qolganpool.end().- Instanslar × jarayonlar ×
max× deploy ≤max_connections− zaxira; sig'masa — tashqi pooler.
Keyingi dars: PgBouncer va tashqi pooler'lar — yuzlab mijozni o'nlab ulanishga sig'diradigan vosita, session va transaction rejimlari va SET nega xavfli.
Manbalar
- node-postgres — Pooling,
pg.Poolsozlamalari va hodisalari: https://node-postgres.com/features/pooling, https://node-postgres.com/apis/pool - PostgreSQL 18 hujjati —
max_connections,superuser_reserved_connections: https://www.postgresql.org/docs/18/runtime-config-connection.html - PostgreSQL wiki — Number Of Database Connections: https://wiki.postgresql.org/wiki/Number_Of_Database_Connections
- Kanon:
tajriba/t44-pool.mjs,natijalar/tajriba/t44-pool.txt(PostgreSQL 18.6, Node 24.21.0, pg 8.23.1, 2026-10-24). Hisoblagichlar misoli va 3-mashq — biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!