Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. PgBouncer rejimlari
- 2.1 Uch rejim
- 2.2 Transaction rejimida nima buziladi
- 3. SET oqib o'tadi: o'zimiz ko'ramiz
- 3.1 pg hovuzida ham xuddi shu muammo
- 3.2 Bu bizning sxemada nimani buzadi
- 4. Prepared statement muammosi
- 4.1 Muammo qayerda edi
- 4.2 Hozirgi holat (hujjat bo'yicha, 2026-10-24)
- 5. Boshqariladigan pooler'lar
- 6. PgBouncer sozlamasi (namuna)
- 7. Qachon kerak emas
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi rejim
- 2-mashq (o'rta): transaction rejimida ishlaydimi?
- 3-mashq (qiyin): kimSifatida yordamchisi
- 10. Node qadami: holat 44 yakuni va commit
- 10.1 SET LOCAL va API: ochiq qolgan joy
- 10.2 Yozuv metodlari — qisqacha
- 10.3 Tekshiruv
- 10.4 Commit
- 11. Real ishda
- Xulosa
- Manbalar
PgBouncer va tashqi pooler'lar: session va transaction rejimi, SET LOCAL
Qisqacha: Tashqi pooler (PgBouncer, Supavisor, RDS Proxy, Neon pooler) — ilova bilan Postgres orasida turuvchi alohida dastur: minglab mijoz ulanishini o'nlab haqiqiy server ulanishiga sig'diradi. Eng samarali transaction rejimida ulanish mijozga faqat tranzaksiya davomida beriladi — shuning uchun sessiyaga bog'liq narsalar (
SET,LISTEN, sessiya advisory qulflari, SQLPREPARE) buziladi. Qoida: sessiya sozlamasi faqatSET LOCALbilan, tranzaksiya ichida. Bu dars hujjat bo'yicha: kanon PgBouncer'ni o'rnatmadi (rasmiy Docker image'i yo'q), lekinSETning oqib o'tishini oddiypghovuzida o'zimiz ko'rsatamiz.
Bu darsda
- Ilova ichidagi hovuz bilan tashqi pooler farqini tushuntirasiz.
- PgBouncer'ning uch rejimini va qaysi birida nima buzilishini bilasiz.
SETvaSET LOCALfarqini o'z ko'zingiz bilan ko'rasiz va to'g'risini tanlaysiz.- Prepared statement muammosi va uning hozirgi yechimini (
max_prepared_statements) izohlaysiz. - Supavisor, RDS Proxy va Neon pooler'ni qachon ishlatishni bilasiz.
Oldin bilishingiz kerak: Connection pooling, node-postgres (pg) asoslari, Soft delete, timestamps va tarix jadvallari.
1. Nega bu kerak?
Connection pooling darsining 2-mashqida hisoblagan edik: 10 ta server, har birida 8 ta Node jarayoni, deploy paytida ikki nusxa — 160 ta jarayon. Postgres'da esa 92 ta bo'sh joy. Har jarayonga bittadan ulanish ham yetmaydi.
Serverless'da (Vercel funksiyalari, AWS Lambda) ahvol yanada og'ir: har so'rov alohida funksiya nusxasida ishlashi mumkin, nusxalar yuzlab, va har biri o'z ulanishini ochadi. Postgres esa har ulanish uchun alohida jarayon ushlaydi.
Lekin bir narsaga e'tibor bering: 160 ta jarayonning hammasi bir paytda so'rov bajarmaydi. Ko'pi HTTP so'rov kutyapti, JSON yig'yapti yoki boshqa xizmatga murojaat qilyapti. Bir lahzada bazada faol ishlayotgani — o'nlab. Demak 160 ta "doimiy" ulanish o'rniga, 20 ta ulanishni kimga kerak bo'lsa shunga berib tursak yetadi.
Buni bitta ilova ichidagi hovuz qila olmaydi — u faqat o'z jarayoni ichida bo'lishadi. Kerak — hamma jarayonlar umumiy ishlatadigan, alohida turgan hovuz. Bu — tashqi pooler.
O'xshatish: katta binodagi lift. Har xonadonga alohida lift qurilmaydi (har jarayonga o'z ulanishi). Bitta lift dispetcheri bor: kim tugmani bossa, bo'sh liftni yuboradi, odam o'z qavatiga chiqqach, lift boshqaga xizmat qiladi. Odamlar ko'p, liftlar oz — chunki hamma bir vaqtda minmaydi.
flowchart LR
A[Node 1..80] --> B[PgBouncer]
C[Node 81..160] --> B
B --> P[(Postgres: 20 ulanish)]Diagrammada 160 ta mijoz PgBouncer'ga ulanadi (unga ulanish arzon — u yengil dastur, jarayon ochmaydi). PgBouncer esa Postgres'ga atigi 20 ta haqiqiy ulanish ochadi.
Diqqat: Bu dars hujjat bo'yicha. Kanon PgBouncer'ni sinamadi: uning Docker Hub'da rasmiy image'i yo'q (faqat uchinchi tomon: bitnami, edoburu), kanon esa faqat rasmiy image'lar bilan ishlaydi. Raqamlar va xatti-harakatlar — pgbouncer.org va provayderlar hujjatidan, 2026-10-24 da o'qildi.
SETmisoli — oddiypghovuzida, biz ishga tushirdik.
2. PgBouncer rejimlari
2.1 Uch rejim
PgBouncer'ning asosiy sozlamasi — pool_mode: server ulanishi mijozga qancha vaqtga beriladi.
| Rejim | Ulanish mijozda qachongacha | Postgres imkoniyatlari |
|---|---|---|
session (sukut) |
mijoz uzilguncha | hammasi ishlaydi |
transaction |
tranzaksiya tugaguncha | sessiyaga bog'liqlari buziladi |
statement |
har bitta so'rov | ko'p so'rovli tranzaksiya taqiqlangan |
Session — eng "muloyim": PgBouncer shunchaki ulanishlarni qayta ishlatadi (mijoz uzilganda), lekin mijoz ulanib turguncha server ulanishi uniki. 160 ta doimiy ulangan Node jarayoni bo'lsa — 160 ta server ulanishi kerak. Muammo hal bo'lmaydi.
Transaction — amalda ishlatiladigan rejim: mijoz BEGIN dan COMMIT gacha (yoki bitta autocommit so'rov davomida) server ulanishini oladi, keyin u boshqa mijozga ketadi. 160 mijoz → 20 ulanish shu tufayli mumkin.
Statement — eng qattiq: har so'rovdan keyin ulanish qaytadi, BEGIN … COMMIT umuman taqiqlangan. Hujjat bo'yicha asosan PL/Proxy kabi maxsus holatlar uchun.
sequenceDiagram
participant A as Mijoz A
participant B as Mijoz B
participant X as PgBouncer
participant P as Server ulanish 1
A->>X: BEGIN, UPDATE …
X->>P: (A ga berildi)
A->>X: COMMIT
X-->>P: bo'shadi
B->>X: SELECT …
X->>P: (endi B ga berildi)Diagrammaga qarang: bitta server ulanishi navbat bilan A ga, keyin B ga xizmat qildi. B uchun bu ulanish "yangi" emas — A undan foydalangan. A sessiyada biror narsani o'zgartirib qo'ygan bo'lsa (masalan SET), B uni meros qilib oladi.
2.2 Transaction rejimida nima buziladi
PgBouncer hujjatidagi jadval (qisqartirilgan):
| Imkoniyat | Transaction rejimida |
|---|---|
SET / RESET (sessiya) |
yo'q |
LISTEN |
yo'q |
Sessiya advisory qulflari (pg_advisory_lock) |
yo'q |
SQL PREPARE / DEALLOCATE |
yo'q |
WITH HOLD kursorlar, PRESERVE ROWS vaqtinchalik jadvallar |
yo'q |
NOTIFY, WITHOUT HOLD kursorlar |
bor |
| Protokol darajasidagi prepared statement | bor, max_prepared_statements > 0 bo'lsa |
ON COMMIT DROP vaqtinchalik jadvallar |
bor |
Umumiy qoida bitta: tranzaksiyadan oshib yashaydigan har narsa buziladi. Sababi — keyingi tranzaksiya boshqa server ulanishida bo'lishi mumkin. LISTEN — "shu ulanishga xabar yubor" degani, lekin ulanish keyin boshqa mijozga ketadi (LISTEN/NOTIFY darsida — shuning uchun tinglovchiga alohida, to'g'ridan ulanish kerak). Advisory qulflar — Qulflar darsida; ularning _xact_ variantlari (tranzaksiya oxirida o'zi yechiladi) transaction rejimiga mos.
Tekshirib ko'ring: Transaction rejimida
NOTIFYishlaydi,LISTENesa yo'q. Nega farq?
Javob
NOTIFY — bir martalik harakat: xabar tranzaksiya COMMIT bo'lganda yuboriladi va hammasi shu tranzaksiya ichida tugaydi. LISTEN esa davomli holat: "bundan keyin ham shu ulanishga xabar kelsin". Tranzaksiya tugagach ulanish boshqa mijozga ketadi — xabarlar endi kimga borishi noma'lum.
3. SET oqib o'tadi: o'zimiz ko'ramiz
3.1 pg hovuzida ham xuddi shu muammo
PgBouncer'siz ham bu xavfni ko'rish mumkin — oddiy pg hovuzi ham ulanishlarni so'rovlar orasida qayta ishlatadi. max: 1 li hovuz olamiz (hamma so'rov bitta ulanishga tushadi — PgBouncer'ning bitta server ulanishiga o'xshab):
import pg from "pg";
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
max: 1,
});
const kim =
"SELECT current_setting('ilova.foydalanuvchi_id', true) AS kim";
console.log("boshida:", (await pool.query(kim)).rows[0].kim);
const c = await pool.connect();
try {
await c.query("BEGIN");
await c.query("SET LOCAL ilova.foydalanuvchi_id = '1'");
const ichida = (await c.query(kim)).rows[0].kim;
console.log("tranzaksiya ichida:", ichida);
await c.query("COMMIT");
} finally {
c.release();
}
const keyin = (await pool.query(kim)).rows[0].kim;
console.log("COMMIT dan keyin:", JSON.stringify(keyin));
await pool.query("SET ilova.foydalanuvchi_id = '1'");
const meros = (await pool.query(kim)).rows[0].kim;
console.log("SET dan keyin, keyingi so'rov:", meros);
await pool.end();Biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, pg 8.23.1, Windows 11):
boshida: null
tranzaksiya ichida: 1
COMMIT dan keyin: ""
SET dan keyin, keyingi so'rov: 1Qatorma-qator:
- boshida: null —
ilova.foydalanuvchi_idhech qachon o'rnatilmagan;current_setting(…, true)dagitrue— "yo'q bo'lsa xato emas, NULL qaytar". - tranzaksiya ichida: 1 —
SET LOCALishladi. - COMMIT dan keyin: "" — qiymat yo'qoldi. Bo'sh satr (NULL emas): o'zgaruvchi shu sessiyada bir marta yaratilgan, endi qiymati bo'sh. Kanon sxemasidagi trigger aynan shuning uchun
nullif(…, '')yozadi. - SET dan keyin, keyingi so'rov: 1 — oddiy
SETbittapool.queryda qilindi, lekin keyingi, butunlay boshqa so'rov ham1ni ko'rdi. Haqiqiy ilovada keyingi so'rov boshqa foydalanuvchiniki bo'lishi mumkin.
3.2 Bu bizning sxemada nimani buzadi
Soft delete, timestamps va tarix jadvallari darsida vazifa_tarixi jadvali qurgan edik. Har o'zgarishni kim qilgani trigger ichida shunday olinadi (kanon sql/010-vaqt-tarix.sql dan, qator bo'lingan):
kim bigint := nullif(
current_setting('ilova.foydalanuvchi_id', true), '')::bigint;Ilova har tranzaksiya boshida "hozir kim ishlayapti" ni shu o'zgaruvchiga yozadi. Agar buni SET bilan qilsa:
- Aziz (1) vazifasini tahrirladi — ulanishda
ilova.foydalanuvchi_id = '1'qoldi. - Ulanish hovuzga qaytdi va Dilnozaning (2) so'roviga berildi. Dilnozaning kodi o'zgaruvchini o'rnatishni unutgan yo'lda ketdi.
- Tarixda Dilnozaning o'zgarishi Aziz nomidan yozildi.
Audit jurnali — "kim nima qildi" ning yagona isboti. Uning yolg'on gapirishi — jiddiy xato. Kanon qoidasi: faqat SET LOCAL, faqat tranzaksiya ichida — tranzaksiya tugashi bilan qiymat o'chadi, qaysi pooler, qaysi rejim bo'lishidan qat'i nazar. (Kanon SET LOCAL naqshini tranzaksiya tajribasida qo'llagan — Tranzaksiya asoslari; PgBouncer bilan sinalmagan.)
Maslahat: Funksiya ko'rinishi ham bor:
SELECT set_config('ilova.foydalanuvchi_id', $1, true)— uchinchi argumenttrue= LOCAL. Uning afzalligi — qiymatni parametr bilan berish mumkin (SET LOCAL … = $1parametrni qabul qilmaydi, Parametrli so'rovlar dagi "nom va tuzilma parametr bo'lmaydi" qoidasi).
Tekshirib ko'ring:
SET LOCALniBEGINsiz, oddiypool.query("SET LOCAL …")bilan chaqirsangiz nima bo'ladi?
Javob
Hech narsa foydali: autocommit rejimida har so'rov o'z tranzaksiyasi, SET LOCAL shu bir so'rovli tranzaksiya tugashi bilan o'chadi. Postgres WARNING: SET LOCAL can only be used in transaction blocks ogohlantirishini beradi. SET LOCAL va uni ishlatadigan so'rovlar — bitta BEGIN … COMMIT ichida, bitta client'da.
4. Prepared statement muammosi
4.1 Muammo qayerda edi
node-postgres asoslari darsida nomlangan so'rov ko'rdik: { name: "vazifa-ol", text, values }. Postgres uni shu ulanishda tahlil qilib eslab qoladi. postgres.js esa sukut bo'yicha har so'rovni shunday tayyorlaydi (prepare: true).
Transaction rejimida: so'rov 1-server ulanishida tayyorlandi, keyingi chaqiruv 2-ulanishga tushdi — u yerda bunday nomli so'rov yo'q. Natija — "prepared statement does not exist" turidagi xatolar. Uzoq yillar standart javob bo'ldi: "PgBouncer bilan prepared statement'ni o'chiring".
4.2 Hozirgi holat (hujjat bo'yicha, 2026-10-24)
PgBouncer changelog'i bo'yicha:
- 1.21.0 (2023) — protokol darajasidagi nomlangan prepared statement'lar uchun qo'llab-quvvatlash qo'shildi:
max_prepared_statements> 0 bo'lsa, PgBouncer qaysi mijoz nima tayyorlaganini kuzatadi va kerakli server ulanishida qayta tayyorlaydi. - 1.24.0 (2025-01-10) — sukut qiymati 200 bo'ldi, ya'ni bu qo'llab-quvvatlash sukut bo'yicha yoqilgan.
- Oxirgi versiya — 1.26.1 (2026-10-14).
Cheklov: SQL darajasidagi PREPARE / EXECUTE buyruqlari baribir qo'llab-quvvatlanmaydi — faqat drayver protokol orqali qiladigan tayyorlash. pg ning nomsiz parametrli so'rovlari (oddiy query(text, values)) esa bu muammoga umuman tegmaydi: ular bitta tranzaksiya ichida tahlil qilinib, bajariladi.
Boshqa pooler'lar boshqacha: masalan Supabase hujjati Supavisor'ning transaction rejimi prepared statement'larni qo'llamasligini va ularni drayverda o'chirishni aytadi (postgres.js'da prepare: false). Pooler hujjatini doim tekshiring.
5. Boshqariladigan pooler'lar
O'z PgBouncer'ingizni boshqarish shart emas — bulut provayderlari pooler'ni xizmat sifatida beradi. Hammasi sinalmagan, 2026-10-24 dagi hujjat bo'yicha:
| Xizmat | Asosi | Qanday ulaniladi |
|---|---|---|
| Supabase — Supavisor | o'z pooler'i (Elixir) | port 5432 — session, 6543 — transaction |
| AWS RDS Proxy | AWS xizmati | proxy manzili; sessiya holati "pinning" ga olib keladi |
| Neon pooler | PgBouncer, transaction rejimi | host nomiga -pooler qo'shiladi |
Bir nechta muhim detal:
- Neon — hujjat bo'yicha 10 000 tagacha mijoz ulanishi (
max_client_conn=10000);SET,LISTEN, SQLPREPARE, sessiya advisory qulflari ishlamaydi. Migratsiya,pg_dump, uzoq tahliliy so'rovlar uchun to'g'ridan (pooler'siz) manzil tavsiya qilinadi. - RDS Proxy — Postgres uchun
SET,PREPARE/EXECUTE,LISTEN, kursor e'lon qilish,pg_advisory_lockkabi amallar pinning qiladi: mijoz shu server ulanishiga "mixlanib" qoladi va proxy samarasi yo'qoladi. Tranzaksiya darajasidagi advisory qulflar (pg_advisory_xact_lock) pinning qilmaydi. - Ikki manzil odatiy naqsh: ilova so'rovlari — pooler orqali; migratsiya va admin ishlari — to'g'ridan.
.envdaDATABASE_URLvaDIRECT_URL(Prisma'da aynan shunday nomlanadi — 27-qism).
6. PgBouncer sozlamasi (namuna)
O'zingiz o'rnatsangiz, asosiy fayl — pgbouncer.ini. Quyidagi namuna sinalmagan, faqat qaysi sozlama nima qilishini ko'rsatish uchun (qiymatlar pgbouncer.org/config hujjatidan):
[databases]
vazifalar = host=127.0.0.1 port=5499 dbname=vazifalar
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
max_prepared_statements = 200max_client_conn — PgBouncer'ga nechta mijoz ulana oladi (sukut 100). default_pool_size — har "foydalanuvchi + baza" juftligiga nechta server ulanishi (sukut 20). Ilova DATABASE_URL da endi 5499 emas, 6432 portni ko'rsatadi — ilova kodi o'zgarmaydi. Parol fayllari, TLS va auth_type — production sozlamasi, hujjatda batafsil.
Ilova tomonida hovuz qoladi, lekin kichrayadi: har Node jarayonida max: 2–5 — ular PgBouncer'ga ulanadi, haqiqiy Postgres ulanishlarini esa PgBouncer boshqaradi.
7. Qachon kerak emas
Tashqi pooler — qo'shimcha qism: uni ham kuzatish, yangilash, sozlash kerak. Kerak bo'lmagan holatlar:
- Bitta yoki ikkita server, hisob (Connection pooling)
max_connectionsga sig'adi — ilova hovuzi yetarli. - Ilova sessiya imkoniyatlariga qattiq tayanadi (
LISTEN, sessiya qulflari) — session rejimi kerak, u esa ulanishlarni kam tejaydi.
Kerak bo'lgan holatlar: serverless, ko'p instans, ko'p mikroxizmat bitta bazaga, yoki boshqariladigan Postgres (Neon, Supabase) — u yerda pooler allaqachon bor, faqat to'g'ri manzilni tanlash qoladi.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Audit'da noto'g'ri foydalanuvchi | SET ulanishda qoldi |
SET LOCAL / set_config(…, true) tranzaksiya ichida |
| "prepared statement … does not exist" | eski PgBouncer yoki Supavisor transaction | max_prepared_statements, yoki drayverda prepare: false |
LISTEN xabarlari kelmaydi |
pooler transaction rejimida | tinglovchi — to'g'ridan ulanish |
| Migratsiya qotib qoladi / xato | pooler orqali sessiya amallari | migratsiya — to'g'ridan manzil |
Pooler bor, lekin baribir 53300 |
session rejimi | transaction rejimi |
9. Mashqlar
Mashqlar kurs/mashqlar/26/45-pgbouncer/ da.
1-mashq (oson): qaysi rejim
- Mijoz uzilguncha ulanish uniki — rejimi
- Ulanish faqat
BEGINdanCOMMITgacha beriladi — rejimi - Ko'p so'rovli tranzaksiya taqiqlangan — rejimi
- PgBouncer'ning sukut rejimi —
- Neon pooler'ining rejimi —
Yechim
- session, transaction, statement — ulanish berilish muddati qisqarib boradi.
- session — sukut, eng xavfsiz, lekin eng kam tejaydi.
- transaction — Neon ham, Supabase'ning 6543-porti ham.
2-mashq (o'rta): transaction rejimida ishlaydimi?
Quyidagi amallarni transaction rejimidagi pooler orqali bajarish xavfsizmi? "Ha" yoki "yo'q" va bir gap bilan nega:
BEGIN; SET LOCAL statement_timeout = '2s'; SELECT …; COMMIT;pool.query("SET search_path = hisobot"), keyin alohidapool.query("SELECT * FROM jadval")pool.query("NOTIFY vazifa_ozgardi, 'salom'")SELECT pg_advisory_lock(42)… keyinroq boshqa so'rovdapg_advisory_unlock(42)pool.query("SELECT * FROM vazifalar WHERE id = $1", [5])
Yechim
- Ha —
SET LOCALtranzaksiya bilan birga tugaydi. - Yo'q —
SETsessiyada qoladi: ikkinchi so'rov boshqa server ulanishiga tushib,hisobotni ko'rmasligi, yoki boshqa mijoz uni meros olishi mumkin. - Ha —
NOTIFYjadvalda "bor". - Yo'q — sessiya qulfi birinchi server ulanishida qoladi;
unlockboshqa ulanishga tushsa, qulf yechilmaydi. Tranzaksiya varianti —pg_advisory_xact_lock. - Ha — nomsiz parametrli so'rov bitta tranzaksiya ichida tahlil qilinib bajariladi.
3-mashq (qiyin): kimSifatida yordamchisi
kimSifatida(pool, foydalanuvchiId, fn) funksiyasini yozing: hovuzdan client oladi, BEGIN, set_config('ilova.foydalanuvchi_id', $1, true) (qiymat — parametr!), fn(client) natijasini qaytaradi, COMMIT; xatoda ROLLBACK; doim release(). Tekshirish (max: 1 li hovuz): ichida trigger'dagi ifodani — SELECT nullif(current_setting('ilova.foydalanuvchi_id', true), '')::bigint AS kim — o'qing; keyin hovuzdan oddiy so'rov bilan xuddi shu ifodani o'qing. Natijalar: ichida 1, tashqarida null.
Yechim
import pg from "pg";
async function kimSifatida(pool, foydalanuvchiId, fn) {
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query(
"SELECT set_config('ilova.foydalanuvchi_id', $1, true)",
[String(foydalanuvchiId)],
);
const natija = await fn(client);
await client.query("COMMIT");
return natija;
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release();
}
}
const KIM = "SELECT nullif(current_setting(" +
"'ilova.foydalanuvchi_id', true), '')::bigint AS kim";
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
max: 1,
});
const ichida = await kimSifatida(pool, 1, async (c) =>
(await c.query(KIM)).rows[0].kim,
);
const tashqarida = (await pool.query(KIM)).rows[0].kim;
console.log("ichida:", ichida, "| tashqarida:", tashqarida);
await pool.end();Biz ishga tushirdik (holat 40, PostgreSQL 17.6, Node 24.21.0, pg 8.23.1):
ichida: 1 | tashqarida: nullichida: 1 — aslida '1' satr (bigint → string, node-postgres asoslari); console.log satrni qo'shtirnoqsiz chiqaradi. tashqarida: null — max: 1 bo'lgani uchun bu o'sha ulanish, lekin set_config(…, true) tranzaksiya bilan o'chdi va nullif bo'sh satrni NULL ga aylantirdi. String(foydalanuvchiId) — set_config matn qabul qiladi.
10. Node qadami: holat 44 yakuni va commit
PgBouncer loyihaga qo'shilmaydi: kanonda sinalmagan, bitta server va PG_POOL_MAX=10 esa max_connections ga bemalol sig'adi («Qachon kerak emas» bo'limi). Tajriba qadami — «SET oqib o'tadi» misolini pg-tajriba papkasida takrorlash.
10.1 SET LOCAL va API: ochiq qolgan joy
Avval loyiha kodida SET ni qidiramiz. Natija kutilmagan: vazifalar-api da SET ham, SET LOCAL ham, set_config ham yo'q. Ya'ni ilova.foydalanuvchi_id umuman o'rnatilmaydi va API orqali qilingan har o'zgarish tarixga kim_id = NULL bilan yoziladi. Biz holat 44 ning PgOmbori si bilan bitta vazifani yangiladik va tarixni o'qidik (PostgreSQL 17.6):
tarix: [ { vazifa_id: 9, amal: 'UPDATE', kim_id: null } ]Bu kanondagi ochiq qolgan joy, uni yashirmaymiz. Hozircha zarari kichik: auth yo'q, API bitta foydalanuvchi (EGASI_ID) bilan ishlaydi. Auth kelganda (28-qism) har yozuv tranzaksiyasi boshida set_config('ilova.foydalanuvchi_id', $1, true) chaqirilishi kerak — 3-mashqdagi kimSifatida aynan shu naqsh. Oddiy SET emas: u hovuzdagi keyingi so'rovga o'tadi («SET oqib o'tadi» bo'limi).
10.2 Yozuv metodlari — qisqacha
Holat 44 dagi PgOmbori ning yozuv metodlari oldingi darslardagi naqshlarni ishlatadi:
| Metod | Naqsh | Qayerda o'rgangan edik |
|---|---|---|
yarat, yangila, ochir |
yozuv + ro'yxat versiyasi bitta data-modifying CTE'da | CTE |
yangila, ochir |
AND versiya = $5 — optimistik qulf, 0 qator → 412/409 |
Amaliyot, chuqur — Optimistic concurrency |
ochir |
yumshoq o'chirish (ochirilgan_vaqt = now()) |
Soft delete |
almashtirHammasi (PUT) |
tranzaksiya, egasi qatori FOR UPDATE |
Tranzaksiya asoslari, Qulflar |
| takror matn | 23505 → 409 takror-matn |
node-postgres asoslari |
Bitta farq xotira omboridan: xotira ombori takror matnga ruxsat beradi, Postgres ombori — yo'q (bazadagi qoida). PUT hozircha qatorma-qator yozadi: 1 000 vazifa — 1 010 so'rov, 2 175 ms (kanon o'lchovi). Buni COPY, batch va cursor darsida 12 so'rovga tushiramiz.
10.3 Tekshiruv
Holat 44 — to'rt darsning yig'indisi. Kanon tekshiruvi (PostgreSQL 18.6, Node 24.21.0): sxema test va namuna bazalariga noldan qo'llandi, npm run tip va npm run lint — exit 0, npm test — 79/79 (shundan Postgres testlari 12). Keyin server SAQLASH=postgres bilan ishga tushirildi va curl bilan 9 ta so'rov yuborildi:
| So'rov | Javob | Nimani ko'rsatadi |
|---|---|---|
GET /v1/vazifalar?limit=3 |
200 | tartib bo'yicha ro'yxat, Link: rel="next" kursor |
GET …?teg=o'qish&sort=-yaratildi |
200 | teg filtri va oq ro'yxatdagi saralash |
POST yangi vazifa |
201 | id: 49, tartib: 14 — trigger oxiriga qo'ydi |
POST o'sha matn, katta harfda |
409 | takror-matn — registrga befarq noyoblik |
PATCH If-Match: "1" |
200 | versiya 2 |
PATCH yana If-Match: "1" |
412 | eski versiya — optimistik qulf |
DELETE /v1/vazifalar/28 |
204 | yumshoq o'chirish |
GET /v1/vazifalar/28 |
404 | Savatdagi vazifa API'da yo'q |
GET /v1/teglar |
200 | teglar soni vazifa_teglari dan |
Biz holat 44 ning 12 ta Postgres testini o'z PostgreSQL 17.6 klasterimizda ham ishga tushirdik — hammasi o'tdi.
10.4 Commit
Holat 44 bitta commit — sxema fayli va API birga (kanonda sql/ va vazifalar-api/ yonma-yon). Sarlavha 72 belgidan oshmasin, qolgani — tanada:
git add sql/013-api-tartib.sql vazifalar-api
M="feat: PostgreSQL ombori (pg) — SAQLASH=postgres"
T="DATABASE_URL, pool sozlamalari, shartnoma testlari"
git commit -m "$M" -m "$T".env commit'ga tushmaydi (.gitignore); repoda faqat .env.example.
11. Real ishda
- Serverless + Postgres (Vercel + Neon, Supabase) — pooler manzili va to'g'ridan manzil farqini bilmaslik — eng ko'p uchraydigan deploy muammosi.
- Katta kompaniyalarda PgBouncer yillardan beri standart qatlam; uning metrikalari (
SHOW POOLS:cl_waiting,sv_active) monitoring panellarida turadi. - Ko'p ijarachili (multi-tenant) ilovalar "joriy foydalanuvchi/tashkilot" ni Postgres'ning Row Level Security siyosatlariga
set_config(…, true)orqali beradi — xuddi bizningilova.foydalanuvchi_idkabi (28-qism atrofida). - Intervyuda: "PgBouncer nima uchun kerak?", "Session va transaction rejimi farqi?", "Transaction rejimida nima ishlamaydi?"
Xulosa
- Tashqi pooler ko'p mijoz ulanishini oz sonli server ulanishiga aylantiradi — ilova hovuzi buni jarayonlar orasida qila olmaydi.
- Rejimlar: session (sukut, kam tejaydi), transaction (asosiy), statement (maxsus).
- Transaction rejimida tranzaksiyadan oshib yashaydigan hamma narsa buziladi:
SET,LISTEN, sessiya qulflari, SQLPREPARE. SEThatto oddiypghovuzida ham keyingi so'rovga o'tadi (biz ko'rsatdik); qoida —SET LOCAL/set_config(…, true)tranzaksiya ichida.- PgBouncer 1.21 dan protokol prepared statement'larini qo'llaydi, 1.24 dan sukut bo'yicha yoqilgan; boshqa pooler'larda — hujjatni tekshiring.
Keyingi dars: Ommaviy ma'lumot: COPY, batch va cursor — 100 000 qatorni bittalab emas: kanonda 145 qator/s dan 409 000 qator/s gacha.
Manbalar
- PgBouncer — Features (pool rejimlari, transaction rejimidagi SQL imkoniyatlari jadvali): https://www.pgbouncer.org/features.html
- PgBouncer — Configuration (
pool_mode,max_client_conn,default_pool_size,max_prepared_statements): https://www.pgbouncer.org/config.html - PgBouncer — Changelog (1.21.0, 1.24.0 — 2025-01-10, 1.26.1 — 2026-10-14): https://www.pgbouncer.org/changelog
- Neon — Connection pooling: https://neon.com/docs/connect/connection-pooling
- Supabase — Connecting to Postgres (Supavisor, 5432/6543): https://supabase.com/docs/guides/database/connecting-to-postgres
- AWS — Avoiding pinning an RDS Proxy: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy-pinning.html
- PostgreSQL 18 hujjati —
SET(LOCAL),set_config: https://www.postgresql.org/docs/18/sql-set.html, https://www.postgresql.org/docs/18/functions-admin.html - Hammasi 2026-10-24 da o'qildi; PgBouncer va boshqariladigan pooler'lar sinalmagan.
SET/SET LOCALmisoli va 3-mashq — biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, pg 8.23.1). Kanon hisoboti (#45: rasmiy image yo'qligi,kim_idvaSET LOCAL); holat 44 —natijalar/tekshiruv-44.txt(tip, lint, 79/79 test, 9 ta jonli so'rov). Holat 44 ning Postgres testlari vakim_idtekshiruvi — biz ishga tushirdik (PostgreSQL 17.6).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!