open atlas
↑ К треку
Основы System Design SD · 05 · 07

Кэширование при масштабе: чтение кода и конфига

Читай реальный код и конфиг кэширования, затем рассуждай о поведении: гонка устаревшего чтения, расчёт от доли промахов к нагрузке БД, misconfiguration noeviction и стампида горячего ключа в пути чтения.

SD Senior ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior

Баги кэширования живут в коде и конфиге, не в прозе: пропущенная инвалидация, синхронный путь промаха, дефолтная политика вытеснения, чтение горячего ключа без коалесцирования. Читай каждый сниппет, рассуждай, что он делает под нагрузкой, и выбирай ответ, на который подписался бы senior-инженер.

Практикуй цикл, что прогоняешь на ревью дизайна кэширования или инцидента: найди стратегию в коде, предскажи её поведение под конкурентностью и нагрузкой и выбери изменение, что механика реально поддерживает.

Сниппет 1 — порядок инвалидации

# чтение cache-aside + инвалидация «удаляй на записи»
def get_user(uid):
    u = cache.get(f"user:{uid}")
    if u is None:                       # промах
        u = db.read_user(uid)           # медленная загрузка ТЕКУЩЕГО значения
        cache.set(f"user:{uid}", u)     # заполнить (без TTL)
    return u

def update_user(uid, patch):
    db.write_user(uid, patch)           # новое значение в БД
    cache.delete(f"user:{uid}")         # инвалидировать
Викторина

Под конкурентностью устаревшее значение пользователя изредка переживает обновление навсегда. В чём баг и самая надёжная починка?

Сниппет 2 — математика hit ratio

def db_reads_per_sec(total_rps: float, hit_ratio: float) -> float:
    miss_rate = 1.0 - hit_ratio
    return total_rps * miss_rate

before = db_reads_per_sec(80_000, 0.99)   # ?
after  = db_reads_per_sec(80_000, 0.90)   # ?
Викторина

Что возвращают before и after и каков вывод для базы?

Сниппет 3 — конфиг кэша Redis

# инстанс redis, используемый чисто как кэш
maxmemory 16gb
maxmemory-policy noeviction        # оставлен дефолт
# приложение делает SET ... EX 600 для каждого ключа
Викторина

Что происходит, когда этот кэш заполняет свои 16ГБ, и какой должна быть политика?

Сниппет 4 — путь чтения горячего ключа

# страница товара вирусного айтема, пересчёт ~300мс
def get_page(pid):
    html = cache.get(f"page:{pid}")
    if html is None:                    # промах
        html = render_page(pid)         # дорого: 6 запросов + рендер
        cache.set(f"page:{pid}", html, ex=30)
    return html
# на пике ~40 000 параллельных запросов на ОДИН pid
Викторина

Каждые 30 секунд бэкенд бьют тысячи одновременных вызовов render_page на один pid. В чём сбой и починка?

Вспомните перед уходом
  1. 01
    Почему удаление-на-записи гоняется и какова надёжная починка?
  2. 02
    Как из hit ratio посчитать нагрузку БД и почему асимметрия опасна?
  3. 03
    Почему noeviction неверен для кэша и как остановить стампиду горячего ключа в пути чтения?
Итог

Каждое решение кэширования в этом разделе всплывает прямо в коде и конфиге. Инвалидация удалением-на-записи гоняется под конкурентностью — медленный читатель записывает старое значение обратно после delete, и без TTL оно не восстанавливается — поэтому используешь версионированные ключи (и TTL-подстраховку). Математика доли промахов (чтения БД = total × (1 − hit_ratio)) показывает, почему 99%→90% попаданий это 10× всплеск нагрузки БД: база чувствует сторону промахов, размеренная под ~1%. Дефолт noeviction заставляет кэш Redis валить записи под давлением памяти вместо сброса холодных ключей, так что чистый кэш хочет allkeys-lru/lfu. А путь чтения горячего ключа без коалесцирования штурмует бэкенд при каждом истечении — тысячи одновременных пересчётов одного значения — чинится single-flight (один пересчёт, остальные ждут) и опционально вероятностным ранним пересчётом. Senior-привычка — читать механику, предсказывать поведение под нагрузкой и конкурентностью и выбирать структурную починку, что код поддерживает — а не ту (бо́льший TTL, больше узлов, быстрее рендер), что лишь её замазывает.

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.