Зачем нужны кэши
Что проверяют
Понимаете ли вы, что кэш есть на каждом уровне, от процессора до CDN, и что за скорость платят свежестью данных и памятью. На Go следом почти всегда просят написать потокобезопасный in-memory кэш с TTL.
Как отвечать
Причина одна: разная стоимость доступа. Обращение к L1 процессора занимает около наносекунды, к оперативной памяти порядка 100 нс, к SSD десятки микросекунд, к базе по сети миллисекунды. Кэш держит нужные данные ближе и опирается на локальность: к недавно прочитанному и к соседним данным обращаются снова.
Уровни, которые стоит назвать:
- кэши CPU: в Go это влияет на раскладку данных. Срез структур обходится быстрее среза указателей, а счётчики разных горутин в одной кэш-линии дают false sharing;
- page cache ОС: повторное чтение файла идёт из памяти;
- кэш приложения:
mapподsync.RWMutexс TTL или готовыеristretto,bigcache,freecache. Последние две хранят данные в больших байтовых срезах, чтобы GC не обходил миллионы указателей; - распределённый кэш: Redis, общий для всех экземпляров сервиса;
- HTTP и CDN: заголовки
Cache-Control,ETag.
Про sync.Map скажите точно: он выгоден, когда ключ записывают один раз и много читают или горутины работают с разными ключами. TTL и вытеснения в нём нет.
Назовите проблемы и решения: устаревание (TTL, инвалидация при записи, cache-aside), рост памяти (лимит и вытеснение LRU или LFU), лавина запросов в базу при истечении популярного ключа (golang.org/x/sync/singleflight склеивает одновременные промахи в один запрос).
Где спотыкаются
Пишут кэш на голом map без мьютекса. Конкурентная запись в map роняет процесс с fatal error: concurrent map writes, и перехватить это через recover нельзя.
Не ограничивают размер. Кэш без вытеснения это утечка памяти, которую просто долго не замечают.
Кэшируют всё подряд без плана инвалидации, а потом ищут, почему пользователь видит старые данные после обновления.