Сьогодні нам треба зрозуміти, як керувати життєвим циклом даних у кеші, освоїти підходи до видалення застарілих даних і навчитися ефективно використовувати можливості Redis для оптимізації.
Одним із ключових аспектів кешування є керування часом життя записів у кеші. У Redis це реалізовано через механізм TTL (Time To Live), який визначає, скільки часу ключ існуватиме до його автоматичного видалення.
Приклад задання TTL для ключа:
import redis
# Підключення до Redis
r = redis.Redis(host='localhost', port=6379, db=0)
# Встановлення значення з часом життя 10 секунд
r.set('username', 'Alice', ex=10)
# Отримання значення до закінчення TTL
print(r.get('username')) # b'Alice'
# Через 10 секунд ключ автоматично видалиться
Флаг ex задає час життя в секундах, а px — в мілісекундах.
Більшість кешуючих систем встановлюють TTL, щоб не накопичувалися застарілі дані і кеш залишався актуальним.
Політики витіснення даних
Redis підтримує різні політики витіснення даних, які активуються, якщо пам'ять закінчилась. Найпопулярніші політики:
- noeviction: нічого не видаляється, при вичерпанні пам'яті сервер повертає помилку.
- allkeys-lru: видаляються найменш нещодавно використані ключі (Least Recently Used).
- volatile-lru: видаляються найменш нещодавно використані ключі з встановленим TTL.
- allkeys-lfu: видаляються найменш часто використовувані ключі (Least Frequently Used).
Ці політики налаштовуються у конфігураційному файлі Redis (зазвичай redis.conf) через параметр maxmemory-policy. Наприклад:
maxmemory-policy allkeys-lru
Якщо хочете протестувати витіснення даних, використайте таку налаштування для запуску Redis:
redis-server --maxmemory 100mb --maxmemory-policy allkeys-lru
Видалення даних з кешу
Видалення конкретних ключів у Redis здійснюється командою DEL. Це можна зробити як для одного ключа, так і для кількох:
# Видалення одного ключа
r.delete('username')
# Видалення кількох ключів
r.delete('key1', 'key2', 'key3')
Але інколи потрібно видаляти дані, які відповідають певному шаблону. Для цього використовують комбінацію SCAN і DEL:
# Знайти й видалити ключі, що починаються з "user:"
keys = r.scan_iter('user:*')
for key in keys:
r.delete(key)
Використовуйте SCAN замість KEYS, бо остання може дуже навантажувати сервер при великій кількості записів.
Для очищення всіх даних з Redis застосовують команду FLUSHDB (очищає поточну базу даних) або FLUSHALL (очищає всі бази даних):
# Повністю очистити поточну базу
r.flushdb()
# Повністю очистити всі бази Redis
r.flushall()
Будьте обережні! Ці команди видаляють всі дані без можливості відновлення.
Обробка застарілих даних
Інвалідизація — це процес видалення застарілих або змінених даних з кешу, щоб уникнути використання невірної інформації. Наприклад, уявімо, що ми кешуємо інформацію про товар:
# Кешуємо інформацію про товар
product_id = 42
product_data = {'name': 'Laptop', 'price': 1500}
r.set(f'product:{product_id}', json.dumps(product_data), ex=300)
Якщо ціна товару змінюється в базі даних, потрібно видалити кеш, щоб він не повертав застарілі дані:
# Видаляємо старий кеш товару
r.delete(f'product:{product_id}')
У складних системах це може бути автоматизовано за допомогою механізму подій або сигналів, які спрацьовують при зміні даних.
Підходи до інвалідизації кешу:
- Явне скидання кешу: кеш видаляють вручну при оновленні даних.
- Lazy Loading (ліниве завантаження): застарілі дані видаляються лише при наступному запиті.
- Time-based Expiration: дані автоматично видаляються після закінчення TTL.
Приклад використання явної інвалідизації в FastAPI:
from fastapi import FastAPI
import redis
import json
app = FastAPI()
r = redis.Redis(host='localhost', port=6379, db=0)
@app.put("/update_product/{product_id}")
async def update_product(product_id: int, name: str, price: float):
# Оновлення даних у БД (припустимо)
database_update(product_id, name, price)
# Видалення застарілих даних у кеші
r.delete(f'product:{product_id}')
return {"message": "Product updated and cache invalidated"}
Використання TTL і LRU для керування
Щоб мінімізувати кількість застарілих даних і оптимізувати використання пам'яті:
- Встановлюйте TTL для даних, які змінюються рідко, але потребують регулярного оновлення.
- Налаштовуйте політику витіснення, наприклад,
volatile-lruдля ключів з TTL.
Приклад:
# Кешуємо з TTL
r.set('user_preferences', json.dumps({'theme': 'dark'}), ex=86400)
# Перевірка залишкового часу життя
ttl = r.ttl('user_preferences')
print(f"Remaining TTL: {ttl} seconds")
Практика: керування даними в складній системі
Припустімо, ми зберігаємо інформацію про користувачів у кеші з ключами user:{id}. Коли користувач оновлює свої дані, кеш повинен оновлюватися, щоб уникнути застарілої інформації.
Приклад коду:
@app.put("/update_user/{user_id}")
async def update_user(user_id: int, name: str):
# Оновлюємо дані у БД
update_user_in_db(user_id, name)
# Інвалідуємо кеш
r.delete(f'user:{user_id}')
# Заново додаємо дані в кеш
new_data = {"id": user_id, "name": name}
r.set(f'user:{user_id}', json.dumps(new_data), ex=3600)
return new_data
Механізми очищення: автоматизація
Якщо ручне очищення здається занадто складним, можна використати:
- Періодичні задачі: запускати очищення кешу за розкладом через Celery.
- Подієвий підхід: зв'язувати операції в БД з кешуванням через сигнали.
Приклад очищення:
# Celery завдання для автоматичного очищення
@app.task
def clear_expired_cache():
keys = r.scan_iter()
for key in keys:
if r.ttl(key) == -1: # Якщо у ключа немає TTL
r.delete(key)
Підсумкове застосування знань
Тепер ви знаєте, як керувати даними в кеші — від видалення конкретного ключа до налаштування складних політик витіснення. У реальному житті грамотне керування кешем не лише підвищує продуктивність — воно рятує вас від головного болю через застарілі дані. А що може бути гірше, ніж користувач, який бачить у системі стару інформацію? Хіба що баг у п'ятницю ввечері!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ