Стратегии кэширования: отложенная загрузка и сквозная запись
Реализуйте отложенную загрузку для заполнения кэша при отсутствии данных и сквозную запись, чтобы поддерживать согласованность кэша при каждой записи в базу данных.
«Стратегии кэширования: отложенная загрузка и сквозная запись» — бесплатный урок AWS Solutions Architect на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AWS Solutions Architect, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AWS Solutions Architect содержит 4 уроков всего.
Почему стратегии кэширования важны
Размещение кэша между приложением и базой данных требует стратегии кэширования — набора правил, определяющих, когда данные помещаются в кэш, когда считываются из него и когда удаляются. Неправильный выбор стратегии приводит либо к устаревшим данным (кэш возвращает неактуальные значения), либо к промахам холодного кэша (кэш пуст, и каждый запрос обращается к базе данных), либо к лавине запросов к кэшу (много одновременных запросов к одному отсутствующему ключу одновременно обращаются к базе данных). Две основные стратегии — отложенная загрузка и сквозная запись.
Отложенная загрузка (Cache-Aside): принцип работы
Отложенная загрузка (также называемая Cache-Aside) — наиболее распространенный шаблон кэширования. Сначала приложение проверяет кэш. При попадании в кэш данные напрямую возвращаются из кэша — это быстрый путь. При промахе кэша приложение получает данные из базы данных, записывает результат в кэш с указанием TTL и возвращает его вызывающей стороне. В кэш помещаются только действительно запрошенные данные — отсюда и название «отложенная». Следующий запрос по тому же ключу найдет данные в кэше.
# Lazy loading pattern (Python with redis-py)
def get_user(user_id, redis_client, db):
cache_key = f'user:{user_id}'
# 1. Check cache
cached = redis_client.get(cache_key)
if cached:
return json.loads(cached) # Cache HIT
# 2. Cache MISS: fetch from DB
user = db.query('SELECT * FROM users WHERE id = %s', user_id)
# 3. Populate cache with TTL of 300 seconds
redis_client.setex(cache_key, 300, json.dumps(user))
return userОтложенная загрузка: преимущества
У отложенной загрузки есть три основных преимущества: кэшируются только запрошенные данные — кэш не заполняется данными, которые никто не читает, поэтому память используется эффективно. Холодный кэш не нарушает работу приложения — при промахах кэша приложение обращается к базе данных, поэтому даже после перезапуска ElastiCache или сбоя узла приложение продолжает работать, хотя и с большей задержкой. Кэш всегда в конечном счете согласован с базой данных, поскольку устаревшие данные удаляются по истечении TTL, даже если обновления были пропущены.
Отложенная загрузка: недостатки
У отложенной загрузки есть три основных недостатка: промахи кэша обходятся дорого — требуется три операции (проверка кэша, чтение из базы данных и запись в кэш) вместо одной при попадании, что увеличивает задержку для запросов при холодном кэше. Устаревшие данные — после обновления базы данных кэш продолжает возвращать старое значение, пока не истечет TTL или ключ не будет явно недействителен (окно несогласованности кэша). Лавина запросов к кэшу — если срок действия популярного ключа истекает, множество одновременных запросов сталкивается с промахом кэша и параллельно обращается к базе данных, что может ее перегрузить.
# Mitigating cache stampede with a probabilistic early expiration
# (Refresh the key before it expires to avoid simultaneous misses)
def get_with_stampede_protection(key, redis_client, db_fetch_fn, ttl=300):
value = redis_client.get(key)
ttl_remaining = redis_client.ttl(key)
# Probabilistically refresh before expiry
if value is None or (ttl_remaining < 30 and random.random() < 0.1):
value = db_fetch_fn()
redis_client.setex(key, ttl, json.dumps(value))
return json.loads(value)Сквозная запись: принцип работы
При использовании стратегии сквозной записи каждая запись в базу данных одновременно записывается и в кэш. Приложение записывает данные и в кэш, и в базу данных в рамках одной операции (или база данных инициирует обновление кэша). Поэтому кэш всегда синхронизирован с базой данных — окна устаревших данных нет. Сквозная запись гарантирует, что данные в кэше всегда актуальны, поэтому последующие чтения недавно записанных данных всегда будут попаданиями в кэш.
# Write-through pattern (Python)
def update_user(user_id, user_data, redis_client, db):
# 1. Write to database FIRST
db.execute('UPDATE users SET name=%s WHERE id=%s',
(user_data['name'], user_id))
# 2. Update cache immediately (write-through)
cache_key = f'user:{user_id}'
redis_client.setex(cache_key, 3600, json.dumps(user_data))
return user_data
# Every read is now a cache hit for recently updated dataСквозная запись: преимущества
Преимущества сквозной записи: данные в кэше всегда актуальны — устаревших данных нет, поскольку кэш обновляется при каждой записи. Чтение всегда выполняется быстро — часто используемые данные, которые регулярно записываются, всегда находятся в кэше. При чтении не возникает лавины запросов к кэшу — поскольку данные предварительно помещаются в кэш до чтения, для недавно записанных данных не бывает промахов холодного кэша. Эта стратегия идеально подходит для рабочих нагрузок с преобладанием чтения и частыми обновлениями, где актуальность данных критична, например для каталогов товаров, систем ценообразования или кэшей профилей пользователей.
Сквозная запись: недостатки
Недостатки сквозной записи: штраф за запись — каждая запись требует двух операций (база данных и кэш), что увеличивает задержку записи. Загрязнение кэша — данные кэшируются, даже если больше никогда не читаются (записаны один раз и больше не запрашиваются), что напрасно расходует память кэша. Перезапуск кэша приводит к холодному кэшу — при перезапуске кластера кэша все предварительно записанные данные теряются, и кэш необходимо повторно заполнить посредством записей или процесса прогрева. Сочетайте сквозную запись с TTL, чтобы предотвратить неограниченный рост редко читаемых кэшированных данных.
Сочетание отложенной загрузки и сквозной записи
На практике многие рабочие системы сочетают обе стратегии: используйте сквозную запись для часто обновляемых и часто читаемых данных (например, пользовательских сеансов или текущих цен) и отложенную загрузку для редко обновляемых, но часто читаемых данных (например, описаний товаров или содержимого статей). Установите подходящие TTL для обоих типов данных: ключи со сквозной записью получают длительный TTL, поскольку данные всегда актуальны; ключи с отложенной загрузкой — более короткий TTL, чтобы ограничить окно устаревших данных. Такой гибридный подход повышает долю попаданий в кэш и одновременно сводит устаревание данных к минимуму.
# Hybrid: write-through for sessions, lazy loading for product data
# Write-through for session data (always fresh, critical)
def save_session(session_id, data, redis_client, db):
db.upsert('sessions', session_id, data)
redis_client.setex(f'session:{session_id}', 3600, json.dumps(data))
# Lazy loading for product catalog (infrequent updates OK)
def get_product(product_id, redis_client, db):
cached = redis_client.get(f'product:{product_id}')
if cached:
return json.loads(cached)
product = db.query('SELECT * FROM products WHERE id=%s', product_id)
redis_client.setex(f'product:{product_id}', 86400, json.dumps(product))
return productПринципы проектирования TTL
Время жизни (TTL) кэшированных данных определяет максимальное окно устаревших данных и объем памяти, занимаемый кэшем. Проектируйте TTL с учетом следующих факторов: частота обновления данных (данные сеанса часто меняются → короткий TTL; статическое содержимое меняется редко → длительный TTL), допустимая степень устаревания (финансовые цены → очень короткий TTL; текст записи в блоге → часы или дни) и объем памяти кэша (мало памяти → более короткий TTL для ускоренного удаления устаревших данных). Всегда устанавливайте TTL — никогда не кэшируйте данные без срока действия, иначе кэш со временем заполнится устаревшими данными.
# TTL examples by data type
# API rate limit counter: 60 seconds
redis_client.setex(f'ratelimit:{ip}', 60, count)
# User session: 30 minutes
redis_client.setex(f'session:{id}', 1800, json.dumps(session))
# Product catalog: 24 hours
redis_client.setex(f'product:{id}', 86400, json.dumps(product))
# Stock price: 10 seconds
redis_client.setex(f'price:{symbol}', 10, price)
# Static site content: 7 days
redis_client.setex(f'page:{slug}', 604800, html_content)Инвалидация кэша при обновлении
Вместо того чтобы полагаться только на истечение TTL, можно явно инвалидировать (удалять) ключи кэша при изменении исходных данных. Это полностью устраняет окно устаревших данных. Распространенные шаблоны: удаление при записи (удалять ключ кэша после каждого обновления базы данных — при следующем чтении он будет заполнен посредством отложенной загрузки), инвалидация по событиям (DynamoDB Streams или отслеживание изменений в RDS запускает Lambda, которая удаляет затронутые ключи). Инвалидация кэша — одна из самых сложных задач в распределенных системах; чем проще логика инвалидации, тем надежнее кэш.
# Delete-on-write invalidation pattern
def update_product(product_id, new_data, redis_client, db):
# Update the database
db.execute('UPDATE products SET ... WHERE id=%s', (product_id,))
# Invalidate the cache key
redis_client.delete(f'product:{product_id}')
# Also invalidate any list/search caches that may include this product
redis_client.delete('products:list:page:1')
redis_client.delete(f'products:category:{new_data["category_id"]}')
# Next read will trigger lazy loading with fresh dataКэширование с отложенной записью (Write-Back)
Менее распространенный, но мощный шаблон — отложенная запись (Write-Back): приложение записывает данные только в кэш, а кэш асинхронно выгружает их в базу данных в фоновом режиме. Это обеспечивает чрезвычайно быструю запись (только в память), но создает риск потери данных, если кэш выйдет из строя до выгрузки. Отложенная запись подходит для высокочастотных операций записи большого объема, когда данные можно восстановить или допустимы небольшие потери, например для счетчиков просмотров, событий аналитики или обновлений игровых результатов. ElastiCache не поддерживает отложенную запись изначально; ее необходимо реализовать на уровне приложения.
# Write-behind pattern: write to cache, flush to DB asynchronously
# Application writes:
# redis_client.incr('post:42:views') # fast, in-memory only
# Background job (runs every 60 seconds):
def flush_view_counts(redis_client, db):
for key in redis_client.scan_iter('post:*:views'):
count = redis_client.getdel(key) # atomic get-and-delete
post_id = key.split(':')[1]
db.execute('UPDATE posts SET views = views + %s WHERE id = %s',
(int(count), post_id))Быстрая проверка
Проверьте, насколько хорошо Вы поняли концепции AWS Solutions Architect (SAA-C03) из этого урока.
Итоги урока
В этом уроке Вы узнали, что отложенная загрузка заполняет кэш при промахах чтения и эффективно использует память, но может возвращать устаревшие данные до истечения TTL; сквозная запись обновляет кэш при каждой записи, исключая устаревшие данные, но расходует память на нечитаемые данные; а явная инвалидация удаляет ключи кэша при обновлении базы данных, устраняя окна устаревания. Далее мы рассмотрим хранение сеансов и шаблоны таблиц лидеров с ElastiCache.
Часто задаваемые вопросы
Урок «Стратегии кэширования: отложенная загрузка и сквозная запись» бесплатный?
Да — полный текст урока «Стратегии кэширования: отложенная загрузка и сквозная запись» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AWS Solutions Architect, подпишись на CoddyKit PRO. Курс AWS Solutions Architect содержит 4 уроков всего.
Чему я научусь в уроке «Стратегии кэширования: отложенная загрузка и сквозная запись»?
Реализуйте отложенную загрузку для заполнения кэша при отсутствии данных и сквозную запись, чтобы поддерживать согласованность кэша при каждой записи в базу данных. Ты практикуешь AWS Solutions Architect с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AWS Solutions Architect?
Предыдущий опыт не требуется. AWS Solutions Architect на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Стратегии кэширования: отложенная загрузка и сквозная запись»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AWS Solutions Architect?
Да. Каждый урок AWS Solutions Architect включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Redis и Memcached: выбор подходящего механизма
- Группы репликации ElastiCache Redis и кластерный режим
- Стратегии кэширования: отложенная загрузка и сквозная запись
- Хранение сеансов и шаблоны таблиц лидеров