Connection Pool: Как Go убивает базу данных (и как этого избежать) — 22 июня 2026 г. в 05:42:02.770
Connection Pool: Как Go убивает базу данных (и как этого избежать) Выкатываете вы новый сервис, запускаете нагрузочное тестирование, и тут логи начинают истекать кровью: FATAL: sorry, too many clients already. Вы бежите к админам (или в консоль AWS) и видите, что ваш скромный сервис на Go открыл 1500 соединений к PostgreSQL и положил базу. Почему так вышло? Потому что по умолчанию стандартный пакет database/sql не имеет лимита на количество открытых соединений. Если к вам прилетит 1000 запросов одновременно, Go честно попытается открыть 1000 TCP-соединений. Для БД каждый коннект - это отдельный тяжелый процесс, который жрет память. Чтобы не быть врагом своим девопсам, нужно всегда настраивать пул соединений. Это делается тремя магическими методами. 1. SetMaxOpenConns(n) - Ограничение жадности Это жесткий лимит на количество одновременно открытых соединений к базе. Если вы поставили лимит 50, а пришел 51-й запрос, горутина просто заблокируется и будет покорно ждать, пока кто-нибудь не освободит коннект (или пока не отвалится по таймауту контекста). 2. SetMaxIdleConns(n) - Пул в режиме ожидания Сколько соединений держать открытыми, когда нет нагрузки? Если поставить слишком мало, при скачке трафика Go начнет судорожно устанавливать новые TCP-соединения (это долгий хендшейк, потеря драгоценных миллисекунд). Золотое правило: Часто MaxIdleConns ставят равным MaxOpenConns. Это гарантирует, что пул всегда прогрет и готов к бою. 3. SetConnMaxLifetime(d) - Защита от тухлых коннектов Если коннект висит слишком долго, база данных, балансировщик (HAProxy) или файрвол могут закрыть его в одностороннем порядке. Go об этом не узнает. Когда приложение попытается послать запрос в такой коннект, вы получите классическую ошибку driver: bad connection. Заставьте Go принудительно пересоздавать коннекты раз в час или несколько минут. Как это выглядит в коде: db, err := sql.Open("postgres", dsn) if err != nil { log.Fatal(err) } // Задаем лимиты в зависимости от размера вашего инстанса БД // и количества запущенных подов сервиса. db.SetMaxOpenConns(50) db.SetMaxIdleConns(50) db.SetConnMaxLifetime(30 * time.Minute) // Опционально: время жизни коннекта в простое (Go 1.15+) db.SetConnMaxIdleTime(5 * time.Minute) 🔥 Senior Tip: PgBouncer Если у вас 20 подов микросервиса, и каждый имеет MaxOpenConns = 50, суммарно в базу может прилететь 1000 коннектов. Postgres от такого станет плохо (оптимально для него - пара сотен коннектов). Поэтому в мире взрослых нагрузок между Go и базой всегда ставят PgBouncer (в режиме transaction pooling). Он мультиплексирует тысячи легковесных клиентских коннектов в десятки реальных коннектов к базе. В таком случае в Go можно ставить лимиты побольше, а общую нагрузку будет сглаживать PgBouncer. У кого базы падали в пятницу вечером из-за дефолтных настроек sql.DB? #golang #database #architecture #performance #bestpractices 👉 @golang_lib

