Dog-pile эффект. Как отгонять стаи собак.
Dog-pile эффект — ситуация когда кэш протухает, а большое количество запросов генерирует высокую нагрузку на источник данных, из которых строиться кэш.
Представьте, что вы кэшируете результат какого то тяжёлого запроса, например, список популярных статей. В какой-то момент времени кэш протухает, и его кто-то должен построить заново. В общем то пока все хорошо. Кроме случаев когда построение кэша тяжёлая операция, а запросов на него много. Например, запрос для генерации кэша занимает 1 секунду, а пользователи ломятся по 10 штуков в секунду. Соответственно, 9 пользователей (кроме первого) будут только зря нагружать базу. А при большом количестве запросов могут и полностью ее положить.
И пусть весь мир подождёт
Первое, что нужно решить — может ли пользователь ждать генерации кэша. Пример с популярными статьями это цветочки, ибо есть еще сложно рассчитываемые рейтинги, которые могут считаться оооочень долго.
Предположим, что у нас простой случай и пользователь не сломается, если подождёт секунду-другую.
Честные блокировки
Решение в лоб. Первый пришедший лочит кэш на запись и отправляется генерировать кэш. Остальные, увидев лок, понимают, что не успели, чешут репы и думают, что делать.
Как именно делать лок зависит только от вашей фантазии и имеющихся средств. Это может быть, что угодно:
- Лок файловой системы
- много косяков, но иногда работает :). Подробности в описании функции flock().
- Мьютекс в хранилище
- Если мы используем memcache и генерируемый кэш имеет ключ «popular_articles», тогда наличие данных с ключем «popular_articles_lock», говорит о том, что наш кэш уже кто-то генерирует. Тоже самое спарведливо и для других хранилищ.
- IPC семафоры
- Настоящие пацанские семафоры ;) Ни разу не использовал — руки не доходят.
Плюсы локов в том, что клиент сам решает, что делать в ситуации, когда ему нужны данные, а их кто-то генерирует. Например, если есть старые данные, то мы можем их отдать, а если данных нет, то либо подождать, либо честно вывести пользователю, что данных нет. На самом деле висящие в течении секунды пользователи совсем не есть гуд, но иногда можно допустить и такое.
Так же не стоит забывать, что при генерации может возникнуть ошибка, и в этом случае лок может остаться висеть.
Организация «окна» для генерации
Способ был подсмотрен в исходниках какого-то фреймворка :)
Суть его в том, что данные после истечения ttl не удаляются. Т.е. приходит первый запрос, видит что ttl истек, и начинает генерировать новое значение. Чтобы остальные запросы подумали, что все нормально, он продлевает ttl существующего кэша на какую-то заранее определённую величину. Если писатель умирает, то кэш быстро снова протухнет и кто-нибудь подхватит знамя генерации с трупа павшего товарища. Если же все нормально, то будет записан новое значение кэша и установлен новый ttl.
Основным минусом такого подхода является то, что ttl нельзя хранить средствами самого кэш-storage, т.к. во всех самых известных хранилищах невозможно получить значение ttl и сами данные с истекшим ttl.
Я хочу вас всех, я хочу вас сразу!
Случай у нас тяжёлый, и пользователю будет скучно коротать 20 секунд рисуя матом надписи на пыльном столе. Я знаю про кого будут эти надписи.
Главное откровение: чтобы избавиться от последствий многопоточности надо свести ее к одному потоку. Просто и со вкусом. Убираем всю генерацию кэша в оффлайн. ttl-ем в данном случае будет время перезапуска генерирующих кэш скриптов. Помимо быстрого ответа пользователю тут есть еще один плюс — наборы кэшей часто генерировать быстрее, чем каждый из них по отдельности, ибо можно хранить какие-то промежуточные данные.



