IlmHamroh
JavaScript Full-stack/26-qism. SQL va PostgreSQL45/64-dars17 daqiqa
Mundarija (27)

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, SQL PREPARE) buziladi. Qoida: sessiya sozlamasi faqat SET LOCAL bilan, tranzaksiya ichida. Bu dars hujjat bo'yicha: kanon PgBouncer'ni o'rnatmadi (rasmiy Docker image'i yo'q), lekin SET ning oqib o'tishini oddiy pg hovuzida o'zimiz ko'rsatamiz.

Bu darsda

  • Ilova ichidagi hovuz bilan tashqi pooler farqini tushuntirasiz.
  • PgBouncer'ning uch rejimini va qaysi birida nima buzilishini bilasiz.
  • SET va SET LOCAL farqini 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. SET misoli — oddiy pg hovuzida, 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 NOTIFY ishlaydi, LISTEN esa 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):

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

text
boshida: null
tranzaksiya ichida: 1
COMMIT dan keyin: ""
SET dan keyin, keyingi so'rov: 1

Qatorma-qator:

  • boshida: null — ilova.foydalanuvchi_id hech qachon o'rnatilmagan; current_setting(…, true) dagi true — "yo'q bo'lsa xato emas, NULL qaytar".
  • tranzaksiya ichida: 1 — SET LOCAL ishladi.
  • 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 SET bitta pool.query da qilindi, lekin keyingi, butunlay boshqa so'rov ham 1 ni 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):

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

  1. Aziz (1) vazifasini tahrirladi — ulanishda ilova.foydalanuvchi_id = '1' qoldi.
  2. Ulanish hovuzga qaytdi va Dilnozaning (2) so'roviga berildi. Dilnozaning kodi o'zgaruvchini o'rnatishni unutgan yo'lda ketdi.
  3. 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 argument true = LOCAL. Uning afzalligi — qiymatni parametr bilan berish mumkin (SET LOCAL … = $1 parametrni qabul qilmaydi, Parametrli so'rovlar dagi "nom va tuzilma parametr bo'lmaydi" qoidasi).

Tekshirib ko'ring: SET LOCAL ni BEGIN siz, oddiy pool.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, SQL PREPARE, 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_lock kabi 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. .env da DATABASE_URL va DIRECT_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):

ini
[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 = 200

max_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_connections ga 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 BEGIN dan COMMIT gacha 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:

  1. BEGIN; SET LOCAL statement_timeout = '2s'; SELECT …; COMMIT;
  2. pool.query("SET search_path = hisobot"), keyin alohida pool.query("SELECT * FROM jadval")
  3. pool.query("NOTIFY vazifa_ozgardi, 'salom'")
  4. SELECT pg_advisory_lock(42) … keyinroq boshqa so'rovda pg_advisory_unlock(42)
  5. pool.query("SELECT * FROM vazifalar WHERE id = $1", [5])
Yechim
  1. Ha — SET LOCAL tranzaksiya bilan birga tugaydi.
  2. Yo'q — SET sessiyada qoladi: ikkinchi so'rov boshqa server ulanishiga tushib, hisobot ni ko'rmasligi, yoki boshqa mijoz uni meros olishi mumkin.
  3. Ha — NOTIFY jadvalda "bor".
  4. Yo'q — sessiya qulfi birinchi server ulanishida qoladi; unlock boshqa ulanishga tushsa, qulf yechilmaydi. Tranzaksiya varianti — pg_advisory_xact_lock.
  5. 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
js
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):

text
ichida: 1 | tashqarida: null

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

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

bash
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 bizning ilova.foydalanuvchi_id kabi (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, SQL PREPARE.
  • SET hatto oddiy pg hovuzida 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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
PgBouncer va tashqi pooler'lar: session va transaction rejimi, SET LOCAL — IlmHamroh