В каких задачах важны кэши и почему
Что проверяют
Понимаете ли вы, откуда кэш берёт выигрыш и чем за него платят. Нужен не список мест, где «можно кэшировать», а условия: частые повторные чтения, дорогое получение данных, терпимость к устареванию. Следом почти всегда спрашивают про инвалидацию.
Как отвечать
Кэш выигрывает, когда данные читают намного чаще, чем меняют, а получить их заново дорого: сеть, диск, тяжёлый расчёт. Пройдитесь по уровням:
- внутри процесса:
functools.lru_cacheиfunctools.cacheдля чистых функций. Мемоизация в динамическом программировании превращает экспоненциальный перебор в полиномиальный. Такой кэш живёт в памяти одного процесса, у каждого воркера Gunicorn он свой; - общий кэш: Redis или Memcached для результатов запросов к базе, сессий, ответов внешних API. В Django это
django.core.cache, во FastAPI обычноredis.asyncio; - HTTP и CDN:
Cache-Control,ETag, кэш в Nginx или CDN для статики и публичных страниц; - процессор: массив NumPy лежит в памяти подряд и хорошо попадает в кэш CPU, а список Python хранит указатели на разбросанные объекты.
Затем цена:
- устаревание: TTL, явная инвалидация при записи, паттерн cache-aside, где приложение само читает и заполняет кэш;
- вытеснение: LRU или LFU, у
lru_cacheразмер задаётmaxsize; - лавина промахов: истёк популярный ключ, и все запросы разом идут в базу. Лечится блокировкой на пересчёт, разбросом TTL или обновлением заранее.
Где спотыкаются
Вешают lru_cache на метод. Кэш держит ссылку на self, и объекты не освобождаются, пока жив кэш. Для значения на экземпляр есть functools.cached_property.
Кэшируют функцию с изменяемыми аргументами. lru_cache требует хэшируемых аргументов, список в параметрах даст TypeError.
Не называют стратегию инвалидации. Кэш без правила устаревания рано или поздно покажет пользователю старую цену или чужие данные.