IlmHamroh
JavaScript Full-stack/26-qism. SQL va PostgreSQL44/64-dars19 daqiqa
Mundarija (30)

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-chisi 53300 sorry, too many clients already bilan rad etiladi. Eng xavfli xato — release() ni unutish: kanonda 3 ta qaytarilmagan ulanishdan keyin 4-so'rov timeout exceeded when trying to connect bilan yiqildi, pool.end() esa abadiy osilib qoldi.

Bu darsda

  • Ulanish nega qimmat ekanini va hovuz buni qanday hal qilishini tushuntirasiz.
  • max, idleTimeoutMillis, connectionTimeoutMillis ni o'z ilovangiz uchun tanlaysiz.
  • Hovuz hajmini kanon o'lchovi va formula asosida asoslaysiz.
  • release() unutilishini (pool leak) taniysiz va try/finally bilan oldini olasiz.
  • Ko'p server instansi max_connections ni 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:

text
bitta so'rov: har safar yangi ulanish 12.7 ms, pool'dan 1.12 ms

11 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 xato

Diagrammada 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:

js
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):

text
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 0

Qatorma-qator:

  • darhol — 4 ta so'rov keldi: 2 tasi ulanish oldi (ular hali ochilyapti), 2 tasi navbatda. totalCount ochilayotgan 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: 1000 o'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: 2 li 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).

text
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
400 ta qisqa so'rov: hovuz hajmi va vaqt
vaqt, ms
56139150max, ulanishqisqa so'rov: 1 ulanish → 561 msqisqa so'rov: 2 ulanish → 280 msqisqa so'rov: 5 ulanish → 149 msqisqa so'rov: 10 ulanish → 84 msqisqa so'rov: 20 ulanish → 52 msqisqa so'rov: 50 ulanish → 39 ms
400 ta qisqa so'rov: hovuz hajmi va vaqt
maxqisqa so'rov
1561
2280
5149
1084
2052
5039

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:

js
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:

text
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"):

text
ulanishlar ≈ (yadrolar × 2) + effektiv disklar soni

4 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:

  1. Formuladan boshlang (masalan max: 10 — pg sukutining o'zi).
  2. Yuklama testi bilan o'lchang: waitingCount va so'rov vaqtini kuzating.
  3. 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.

js
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'rov

Kanon chiqishi:

text
leak'dan keyin: { total: 3, idle: 0, waiting: 0 }
4-so'rov: 1007 ms dan keyin — timeout exceeded when trying to connect

Uchta 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

js
const client = await pool.connect();
try {
  await client.query("SELECT 1");
} finally {
  client.release();   // xato bo'lsa ham qaytadi
}

Kanon chiqishi:

text
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: pg Release called on client which has already been released to the pool. deb xato tashlaydi. Qoida: bitta connect() — bitta release(), finally ichida.

Tekshirib ko'ring: API'da har HTTP so'rovda pool.connect() bor, lekin xato bo'lgan yo'lda release() 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:

text
server: { max: '100', zaxira: '3', hozir: '9' } | konteyner CPU: 16 (Docker VM)
ochildi 100; 101-ulanish: 53300 sorry, too many clients already

53300 — 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.
js
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: false

240 > 97 — deploy paytida yangi jarayonlar 53300 oladi. Bu xato hisob qog'ozda emas, aynan eng xavfli paytda — yangilash paytida chiqadi.

Uch yo'l:

  1. Har jarayonning max ini kamaytirish: 97 / (3 × 4 × 2) ≈ 4.
  2. max_connections ni oshirish — har ulanish xotira oladi va §3 dagi raqobat ortadi; cheksiz emas.
  3. 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", …) — pg hujjati 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, xato pool.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: 300 bilan 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; pg da statement_timeout opsiyasi). 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

  • pg hovuzining sukut max i —
  • 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 connectionTimeoutMillis ni 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
js
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:

text
3
160 ta jarayonga 92 ulanish yetmaydi — pooler kerak

Math.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
js
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):

text
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):

ts
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):

ts
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:

ts
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:

ts
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_activity dagi 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: 1000 yomon g'oya?"

Xulosa

  • Ulanish qimmat (kanon: 12.7 ms va hovuzdan 1.1 ms) va cheklangan (max_connections sukut 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 qolgan pool.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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Connection pooling: hovuz hajmi, release unutish va maxconnections — IlmHamroh