بازگشت به کتابخانهکتابخانه12.2اتصال و کوئری در production: Pooling و N+1
طراحی سیستم نرم‌افزاریSYSTEM DESIGNاز صفر تا تسلط
v1.0.0
01مبانی و تصویر بزرگ
02Scalability و ظرفیت
03لایه داده
04Cache، Queue و جریان
05معماری نرم‌افزار
06قابلیت اطمینان و عملیات
07متد طراحی
08Case Study های واقعی
09سیستم‌های توزیع‌شده عمیق
10مهندسی تولید: داده، امنیت و کارایی
11تمرین پیشرفته و کیس‌استادی‌های مکمل
12زیر کاپوت دیتابیس و معماری داده
13وب بلادرنگ و پروتکل‌های مدرن
14سیستم‌های توزیع‌شده پیشرفته
15SaaS ،SRE ،امنیت و شبکه پیشرفته
16طراحی سیستم در عصر AI
17Case Study های تکمیلی
LESSON 12.2فصل ۱۲زیر کاپوت دیتابیس و معماری داده

اتصال و کوئری در production: Pooling و N+1

  • ~۱۳ دقیقه
  • ۴ پرسش
  • متن را انتخاب کن تا هایلایت شود

بیشتر دیتابیس‌های production نه به‌خاطر کوئری بد، بلکه به‌خاطر مدیریت بدِ اتصال و تراکنش می‌خوابند: اتصال‌های انباشته، تراکنش‌های رها، و الگوی N+1 که در لوکال بی‌صداست و در مقیاس فاجعه می‌شود.

اتصال دیتابیس گران است

  • ساخت هر connection یعنی TCP handshake + TLS + احراز هویت + تخصیص حافظه سمت دیتابیس (در Postgres هر connection یک process با چند مگابایت RAM).
  • Postgres عملاً بعد از چند صد اتصال فعال دچار افت می‌شود؛ ۲۰ سرویس × ۲۰ connection یعنی ۴۰۰ اتصال — قبل از هر کوئری، باید برای اتصال برنامه ریخت.
  • Connection Pool (داخل اپ یا بیرونی مثل PgBouncer) اتصال‌ها را بازاستفاده می‌کند؛ در transaction mode اتصال فقط برای مدت تراکنش به کلاینت داده می‌شود — مقیاس اتصال ده‌ها برابر می‌شود.
  • هشدار transaction mode: ویژگی‌های session-محور (SET ،prepared statement نام‌دار، temp table) کار نمی‌کنند یا باید فعالشان کرد.

N+1: قاتل خاموش

-- بد: ۱ کوئری برای لیست + N کوئری برای جزئیات هر آیتم
SELECT * FROM orders LIMIT 50;            -- ۱
SELECT * FROM users WHERE id = ?;          -- ×۵۰ بار!

-- خوب: یک کوئری با JOIN یا IN
SELECT * FROM orders JOIN users ON users.id = orders.user_id LIMIT 50;

در لوکال با ۱۰ رکورد تست، N+1 یعنی ۱۱ کوئری به loopback — هیچ‌کس نمی‌فهمد. در production با ۵۰۰ آیتم و RTT واقعی یعنی ۵۰۰ رفت‌وبرگشت شبکه برای یک صفحه. راه‌ها: JOIN ،IN با batch ،DataLoader در GraphQL، و مانیتورکردن تعداد کوئری هر endpoint.

بهداشت تراکنش

  • تراکنش را کوتاه نگه دار: هر لحظه بازماندن، قفل‌ها را نگه می‌دارد و vacuum/autovacuum را عقب می‌اندازد.
  • Idle in transaction یعنی تراکنشی که رها شده — قفل گرفته و هیچ کاری نمی‌کند؛ باید timeout داشته باشد (idle_in_transaction_session_timeout).
  • ترتیب قفل‌گرفتن را در همه مسیرها یکسان کن (مثلاً همیشه اول حساب کوچک‌تر id) تا deadlock نگیری — و retry idempotent برای deadlock های اجتناب‌ناپذیر (فصل ۱۰).

به زبان ساده

اتصال به دیتابیس گران است؛ با استخر اتصال (pool) چند اتصال آماده را بین صدها درخواست شریک می‌شویم و با دیدن N+1 ،کوئری‌ها را دسته‌ای می‌کنیم.

مثال واقعی

مثل آسانسور ساختمان: به‌جای ساختن آسانسور تازه برای هر مسافر (اتصال تازه)، چند آسانسور ثابت همه را جابه‌جا می‌کند؛ و N+1 مثل رفتن به فروشگاه برای هر قلم یک‌بار است به‌جای یک سبد کامل.

دانش‌سنجی

آزمون درس

۴ Q
01
چرا ۴۰۰ اتصال مستقیم به Postgres مشکل‌ساز است؟
02
الگوی N+1 در production چه پیامدی دارد؟
03
در transaction mode ی PgBouncer کدام مورد ممکن است بشکند؟
04
بهترین دفاع در برابر deadlock های اجتناب‌ناپذیر چیست؟