Как реализовать TTL кэш на Redis без использования таблиц
Что проверяют
Знаете ли вы, что срок жизни в Redis встроен в сам ключ и отдельная структура с метками времени не нужна. Следом обычно спрашивают, как именно Redis удаляет просроченные ключи и что будет при нехватке памяти.
Как отвечать
Суть в том, что TTL задаётся атомарно вместе с записью, а удаление берёт на себя сервер:
- запись:
SET key value EX 60(илиPXв миллисекундах). В клиентеgithub.com/redis/go-redis/v9этоrdb.Set(ctx, key, val, time.Minute), нулевая длительность означает «без срока»; - чтение:
rdb.Get(ctx, key), промах кэша приходит ошибкойredis.Nil, её нужно отличать от сетевой ошибки; - управление сроком:
EXPIRE,PERSIST,TTL, который возвращает-2, если ключа нет, и-1, если срок не задан.GETEXчитает и продлевает срок одной командой; - удаление: Redis стирает ключ лениво при обращении и фоново, выборочно проверяя ключи со сроком. Память освобождается не точно в момент истечения, но просроченное значение клиент уже не получит.
Вокруг этого строится cache-aside: читаем, при redis.Nil идём в базу, кладём результат с TTL. Добавьте защиту от лавины: singleflight из golang.org/x/sync, чтобы на промахе в базу шёл один запрос, и случайный разброс TTL, чтобы тысячи ключей не истекали одновременно. Если нужен срок у отдельных полей хэша, с Redis 7.4 есть HEXPIRE, раньше срок ставился только на ключ целиком.
Назовите и maxmemory-policy. По умолчанию это noeviction, и при заполнении памяти запись падает с ошибкой. Для кэша обычно ставят allkeys-lru или volatile-lru.
Где спотыкаются
Делают SET, а потом отдельный EXPIRE. Если процесс упадёт между командами, ключ останется навсегда. К тому же повторный SET без KEEPTTL сбрасывает срок, и об этом забывают при обновлении значения.
Тянут старый импорт github.com/go-redis/redis/v8 и golang.org/x/net/context. Актуальный модуль живёт в github.com/redis/go-redis/v9, а контекст берётся из стандартной библиотеки.
Проверяют истечение через time.Sleep в тесте. Такой тест медленный и нестабильный, лучше miniredis с FastForward или проверка TTL сразу после записи.