Memcached для HTML, Redis для lock
Два хранилища, у каждого своя работа. Страницы живут в Memcached. Lock на пересборку живёт в Redis.
Кэш листинга протухал у всех сразу. Memcached ронял ключ, двести запросов уходили в MySQL, и каталог выглядел как всплеск трафика. Это был всплеск часов. Каждый TTL ставили на 60 секунд от одной и той же записи.
В стеке уже было два хранилища в памяти. CACHE_STORE смотрел в Memcached. QUEUE_CONNECTION и SESSION_DRIVER смотрели в Redis. Я перестал считать их взаимозаменяемыми и дал каждому работу, в которой он силён.
Проблема была в thundering herd на синхронном TTL. Посетители ждали MySQL. Нужна была одна пересборка, устаревший парный ключ для остальных и lock, который вытеснение HTML не снимет.
Почему не одно хранилище
Memcached это slab-аллокатор. Он быстр на get/set блобов и плох на lock. GET с промахом не обещает, что пересобирать будет один вызывающий. Redis обещает. SET key NX EX 5 это lock с TTL, и тот же инстанс Redis уже крутит Horizon.
Сложить HTML в Redis тоже можно было. Тогда блобы страниц смешались бы с payload очереди и хешами сессий в одной политике вытеснения. Когда Redis упирался в maxmemory, страница каталога и зарезервированный job могли уйти вместе. Так в один день получают и stampede, и застрявшую очередь.
Lock
При промахе воркер делает SET lock:rebuild:{key} NX EX 5 в Redis. Если lock взят, читает MySQL, пишет Memcached, снимает lock. Если нет, отдаёт предыдущий HTML из парного ключа, который не истекает до следующей успешной записи. Две секунды устаревшего лучше stampede.
TTL ключей листинга сдвинуты. К записи добавляется случайный сдвиг в несколько секунд, чтобы сетка не истекала одним тиком часов. Парный ключ это последний хороший HTML. Это не второй источник правды. Правда по-прежнему MySQL.
nginx спереди
Анонимные запросы ещё проходят двухсекундный microcache в nginx. Ключ кэша это scheme, host, uri и локаль. Query-строки, которые не меняют страницу, в ключ не входят. Запросы с cookie auth_token этот кэш обходят, поэтому вошедший пользователь не получает публичный фрагмент.
Доля попаданий на листинге выросла с единиц процентов до большинства запросов. CPU MySQL на этом хосте между записями стал плоской линией. Код живёт в одном хелпере. Новое кэшированное чтение получает lock и парный ключ без нового дизайна.
Какой опыт из этого
Два хранилища в памяти это не резерв. Это две работы. Блобы в Memcached. Lock и очереди в Redis. Одна политика вытеснения на оба это stampede и застрявший Horizon в один день.
Синхронный TTL это запланированный отказ. Jitter и lock лучше длинного TTL, который всё равно истечёт у всех сразу.
Устаревший HTML на две секунды я возьму вместо двухсот одинаковых SELECT.
