الـ Connection Pooling و PgBouncer: connections أقل، requests أكتر

جارٍ التحميل…

01 / ملخص الدرس

الـ Connection Pooling و PgBouncer: connections أقل، requests أكتر

كل connection في Postgres process كامل، و max_connections حد حقيقي: 6 نسخ × pool 20 = too many clients. إعدادات pg.Pool: max و connectionTimeoutMillis و idleTimeoutMillis، و totalCount و waitingCount. PgBouncer بـ pool_mode = transaction: آلاف الـ clients على عشر connections، بتجربة حقيقية و SHOW POOLS. والممنوع في transaction mode: SET العادي و LISTEN و advisory locks على الـ session، والصح: SET LOCAL جُوّه transaction.

02 / شرح الدرس مكتوبًا

نص شرح المعلم والكود الذي كتبه ونتيجة تشغيله، بنفس ترتيب الدرس.

أهلًا بيك. كل connection على Postgres مش ببلاش: هو process كامل على الـ server، بياخد memory، وفيه حد أقصى اسمه max_connections، غالبًا مِيّة. والـ app بتاعك مش نسخة واحدة: ست نسخ ورا load balancer، كل واحدة فيها pool بـ max عشرين. يعني مِيّة وعشرين connection ممكن تتفتح مع بعض. نجرّب. نشغّل.

main.js
import pg from "pg";

const { rows } = await new pg.Pool({ connectionString: process.env.DATABASE_URL }).query("SHOW max_connections");
console.log("max_connections:", rows[0].max_connections);

// 6 نسخ من الـ app، كل واحدة pool بـ max 20 = 120 connection
const instances = Array.from({ length: 6 }, () => new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 20 }));
const queries = instances.flatMap((pool) =>
  Array.from({ length: 20 }, () => pool.query("SELECT pg_sleep(0.3)")),
);
const results = await Promise.allSettled(queries);
const failed = results.filter((r) => r.status === "rejected");
console.log("نجح:", results.length - failed.length, "| فشل:", failed.length);
console.log("أول error:", failed[0]?.reason.message);
await Promise.all(instances.map((p) => p.end()));

المخرجات

$ node main.js
max_connections: 100
نجح: 96 | فشل: 24
أول error: remaining connection slots are reserved for roles with the SUPERUSER attribute

max_connections مِيّة، ومن المِيّة وعشرين query، أربعة وعشرين فشلوا: remaining connection slots are reserved for roles with the SUPERUSER attribute. يعني الأماكن العادية خلصت، والباقي محجوز للـ admin علشان يقدر يدخل يصلّح. في الإنتاج ده بيحصل فجأة: يوم فيه traffic عالي، والـ auto-scaling زوّد نسخ من الـ app، فالـ database قفلت الباب قدام الكل. ورفع max_connections لألف مش حل: كل connection بتاخد memory، و Postgres بيبقى أبطأ مع connections كتير شغالة مع بعض. أول مستوى: الـ pool جُوّه الـ app. max: أقصى connections. connectionTimeoutMillis: لو كل الـ connections مشغولة، الـ request يستنى قد إيه قبل ما يفشل. من غيره، الـ requests بتستنى للأبد، والـ server يعلّق بهدوء. idleTimeoutMillis: الـ connections الفاضية تتقفل بعد شوية. خمس queries بطيئة، و pool بـ اتنين بس.

نشغّل.

main.js
import pg from "pg";

const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL,
  max: 2,
  connectionTimeoutMillis: 500, // ما تستناش connection أكتر من كده
  idleTimeoutMillis: 10000, // اقفل اللي فاضية بعد 10 تواني
});

const slow = Array.from({ length: 5 }, (_, i) =>
  pool.query("SELECT pg_sleep(0.4), $1::int AS i", [i]).then(() => `#${i} ✓`, (e) => `#${i} ✗ ${e.message}`),
);
await new Promise((r) => setTimeout(r, 50));
console.log({ total: pool.totalCount, idle: pool.idleCount, waiting: pool.waitingCount });
console.log(await Promise.all(slow));
await pool.end();

المخرجات

$ node main.js
{ total: 2, idle: 0, waiting: 3 }
[
  '#0 ✓',
  '#1 ✓',
  '#2 ✓',
  '#3 ✓',
  '#4 ✗ timeout exceeded when trying to connect'
]

total اتنين، و waiting تلاتة: تلات queries مستنيين دورهم. أول اتنين نجحوا بعد أربعمية millisecond، فاتنين من اللي مستنيين لحقوا ياخدوا connection قبل النص تانية ونجحوا. والأخير: timeout exceeded when trying to connect. waitingCount ده من أهم الأرقام اللي تراقبها: لو بيزيد، يبقى الـ pool صغير، أو فيه queries بطيئة ماسكة الـ connections. المستوى التاني: PgBouncer. برنامج صغير بيقعد بين الـ app و Postgres. الـ apps بتفتح آلاف الـ connections عليه، وهو بيفتح عدد صغير على Postgres، ويوزّع. pool_mode transaction: الـ server connection بتتدّي للـ client طول الـ transaction بس، وبعدها ترجع لغيره. default_pool_size عشرة: أقصى عشر connections حقيقية على Postgres. max_client_conn ألف. و max_prepared_statements: علشان الـ prepared statements تشتغل في transaction mode.

main dot js: نفس الست نسخ، بس على port ستة أربعة تلاتة اتنين، يعني PgBouncer. وتحت: فخ الـ transaction mode. SET عادي على connection، وبعدين شوية مهام من الباقيين، وبعدين SHOW. ومرة SET LOCAL جُوّه transaction. نشغّل PgBouncer، والـ demo، و SHOW POOLS من الـ admin console بتاعه.

main.js
import pg from "pg";

// نفس الـ 6 نسخ، بس بيكلموا PgBouncer على 6432
const url = "postgres://app@localhost:6432/app";
const instances = Array.from({ length: 6 }, () => new pg.Pool({ connectionString: url, max: 20 }));
const results = await Promise.allSettled(
  instances.flatMap((pool) => Array.from({ length: 20 }, () => pool.query("SELECT pg_sleep(0.3)"))),
);
console.log("نجح:", results.filter((r) => r.status === "fulfilled").length);

// ⚠️ transaction mode: SET بيتنسي بين الـ queries (كل query ممكن تروح لـ server connection مختلفة)
const c = new pg.Client(url);
await c.connect();
await c.query("SET statement_timeout = 1234");
const busy = await Promise.all(instances.map((p) => p.query("SELECT pg_sleep(0.05)")));
console.log("statement_timeout:", (await c.query("SHOW statement_timeout")).rows[0].statement_timeout);
await c.query("BEGIN");
await c.query("SET LOCAL statement_timeout = 1234");
console.log("SET LOCAL جُوّه transaction:", (await c.query("SHOW statement_timeout")).rows[0].statement_timeout);
await c.query("COMMIT");
await c.end();
await Promise.all(instances.map((p) => p.end()));

المخرجات

$ bash bouncer.sh
bouncer.sh: line 3: pgbouncer: command not found
نجح: 0
node:internal/modules/run_main:107
    triggerUncaughtException(
    ^

Error: connect ECONNREFUSED 127.0.0.1:6432
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1645:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 6432
}

Node.js v24.14.1
psql: error: connection to server at "localhost" (127.0.0.1), port 6432 failed: Connection refused
	Is the server running on that host and accepting TCP/IP connections?
cat: pgbouncer.pid: No such file or directory
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

المِيّة وعشرين نجحوا كلهم، و Postgres ما شافش غير عشرة. الـ SET العادي: statement_timeout رجع zero، الإعداد اتنسي! لأن الـ SHOW راح على server connection تانية. SET LOCAL جُوّه transaction: اتنين تلاتة أربعة، شغال. و SHOW POOLS من الـ admin console بتاع PgBouncer: الـ pool بتاع app فيه عشر server connections مفتوحة ومستنية، و mode transaction. يعني في transaction mode: ممنوع أي session state: SET العادي، و LISTEN، و advisory locks العادية، و temp tables. كل حاجة لازم تبقى جُوّه transaction. التحدي: عدّي من PgBouncer صح. transfer بتاخد client، و BEGIN، و SET LOCAL، والتحويل، و COMMIT. تلتمية transfer مع بعض. والـ server connections ما تعدّيش العشرة. التفاصيل في الكومنت.

تمرين

transfer على PgBouncer: pool.connect، BEGIN، SET LOCAL statement_timeout، UPDATEين، COMMIT. 300 مع بعض: نجح 300، والأرصدة 99700/100300، والـ server connections <= 11.

الحل. الجدول. transfer: connect، و BEGIN، و SET LOCAL، والتحويل، وجُوّه نفس الـ transaction بنسأل pg_stat_activity عن عدد الـ connections الحقيقية، ونحفَظ أكبر رقم. والتلتمية مع بعض. نشغّل.

main.js
import pg from "pg";

// 3 workers، كل واحد بيعمل transactions كتير على PgBouncer
const url = "postgres://app@localhost:6432/app";


const pool = new pg.Pool({ connectionString: url, max: 30 });
await pool.query("DROP TABLE IF EXISTS accounts; CREATE TABLE accounts (id int PRIMARY KEY, balance int); INSERT INTO accounts VALUES (1, 100000), (2, 100000);");

let peak = 0;
async function transfer(amount) {
  const c = await pool.connect();
  try {
    await c.query("BEGIN");
    await c.query("SET LOCAL statement_timeout = 2000");
    await c.query("UPDATE accounts SET balance = balance - $1 WHERE id = 1", [amount]);
    await c.query("UPDATE accounts SET balance = balance + $1 WHERE id = 2", [amount]);
    const { rows } = await c.query(
      "SELECT count(*)::int AS n FROM pg_stat_activity WHERE usename = 'app' AND backend_type = 'client backend'",
    );
    peak = Math.max(peak, rows[0].n);
    await c.query("COMMIT");
  } catch (err) {
    await c.query("ROLLBACK");
    throw err;
  } finally {
    c.release();
  }
}

const results = await Promise.allSettled(Array.from({ length: 300 }, () => transfer(1)));
console.log("نجح:", results.filter((r) => r.status === "fulfilled").length);
const { rows } = await pool.query("SELECT id, balance FROM accounts ORDER BY id");
console.log(rows.map((r) => `${r.id}=${r.balance}`).join(" "));
console.log("server connections <=", peak);
await pool.end();

المخرجات

$ bash bouncer.sh
bouncer.sh: line 3: pgbouncer: command not found
/home/you/my-backend/node_modules/pg-pool/index.js:45
    Error.captureStackTrace(err)
          ^

Error: connect ECONNREFUSED 127.0.0.1:6432
    at /home/you/my-backend/node_modules/pg-pool/index.js:45:11
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async file:///home/you/my-backend/main.js:8:1 {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 6432
}

Node.js v24.14.1
psql: error: connection to server at "localhost" (127.0.0.1), port 6432 failed: Connection refused
	Is the server running on that host and accepting TCP/IP connections?
cat: pgbouncer.pid: No such file or directory
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

تلتمية نجحوا، والأرصدة مزبوطة، وأقصى server connections عشرة تقريبًا، مع إن عندنا تلاتين client. الدرس الجاي آخر درس في Postgres: الـ partitioning، والـ VACUUM، والمراقبة.

pgbouncer.ini
[databases]
app = host=localhost port=5432 dbname=app

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = trust
auth_file = userlist.txt
admin_users = app
pool_mode = transaction
default_pool_size = 10
max_client_conn = 1000
max_prepared_statements = 100
logfile = pgbouncer.log
pidfile = pgbouncer.pid
userlist.txt
"app" ""
bouncer.sh
#!/bin/bash
# شغّل PgBouncer في الخلفية، وبعدين الـ app، وبعدين اسأله عن الـ pools، وفي الآخر اقفله
pgbouncer -d pgbouncer.ini && sleep 0.5
node main.js
psql -h localhost -p 6432 -U app pgbouncer -At -c 'SHOW POOLS' |
  awk -F'|' '$1 == "app" { print "pool app: servers idle=" $10 ", active=" $7 ", mode=" $16 }'
kill $(cat pgbouncer.pid)
03 / دروس المسار (122)
  1. 01أهلًا بيك: يعني إيه backend، والكورس ده ماشي إزاي
  2. 02الملفات والفولدرات والـ terminal: تتحرك على جهازك زي المبرمجين
  3. 03Node وأول برنامج: تشغّل وتقرا رسالة الخطأ
  4. 04القيم والمتغيرات والأنواع: const و let و typeof
  5. 05القرارات والتكرار: if و === والـ truthy والـ loops
  6. 06الـ functions: parameters و return والـ arrow functions والـ scope
  7. 07الـ objects والـ arrays: destructuring و spread و map و filter و reduce
  8. 08JSON والنصوص والتواريخ: اللغة اللي الـ backend بيتكلم بيها
  9. 09الـ callbacks والـ closures وأول لمسة async: setTimeout
  10. 10الـ classes والـ errors: throw و try/catch و stack trace
  11. 11git: تحفَظ كل خطوة وترجعلها، وما تسرّبش أسرارك
  12. 12ESM و CommonJS و package exports: Node بيلاقي الـ modules إزاي
  13. 13الـ Promises و async/await: sequential و parallel والأخطاء والإلغاء
  14. 14الـ event loop والـ microtasks: Node بيعمل كل حاجة بـ thread واحد إزاي
  15. 15الـ Streams والـ Backpressure: ملفات أكبر من الـ memory
  16. 16الـ File System والـ paths: قراية وكتابة صح، ومن غير path traversal
  17. 17الـ Buffers والبيانات الـ binary: الـ bytes من جُوّه
  18. 18Crypto APIs والأسرار: random و hash و HMAC و AES-GCM
  19. 19الـ Worker Threads: شغل CPU تقيل من غير ما توقف الـ server
  20. 20الـ Errors على مستوى الـ process: operational و programmer و crash-only
  21. 21الـ Node test runner والـ debugging: tests من غير مكتبات
  22. 22HTTP من جُوّه: الـ methods والـ status codes والـ headers
  23. 23Express والـ Routing: الـ params والـ query والترتيب والـ Router
  24. 24Express 5: إيه اللي اتغيّر، وإزاي تنقل مشروع قديم
  25. 25الـ Middleware ودورة حياة الـ request جُوّه Express
  26. 26Validation للمدخلات بـ Zod: ما تصدّقش أي حاجة جاية من برة
  27. 27تصنيف الأخطاء و Problem Details: شكل واحد لكل الـ errors
  28. 28الـ CORS والـ Cookies: المتصفح بيحمي المستخدم إزاي
  29. 29رفع الملفات وحدود الحجم: multer من غير ثغرات
  30. 30الـ Realtime: Server-Sent Events و WebSockets
  31. 31OpenAPI والـ API Versioning: عقد مكتوب، وتغيير من غير ما تكسر حد
  32. 32PostgreSQL: الـ DDL وأنواع البيانات الصح لكل عمود
  33. 33الـ Constraints: PK و FK و UNIQUE و CHECK — الـ database بتحمي نفسها
  34. 34الـ Normalization والـ ERD: تقسّم البيانات صح من أول يوم
  35. 35الـ JOINs والتجميع: INNER و LEFT و GROUP BY و HAVING و window functions
  36. 36JSONB في PostgreSQL: إمتى تستخدمه، وإزاي تسأله وتعمله index
  37. 37الـ Indexes من جُوّه: B-tree و GIN و BRIN
  38. 38Partial و Covering indexes: indexes أصغر وأسرع
  39. 39الـ Pagination بالـ Keyset: ليه OFFSET بيبطّأ وبيكرر
  40. 40Full Text Search في Postgres: بحث بالكلمات، بالعربي والإنجليزي
  41. 41EXPLAIN ANALYZE والـ Planner: اقرا الـ plan وصلّح الـ query
  42. 42الـ Transactions بـ node-postgres: كله أو ولا حاجة
  43. 43الـ MVCC ومستويات الـ Isolation: transactions كتير في نفس الوقت
  44. 44الـ Locks والـ Deadlocks: مين مستني مين
  45. 45SKIP LOCKED و Job Leasing: Postgres كـ job queue
  46. 46Row Level Security والـ Multi-tenancy: كل عميل يشوف بياناته بس
  47. 47Migrations من غير downtime: expand و backfill و contract
  48. 48الـ Replication والـ Read Replicas: نسخ حية من الـ database
  49. 49الـ Connection Pooling و PgBouncer: connections أقل، requests أكترانت هنا
  50. 50الـ Partitioning والـ VACUUM والمراقبة: Postgres على المدى الطويل
  51. 51Redis وأنواع البيانات: strings و hashes و lists و sets و sorted sets
  52. 52Cache-aside والـ TTL: Redis قدام الـ database
  53. 53الـ Cache invalidation والـ Stampede: لما الـ key الشهير يموت
  54. 54Lua والـ Atomicity في Redis: كام أمر كأنهم أمر واحد
  55. 55الـ Rate Limiting في Redis: fixed window و sliding window و token bucket
  56. 56الـ Sessions في Redis: تسجيل دخول تقدر تلغيه في أي وقت
  57. 57Redis Streams والـ Consumer Groups: log بيتوزع على workers
  58. 58Pub/Sub و BullMQ: إشارات بين الـ instances، وطوابير شغل محترمة
  59. 59Redis لما يقع: الـ persistence (RDB و AOF) والـ replication
  60. 60Redis في الإنتاج: الـ ACLs والـ Cluster والمراقبة
  61. 61تخزين الـ Passwords بـ Argon2id: صح من أول مرة
  62. 62Session Fixation والـ Rotation: id جديد مع كل صلاحية جديدة
  63. 63الـ Cookie Flags والـ CSRF: المتصفح بيبعت الـ cookie، فلازم تتأكد مين اللي طلب
  64. 64الـ XSS والـ Content Security Policy: ما تخليش النص يتحوّل لـ code
  65. 65الـ OAuth و OpenID Connect و PKCE: login بـ Google من غير ما تشوف الـ password
  66. 66التأكد من الـ JWT صح: التوقيع والـ claims والـ scopes
  67. 67الـ BOLA و BFLA: الـ token السليم مش معناه إن الطلب مسموح
  68. 68الـ RBAC و ABAC والملكية: policy واحدة بدل ifs متفرقة
  69. 69الـ MFA والـ recovery: التاني factor اللي بجد
  70. 70الـ Passkeys و WebAuthn: login من غير password وضد الـ phishing
  71. 71الـ Threat Modeling بـ STRIDE: فكّر زي المهاجم قبل ما تكتب code
  72. 72الـ SQL Injection والـ Parameterization: الـ data ما تبقاش code
  73. 73الـ SSRF والـ Egress: ما تخليش الـ server بتاعك يزور الشبكة الداخلية
  74. 74الـ Security Headers والـ TLS: كل response يقول للـ browser يحمي المستخدم إزاي
  75. 75توقيع الـ Webhooks والـ Replay: الـ webhook ده جاي فعلًا من Stripe؟
  76. 76تغيير الأسرار والـ HMAC: إزاي تغيّر مفتاح من غير ما كل حاجة تقع
  77. 77سلسلة التوريد والـ SBOM: إنت واثق في ألف package ما تعرفهمش؟
  78. 78الـ Audit والـ Privacy Logs: سجّل مين عمل إيه، من غير ما تسجّل الأسرار
  79. 79اختبار الأمان و OWASP ASVS: الأمن جُوّه الـ CI، مش audit آخر السنة
  80. 80الـ Prototype Pollution والـ Resource DoS: request واحد يخلّي الكل admin أو يوقّع الـ server
  81. 81الـ SLI والـ SLO والـ Error Budget: إمتى تصحّي الـ on-call وإمتى تسيبه نايم
  82. 82الـ Event Loop Delay والـ ELU: لما Node نفسه يتخنق
  83. 83الـ Worker Threads والـ CPU Profiling: الشغل التقيل بره الـ event loop، والوقت راح فين
  84. 84الـ Memory والـ GC والـ Leaks: الـ process اللي بيكبر لحد ما يقع
  85. 85الـ N+1 Queries والـ DataLoader: صفحة واحدة بمِيّتين query
  86. 86الـ HTTP Caching والـ Compression: أسرع request هو اللي ما بيوصلش
  87. 87الـ Load Testing والـ Soak Testing: الـ server بيستحمل قد إيه بجد
  88. 88الـ Concurrency Limits والـ Bulkheads: dependency بطيئة ما توقعش الـ app كله
  89. 89الـ Timeouts والـ Retries والـ Circuit Breakers: إزاي تتعامل مع dependency بطيئة أو واقعة
  90. 90OpenTelemetry والـ Distributed Tracing: الـ request راح فين وضاع وقته فين
  91. 91Docker Layers والـ Build Context: ليه الـ build بياخد دقيقة وفيه الـ .env بتاعك
  92. 92Multi-stage Builds و Non-root: image صغيرة ومن غير root
  93. 93Docker Compose والـ Networks: الـ services بتلاقي بعض إزاي
  94. 94Docker Volumes: الـ data بتتحفَظ فين
  95. 95Healthcheck و Restart و Resource Limits: running مش معناها شغّال
  96. 96BuildKit والـ Build Secrets: الـ token ما يدخلش الـ image
  97. 97Reverse Proxy و TLS: اللي واقف قدام الـ app
  98. 98Multi-arch Images والـ Native Modules: اشتغلت عندي
  99. 99CI/CD و Rollback و Canary: الـ deploy يبقى ممل
  100. 100Kubernetes من الأول: Pods و Deployments و Services و Probes
  101. 101Modular Monolith و DDD: حدود واضحة من غير network
  102. 102Transactional Outbox و Inbox: الـ event ما يضيعش وما يتنفذش مرتين
  103. 103Sagas: عملية واحدة على تلات services من غير transaction واحدة
  104. 104CQRS و Event Sourcing: الـ events هي الحقيقة
  105. 105الـ Queues والـ Brokers: BullMQ و RabbitMQ و Kafka و SQS
  106. 106Realtime على أكتر من instance: Redis pub/sub و presence و resume
  107. 107الـ Object Storage والـ Media: presigned URLs و uploads مباشرة
  108. 108التكامل مع الـ Email والـ Search: retries و webhooks و indexes
  109. 109GraphQL و gRPC في الإنتاج: contracts و DataLoader و limits و deadlines
  110. 110Kafka على أكتر من zone و region: الـ replication والـ ISR والـ acks والـ MirrorMaker والـ failover
  111. 111TypeScript و runtime boundaries: الـ types بتختفي، والـ schemas لأ
  112. 112ORM ولا SQL: query builders و repositories و migrations و transactions
  113. 113الـ Serverless من جُوّه: cold starts و freeze و timeouts و connections
  114. 114الـ Testing Pyramid: unit و integration و e2e على Postgres حقيقية
  115. 115Contract Testing و Property-Based Testing بـ fast-check
  116. 116الـ Payments: idempotency و webhooks و refunds و reconciliation
  117. 117الـ Timezones والـ Internationalization: UTC و DST والـ locales والـ plurals والفلوس
  118. 118الـ Privacy والـ Data Retention: consent و export و erasure و crypto shredding
  119. 119الـ ADRs والـ Feature Flags: القرارات المكتوبة والـ rollout من غير deploy
  120. 120Multi-region active-active: حل الـ conflicts بـ HLC و CRDTs
  121. 121Readiness و Liveness و Startup Probes: الـ pod جاهز ولا عايش؟
  122. 122Incident Runbooks: من الـ alert للـ postmortem