Настроить TTL для semantic cache
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить TTL для semantic cache
1. Цель задачи
Научиться проектировать и реализовывать механизм Time-To-Live (TTL) для semantic cache в production-системе. Необходимо настроить разное время жизни кэша для «горячих» (часто запрашиваемых) тем и «редких» (низкочастотных) запросов, чтобы балансировать между latency и актуальностью ответов.
Ключевой результат В semantic cache реализован динамический TTL: для запросов, попадающих в hot topics (частота > 100 запросов/час), TTL = 1 минута; для всех остальных — 1 час. Система корректно применяет TTL при записи и чтении кэша.
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| Работающая RAG-система с семантическим кэшем | Pet-проект (например, из задачи по сборке RAG) |
| Векторная база данных для семантического поиска | Qdrant / Milvus / FAISS (свой инстанс) |
| База данных для хранения метаданных кэша (TTL, frequency) | Redis / PostgreSQL / SQLite |
| Нагрузочный тест или дашборд частоты запросов | Prometheus + Grafana или смоделированные логи |
| Библиотека для вычисления семантической близости | sentence-transformers / OpenAI embeddings |
Если нет реального semantic cache — симулируем:
- Разверните минимальный semantic cache на базе Qdrant (или простого in-memory dict с эмбеддингами).
- Создайте Python-скрипт, который эмулирует разные типы запросов (100 уникальных hot topics, 1000 редких).
- Запишите в лог частоту каждого запроса (можно генерировать экспоненциальное распределение).
- Настройте таймеры TTL в коде: при записи в кэш сохраняйте timestamp создания + TTL, при чтении сверяйте текущее время.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Semantic cache store | Qdrant / Weaviate / Milvus | Хранение векторов запросов и ответов |
| Метаданные (TTL, частота) | Redis / PostgreSQL | Хранение: key: hash, value: {timestamp, ttl, frequency} |
| Эмбеддинг-модель | sentence-transformers (all-MiniLM-L6-v2) / OpenAI API | Преобразование запроса в вектор |
| Определение hot topics | Redis + счётчики (INCR) / скользящее окно | Подсчёт частоты запросов за последние N минут |
| Orchestration | Python 3.10+ (FastAPI / aiohttp) | Слой между RAG и кэшем |
| Мониторинг | Prometheus + Grafana / просто print | Сбор метрик hit rate, miss rate, expired rate |
4. Этапы выполнения
Этап 1: Архитектура и проектирование механизма TTL (оценка: 30 минут)
Действия
-
Определите, как хранить TTL.
-
Разработайте схему Redis
Key: cache:ttl:{hash} → hash field: 'created_at' (unix timestamp) Key: cache:count:{bucket} → sorted set (для скользящего окна) Key: cache:frequency:{hash} → count (общее за последние 5 минут) -
Определите логику классификации hot/rare
- Hot: frequency > THRESHOLD за последние 5 минут (например, >100).
- Rare: иначе.
- TTL_hot = 60 секунд, TTL_rare = 3600 секунд.
-
Нарисуйте flow диаграмму
Запрос → Эмбеддинг → Поиск в Qdrant (cosine > epsilon) → если найден → проверка TTL в Redis → если просрочен → удалить из Qdrant, miss. если актуален → вернуть ответ, увеличить frequency. если не найден → вычислить ответ через LLM, записать в Qdrant, сохранить в Redis с TTL (определить частоту запроса).
Ожидаемый результат этапа Markdown- или draw.io-диаграмма, описание структуры Redis, константы TTL.
Этап 2: Реализация метаданных TTL и частоты (оценка: 1 час)
Действия
-
Напишите класс
CacheTTLManagerна Pythonimport redis import time from collections import defaultdict class CacheTTLManager: def __init__(self, redis_client: redis.Redis, hot_threshold: int = 100, window_seconds: int = 300): self.r = redis_client self.threshold = hot_threshold self.window = window_seconds def get_frequency(self, query_hash: str) -> int: """Возвращает количество запросов за последние window_seconds.""" now = int(time.time()) self.r.zremrangebyscore(f'cache:freq:{query_hash}', 0, now - self.window) return self.r.zcard(f'cache:freq:{query_hash}') def record_request(self, query_hash: str): """Записывает факт запроса (текущее время).""" now = int(time.time()) self.r.zadd(f'cache:freq:{query_hash}', {now: now}) def get_ttl(self, query_hash: str) -> int: freq = self.get_frequency(query_hash) return 60 if freq >= self.threshold else 3600 def is_expired(self, query_hash: str, created_at: int) -> bool: ttl = self.get_ttl(query_hash) return time.time() - created_at > ttl -
Реализуйте логику записи в кэш
- При cache miss: вычислить
query_hash, записать эмбеддинг + ответ в Qdrant. - В Redis установить HSET cache:meta:{hash} created_at <now>.
- EXPIRE cache:meta:{hash} <max_possible_ttl> (например, 3600 + запас).
- При cache miss: вычислить
-
Реализуйте логику чтения
Ожидаемый результат этапа Класс CacheTTLManager с методами get_ttl/is_expired/record_request, интеграционная обвязка с Qdrant.
Этап 3: Интеграция с semantic cache и эмуляция запросов (оценка: 1.5 часа)
Действия
-
Создайте эмулятор запросов (или используйте существующий дашборд).
import random import time from sentence_transformers import SentenceTransformer from qdrant_client import QdrantClient model = SentenceTransformer('all-MiniLM-L6-v2') hot_queries = ["погода в Москве", "курс доллара", "новости сегодня", ...] * 10 rare_queries = ["история 3D-печати", "рецепт панакоты", ...] * 100 # Цикл эмуляции: 80% запросов — hot, 20% — rare -
Запустите эмуляцию на 10 минут, собирайте логи:
stats = {'cache_hit': 0, 'cache_miss': 0, 'expired': 0} for i in range(500): query = random.choice(hot_queries if random.random() < 0.8 else rare_queries) result = get_or_compute(query) # логируем hit/miss/expired -
Выведите метрики
- Hit rate для hot vs rare запросов.
- Средняя задержка.
- Количество просроченных записей.
Ожидаемый результат этапа Скрипт эмуляции, который выводит таблицу метрик, показывающую, что hot-запросы кэшируются лишь на 1 минуту, а редкие — на 1 час.
Этап 4: Тестирование граничных случаев и мониторинг (оценка: 30 минут)
Действия
-
Проверьте сценарии
- Запрос становится hot после периода редких запросов (TTL должен пересчитаться).
- Холодный запрос не должен перезаписывать hot TTL.
- Удаление истекших записей из Qdrant не должно ломать поиск.
- Обработка отсутствия Redis (graceful degradation — отключать кэш, а не падать).
-
Настройте метрики Prometheus (или просто print-лог):
cache_ttl_seconds{type="hot"}= 60cache_ttl_seconds{type="rare"}= 3600cache_hit_count{type="hot"},cache_expired_count
-
Напишите минимальный дашборд в Grafana (или текстовый отчёт с графиками matplotlib).
Ожидаемый результат этапа Лог всех граничных случаев, подтверждающий корректное поведение, описание ошибок и их обработки.
Этап 5: Документирование и ревью (оценка: 30 минут)
Действия
-
Опишите архитектуру в README
- Как TTL определяется (формула частоты).
- Схема Redis и Qdrant.
- Пример конфигурации (TTL thresholds, window).
-
Зафиксируйте результаты тестов
- График hit rate по времени.
- Средняя задержка: без кэша, с кэшем, с TTL.
-
Оцените влияние TTL на cache hit rate:
- Какой процент запросов становится устаревшим до использования.
- Комментарий: стоит ли уменьшать TTL для hot-запросов до 30 секунд.
Ожидаемый результат этапа README-файл с архитектурой и результатами.
5. Критерии приемки (Definition of Done)
- Механизм TTL реализован: для hot topics TTL = 60 сек, для rare = 3600 сек.
- Классификация горячих/редких запросов основана на частоте за последние 5 минут (порог >100).
- При кэш-хите проверяется время создания: если превышен TTL, запись удаляется и возвращается miss.
- Удаление истекших записей из векторной базы данных происходит (или запланировано TTL в Qdrant).
- Эмуляция подтверждает, что hot-запросы обновляются чаще, чем rare.
- Обработаны ошибки: отключение Redis → кэш отключается, система продолжает работу без него.
- Метрики TTL, hit rate, expired count выведены в лог или дашборд.
- README содержит описание архитектуры, пример конфигурации и результаты тестов.
6. Ожидаемый результат
Основной артефакт Python-пакет с модулем cache_ttl_manager.py, содержащим класс CacheTTLManager, и скрипт эмуляции emulate_ttl.py.
Содержание
- Код реализации TTL.
- Лог выполнения с метриками.
- README с архитектурой.
Опциональные результаты
- Docker-образ с Redis + приложение.
- Prometheus-экспортер метрик.
- Дашборд Grafana.
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Частота запросов считается неверно (плавающие hot topics) | Использовать скользящее окно (zrangebyscore) вместо абсолютного счётчика. |
| Redis не поддерживает HSET с TTL для отдельных полей | Использовать EXPIRE на весь ключ, или записывать created_at в отдельный ключ с TTL. |
| Удаление из Qdrant при истечении TTL нагружает BD | Использовать таймер (celery beat) для фоновой чистки раз в минуту, или TTL-индекс в Qdrant (если поддерживается). |
| High contention на счётчиках частоты | Использовать Redis Pipeline или INCR с истечением через EXPIRE. |
| Hot topic порог неправильно выбран | Сделать конфигурируемым через переменные окружения, выставить дефолты. |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| Этап 1: Архитектура и проектирование | 30 мин |
| Этап 2: Реализация Redis и классификации | 1 ч |
| Этап 3: Интеграция и эмуляция | 1.5 ч |
| Этап 4: Тестирование граничных случаев | 30 мин |
| Этап 5: Документирование | 30 мин |
| Итого | 4 часа |
Примечание Для первого раза время может увеличиться до 6 часов из-за отладки.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 112 | Semantic cache — архитектура и метрики |
| 145 | TTL и инвалидация кэша |
| 203 | Redis как store для метаданных кэша |
| 278 | Определение hot topics с помощью скользящего окна |
| 321 | Qdrant: операция удаления по payload |
| 407 | Graceful degradation при отказе Redis |
| 456 | Эмбеддинги и метрики семантической близости |
| 512 | Мониторинг cache hit rate в Prometheus |
| 623 | Настройка TTL в Qdrant (долгоживущие точки) |
| 701 | Паттерны кэширования: Cache-Aside, Write-Through |
10. Чек-лист самопроверки
- Я реализовал
CacheTTLManagerс методамиget_ttlиis_expired. - Я проверил, что hot-запросы (частота >100/5min) получают TTL 60 секунд, а остальные — 3600.
- Я протестировал сценарий: запрос сначала редкий, затем становится горячим — TTL пересчитывается.
- Я убедился, что при отказе Redis кэш отключается, и система не падает.
- Я вывел метрики (hit rate, expired count) в удобном виде (таблица/график).
- Я написал README с описанием архитектуры и инструкцией по запуску эмуляции.
- Я проверил, что удаление истекших записей из Qdrant происходит (вручную или автоматически).