Собираем модуль в инженерную методику: расчёт памяти, целевые метрики, выбор TTL, стратегии инвалидации и разборный пример кеша карточки товара на 50k RPS.
Шаг 1. Оценка памяти до запуска
Грубая модель: память ≈ N_ключей × (ключ + значение + ~70 байт накладных).
Накладные — robj, запись словаря, SDS-заголовки, выравнивание аллокатора (см. практикум 1.5).
Пример: 10 млн ключей, ключ 20 байт, значение 1 КБ → (20+1024+70) × 10М ≈ 11.2 ГБ. Значит инстанс с 16 ГБ RAM — впритык с учётом фрагментации и fork-COW (модуль 4). Правило: держите used_memory ≤ 70–80% RAM хоста.
Проверяйте модель замером на выборке: MEMORY USAGE key на сотне реальных ключей
разных типов, экстраполяция — точнее любой формулы.
Шаг 2. Целевые метрики
INFO stats
keyspace_hits:1250000
keyspace_misses:250000 # hit rate = 1250/(1250+250) = 83.3%
evicted_keys:0 # должно быть 0 или стабильно мало
| Метрика | Цель | Если хуже |
|---|---|---|
| Hit rate | 85–95%+ | пересмотреть TTL/гранулярность/префиксы |
| evicted_keys | ~0 | мало памяти или кешируется хвост |
| Latency p99 GET | < 1–2 мс | смотреть slowlog/сеть/джиттеры (модуль 6) |
| used_memory/maxmemory | < 85% | расширять или чистить |
Hit rate ниже плана почти всегда означает одну из трёх причин: слишком короткий TTL, слишком широкая гранулярность (кешируем то, что редко читается) или трафик по «хвосту» данных, который лучше не кешировать вовсе.
Шаг 3. TTL-стратегия
- Базовый TTL = максимум приемлемой несвежести, а не «какое-то число»;
- Разные классы данных — разные TTL: справочники — часы, профили — минуты, персонализированное — секунды;
- Джиттер (урок 2.3) на всём;
- Событийная инвалидация сокращает нужный TTL: если обновления данных доходят до кеша событием (Streams, модуль 3), TTL становится страховкой, а не основным механизмом.
Шаг 4. Инвалидация: три стратегии
| Стратегия | Механика | Сложность | Когда |
|---|---|---|---|
| TTL-only | ждём истечения | минимальная | несвежесть терпима |
| Прямая (по записи) | update → DELETE известных ключей | низкая, если список ключей известен | 1 запись → 1–N ключей |
| Тегированная | SET тег → список зависимых ключей; событие → DELETE по сету | средняя | объект порождает много ключей (каталог, страницы) |
# Тегированная инвалидация: пересчёт каталога затрагивает N страниц
SADD tag:catalog:42 cache:product:42 page:cat:42:1 page:search:q42
# при изменении каталога 42:
DEL cache:product:42 page:cat:42:1 page:search:q42 # + DEL tag:catalog:42
Разборный пример: кеш карточки товара под 50k RPS
Вводные: 2 млн SKU, топ-10% дают 80% трафика, чтение из БД — 40 мс p99, допустимая несвежесть цены — 60 секунд.
- Формат: hash
product:{sku}(поля: title, price, stock…) — обновление цены без перезаписи всего; либо JSON-строка, если читается всегда целиком; - Память: ~300 байт/ключ × 2 млн ≈ 0.7 ГБ — влезает в 4 ГБ с запасом;
- Политика:
allkeys-lfu, maxmemory 3 ГБ; - TTL: 5 минут + джиттер; цена — отдельный ключ
price:{sku}с TTL 60с (у цены свой ритм изменений); - Инвалидация: сервис каталога публикует событие «sku изменён» в Streams; инвалидатор читает и DELETE'ит ключи (tag-сет не нужен — зависимость прямая);
- Stampede: мьютекс на пересчёт + прогреватель для топ-1000 SKU;
- Hot keys: топ-карточки (распродажа) — локальный микро-кеш 2с в подах;
- Мониторинг: hit rate по префиксам (через счётчики в приложении), p99 GET, evicted_keys=0, длина очереди инвалида.
💡 Обратите внимание на декомпозицию: цена и карточка в разных ключах потому, что у них разные требования к свежести. Разный TTL на разные поля — типичный приём, экономящий и память, и инвалидации.
Практика
- Посчитайте модель памяти для вашего реального домена: N объектов × размер × накладные — и проверьте замером MEMORY USAGE на выборке.
- Реализуйте тегированную инвалидацию (теги в сетах) и проверьте атомарность: пусть тег обновляется «посреди» добавления страницы — какой race видите и как смягчить (подсказка: хранить tag-версию в ключе страницы).
- Соберите «карточку товара» из примера: два ключа, разные TTL, событие-инвалидатор через Streams; нагрузите и снимите hit rate.
Мини-квиз
1. Почему used_memory должен быть ≤ 70–80% RAM? — Запас на фрагментацию, пики, бэкграунд-форк COW при BGSAVE.
2. Hit rate 70%: что делать первым делом? — Посмотреть распределение miss по префиксам: обычно видно, какой класс данных не должен был кешироваться.
3. Зачем разделять ключи по свежести? — Разные TTL и разные инвалидации без перезаписи всей сущности.