چرا اتصال را دوباره استفاده کنیم؟
هر اتصال تازه یعنی handshake شبکه، احتمالاً TLS، احراز هویت و ساخت thread در سرور: چند میلیثانیه برای هر درخواست. در بار بالا هم مسئله فقط زمان نیست؛ تعداد اتصالها به max_connections میرسد و کاربران خطای Too many connections میبینند. Pool تعدادی اتصال باز نگه میدارد و به درخواستها قرض میدهد.
| روش | کجا | توضیح |
|---|---|---|
| CONN_MAX_AGE جنگو | داخل هر thread | اتصال پایدار، نه pool واقعی؛ هر thread یک اتصال |
| QueuePool در SQLAlchemy | داخل هر پروسه | pool واقعی با سقف و صف انتظار |
| ProxySQL | سرویس جدا | multiplexing صدها اتصال برنامه روی چند اتصال واقعی، مسیریابی خواندن به replica |
حسابوکتاب ساده
اگر gunicorn با ۴ پروسه و هر کدام ۲ thread اجرا شود و CONN_MAX_AGE بزرگتر از صفر باشد، هر سرور برنامه حداکثر ۸ اتصال پایدار دارد. سه سرور برنامه یعنی ۲۴، بهعلاوهی workerهای Celery و کاربران مدیریتی. max_connections باید این جمع را بهعلاوهی حاشیهی امن پوشش دهد. زیر ASGI توصیهی خود جنگو CONN_MAX_AGE = 0 و استفاده از pooler بیرونی است.
SQLAlchemy برای اسکریپتها و سرویسها
from sqlalchemy import create_engine, text
engine = create_engine(
"mysql+mysqldb://web:secret@127.0.0.1:3306/carpet_factory?charset=utf8mb4",
pool_size=10, # اتصالهای همیشه باز
max_overflow=5, # اتصال اضافه در اوج بار
pool_timeout=10, # انتظار برای اتصال آزاد، بعد خطا
pool_recycle=1800, # کمتر از wait_timeout سرور
pool_pre_ping=True, # بررسی زنده بودن پیش از قرض دادن
)
with engine.begin() as conn:
rows = conn.execute(
text("SELECT code, hall FROM looms WHERE is_active = :a"), {"a": True}
).all()
پایش اتصالها در سرور
SELECT user, SUBSTRING_INDEX(host, ':', 1) AS client, command, COUNT(*) AS n
FROM performance_schema.processlist
GROUP BY user, client, command
ORDER BY n DESC;
SHOW GLOBAL STATUS LIKE 'Threads_%';
SHOW GLOBAL STATUS LIKE 'Aborted_c%';
اگر دهها اتصال با command برابر Sleep و زمان طولانی از یک میزبان میبینید، احتمالاً برنامهای اتصال را باز میکند و نمیبندد.
نکتههایی که کمتر کسی میداند
pool_recycleباید ازwait_timeoutسرور کمتر باشد؛ وگرنه صبح اولین درخواست باMySQL server has gone away(خطای 2006) شکست میخورد، چون سرور شب اتصال بیکار را بسته است.- اتصال قرضی وضعیت session را با خود میبرد: متغیرهای
SET @x، جدولهای موقت وSET SESSION. در کد برنامه از تغییر وضعیت session پرهیز کنید یا قبل از برگرداندن پاکش کنید. - اتصالی که قبل از fork در پروسهی والد ساخته شده نباید در فرزندان (مثل workerهای Celery در حالت prefork) استفاده شود؛ نتیجه خطاهای عجیبی مثل
Packet sequence number wrongاست. در SQLAlchemy بعد از forkengine.dispose()بزنید. - اگر
Threads_createdپیوسته بالا میرود، سرور برای هر اتصال thread تازه میسازد؛ بزرگتر کردنthread_cache_sizeهزینهی اتصال را کم میکند. - ProxySQL برای اتصالی که از متغیر کاربری، جدول موقت یا تراکنش باز استفاده کند multiplexing را خاموش میکند؛ کد پر از
SET @varمزیتش را از بین میبرد.