MAATRIX / Блог / Сколько эмбеддингов влезет в один сервер: считаем предел векторной базы в цифрах

Сколько эмбеддингов влезет в один сервер: считаем предел векторной базы в цифрах

MAATRIX

Задача обычно ставится наоборот: не «сколько памяти нужно под миллион векторов», а «у меня сервер на 32 ГБ — сколько документов туда влезет, прежде чем начнутся проблемы». Ответ зависит не только от количества эмбеддингов, но и от их размерности, от накладных расходов индекса и от того, включено ли квантование. Ниже — как посчитать этот потолок заранее, а не выяснить его по факту падения сервиса.

Из чего складывается память под векторную базу

Векторная база — не файл с числами, а минимум три статьи расхода одновременно.

Сырые векторы. Каждый эмбеддинг — это массив чисел фиксированной длины (размерность модели), и почти все векторные движки по умолчанию хранят каждое число как float32 — 4 байта. Вектор размерностью 384 весит 384 × 4 = 1536 байт, размерностью 1536 — уже 6144 байта. Это единственная часть расчёта, которая линейна и предсказуема без оговорок: умножили число векторов на размерность на 4 байта — получили нижнюю границу.

Индекс. Линейный перебор всех векторов на каждый запрос работает, но на миллионе записей превращается в секунды ожидания. Поэтому поверх сырых данных почти всегда стоит структура приближённого поиска ближайших соседей (ANN) — чаще всего HNSW (Hierarchical Navigable Small World), но также встречаются IVF, LSH и их варианты. Индекс — это не бесплатная надстройка: он хранит связи между точками графа, и эти связи занимают память сверх самих векторов. Насколько именно — разбираем в отдельном разделе ниже.

Метаданные и служебные структуры. У каждой точки обычно есть ID, ссылка на исходный документ, иногда произвольный payload (заголовок, права доступа, теги для фильтрации). Плюс внутренние структуры движка — сегменты, ID-маппинги, буферы. Для векторов низкой размерности (384–768) эта статья может оказаться заметной долей от общего объёма; для векторов высокой размерности (1536+) она обычно теряется на фоне самих координат.

Дальше считаем каждую статью отдельно и складываем — только так получается число, которому можно доверять при выборе сервера.

Формула наоборот: от объёма памяти к числу эмбеддингов

Стандартная формула расчёта памяти под известное число векторов выглядит так:

RAM ≈ N × D × 4 байта × K

где N — число векторов, D — размерность эмбеддинга, 4 — размер float32 в байтах, K — коэффициент накладных расходов индекса (обычно 1,3–1,7 для HNSW при типичных настройках — точное значение зависит от параметров графа, разбор ниже).

Задача «сколько влезет» — та же формула, решённая относительно N:

N ≈ RAM_доступная / (D × 4 × K)

Считается это одной строкой, без специального инструмента:

python3 -c "
ram_gb = 16          # памяти под коллекцию, ГБ (не весь сервер!)
d = 1024              # размерность модели эмбеддингов
k = 1.5               # накладные расходы HNSW
n = ram_gb * 1024**3 / (d * 4 * k)
print(f'{int(n):,} векторов'.replace(',', ' '))
"

Ключевой нюанс — ram_gb в формуле это не весь объём сервера, а память, которую вы реально готовы отдать под коллекцию. Операционная система, сам процесс движка в простое, файловый кэш, буферы на вставку и запас на скачок при массовой заливке съедают часть RAM ещё до того, как в неё попадёт первый вектор. На практике для расчёта берут 60–70% от общего объёма сервера, а не 100% — иначе первая же массовая загрузка данных упирается в потолок раньше расчётного числа.

Вот как выглядит потолок для нескольких размерностей моделей эмбеддингов при доступных 16 ГБ и коэффициенте накладных расходов 1,5:

РазмерностьПример моделиВекторов на 16 ГБ (без квантования)
384all-MiniLM-L6-v2, bge-small≈7,4 млн
768bge-base, e5-base≈3,7 млн
1024bge-large, Cohere embed-v3≈2,8 млн
1536text-embedding-3-small≈1,9 млн
3072text-embedding-3-large≈0,93 млн

Разница между первой и последней строкой — почти восьмикратная при одном и том же объёме RAM. Выбор модели эмбеддингов — это прямой выбор потолка коллекции, и об этом стоит думать до того, как коллекция выросла, а не после. Про сам выбор модели — отдельный разговор, разобранный в статье о выборе модели эмбеддингов для RAG.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Во сколько обходится HNSW сверх сырых векторов

Коэффициент K в формуле — не абстрактный запас, а конкретная структура данных. HNSW строит многоуровневый граф: каждая точка на нижнем уровне связана с 2×m соседями (по умолчанию m чаще всего 16, то есть до 32 связей), и каждая связь — это идентификатор соседней точки, обычно 4 или 8 байт в зависимости от реализации движка. Для вектора размерностью 1024 сами координаты весят 4096 байт; связи графа при m=16 добавляют порядка 32 × 4 = 128 байт на нижнем уровне плюс меньшие уровни выше — суммарно заметно меньше самих векторов, но не нулевая величина.

Параметр m в конфигурации индекса — прямой рычаг между потолком памяти и качеством поиска: выше m — больше связей на точку, точнее граф (выше recall), но каждая точка стоит дороже в памяти, и построение индекса при вставке идёт медленнее. Уменьшать m ради экономии памяти можно, но recall падает не линейно — на некоторых распределениях данных просадка становится заметной уже при переходе с 16 на 8. Проверяется это не расчётом, а прогоном собственного набора запросов с замером recall до и после изменения параметра — универсальной безопасной цифры здесь не существует, и у разных движков (Qdrant, Milvus, Weaviate, pgvector с расширением HNSW) реализация графа и его память чуть отличаются в деталях, хотя общая идея одна.

Второй параметр, ef_construction, влияет на качество построения графа при вставке, но не входит в формулу памяти напрямую — это скорее цена по времени индексации, а не по объёму RAM. Не путайте его с m, когда ищете, что уменьшить ради снижения потребления памяти.

Квантование: как раздвинуть потолок в 4–32 раза

Если посчитанный потолок меньше нужного числа документов, следующий шаг — не покупка сервера побольше, а сжатие самих векторов. Квантование переводит координаты из float32 в более компактный тип, и это прямое умножение потолка N из формулы выше.

Int8 (скалярное квантование). Каждая координата занимает 1 байт вместо 4 — потолок вырастает ровно в 4 раза при той же памяти. Работает почти всегда без заметной просадки поиска и обычно это первый шаг, который стоит попробовать вне зависимости от движка.

Бинарное квантование. Одна координата — один бит: сокращение в 32 раза против float32. Разработчики большинства движков, поддерживающих этот режим, рекомендуют его для векторов высокой размерности (от 1024 и выше) — именно там, где после огрубления координаты до знака классы всё ещё разделимы. На низкой размерности (384) бинарное квантование теряет точность заметнее и требует проверки на своих данных перед включением в проде.

Product Quantization (PQ). Компрессия настраивается явно (например, x4/x8/x16/x32) и может ужать данные сильнее бинарного варианта, но ценой декомпрессии на каждый запрос — это уже нагрузка на CPU, а не только экономия RAM, и при агрессивных настройках recall падает заметнее, чем у скалярного или бинарного вариантов.

Пересчитаем таблицу из предыдущего раздела для размерности 1536 (text-embedding-3-small) с разными режимами при тех же 16 ГБ:

РежимБайт на координатуВекторов на 16 ГБ
float32 (без квантования)4≈1,9 млн
int8 (scalar)1≈7,4 млн
binary0,125≈59 млн*

\* Цифра для бинарного квантования — расчёт по чистому объёму сжатых координат без учёта дополнительной памяти на хранение оригинальных векторов для дозачёта точности (rescore) и служебных структур графа поверх сжатого представления — на практике реальный прирост меньше формального 32-кратного, но остаётся в разы больше, чем у int8.

Важная оговорка: во многих движках при бинарном и product-квантовании часть оригинальных float32-векторов может дополнительно храниться на диске (не в RAM) для механизма rescore — повторного точного ранжирования top-кандидатов после быстрого приближённого прохода по сжатым данным. Это не отменяет выигрыша по RAM, но означает, что общий объём хранения на диске у квантованной коллекции не обязательно меньше исходного — экономится именно оперативная память, а не место на NVMe.

Компромисс: точность поиска против памяти и скорости

Каждый шаг сжатия — это не бесплатный обмен, а сделка с конкретной ценой, и цену стоит называть прямо, а не прятать за словом «оптимизация».

Recall. Приближённый поиск (ANN) в принципе не гарантирует нахождение точно ближайших соседей — только вероятностно близких, и уменьшение m в графе или огрубление координат при квантовании снижает эту вероятность дальше. Для каталога товаров с широкими категориями просадка recall на несколько процентов часто незаметна пользователю. Для юридического или медицинского поиска, где пропущенный релевантный документ — это не неудобство, а ошибка, тот же процент может быть неприемлем. Единого порога не существует — это решение зависит от домена, и его стоит принимать осознанно, а не по умолчанию.

Скорость. Экономия памяти через on-disk хранение (когда часть векторов или весь граф не резидентны в RAM, а подгружаются с диска по требованию) снижает требования к серверу, но добавляет задержку на каждое обращение к диску при обходе графа. На NVMe с низкой задержкой случайного чтения это часто остаётся в пределах десятков миллисекунд на запрос; на медленном сетевом хранилище разница ощутима. Product Quantization добавляет CPU-нагрузку на декомпрессию — при высоком QPS это может стать новым узким местом раньше, чем память.

Практический ориентир, а не рецепт. Если коллекция помещается в RAM без квантования при разумном сервере — не квантуйте превентивно, это лишняя сложность без выгоды. Если не помещается — начинайте с int8 как наименее рискованного шага, проверяйте recall на собственных запросах (не на синтетическом бенчмарке из чужой статьи — распределение ваших эмбеддингов может вести себя иначе), и только если этого мало — переходите к бинарному варианту или к on-disk хранению. Порядок важен: от дешёвого по риску шага к дорогому, а не наоборот.

Гибридный поиск — сочетание векторного и текстового индексов — отдельная тема, где чистого числа «влезет / не влезет» уже недостаточно: она разобрана в статье про то, почему одних векторов часто мало.

Считаем потолок для реальных конфигураций серверов

Соберём формулу в практическую таблицу: сколько документов (при условии один чанк ≈ один эмбеддинг) влезает на разных объёмах RAM для нескольких сценариев — размерность 768 (типичная модель среднего размера) без квантования и с int8, при коэффициенте накладных расходов HNSW 1,5 и с учётом, что под коллекцию отдаётся 65% от объёма сервера, а не всё:

RAM сервераДоступно под коллекциюБез квантования (D=768)С int8-квантованием (D=768)
8 ГБ≈5,2 ГБ≈1,2 млн≈4,7 млн
16 ГБ≈10,4 ГБ≈2,4 млн≈9,4 млн
32 ГБ≈20,8 ГБ≈4,7 млн≈18,8 млн
64 ГБ≈41,6 ГБ≈9,4 млн≈37,6 млн
128 ГБ≈83,2 ГБ≈18,9 млн≈75,4 млн

Пример расчёта количества документов из этого числа векторов: если каждый исходный документ на 5–10 страниц бьётся при чанкинге на 15–30 фрагментов (типичный диапазон при размере чанка 300–500 токенов), то 2,4 млн векторов на сервере с 16 ГБ — это порядка 100–150 тысяч документов, а не 2,4 млн. Разница между «влезет столько-то векторов» и «влезет столько-то документов» — это множитель чанкинга, и его стоит держать в голове отдельно от формулы памяти. Сам подбор размера чанка — тема статьи про размер куска при чанкинге документов; от него напрямую зависит, сколько векторов породит одна и та же база документов.

Числа в таблице — расчёт по формуле, а не результат замера на конкретном движке и конкретной версии: реальное потребление отклоняется на 10–20% в зависимости от того, Qdrant это, Milvus, Weaviate или pgvector, какая у движка версия и какие настройки сегментации. Для точного расчёта под конкретный движок Qdrant есть отдельная статья с расчётом RAM по векторам для Qdrant — там формула та же по сути, но с параметрами именно этого движка. Общий же порядок цифр из таблицы выше работает как ориентир при выборе конфигурации сервера до того, как выбран конкретный движок.

Если коллекция уже растёт быстрее, чем предсказывает формула, стоит проверить не только память, но и то, не пора ли вообще менять подход к хранению — от одного узла к шардированию — это разобрано в статье о пределе размера базы, после которого пора шардировать.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Формула одна и та же для всех векторных баз — Qdrant, Milvus, Weaviate, pgvector?

Общий принцип (векторы × размерность × 4 байта × коэффициент индекса) одинаковый, потому что физика хранения float32-координат не зависит от движка. Конкретный коэффициент накладных расходов, реализация квантования и наличие on-disk режима отличаются в деталях между движками — расчёт по общей формуле даёт верную оценку порядка величины для выбора сервера, а точную цифру для конкретного движка стоит сверить по его документации.

Что произойдёт, если превысить расчётный потолок?

Зависит от движка и настроек, но чаще всего — не плавная деградация, а резкий обрыв: ядро Linux останавливает процесс через OOM killer, когда физической памяти не хватает на выделение. Данные на диске обычно сохраняются, если хранилище смонтировано постоянно, но индекс приходится перестраивать после перезапуска, а само событие происходит без предупреждения — метрики выглядят нормально до последней секунды.

Можно ли держать часть коллекции в RAM, а часть на диске?

Да, у большинства современных движков есть режим, при котором горячие (часто запрашиваемые) сегменты остаются в памяти, а холодные подгружаются с диска по требованию. Это снижает требования к RAM ценой задержки на обращения к холодным данным — разумный вариант, если часть коллекции запрашивается сильно реже остальной.

Стоит ли сразу включать квантование, чтобы не думать о потолке?

Не всегда. Если коллекция комфортно помещается в память сервера без квантования, включать его превентивно — это лишний риск потери точности без реальной выгоды. Квантование стоит включать, когда посчитанный потолок реально мешает — а не по умолчанию «на всякий случай».

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →