Настроить sharding для petabyte embeddings

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить sharding для petabyte embeddings

1. Цель задачи

Разработать и развернуть шардированную систему хранения и поиска по эмбеддингам объёмом до петабайта (1 млрд векторов размерности 768). Использовать HNSW как алгоритм поиска ближайших соседей, Product Quantization (PQ) для сжатия векторов и шардирование по ключу (user/tenant id) для горизонтального масштабирования. Система должна обеспечивать p99 latency <100 ms при поиске и поддерживать добавление новых шардов без даунтайма.

Ключевой результат Рабочий прототип на кластере (или симулированном окружении), обрабатывающий 1 млрд синтетических векторов с latency <100 ms.


2. Исходные данные

Перед началом необходимо подготовить:

Что нужноОткуда взять
Набор синтетических эмбеддингов (768d, float32) – 1 млрд векторовСгенерировать скриптом (см. ниже)
Ключи шардирования (tenant/user id) – 10 000 уникальныхПривязывать случайно к каждому вектору
Кластер серверов (минимум 3 узла)Локальные виртуалки (Docker Compose)
Векторная БД, поддерживающая шардинг + HNSW + PQQdrant (рекомендовано) или Milvus
Инструменты нагрузочного тестированияwrk2, locust, или кастомный Python скрипт

Если нет реального кластера — симулируем:

  1. Используем Docker Compose для запуска Qdrant cluster (3 узла) на одной машине.
  2. Генерируем 1 млн векторов (масштабирование до 1 млрд симуляцией: для теста производительности используем 1 млн, latency экстраполируем по модели O(log N)).
  3. Для имитации 1 млрд можно запустить тесты с 100 млн на 3 узлах и проверить линейность масштабирования.

Генерация синтетических данных

import numpy as np
import uuid

n_vectors = 10_000_000  # 10 млн для финального теста
dim = 768
tenants = [f"tenant_{i}" for i in range(1000)]
vectors = np.random.rand(n_vectors, dim).astype(np.float32)
keys = [tenants[i % len(tenants)] for i in range(n_vectors)]

# Пример записи в Qdrant через batch

3. Технологический стек

КомпонентИнструментыНазначение
Векторная БДQdrant 1.10+In-memory/disk HNSW, PQ, шардинг по ключу
ОркестрацияDocker Compose, Qdrant cluster configПоднятие 3 узлов
КлиентPython (qdrant-client, pytest)Загрузка, поиск, замеры
БалансировщикNginx (round-robin) или встроенный Qdrant proxyМаршрутизация запросов к шардам
МетрикиPrometheus + Grafana (опционально)p99 latency, QPS, память
Тестированиеlocust, jupyter notebookНагрузочное тестирование

4. Этапы выполнения

Этап 1: Проектирование схемы шардирования и конфигурации Qdrant (30 минут)

Действия

  1. Определить ключ шардирования – используем tenant_id. Каждый вектор привязывается к одному tenant.
  2. Разбить ключи на logical shards – 3 шарда (по числу узлов). Используем consistent hashing (Qdrant умеет сам, если настроить shard_key).
  3. Настроить конфигурацию Qdrant cluster
  4. Создать Docker Compose файл с 3 сервисами qdrant (node1, node2, node3) и одним балансировщиком nginx.

Ожидаемый результат этапа Файл docker-compose.yml, конфиги для Qdrant, файл инициализации коллекции.


Этап 2: Развёртывание кластера и загрузка данных (1 час)

Действия

  1. Запустить кластер docker-compose up -d
  2. Создать коллекцию через REST API или qdrant-client:
    from qdrant_client import QdrantClient
    client = QdrantClient(host="localhost", port=6333)
    client.create_collection(
        collection_name="embeddings",
        vectors_config={"size": 768, "distance": "Cosine", "hnsw_config": {"m": 16, "ef_construct": 200}},
        quantization_config=models.QuantizationConfig(
            models.ProductQuantization({"m": 32, "size_bits": 8})
        ),
        shard_number=3,
        replication_factor=1
    )
    
  3. Написать скрипт генерации и загрузки 10 млн векторов (1/100 от целевого объёма) с разбивкой на батчи по 10k.
  4. Загрузить данные в параллель используя threading или asyncio – по одному потоку на узел кластера. Фиксировать время загрузки.
  5. Проверить распределение выполнить client.get_collection("embeddings") и посмотреть точки по шардам.

Ожидаемый результат этапа 10 млн векторов загружены, кластер показывает ~3.3 млн векторов на шард (равномерно).


Этап 3: Настройка HNSW + PQ и тестирование качества поиска (1 час)

Действия

  1. Настроить HNSW параметры ef_construct=400, m=32 (экспериментально).
  2. Оптимизировать PQ попробовать m=64 (больше сжатие, ниже recall), измерить recall@10 на случайном наборе запросов.
  3. Написать валидационный скрипт
    • взять 1000 случайных запросов (тенант + вектор)
    • для каждого выполнить brute‑force поиск (на маленькой выборке или через exact_search) – получить ground truth top-10
    • выполнить HNSW+PQ поиск с ef=128 – сравнить результаты (recall@10)
    • добиться recall >= 0.95
  4. Зафиксировать финальные параметры в файл конфигурации коллекции.

Ожидаемый результат этапа Параметры HNSW и PQ, при которых recall@10 >= 0.95, и сжатие векторов ~4x (768 * 4 байта → 768/32 * 8 бит = 24 байта на вектор – грубая оценка).


Этап 4: Нагрузочное тестирование и замер latency (1,5 часа)

Действия

  1. Разработать нагрузочный скрипт на locust
    • каждое виртуальное пользователь отправляет запрос поиска по случайному tenant (ключ шардирования) и случайному вектору.
    • замерять latency (ms) и успешность.
  2. Увеличить параллелизм от 10 до 500 пользователей, снять графики p50, p95, p99.
  3. Проверить влияния количества векторов на latency:
    • загрузить дополнительно ещё 90 млн векторов (суммарно 100 млн) и повторить тест.
    • построить график зависимости latency от log(размер коллекции).
    • экстраполировать на 1 млрд (должно быть <100 ms при ef=128).
  4. Оптимизировать если p99 > 100 ms, уменьшить ef (до 64), увеличить CPU, поднять параллелизм.

Ожидаемый результат этапа Для 100 млн векторов p99 latency < 80 ms; экстраполяция на 1 млрд даёт <100 ms (с учётом логарифмического роста).


Этап 5: Документирование и скрипты для production (1 час)

Действия

  1. Написать README со схемой кластера, инструкцией по развертыванию, параметрами.
  2. Подготовить Terraform/Ansible скрипты (опционально) для развертывания на реальных серверах.
  3. Написать runbook "Добавление нового шарда" (scale out): создать дополнительный узел, изменить shard_number, перебалансировать.
  4. Создать мониторинг – экспорт метрик Qdrant в Prometheus (через встроенный эндпоинт /metrics) и дашборд в Grafana.

Ожидаемый результат этапа Репозиторий с конфигами, скриптами и документацией, готовый к развертыванию в production.


5. Критерии приемки (Definition of Done)

  • 1. Кластер Qdrant развёрнут (3 узла), коллекция настроена с HNSW и PQ.
  • 2. Загружено не менее 10 млн векторов, распределение по шардам близко к равномерному (отклонение <10%).
  • 3. Recall@10 при HNSW+PQ >= 0.95 по сравнению с точным поиском.
  • 4. p99 latency поиска < 100 ms при 100 параллельных запросах (на 10 млн векторов).
  • 5. Экстраполяция на 1 млрд векторов (на основе тестов с 100 млн) даёт оценку <100 ms.
  • 6. Написана документация (README, конфиги, шаги по добавлению шарда).
  • 7. Нагрузочное тестирование выполнено с locust, результаты сохранены (csv/графики).
  • 8. Реализован мониторинг latency и QPS через Prometheus/Grafana (опционально).

6. Ожидаемый результат

  • Основной артефакт Репозиторий с:
    • docker-compose.yml и конфигами Qdrant
    • скрипт генерации и загрузки синтетических данных (generate_and_upload.py)
    • скрипт нагрузочного тестирования (locustfile.py)
    • ноутбук валидации качества (recall) – validation.ipynb
    • файл с финальными параметрами (params.yml)
    • документация README.md
  • Дополнительные результаты
    • Графики latency vs QPS для разных объёмов (10M, 100M)
    • Оценка стоимости хранения и числа узлов для 1 млрд векторов
    • Runbook по горизонтальному масштабированию

7. Возможные сложности и их решение

СложностьРешение
Диспропорция в распределении векторов по tenantИспользовать shard_key + routing с consistent hashing; при сильном перекосе – добавить виртуальные шарды (virtual shards)
High p99 latency при увеличении данныхУвеличить ef_construct или добавить больше шардов; включить memmap на Qdrant; уменьшить m в PQ
PQ снижает recall > 5%Увеличить m (но уменьшает сжатие); использовать OPQ (optimized product quantization)
Отказ одного узлаВключить replication_factor=2 (влияет на latency записи, но читать можно с реплик)
Сложность отладки производительностиВключить профайлинг Qdrant (profiling: true); собрать flamegraph через async-profiler

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Проектирование0,5 часа
Этап 2: Развёртывание и загрузка1 час
Этап 3: Настройка HNSW+PQ и валидация1 час
Этап 4: Нагрузочное тестирование1,5 часа
Этап 5: Документирование1 час
Итого5 часов

При первом выполнении возможно +2 часа на отладку.

9. Связанные вопросы из базы знаний

ВопросТема
12Основные метрики качества поиска (Recall, MAP, NDCG)
45HNSW: параметры и компромиссы
78Product Quantization: теория и реализация
132Шардирование векторных БД: стратегии и trade-offs
189Consistent hashing для распределённых систем
231Профилирование latency в распределённых системах
305Нагрузочное тестирование векторных БД
478Масштабирование Qdrant: кластеризация и репликация
512Оптимизация поиска: ef, ef_construct, сканирование
688Мониторинг в production: Prometheus + Grafana

10. Чек-лист самопроверки

  • Я разобрался в архитектуре Qdrant sharding по ключу.
  • Я проверил, что recall@10 >= 0.95 при HNSW+PQ на тестовой выборке.
  • Я измерил p99 latency при 100 параллельных запросах и получил <100 ms.
  • Я задокументировал параметры HNSW, PQ и количество шардов.
  • Я смоделировал добавление нового шарда и убедился, что данные перераспределяются корректно.