Архитектура поискового сервиса: Автономное Flask-приложение с Valkey
В этой статье объясняется, как наш поисковый сервис работает как автономное Flask-приложение на отдельном сервере, используя Valkey (форк Redis) для высокопроизводительного векторного поиска, кэширования и автодополнения.
Проблема: Производительность и масштабируемость поиска
Поисковые операции требуют больших вычислительных ресурсов:
-
Извлечение фильтров: Сопоставление 2500+ фраз с каждым запросом
-
Похожие запросы: Вычисление сходства по 65К запросам
-
Автодополнение: Префиксный поиск по 65К запросам
-
Фильтрация товаров: Фильтрация 5К+ товаров по множеству критериев
Выполнение этих операций на основном веб-сервере приводит к:
-
Медленной загрузке страниц: Поиск блокирует другие запросы
-
Нагрузке на память: Эмбеддинги занимают огромный объем памяти
-
Конкуренции за CPU: Вычисление сходства требует много процессорного времени
-
Сложности масштабирования: Невозможно масштабировать поиск независимо
Нам нужен выделенный поисковый сервис, который можно масштабировать независимо.
Решение: Автономный поисковый сервис
Мы запускаем отдельное Flask-приложение на выделенном сервере:
Основной веб-сервер
↓ HTTP API вызовы
Поисковый сервис
↓ Запросы к Valkey
Сервер Valkey
Эта архитектура обеспечивает:
-
Независимое масштабирование: Масштабирование поискового сервиса без влияния на основной веб-сервер
-
Изоляцию ресурсов: Поисковые операции не влияют на основной веб-сервер
-
Кэширование: Valkey кэширует результаты для быстрых повторных запросов
-
Высокую доступность: Поисковый сервис может перезапускаться без влияния на основной веб-сервер
Компоненты поискового сервиса
1. API извлечения фильтров
-
Эндпоинт:
/api/extract_filters -
Назначение: Извлекать структурированные фильтры из запросов на естественном языке
-
Пример:
GET /api/extract_filters?q=mini+pc+16gb+ram
Ответ:
{
"Form Factor": "Mini PC",
"Main Memory": "16"
}
Реализация:
-
Загрузка соответствий фраз и фильтров из Valkey или JSON
-
Сопоставление фраз с помощью регулярных выражений с границами слов
-
Возврат структурированных фильтров
Кэширование: Соответствия фраз кэшируются в Valkey (TTL 30 дней)
2. API похожих запросов
-
Эндпоинт:
/api/related -
Назначение: Находить семантически похожие запросы с помощью векторного поиска
-
Пример:
POST /api/related
{
"query": "mini pc",
"limit": 10
}
Ответ:
{
"related": [
{"query": "small computer", "similarity": 0.92},
{"query": "compact desktop", "similarity": 0.89},
{"query": "mini pc 8gb", "similarity": 0.87}
]
}
Реализация:
-
Векторизация запроса с помощью all-mpnet-base-v2
-
Запрос к Valkey RediSearch для поиска ближайших соседей
-
Возврат топ-N результатов, отсортированных по сходству
Кэширование: Результаты кэшируются в Valkey (TTL 7 дней)
3. API автодополнения
-
Эндпоинт:
/api/autocomplete -
Назначение: Предлагать запросы по мере ввода пользователем
-
Пример:
GET /api/autocomplete?q=mini+p&limit=5
Ответ:
{
"suggestions": [
"mini pc",
"mini pc 16gb",
"mini pc 8gb ram",
"mini pc fanless",
"mini pc windows 11"
]
}
Реализация:
-
Запрос к Valkey RediSearch с префиксным поиском
-
Ранжирование по популярности (оценка показов + кликов)
-
Возврат топ-N предложений
Кэширование: Индекс автодополнения в Valkey (обновляется ежедневно)
4. API популярных запросов
-
Эндпоинт:
/api/popular -
Назначение: Получать самые популярные запросы
-
Пример:
GET /api/popular?limit=10
Ответ:
{
"queries": [
"mini pc",
"thin client",
"industrial pc",
"all in one pc"
]
}
Реализация:
-
Загрузка запросов из Valkey или JSON
-
Сортировка по оценке трафика (показы + клики)
-
Возврат топ-N запросов
Кэширование: Популярные запросы кэшируются в Valkey (TTL 30 дней)
Интеграция с Valkey
Valkey — это форк Redis, который предоставляет:
-
Векторный поиск: Модуль RediSearch для поиска по сходству
-
Кэширование: Быстрое хранилище ключ-значение в памяти
-
Автодополнение: Префиксный поиск с отсортированными множествами
-
Сохранность данных: AOF (Append-Only File) для устойчивости
Векторный поиск с RediSearch
Мы используем модуль RediSearch Valkey для векторного поиска по сходству:
Создание индекса:
client.ft("queries_idx").create_index([
VectorField("embedding", "FLAT", {
"TYPE": "FLOAT32",
"DIM": 768,
"DISTANCE_METRIC": "COSINE"
}),
TextField("query"),
NumericField("score")
])
Векторный поиск:
query_embedding = model.encode(query)
results = client.ft("queries_idx").search(
Query("*=>[KNN 10 @embedding $vec AS score]")
.sort_by("score")
.return_fields("query", "score")
.dialect(2),
query_params={"vec": query_embedding.tobytes()}
)
Это возвращает 10 ближайших соседей по косинусному сходству.
Стратегия кэширования
Мы кэшируем несколько типов данных в Valkey:
Соответствия фраз (TTL 30 дней):
client.setex(
"seo:phrase_mappings",
30 * 24 * 3600,
json.dumps(phrase_mappings)
)
Похожие запросы (TTL 7 дней):
cache_key = f"related:{query_hash}"
client.setex(cache_key, 7 * 24 * 3600, json.dumps(results))
Популярные запросы (TTL 30 дней):
client.setex(
"seo:popular_queries",
30 * 24 * 3600,
json.dumps(popular_queries)
)
Индекс автодополнения (обновляется ежедневно):
for query, score in queries:
client.zadd("autocomplete:mini", {query: score})
Автодополнение с отсортированными множествами
Мы используем отсортированные множества Valkey для автодополнения:
Структура индекса:
autocomplete:m → ["mini pc": 5000, "mini computer": 3000]
autocomplete:mi → ["mini pc": 5000, "mini computer": 3000]
autocomplete:min → ["mini pc": 5000, "mini computer": 3000]
autocomplete:mini → ["mini pc": 5000, "mini computer": 3000]
Префиксный поиск:
prefix = "mini"
results = client.zrevrange(f"autocomplete:{prefix}", 0, 9, withscores=True)
Это возвращает топ-10 запросов, начинающихся с "mini", отсортированных по оценке.
Взаимодействие через API
Основной веб-сервер обращается к поисковому сервису через HTTP:
Извлечение фильтров
from app.shared.filter_service import extract_filters_from_query
filters = extract_filters_from_query("mini pc 16gb ram")
# Внутренне вызывает: GET SEARCH_SERVICE_URL/api/extract_filters?q=...
Похожие запросы
import requests
response = requests.post(
"SEARCH_SERVICE_URL/api/related",
json={"query": "mini pc", "limit": 10},
timeout=2
)
related = response.json()["related"]
Автодополнение
response = requests.get(
"SEARCH_SERVICE_URL/api/autocomplete",
params={"q": "mini p", "limit": 5},
timeout=1
)
suggestions = response.json()["suggestions"]
Обработка ошибок и отказоустойчивость
Основной веб-сервер корректно обрабатывает сбои поискового сервиса:
try:
filters = extract_filters_from_query(query)
except Exception as e:
logger.error(f"Search service failed: {e}")
filters = {} # Резервный вариант — пустые фильтры
Это гарантирует, что основной веб-сервер продолжит работу, даже если поисковый сервис недоступен.
### Сетевая конфигурация
Все серверы находятся в частной сети:
- **Основной веб-сервер**: Может обращаться к поисковому сервису и Valkey
- **Поисковый сервис**: Может обращаться к Valkey
- **Valkey**: Доступен только с основного веб-сервера и поискового сервиса
Внешнего доступа к поисковому сервису или Valkey нет.
## Интеграция с SEO-пайплайном
Поисковый сервис интегрирован с SEO-пайплайном:
### Шаг 11: Миграция на Valkey
SEO-пайплайн загружает данные в Valkey:
```python
# Загрузка эмбеддингов запросов
for query, embedding in zip(queries, embeddings):
client.hset(f"query:{query_hash}", mapping={
"query": query,
"embedding": embedding.tobytes(),
"score": score
})
# Создание индекса RediSearch
client.ft("queries_idx").create_index([...])
Подробности см. в Миграция на Valkey.
Логирование запросов
Поисковый сервис логирует запросы для SEO-пайплайна:
log_entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"query": query,
"filters_extracted": filters,
"results_count": len(results)
}
with open(SEO_LIVE_QUERIES_LOG, "a") as f:
f.write(json.dumps(log_entry) + "\n")
Эти логи поступают обратно в Шаг 1d: Получение живых запросов.
Ссылки
Технические концепции
-
Valkey - Официальный сайт
-
Redis - Официальный сайт (форк Valkey)
-
RediSearch - Модуль векторного поиска
-
Косинусное сходство - Википедия
Связанные статьи
-
Извлечение фильтров - Как извлекаются фильтры
-
Генерация похожих запросов - Как генерируются похожие запросы
-
Стратегия эмбеддингов - Как генерируются эмбеддинги
-
Обзор SEO-пайплайна - Архитектура полного пайплайна
-
Миграция на Valkey - Загрузка данных в Valkey
Резюме
Наш поисковый сервис работает как автономное Flask-приложение на отдельном сервере:
Архитектура:
-
Автономное Flask-приложение на выделенном сервере
-
Valkey (форк Redis) для кэширования и векторного поиска
-
HTTP API для взаимодействия с основным веб-сервером
API:
-
/api/extract_filters- Извлечение фильтров из запросов -
/api/related- Поиск похожих запросов (векторный поиск) -
/api/autocomplete- Предложение запросов (префиксный поиск) -
/api/popular- Получение популярных запросов
Возможности Valkey:
-
Векторный поиск (модуль RediSearch)
-
Кэширование (TTL 30 дней для соответствий фраз)
-
Автодополнение (отсортированные множества)
-
Сохранность данных (AOF)
Преимущества:
-
Независимое масштабирование
-
Изоляция ресурсов
-
Высокая производительность (кэширование Valkey)
-
Отказоустойчивость (плавная деградация)
Эта архитектура обеспечивает быстрый, масштабируемый поиск, сохраняя при этом отзывчивость основного веб-сервера.