Короткий ответ
Для часто изменяемых критичных данных типа цен нет единой оптимальной стратегии — выбор между коротким TTL, event-driven инвалидацией и отказом от кеширования зависит от допустимого окна неточности, частоты изменений и последствий показа устаревшей цены для конкретного бизнеса.
Как это работает подробнее
Event-driven инвалидация даёт минимальное окно устаревания, но требует надёжной доставки события во все кеширующие узлы (подвержена тем же проблемам at-least-once/дублей); короткий TTL проще, но гарантирует окно устаревания размером с TTL независимо от того, изменилась цена или нет.
Чисто TTL-подход прост и устойчив к сбоям доставки событий, но создаёт предсказуемое окно устаревания даже без изменений; event-driven подход даёт минимальную задержку обновления, но добавляет инфраструктурную сложность и сам подвержен потере сообщений, поэтому на практике их комбинируют. Независимо от стратегии витрины, критичную точку (подтверждение оплаты) стоит защищать финальной сверкой цены из источника правды, даже если это добавляет дополнительный синхронный вызов.
Пример кода
# витрина — кешируется с коротким TTL + событие инвалидации
price = cache.get(f"price:{product_id}") # TTL=10s + invalidate-on-change
# перед фактическим списанием — обязательная сверка с источником правды
actual_price = pricing_service.get_current_price(product_id)
if actual_price != displayed_price:
raise PriceChangedError(actual_price)Что использовать на практике
- Практика. При высокой частоте изменений (динамическое ценообразование) кеширование может быть неприемлемо вовсе, либо используют push-инвалидацию с коротким TTL как страховку; при редких изменениях подходит более долгий TTL с принудительной инвалидацией при обновлении.
Подводные камни
- Риск. Полагаться только на событие инвалидации без TTL-страховки — при потере события устаревшая цена может остаться навечно; показывать покупателю устаревшую цену без финальной сверки перед фактическим списанием денег.