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