Могут ли возникнуть проблемы при одновременном подключении 70 процессов к базе данных без прокси и пулов?
Что проверяют
Понимаете ли вы, во что обходится соединение с базой, и умеете ли посчитать реальное число соединений от приложения. Цифра 70 выбрана не случайно: она близка к лимиту PostgreSQL по умолчанию. Следом обычно спрашивают про PgBouncer и то, что он ломает.
Как отвечать
Могут, и начать стоит с арифметики:
- лимит: в PostgreSQL по умолчанию
max_connections = 100, и несколько слотов зарезервированы под суперпользователя, у MySQL по умолчанию 151; - реальное число соединений: 70 процессов не значит 70 соединений.
create_engineв SQLAlchemy по умолчанию держит пулpool_size=5плюсmax_overflow=10, то есть до 15 соединений на процесс. У 70 воркеров Gunicorn или Celery это в пределе больше тысячи; - цена соединения: в PostgreSQL каждое соединение это отдельный серверный процесс со своей памятью. Открытие стоит TCP, TLS, аутентификации и запуска процесса. Если соединение открывается на каждый запрос, как в Django при
CONN_MAX_AGE = 0, накладные расходы заметны; - деплой и рестарт: при раскатке старые и новые экземпляры живут одновременно, соединений на время вдвое больше. После перезапуска базы все процессы переподключаются разом.
Симптом: ошибка FATAL: sorry, too many clients already.
Решения: ограничить пул в каждом процессе, чтобы сумма помещалась в лимит с запасом, и поставить PgBouncer в режиме transaction, где тысячи клиентских соединений обслуживаются десятками серверных. Цена режима: сессионные SET, advisory-блокировки и LISTEN работают не так, как ожидается. В свежих версиях Django пул для PostgreSQL включается через "pool": True в OPTIONS при psycopg 3.
Где спотыкаются
Считают процессы, а не соединения, и забывают про пул внутри каждого процесса.
Открывают соединения в родительском процессе до форка воркеров, например при --preload в Gunicorn. Дочерние процессы наследуют те же сокеты, и появляются странные ошибки протокола. Помогает engine.dispose(close=False) в каждом дочернем процессе или создание движка уже после форка.
Лечат ошибку увеличением max_connections до тысяч. Каждое соединение съедает память, и под нагрузкой база тормозит уже от числа процессов, а не от запросов.