Что делать при забитом пуле соединений к базе данных и зависших запросах
Что проверяют
Знаете ли вы, что пул в Go настраивается руками и по умолчанию настроен неудачно, и понимаете ли, что забитый пул почти всегда следствие, а не причина. Смотрят, полезете ли вы сразу крутить лимит вверх или сначала выясните, кто держит соединения.
Как отвечать
Сначала измерить. db.Stats() даёт InUse, Idle, WaitCount и WaitDuration: растущий WaitCount означает, что горутины стоят в очереди за соединением. Со стороны базы смотрят активные сессии, в Postgres это pg_stat_activity, где важно state: значение idle in transaction говорит, что код открыл транзакцию и не закрыл её.
Дальше типовые причины:
- незакрытые
rows: пока строки не вычитаны до конца и не вызванrows.Close(), соединение занято. Правило простое:defer rows.Close()сразу после проверки ошибки; - забытый
tx.Rollback()на ветке с ошибкой; - запросы без контекста. Ходить в базу надо через
QueryContextиExecContextс дедлайном, иначе одна зависшая выборка держит соединение бесконечно; - нет ограничений на стороне базы, помогают
statement_timeoutиidle_in_transaction_session_timeout.
Настройки пула назовите осознанно: SetMaxOpenConns ограничивает одновременную нагрузку на базу (по умолчанию предела нет, и приложение способно её задушить), SetMaxIdleConns по умолчанию всего два, из-за чего соединения постоянно пересоздаются, SetConnMaxLifetime и SetConnMaxIdleTime нужны, чтобы соединения обновлялись и не копились за балансировщиком.
Где спотыкаются
Первым делом увеличивают размер пула. Очередь при этом переезжает с приложения на базу: там появляются сотни активных сессий, конкуренция за блокировки и память, и становится хуже. Размер пула считается от возможностей базы, а не от аппетита сервиса.
Второе: считают, что отмена контекста мгновенно освобождает соединение. Отмена отправляет запрос на прерывание, но пока драйвер договаривается с сервером, соединение занято, а без statement_timeout запрос может продолжать выполняться.
Третье: путают idle-соединения с лимитом нагрузки. Это лишь кэш: маленькое значение даёт постоянный реконнект, большое держит бесполезные сессии, а ограничивает нагрузку всё равно SetMaxOpenConns.