> Как реализовать TTL кэш на Redis без использования таблиц (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Русклимат
Стек: Redis, Go
> Пример ответа
Короткий ответ
TTL кэш на Redis реализуется через встроенный механизм expiration: команда SET с параметром EX или PX, либо EXPIRE для существующего ключа. Redis удаляет ключи лениво при обращении и активно фоновым процессом. Для Go используем клиент go-redis и метод Set(ctx, key, value, ttl). Никаких таблиц не нужно - Redis сам управляет временем жизни ключей, что даёт атомарность и производительность без дополнительного кода.
Подробное объяснение
Redis предоставляет нативные механизмы TTL, которые не требуют внешних структур данных:
- Команда
SET key value EX seconds- задаёт значение и время жизни атомарно. - Команда
EXPIRE key seconds- устанавливает TTL для уже существующего ключа. - Команда
TTL key- возвращает оставшееся время жизни в секундах (-1- без срока,-2- ключ не существует). - Команда
PERSIST key- снимает TTL.
Механизм удаления работает в двух режимах:
- Ленивое удаление - при попытке доступа к истёкшему ключу Redis возвращает
nilи удаляет его. - Активное удаление - фоновый цикл периодически сканирует выборку ключей с TTL и удаляет истёкшие.
В Go с клиентом go-redis/v9 это выглядит тривиально:
GOerr := rdb.Set(ctx, "user:123", data, 10*time.Second).Err()
Важные нюансы:
- TTL задаётся в секундах (
EX) или миллисекундах (PX), в Go - черезtime.Duration. - Для атомарной установки с условием используйте
SET key value EX ttl NX(только если ключа нет) илиXX(только если есть). - Истечение не гарантирует мгновенное удаление - ключ может существовать до следующего цикла активной очистки, но для клиента он уже недоступен.
- Не используйте
GET+EXPIREотдельно - это не атомарно и может привести к гонкам.
На практике
Типичный сценарий - кэширование результата тяжёлой операции:
- Пытаемся получить значение по ключу.
- Если
nil- выполняем операцию, сохраняем результат с TTL. - Если значение есть - возвращаем из кэша.
Дополнительные практики:
- Используйте префиксы в ключах для логической группировки, например
cache:user:123. - Для защиты от cache stampede применяйте
SET NX EXс коротким TTL на блокировку. - Для инвалидации по паттерну используйте
SCANсMATCHи удаляйте ключи черезDEL- но это не атомарно и дорого при большом количестве ключей. - Если нужно хранить сложные структуры (хэши, списки), TTL задаётся отдельной командой
EXPIREпослеHSETилиLPUSH.
Пример кода
GOpackage mainimport ("context""encoding/json""fmt""time""github.com/redis/go-redis/v9")type User struct {ID int `json:"id"`Name string `json:"name"`}func getUserFromCache(ctx context.Context, rdb *redis.Client, id int) (*User, error) {key := fmt.Sprintf("cache:user:%d", id)// 1. Пытаемся получить из кэшаval, err := rdb.Get(ctx, key).Result()if err == redis.Nil {// 2. Кэш пуст - грузим из источникаuser := &User{ID: id, Name: "Alice"}data, _ := json.Marshal(user)// 3. Сохраняем с TTL 5 минутerr = rdb.Set(ctx, key, data, 5*time.Minute).Err()if err != nil {return nil, err}return user, nil} else if err != nil {return nil, err}// 4. Декодируем из кэшаvar user Userif err := json.Unmarshal([]byte(val), &user); err != nil {return nil, err}return &user, nil}func main() {rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379",})ctx := context.Background()user, err := getUserFromCache(ctx, rdb, 1)if err != nil {panic(err)}fmt.Printf("User: %+v\n", user)}
Как отвечать на собеседовании
Начните с прямого ответа: Redis сам поддерживает TTL через EXPIRE и SET EX, поэтому таблицы не нужны. Затем покажите понимание механики удаления - ленивое и активное. Упомяните атомарность операций и типичный паттерн cache-aside. Если спросят про edge cases, расскажите про cache stampede и решение через SET NX EX. Для senior-уровня добавьте рассуждение о trade-off между точным удалением и производительностью: Redis не гарантирует мгновенное удаление, но для клиента ключ недоступен сразу после истечения.
Что проверяет интервьюер
- Знание базовых команд Redis и их параметров.
- Понимание внутреннего механизма expiration.
- Умение применять TTL в реальном сценарии на Go.
- Осознание проблем конкурентного доступа и способов их решения.
- Способность объяснить, почему Redis не требует отдельной таблицы для TTL - это встроенная фича.
Типичные ошибки
- Попытка реализовать TTL вручную через хранение timestamp и сравнение в коде - избыточно и неатомарно.
- Использование
GETиEXPIREотдельно вместо одной командыSET EX- гонки. - Забывают, что
TTLвозвращает-2для несуществующего ключа, и путают с-1. - Предположение, что истёкший ключ мгновенно удаляется из памяти - на самом деле удаление может быть отложено.
- Не учитывают, что
SETс TTL перезаписывает значение и сбрасывает TTL - если нужно обновить только значение, используйтеSETбез TTL, а затемEXPIRE.
> Похожие задачи по Go
> Похожие задачи по backend
Как работает Redis и почему он быстрый
Работал ли ты с Cassandra, MongoDB, Redis, ElasticSearch, ClickHouse
Работал ли ты с Cassandra, MongoDB, Redis, ElasticSearch, ClickHouse
Что происходит при запросе с использованием звездочки в Redis
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью