Files
core/docs/search-system.md
Untone 4489d25913
Some checks failed
Deploy on push / deploy (push) Failing after 1m34s
## [0.9.18] - 2025-01-09
### 🔍 Search System Redis Storage
- **💾 Redis-based vector index storage**: Переключились обратно на Redis для хранения векторного индекса
  - Заменили файловое хранение в `/dump` на Redis ключи для надежности
  - Исправлена проблема с правами доступа на `/dump` папку на сервере
  - Векторный индекс теперь сохраняется по ключам `search_index:{name}:data` и `search_index:{name}:metadata`
- **🛠️ Improved reliability**: Убрали зависимость от файловой системы для критичных данных
- ** Better performance**: Redis обеспечивает более быстрый доступ к индексу
- **🔧 Technical changes**:
  - Заменили `save_index_to_file()` на `save_index_to_redis()`
  - Заменили `load_index_from_file()` на `load_index_from_redis()`
  - Обновили автосохранение для использования Redis вместо файлов
  - Удалили неиспользуемые импорты (`gzip`, `pathlib`, `cast`)
2025-09-01 15:09:36 +03:00

10 KiB
Raw Blame History

🔍 Система поиска Discours

Обзор

Система поиска Discours использует семантические эмбединги для точного поиска по публикациям. Реализована на базе SentenceTransformers с поддержкой русского языка и FDE (Fast Document Encoding) для оптимизации.

🚀 Основные возможности

1. Семантический поиск

  • Понимание смысла запросов, а не только ключевых слов
  • Поддержка русского и английского языков
  • Векторное представление документов через SentenceTransformers

2. Оптимизированная индексация

  • Batch-обработка для больших объёмов данных
  • Тихий режим для массовых операций
  • FDE кодирование для сжатия векторов

3. Высокая производительность

  • Косинусное сходство для ранжирования
  • Кеширование результатов
  • Асинхронная обработка

📋 API

GraphQL запросы

# Поиск по публикациям
query SearchShouts($text: String!, $options: ShoutsOptions) {
  load_shouts_search(text: $text, options: $options) {
    id
    title
    body
    topics {
      title
    }
  }
}

# Поиск по авторам
query SearchAuthors($text: String!, $limit: Int, $offset: Int) {
  load_authors_search(text: $text, limit: $limit, offset: $offset) {
    id
    name
    email
  }
}

Параметры поиска

options = {
    "limit": 10,        # Количество результатов
    "offset": 0,        # Смещение для пагинации
    "filters": {        # Дополнительные фильтры
        "community": 1,
        "status": "published"
    }
}

🛠️ Техническая архитектура

Компоненты системы

📦 Search System
├── 🧠 SentenceTransformer    # Генерация эмбедингов
├── 🗜️ Muvera FDE            # Сжатие векторов
├── 🗃️ MuveraWrapper         # Хранение и поиск
├── 💾 File Persistence      # Сохранение в /dump папку
└── 🔍 SearchService         # API интерфейс

Модель эмбедингов

Основная модель: paraphrase-multilingual-MiniLM-L12-v2

  • Поддержка 50+ языков включая русский
  • Размерность: 384D
  • Fallback: all-MiniLM-L6-v2

Процесс индексации

# 1. Извлечение текста
doc_content = f"{title} {subtitle} {lead} {body}".strip()

# 2. Генерация эмбединга
embedding = encoder.encode(doc_content)

# 3. FDE кодирование
compressed = muvera.encode_fde(embedding, buckets=128, method="avg")

# 4. Сохранение в индекс
embeddings[doc_id] = compressed

# 5. Автосохранение в файл
await self.save_index_to_file("/dump")

Алгоритм поиска

# 1. Эмбединг запроса
query_embedding = encoder.encode(query_text)
query_fde = muvera.encode_fde(query_embedding, buckets=128, method="avg")

# 2. Косинусное сходство
for doc_id, doc_embedding in embeddings.items():
    similarity = cosine_similarity(query_fde, doc_embedding)
    results.append({"id": doc_id, "score": similarity})

# 3. Ранжирование
results.sort(key=lambda x: x["score"], reverse=True)

⚙️ Конфигурация

Переменные окружения

# Поиск
MUVERA_INDEX_NAME=discours_search
SEARCH_MAX_BATCH_SIZE=100
SEARCH_PREFETCH_SIZE=200
SEARCH_CACHE_ENABLED=true
SEARCH_CACHE_TTL_SECONDS=600

Настройки производительности

# Batch размеры
SINGLE_DOC_THRESHOLD = 10      # Меньше = одиночная обработка
BATCH_SIZE = 32                # Размер batch для SentenceTransformers
FDE_BUCKETS = 128              # Количество bucket для сжатия

# Logging
SILENT_BATCH_MODE = True       # Тихий режим для batch операций
DEBUG_SINGLE_DOCS = True       # Подробные логи для одиночных документов

🔧 Использование

Индексация новых документов

from services.search import search_service

# Одиночный документ
search_service.index(shout)

# Batch индексация (тихий режим)
await search_service.bulk_index(shouts_list)

Поиск

# Поиск публикаций
results = await search_service.search("машинное обучение", limit=10, offset=0)

# Поиск авторов
authors = await search_service.search_authors("Иван Петров", limit=5)

Проверка статуса

# Информация о сервисе
info = await search_service.info()

# Статус индекса
status = await search_service.check_index_status()

# Проверка документов
verification = await search_service.verify_docs(["1", "2", "3"])

🐛 Отладка

Логирование

# Включить debug логи
import logging
logging.getLogger("services.search").setLevel(logging.DEBUG)

# Проверить загрузку модели
logger.info("🔍 SentenceTransformer model loaded successfully")

Диагностика

# Проверить количество проиндексированных документов
info = await search_service.info()
print(f"Documents: {info['muvera_info']['documents_count']}")

# Найти отсутствующие документы
missing = await search_service.verify_docs(expected_doc_ids)
print(f"Missing: {missing['missing']}")

📈 Метрики производительности

Типичные показатели

📊 Производительность поиска:
├── Поиск по 1000 документов: ~50ms
├── Индексация 1 документа: ~100ms
├── Batch индексация 100 документов: ~2s
└── Память на 1000 документов: ~50MB

Оптимизация

  1. Batch обработка - для массовых операций используйте bulk_index()
  2. Тихий режим - отключает детальное логирование
  3. Кеширование - результаты поиска кешируются в Redis
  4. FDE сжатие - уменьшает размер векторов в 2-3 раза

💾 Персистентность и восстановление

Автоматическое сохранение в Redis

Система автоматически сохраняет индекс в Redis после каждой успешной индексации:

# Автосохранение после индексации
if indexed_count > 0:
    await self.save_index_to_redis()
    logger.debug("💾 Индекс автоматически сохранен в Redis")

Структура Redis ключей

Redis:
├── search_index:discours_search:data        # Основной индекс (pickle)
└── search_index:discours_search:metadata    # Метаданные (JSON)

Восстановление при запуске

При запуске сервиса система автоматически восстанавливает индекс из Redis:

# В initialize_search_index()
await search_service.async_init()  # Восстанавливает из Redis

🆕 Преимущества Redis хранения

По сравнению с файлами/БД

  • Скорость: Мгновенный доступ к векторному индексу
  • 🔄 Надежность: Нет проблем с правами доступа к файловой системе
  • 💾 Эффективность: Pickle сериализация для быстрого сохранения/загрузки
  • 🔒 Целостность: Атомарные операции записи в Redis
  • 📊 Метаданные: Отдельный JSON ключ для быстрого доступа к статистике

Производительность

📊 Сравнение методов хранения:
├── Redis: ~50MB RAM, мгновенное восстановление ✅
├── БД: ~75MB RAM, медленное восстановление  
└── Файл: ~25MB RAM, проблемы с правами ❌

🔄 Миграция и обновления

Переиндексация

# Полная переиндексация
from main import initialize_search_index_with_data
await initialize_search_index_with_data()

Обновление модели

  1. Остановить сервис
  2. Обновить sentence-transformers
  3. Изменить модель в MuveraWrapper.__init__()
  4. Запустить переиндексацию

Резервное копирование

# Создание бэкапа Redis ключей
redis-cli --rdb backup.rdb

# Или экспорт конкретных ключей
redis-cli GET "search_index:discours_search:data" > backup_data.pkl
redis-cli GET "search_index:discours_search:metadata" > backup_metadata.json

# Восстановление из бэкапа
redis-cli SET "search_index:discours_search:data" < backup_data.pkl
redis-cli SET "search_index:discours_search:metadata" < backup_metadata.json

🔗 Связанные документы