Какие стратегии масштабирования кэша существуют
Что проверяют
Понимаете ли вы, что масштабирование кэша упирается не в объём памяти, а в распределение ключей, горячие ключи и согласованность с базой. Следом обычно спрашивают, что будет, если кэш упадёт или одновременно истечёт популярный ключ.
Как отвечать
Идите от простого к сложному:
- локальный кэш в процессе:
mapподsync.RWMutexили библиотеки вродеristrettoиbigcache. Самый быстрый вариант, но у каждого пода своя копия, и данные расходятся между репликами; - вертикальный рост: больше памяти одному узлу Redis. Потолок наступает быстро, команды Redis исполняются в одном потоке;
- шардирование: ключи раскладываются по узлам. Redis Cluster делит пространство на 16384 слота, для Memcached клиент сам выбирает узел консистентным хешированием, чтобы при добавлении узла переезжала малая доля ключей;
- репликация: реплики на чтение разгружают горячие ключи, но добавляют отставание;
- двухуровневый кэш: L1 в процессе с коротким TTL плюс L2 в Redis, инвалидация через pub/sub.
Затем назовите защиту от лавины промахов. golang.org/x/sync/singleflight схлопывает одновременные запросы по одному ключу в один поход в базу, а случайный разброс TTL не даёт тысячам ключей истечь в одну секунду. Горячий ключ спасает копия в локальном кэше или разбиение на несколько ключей с суффиксом.
Где спотыкаются
Распределяют ключи через hash(key) % N. При добавлении узла меняется адрес почти каждого ключа, кэш фактически обнуляется, и вся нагрузка падает на базу.
Забывают про мультиключевые операции. В Redis Cluster MGET и транзакции работают только в пределах одного слота, для этого нужны хеш-теги вида {user:42}.
Считают локальный кэш бесплатным. При десяти подах это десять копий и десять версий правды, поэтому нужны короткий TTL или явная инвалидация, а размер стоит ограничить, иначе кэш съест память контейнера.