System Design
Кеширование
В 2012 году Instagram после переезда на AWS зафиксировал: добавление одного слоя Redis перед PostgreSQL снизило число запросов к базе с 800 до 12 в секунду при той же нагрузке. Stack Overflow обслуживает 10 миллионов вопросов с 25 серверами - потому что 95% ответов отдаёт Memcached, а не база данных. Кеширование - это не оптимизация: без него большинство высоконагруженных систем физически не могут работать.
- **Instagram + Redis:** добавление одного слоя Redis перед PostgreSQL снизило число запросов к БД с 800 до 12 в секунду при той же нагрузке
- **Stack Overflow:** обслуживает 10 млн вопросов с 25 серверами - 95% ответов отдаёт L2-кеш на локальных SSD
- **Twitter:** home timeline формируется за 2 мс благодаря pre-computed fan-out в Redis, без кеша это 300 мс на 300+ SQL-запросов
- **Facebook Memcached:** 28 тысяч серверов Memcached хранят trillion объектов - без этого слоя MySQL лёг бы под первым же spiky traffic
Цели урока
- Понимать, почему кеширование критично для производительности
- Знать уровни кеширования: от браузера до БД
- Уметь реализовать Cache-Aside pattern
- Различать Write-Through и Write-Behind стратегии
- Понимать способы инвалидации: TTL, explicit, event-driven
- Знать eviction policies: LRU, LFU
Предварительные знания
- Базовое понимание работы БД
- Понятие latency и QPS
Зачем нужно кеширование
**Кеширование** - хранение часто запрашиваемых данных ближе к потребителю. Это самый эффективный способ уменьшить latency и снизить нагрузку на БД.
База данных отвечает за 10ms, а Redis - за 0.5ms. При 100K QPS это разница между 'работает' и 'лежит'.
- RAM в 100-1000 раз быстрее диска
- Локальный кеш быстрее сетевого запроса
- 95% cache hit = база обрабатывает только 5% запросов
| Источник | Latency | Относительно |
|---|---|---|
| L1 CPU Cache | 1 ns | 1x |
| RAM (Redis) | 100 ns - 1 ms | ~1000x |
| SSD (PostgreSQL) | 1-10 ms | ~10,000x |
| Network (другой DC) | 10-100 ms | ~100,000x |
Экономия от кеша
Как кеш снижает нагрузку на БД
Без кеша: • 100K QPS → все идут в PostgreSQL • БД не выдерживает → timeout, errors С кешем (95% hit rate): • 100K QPS → 95K из Redis, 5K в PostgreSQL • Redis: 0.5ms, PostgreSQL: 10ms • Средняя latency: 0.95×0.5 + 0.05×10 = 0.975ms • БД легко справляется с 5K QPS
**Cache Hit Rate** - главная метрика кеша. 95%+ - отлично. Ниже 80% - нужно разбираться, почему так много промахов.
При cache hit rate 90% и 50K QPS, сколько запросов дойдёт до БД?
Уровни кеширования
Кеширование можно применять на каждом уровне системы. Чем ближе к пользователю - тем быстрее, но сложнее инвалидация.
| Уровень | Latency | Shared? | Инвалидация |
|---|---|---|---|
| Browser | 0 (local) | Нет | Cache-Control headers |
| CDN | 1-10 ms | Да | Purge API, TTL |
| Application | μs | Нет | In-process |
| Distributed | 0.1-1 ms | Да | Explicit, TTL |
| Database | 1-10 ms | Да | Automatic |
**Не забывай про Browser Cache!** Правильные HTTP headers (Cache-Control, ETag) - бесплатная оптимизация. Статика может кешироваться на месяцы.
Какой уровень кеша shared между всеми app серверами?
Стратегия Cache-Aside
**Cache-Aside** (Lazy Loading) - самая популярная стратегия. Приложение явно управляет кешем: проверяет, читает из БД при промахе, сохраняет в кеш.
- **Плюсы**: Простая логика, кешируем только запрашиваемые данные
- **Минусы**: Первый запрос всегда медленный (cold cache)
- **Когда использовать**: Read-heavy workloads, когда данные запрашиваются повторно
**Проблема Thundering Herd**: Когда TTL истекает для популярного ключа, все серверы одновременно идут в БД. Решение: добавь jitter к TTL (TTL ± random) или используй locking.
В Cache-Aside, когда данные попадают в кеш?
Стратегии записи
Как обновлять кеш при записи в БД? Два основных подхода: **Write-Through** и **Write-Behind**.
Write-Through
Write-Behind (Write-Back)
| Стратегия | Latency | Consistency | Риск потери |
|---|---|---|---|
| Write-Through | Выше | Strong | Низкий |
| Write-Behind | Низкая | Eventual | Есть |
| Cache-Aside + Delete | Средняя | Eventual | Низкий |
**Рекомендация**: Write-Through для критичных данных (платежи, профиль). Write-Behind для аналитики, логов, счётчиков.
Какая стратегия лучше для записи платёжных транзакций?
Инвалидация кеша
> "There are only two hard things in Computer Science: cache invalidation and naming things." - Phil Karlton
Как держать кеш актуальным? Несколько стратегий с разными trade-offs.
TTL (Time To Live)
Explicit Invalidation
Event-Driven Invalidation
**Cache Stampede**: Когда TTL истекает для популярного ключа → все серверы идут в БД → overload. Решения: jitter (TTL ± random), probabilistic early refresh, locking.
Почему при обновлении данных лучше DELETE из кеша, а не SET?
Eviction Policies
Когда кеш переполнен (maxmemory достигнут), нужно решить, какие данные удалить. **Eviction policy** определяет эту логику.
| Policy | Описание | Когда использовать |
|---|---|---|
| LRU | Least Recently Used - удаляем давно не используемое | Универсальный, default |
| LFU | Least Frequently Used - удаляем редко используемое | Когда частота важнее recency |
| FIFO | First In First Out - очередь | Простой, не оптимальный |
| Random | Случайный выбор | Непредсказуемый access pattern |
| TTL-based | Удаляем с истекшим TTL | Дополнение к другим |
Redis Eviction Policies
Redis vs Memcached
| Критерий | Redis | Memcached |
|---|---|---|
| Структуры данных | Rich (lists, sets, hashes) | Только strings |
| Persistence | RDB, AOF | Нет |
| Threading | Single-threaded* | Multi-threaded |
| Pub/Sub | Да | Нет |
| Кластеризация | Redis Cluster | Ручной sharding |
| Когда выбрать | Большинство случаев | Simple high-throughput |
**Redis 6+** поддерживает I/O threading, но command execution остаётся single-threaded. Это обеспечивает атомарность операций.
Какая eviction policy лучше для кеша с unpredictable access patterns?
Ключевые выводы
- **Cache Hit Rate** - главная метрика, цель 95%+
- **Cache-Aside** - самая популярная стратегия для read-heavy
- **Write-Through** для консистентности, **Write-Behind** для скорости
- **TTL** - простой и надёжный способ инвалидации
- **LRU** - универсальная eviction policy
- **Redis** для большинства случаев, **Memcached** для простого high-throughput
Связанные темы
Кеширование связано с другими компонентами системы
- CDN — Edge caching - кеширование ближе к пользователю
- Database Scaling — Кеш снижает нагрузку на БД
- Consistency — Кеш = eventual consistency. Trade-off с freshness
Вопросы для размышления
- При cache hit rate 80% система начала деградировать под нагрузкой. Какие причины стоит проверить первыми - рост уникальных ключей, некорректный TTL, или смещение в паттерне запросов?
- Write-Through гарантирует консистентность, но удваивает latency записи. В каких сценариях это неприемлемо, и как тогда решается проблема консистентности без кеша?
- LRU eviction работает плохо при скачкообразном сканировании большого датасета (cache pollution). Какие альтернативы предусмотрены в Redis для таких access pattern'ов?