Лимит одновременных транзакций подняли с тысячи до десяти тысяч, чтобы убрать ошибки, и получили шестнадцать минут деградации
Лиз ван Дейк из PlanetScale разобрала 7 августа инцидент, в котором продакшен-база MySQL складывалась на шестнадцать минут. Ошибки шли по нарастающей: на нулевой минуте 5 ошибок в минуту при 15 000 запросов в секунду, на десятой 1400 ошибок и 1500 запросов, потом транзакцию убил таймаут, накопленное разгреблось примерно за тридцать секунд, и пропускная способность подскочила до 8500.
Повод банальный: пакетная задача открыла транзакцию к горячей таблице, взяла блокировки строк и держала их пятнадцать минут без коммита. Интересно то, что происходило вокруг неё. Запросы, копившиеся следом, в основном на блокировках вообще не стояли: это обычные чтения, которым InnoDB отдаёт согласованный снимок. Но построение снимка требует пройти назад по истории версий каждой затронутой строки, а эта история росла все пятнадцать минут.
🔘 чтения, которые обычно занимают миллисекунды, начали упираться в потолок выполнения в 90 секунд, приложение повторяло их в тесном цикле;
🔘 за минуты внутри движка хранения скопилось больше десяти тысяч запросов, каждый восстанавливал всё более длинную цепочку версий;
🔘 чтения страниц обогнали способность буферного пула освобождать память, и падать начали запросы к совершенно другим таблицам, никак не связанным с заблокированными строками;
🔘 предыстория в том, что нагрузку переехали с прежней базы, где перед сервером стоял пул потоков: он ограничивал число одновременных запросов примерно тысячей, а остальных ставил в очередь на миллисекунды;
🔘 на новом месте пул при переполнении не ставит в очередь, а ждёт фиксированное время и возвращает ошибку. Приложение таких ошибок никогда не видело и обрабатывать их не умело, поэтому лимит подняли до десяти тысяч. Число выбрали не по расчёту, а потому что с ним ошибки прекратились;
🔘 лечение оказалось обратным: лимит вернули примерно к тысяче и заменили ошибку ожиданием в очереди с увеличенным таймаутом, то есть повторили поведение прежнего пула потоков.
На следующее утро конфигурация прошла проверку на другом шарде, который держит на порядок больше трафика: на пике около 25 тысяч заявок на слот в секунду при тысяче слотов, ноль ошибок и ровные 60 тысяч запросов в секунду. За весь день отклонена одна транзакция, а внутри MySQL в каждый момент выполнялось меньше двухсот команд: по закону Литтла это примерно три миллисекунды на запрос.
Автор оговаривает, что это не универсальный совет всегда зажимать параллелизм. Совет работает там, где есть пессимистичные блокировки и общее состояние: горячие строки, SELECT ... FOR UPDATE по популярным ключам, счётчики, балансы, очереди задач. И это не свойство MySQL: в PostgreSQL бывает то же самое, а роль ограничителя там играет PgBouncer.
Полная статья: https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql
@prog_stuff
Post #2904
614