Что делать если нагрузка на сервис выросла и время отклика увеличилось, несмотря на оптимизацию запросов и индексы
Что проверяют
Умеете ли искать узкое место за пределами SQL, понимаете ли, почему задержка растёт нелинейно при приближении к пределу, и владеете ли штатным профилировщиком Go.
Как отвечать
Сначала измерить, где именно проводится время: разложить задержку по стадиям (ожидание в очереди, работа процессора, база, внешние вызовы) через трассировку и метрики, и смотреть процентили, а не среднее.
Типовые виновники:
- пул соединений к базе:
SetMaxOpenConnsменьше, чем одновременных запросов, и время уходит в ожидание свободного соединения. Видно поdb.Stats(), поляWaitCountиWaitDuration; - насыщение ресурса: при загрузке около предела очередь растёт лавинообразно, и оптимизация на проценты уже ничего не меняет. Спасает ограничение параллелизма и отсечение по дедлайну, а не ускорение обработчика;
- процессор и сборщик мусора: профили
cpuиheapчерезpprof, аллокации в горячем пути дают паузы; - блокировки: профили
mutexиblock. Один глобальный мьютекс или логирование под замком превращают сервис в однопоточный; - утечка горутин: их число растёт, каждая держит память и соединение. Запросы без
contextс дедлайном висят вечно.
Дальше по ситуации: кэш на чтение, батчинг обращений, семафор на конкурентность, и только потом добавление инстансов.
Где спотыкаются
Смотрят среднее время ответа. Оно прячет хвост распределения, а жалуются пользователи именно на хвост, и растёт при насыщении сначала он.
Сразу масштабируют горизонтально. Если упирается в мастер базы или в один общий ресурс, новые инстансы просто добавят конкурентов за него и сделают хуже.
Ставят кэш поверх, не разобравшись в причине. Он маскирует проблему до первого сброса, после которого вся отложенная нагрузка приходит на базу разом, плюс появляется отдельный класс багов с устаревшими данными.