Valkey 9.0 TTL на поле в hashmap
db redisВ новом Valkey (открытый аналог Redis) версии 9.0 появилась возможность выставлять TTL на конкретное поле в hashmap. Раньше можно было выставлять только на весь ключ, что на практике приводило к неудобным компромиссам: либо все поля одного hash разделяют один TTL, либо приходилось разбивать данные на отдельные ключи, что увеличивало overhead.
Давайте разберем, как этот механизм устроен изнутри. У команды Valkey были несколько вариантов решения:
- secondary hashtable - рядом с каждым hashmap объектом положить еще одну hashmap для полей с TTL. Плюсы: простота, минусы: неэффективное сканирование при поиске истекших полей
- radix tree индекс - быстрый доступ к отсортированным данным, но overhead в 54 байта на поле 🙁
- sorted skip-list - эффективное сканирование истекших полей, но сложность доступа O(log N) вместо ожидаемого для hashmap O(1)
Чтобы понять итоговое решение, нужно разобраться, как Redis/Valkey работает с TTL для ключей. Используются 2 стратегии:
- lazy expiration: при обращении к ключу проверяется TTL, и если он истек ключ удаляется. Недостаток если к ключу долго не обращаются, он занимает память
- active expiration: фоновый cron-процесс (10 раз в секунду) сканирует небольшой набор ключей с TTL и удаляет истекшие
Для полей хэшей команда отказалась от lazy expiration, чтобы не усложнять hot path команд типа HGET/HSET. Используется только активное удаление.
Итоговое решение - volatile set (vset) структура данных, похожая на B+tree:
- поля с TTL группируются в бакеты по временным интервалам
- если бакет слишком большой, то он разбивается на два с меньшим интервалом
- если слишком маленький, то сливается с соседом
При этом бакеты используют разные представления в зависимости от размера:
- 1 элемент = один указатель
- мало элементов = вектор указателей
- много элементов = hashtable
Это позволяет cron-задаче сканировать только бакеты,чьи интервалы уже истекли, избегая лишней работы.
Overhead памяти: 8 байт на timestamp + 16-29 байт на трекинг в volatile set ≈ 24-37 байт на поле с TTL
Автор: Никита Галушко - Golang инженер, специализирующийся на производительности и распределённых системах. Больше технических разборов в Telegram: https://t.me/b1tw1se