Connection Pooling: למה מסדי נתונים נגמרים מחיבורים ואיך PgBouncer פותר את זה
מאת צוות מדיה דיל · 02.09.2026 · טכנולוגיה · 6 דק׳
כל חיבור למסד נתונים צורך תהליך OS יקר. איך PgBouncer עובד, ההבדל בין Session ל-Transaction Pooling, ולמה Serverless יוצר Connection Storms.
"Too many connections" היא אחת השגיאות המתסכלות ביותר בפרודקשן — במיוחד כשהמסד נתונים לכאורה לא עמוס בשאילתות. הבעיה כמעט תמיד לא קשורה לעומס אמיתי אלא לכך שכל חיבור למסד נתונים צורך משאבי OS יקרים, ואפליקציות מודרניות — במיוחד Serverless — פותחות חיבורים הרבה יותר מהר משהן סוגרות אותם.
למה חיבור למסד נתונים כל כך יקר
בניגוד לאובייקט רגיל בזיכרון, כל חיבור Postgres פותח תהליך OS נפרד בצד השרת, עם Stack, Buffers ומבני נתונים משלו — כ-5 עד 10 מגהבייט זיכרון לחיבור, גם כשהוא לא עושה כלום. מסד עם max_connections=100 יכול לכן לצרוך גיגהבייטים רק על תחזוקת חיבורים פתוחים ולא פעילים, עוד לפני שרצה שאילתה אחת.
PgBouncer: פרוקסי שמחזיק Pool קטן ומשותף
PgBouncer יושב בין האפליקציה למסד הנתונים ומחזיק Pool קטן וקבוע של חיבורים אמיתיים, בעוד שהאפליקציה יכולה "לחשוב" שיש לה מאות חיבורים זמינים. כל בקשה מקבלת חיבור פנוי מה-Pool לזמן קצר ומחזירה אותו מיד בסיום — כך אלפי לקוחות לוגיים משתפים מאות חיבורים פיזיים בפועל.
שלושת מצבי הפעולה — וההבדל הקריטי ביניהם
Session Pooling מקצה חיבור פיזי אחד לכל session עד שהוא מתנתק — הכי בטוח אבל לא חוסך הרבה. Transaction Pooling מחזיר את החיבור ל-Pool מיד בסיום כל טרנזקציה — חיסכון משמעותי, אבל שובר תכונות שתלויות ב-session state (כמו Prepared Statements או SET משתני session). Statement Pooling הכי אגרסיבי ומתאים רק לשאילתות בודדות ללא טרנזקציות מרובות-פקודות. רוב הפרודקשן משתמש ב-Transaction Pooling כפשרה, אבל צריך לדעת מה זה שובר לפני שמפעילים אותו.
Little's Law: איך קובעים גודל Pool נכון
גודל Pool אופטימלי לא אמור להיות "כמה שיותר גדול" — Little's Law אומר שמספר החיבורים הדרוש שווה בקירוב לקצב הבקשות (Throughput) כפול זמן הטיפול הממוצע בכל אחת. Pool גדול מדי לא רק מבזבז זיכרון בשרת, אלא בפועל מוריד תפוקה כי מסד הנתונים מבזבז זמן ב-Context Switching בין חיבורים במקום לעבד שאילתות — יש נקודה שמעברה הוספת חיבורים פוגעת בביצועים במקום לשפר.
Serverless: כשכל invocation רוצה חיבור חדש
בסביבת Serverless, כל קריאה יכולה להריץ instance חדש שפותח חיבור משלו — עשרות או מאות invocations במקביל שקולות לעשרות או מאות חיבורים חדשים בבת אחת, "Connection Storm" קלאסי שמציף את המסד תוך שניות. הפתרון המקובל הוא PgBouncer (או שירות מנוהל כמו Supabase Pooler) כשכבה חיצונית משותפת בין ה-Functions למסד, ולא הסתמכות על Pooling פנימי בקוד שרץ בכל instance בנפרד.
Pooling לא פותר עומס — הוא פותר בזבוז חיבורים
חשוב להבין את הגבול: PgBouncer מונע Connection Exhaustion, אבל אם המסד עצמו עמוס בכתיבות או קריאות כבדות, שום Pooling לא יעזור — שם הפתרון הוא Read Replicas לפיזור עומס קריאה, או Sharding כשגם זה לא מספיק. Pooling הוא תנאי הכרחי לפרודקשן יציב, לא תחליף לקיבולת אמיתית.
תקלה נפוצה: PgBouncer מעל PgBouncer
ארגונים שמשתמשים גם ב-ORM עם Connection Pool פנימי משלו וגם ב-PgBouncer חיצוני לפעמים יוצרים כפל Pooling מיותר, ולפעמים אפילו מזיק — ה-Pool הפנימי מחזיק חיבורים "בלוק" ל-PgBouncer בלי לשחרר אותם באמת ל-Pool המשותף, מה שמייתר חלק ניכר מהחיסכון שלשמו בכלל הוספתם את השכבה החיצונית. הכלל הפשוט: לבחור שכבה אחת שאחראית על ניהול Pool בפועל, ולהגדיר את השנייה למינימום (Pool Size נמוך מאוד או Pooling כבוי) כדי למנוע כפילות ולשמור על תמונת מצב ברורה של כמה חיבורים באמת פתוחים מול המסד בכל רגע נתון.
מקבלים שגיאות Connection Limit בפרודקשן? נשמח לעזור לכם לאבחן ולתקן בוואטסאפ.
תגיות: Connection Pooling · PgBouncer · Postgres · Serverless · Database