Что такое кэш и как он работает
Что проверяют
Понимаете ли вы, что кэш это компромисс между скоростью и свежестью данных, и умеете ли его ограничивать. Для Go-разработчика вопрос быстро уходит в конкретику: как сделать потокобезопасный кэш, что будет с памятью и GC, как не положить базу, когда популярный ключ протух.
Как отвечать
Кэш хранит копию данных ближе к потребителю, чтобы не ходить к медленному источнику. Уровни стоит назвать хотя бы кратко: кэши процессора (линия 64 байта, отсюда false sharing между счётчиками разных горутин), кэш в памяти процесса, распределённый Redis или Memcached, HTTP-кэш.
- стратегии: cache-aside (приложение читает кэш, при промахе идёт в базу и кладёт результат), write-through, write-behind;
- вытеснение: TTL, LRU, LFU. Без ограничения размера кэш рано или поздно становится утечкой памяти;
- инвалидация: при записи обычно обновляют базу, затем удаляют ключ, а не перезаписывают его, чтобы не поймать гонку двух обновлений.
В Go простой кэш это map под sync.RWMutex. sync.Map выгоден в узких случаях: ключ пишется один раз и много читается или горутины работают с непересекающимися ключами. Готовые решения: hashicorp/golang-lru/v2 с дженериками, ristretto с TinyLFU и лимитом по стоимости, bigcache и fastcache, которые держат данные в больших байтовых срезах без указателей, чтобы GC не сканировал миллионы объектов. Для Redis берут redis/go-redis/v9.
Против лавины запросов на протухший ключ есть golang.org/x/sync/singleflight: из сотни одновременных промахов в базу уйдёт один запрос, остальные дождутся его результата. Плюс случайный разброс TTL, чтобы ключи не истекали одновременно.
Где спотыкаются
Пишут map с мьютексом без TTL и лимита, как в учебных примерах. Сервис живёт неделю и падает по OOM, а map в Go не уменьшается после delete, и память не возвращается.
Отдают из кэша указатель или срез, который вызывающий код потом меняет. Получается гонка данных, которую ловит только go test -race.
Называют groupcache универсальным решением. В оригинальном golang/groupcache нет TTL и удаления ключей, он рассчитан на неизменяемые данные.