Запуск векторной базы данных на собственном VPS
Узнайте, как разместить векторную БД на одном сервере с приложением. Сравнение pgvector, Qdrant и Chroma, расчет объема RAM и влияние на задержку при поиске векторов.
Во что на самом деле обходится векторная база данных на VPS
Запуск векторной базы данных на VPS (виртуальном выделенном сервере) устраняет проблему, решение которой продают все провайдеры управляемых сервисов. Ваше приложение и индекс находятся на одной машине, поэтому поисковый запрос проходит через loopback-сокет, а не через сеть. Остаются расходы, которые всегда были основными: преобразование текста в векторы. За этим стоят еще два фактора: время на построение индекса и объем оперативной памяти, который индекс занимает в процессе работы.
Это меняет приоритеты при принятии решений. Регион и время кругового обхода (round trip) до конечной точки перестают быть вашей заботой. Вашей заботой становится произведение количества векторов, их размерности и 4 байт, так как именно оно определяет, поместится ли индекс в объем памяти, который вы арендуете ежемесячно.
Куда на самом деле уходят миллисекунды на одном узле
Проследим путь одного запроса на сходство через стек, размещенный на собственном сервере.
- Текст запроса преобразуется в вектор с помощью модели эмбеддингов. На CPU это занимает от десятков до сотен миллисекунд для короткой строки. На GPU — единицы миллисекунд.
- Вектор отправляется в хранилище. При передаче через loopback TCP или Unix domain socket это занимает доли миллисекунды.
- Хранилище обходит индекс и возвращает ближайшие строки.
- Ваш код считывает найденный текст и формирует промпт.
Шаг 1 обычно является самым затратным в этом списке. Шаг 2 — это то, в чем соревнуются облачные провайдеры, но на одном узле он практически отсутствует. Не пытайтесь угадать распределение времени. Замерьте оба этапа на собственном сервере.
curl http://127.0.0.1:11434/api/embed -s -o /dev/null \
-w 'embed: %{time_total}s\n' \
-d '{"model": "nomic-embed-text", "input": "how do I rotate the api key"}'Затем выполните \timing on в psql перед поисковым запросом. Если первая команда выводит embed: 0.184312s, а psql отвечает Time: 4.201 ms, значит, настройка индекса — неверная задача: ваша задержка вызвана моделью эмбеддингов. Запуск модели эмбеддингов локально с помощью Ollama переносит шаг 1 на тот же CPU, где выполняются шаги 2 и 3, поэтому обе части конкурируют за одни и те же ядра и оперативную память. Цикл приема и извлечения данных, обернутый вокруг этого хранилища, описан в руководстве по RAG-конвейеру на собственном сервере. RAG — это генерация с дополнением извлеченными данными: вы ищете по своим документам и вставляете лучшие совпадения в промпт.
Сканирование всех векторов при объеме до ста тысяч
Исчерпывающее сканирование сравнивает запрос с каждым сохраненным вектором. Полнота (recall) в этом случае идеальна по определению. Индекс и этап построения не требуются, а данные всегда остаются актуальными.
Арифметика показывает, когда этот метод перестает быть эффективным. Сканирование считывает n * d * 4 байт на запрос, где n — количество векторов, а d — размерность. Для 100 000 векторов размерностью 768 это составляет 307 МБ на запрос, что современный процессор считывает за несколько десятков миллисекунд. При 5 миллионах векторов объем достигает 15 ГБ на запрос, что уже нельзя назвать быстрым запросом.
Поэтому храните векторы в SQLite, а сравнение выполняйте в NumPy.
import sqlite3, numpy as np
db = sqlite3.connect("docs.db")
db.execute("CREATE TABLE IF NOT EXISTS docs (id INTEGER PRIMARY KEY, body TEXT, vec BLOB)")
def add(body, vec):
v = np.asarray(vec, dtype=np.float32)
v /= np.linalg.norm(v)
db.execute("INSERT INTO docs (body, vec) VALUES (?, ?)", (body, v.tobytes()))
db.commit()
def search(query_vec, k=5):
rows = db.execute("SELECT id, body, vec FROM docs").fetchall()
mat = np.frombuffer(b"".join(r[2] for r in rows), dtype=np.float32).reshape(len(rows), -1)
q = np.asarray(query_vec, dtype=np.float32)
q /= np.linalg.norm(q)
scores = mat @ q
return [(rows[i][0], rows[i][1], float(scores[i])) for i in np.argsort(-scores)[:k]]Обе стороны приводятся к единичной длине, поэтому скалярное произведение является косинусным сходством, и более высокий результат означает более точное совпадение. Загружайте mat один раз при запуске, а не при каждом запросе, чтобы чтение из SQLite полностью исключалось из критического пути.
Замерьте время на своей машине, прежде чем отказываться от этого подхода.
import time
t = time.perf_counter()
search(q)
print(f"{(time.perf_counter() - t) * 1000:.1f} ms")Честные ограничения: это один процесс, удерживающий всю матрицу в оперативной памяти; он не поддерживает фильтрацию по метаданным и не рассчитан на параллельную запись. Если одна из этих причин вызывает у вас неудобства, переходите на другие решения. Сама по себе SQLite — это серьезное серверное хранилище, что подробно описано в руководстве по использованию SQLite в продакшене. Если ваша реальная рабочая нагрузка заключается в сканировании столбцов, а не в выдаче строк, то сравнение DuckDB и SQLite будет более полезным материалом для чтения.
pgvector при наличии работающего Postgres
Если ваше приложение уже использует базу данных Postgres, pgvector создает минимальную дополнительную поверхность для обслуживания. Это расширение, а не отдельный сервис. Векторы хранятся в обычной таблице рядом с описываемыми ими строками, поэтому фильтрованный поиск выполняется с помощью предложения WHERE, что избавляет от необходимости синхронизировать вторую систему.
В Ubuntu 24.04 пакет доступен в компоненте universe.
sudo apt update
sudo apt install -y postgresql-16-pgvector
sudo -u postgres psql -d yourdb -c 'CREATE EXTENSION vector;'По состоянию на октябрь 2026 года в этом пакете содержится версия pgvector 0.6.0, которая значительно отстает от актуальной версии разработчиков. В частности, для итеративного сканирования индексов требуется версия 0.8, поэтому устанавливайте пакеты из официального репозитория проекта PostgreSQL.
sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
sudo apt install -y postgresql-17-pgvectorЗамените 17 на мажорную версию вашего сервера, которую выводит команда sudo -u postgres psql -tAc 'SHOW server_version'. Если установить пакет расширения, собранный для другой мажорной версии, CREATE EXTENSION завершится ошибкой, так как Postgres ищет файлы только в каталоге share той версии, которая запущена в данный момент:
ERROR: could not open extension control file "/usr/share/postgresql/16/extension/vector.control": No such file or directoryСхема данных представляет собой обычный SQL с одним новым типом.
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
doc_id bigint NOT NULL,
body text NOT NULL,
embedding vector(768)
);
SELECT id, body FROM chunks
ORDER BY embedding <=> '[0.013, -0.021, 0.004]'
LIMIT 5;<=> — это косинусное расстояние, <-> — расстояние L2 (евклидово), а <#> — отрицательное скалярное произведение. Используйте тот вариант, на котором обучалась ваша модель эмбеддингов. Если выбрать неверный тип, ошибок не возникнет, но качество результатов незаметно снизится.
Без индекса такой запрос выполняет точный поиск по каждой строке. Это аналог метода «грубой силы» в Postgres, который обеспечивает такую же идеальную полноту поиска. Увеличение max_parallel_workers_per_gather позволяет задействовать больше ядер процессора. Сделайте это в первую очередь, а индексацию настройте позже: так у вас будет базовый показатель полноты для оценки эффективности индекса.
Qdrant: когда индекс превышает возможности базы данных
Qdrant — это специализированное векторное хранилище, написанное на Rust. Оно оправдывает свое применение, когда индекс становится настолько большим, что его построение не должно конкурировать за ресурсы с основной базой данных Postgres, или когда требуется фильтрация полезной нагрузки и встроенная скалярная или продуктовая квантование, в то время как pgvector поддерживает только половинную точность и бинарное квантование.
docker run -d --name qdrant \
-p 127.0.0.1:6333:6333 -p 127.0.0.1:6334:6334 \
-e QDRANT__SERVICE__API_KEY="$(openssl rand -hex 32)" \
-v "$(pwd)/qdrant_storage:/qdrant/storage:z" \
qdrant/qdrantПорт 6333 обслуживает REST API и панель управления по адресу /dashboard, а порт 6334 — gRPC. При работе на публичном VPS важны две детали. В документации Qdrant указано, что по умолчанию сервис запускается «без шифрования и аутентификации», а конфигурация -p 6333:6333 из руководства по быстрому старту привязывается ко всем сетевым интерфейсам. Docker публикует этот порт в обход правил ufw, так как он создает собственные правила пересылки трафика. Выполняйте привязку к 127.0.0.1 и обязательно установите API-ключ. Экземпляр Qdrant, доступный по публичному IP без ключа, делает ваши документы общедоступными.
curl -s http://127.0.0.1:6333/collections -H "api-key: $QDRANT_API_KEY"Корректный ответ выглядит как {"result":{"collections":[]},"status":"ok","time":0.00002}. Получение {"status":{"error":"Unauthorized"}} означает, что имя заголовка или сам ключ указаны неверно, а отсутствие ответа свидетельствует о том, что контейнер не запущен или привязан к другому адресу. Вопрос о том, подходит ли данный контейнер для вашего сервера, является стандартным для stateful-сервисов, поэтому здесь в полной мере применимо сравнение Docker и установки базы данных на хост.
Chroma и его назначение
Chroma — это кратчайший путь от отсутствия чего-либо до работающей демонстрации поиска.
pip install chromadb
chroma run --path /srv/chromaОн работает на порту 8000, и chromadb.HttpClient(host="localhost", port=8000) подключается к нему. Chroma поставляется с функцией встраивания (embedding) по умолчанию, поэтому для первого прототипа не требуется отдельный сервер моделей.
Будьте честны в отношении этого компромисса. Chroma удобен тем, что скрывает решения, о которых идет речь в этом руководстве: какую метрику расстояния выбрать и сколько оперативной памяти потребуется для хранения результата. Это оправдано для прототипа, но не подходит для системы, из-за которой вас будут вызывать на работу. Если ваши данные уже находятся в Postgres, перенос их в Chroma добавляет дополнительный процесс и проблему синхронизации, чтобы решить задачу, которой нет у pgvector.
Сколько оперативной памяти потребуется для индекса
Начните с исходных векторов, так как это минимальный объем, который невозможно уменьшить настройками.
bytes = number_of_vectors * dimensions * 4Четыре байта — это один 32-битный float на каждое измерение. В руководстве Qdrant по коллекциям, которые не помещаются в оперативную память, рекомендуется использовать множитель 1.5 для учета метаданных, индексов и временных сегментов, создаваемых в процессе оптимизации:
memory_size = number_of_vectors * vector_dimension * 4 bytes * 1.5Ниже приведена эта формула, примененная к одному миллиону векторов с размерностями, которые выдают реальные модели эмбеддингов.
Данные, на которых построен график
[
{
"label": "384 dims",
"raw_gib": 1.43,
"with_overhead_gib": 2.15
},
{
"label": "768 dims",
"raw_gib": 2.86,
"with_overhead_gib": 4.29
},
{
"label": "1024 dims",
"raw_gib": 3.81,
"with_overhead_gib": 5.72
},
{
"label": "1536 dims",
"raw_gib": 5.72,
"with_overhead_gib": 8.58
},
{
"label": "3072 dims",
"raw_gib": 11.44,
"with_overhead_gib": 17.17
}
]Это результаты вычислений по формуле в гибибайтах (GiB), а не фактические измерения. Воспринимайте их как объем памяти, который необходимо зарезервировать. Модель с 768 измерениями, такая как nomic-embed-text, для миллиона фрагментов потребует около 4.29 GiB, что укладывается в план с 8 ГБ ОЗУ с запасом для Postgres. Тот же корпус данных при 3072 измерениях потребует 17.17 GiB, что уже не поместится.
Главный рычаг управления — это первый столбец таблицы, а не последний. Уменьшение размерности вдвое сокращает объем занимаемой памяти в два раза на всех этапах. Модель с 768 измерениями, которая показывает чуть худшие результаты в публичных рейтингах, часто является более рациональным инженерным выбором для VPS. Тип halfvec в pgvector позволяет хранить 16-битные float, что еще раз делит объем памяти пополам, а индексация выполняется через выражение:
CREATE INDEX ON chunks USING hnsw ((embedding::halfvec(768)) halfvec_cosine_ops);Ограничение, которое нужно учитывать перед выбором модели. Тип vector в pgvector поддерживает до 16 000 измерений, но его индексы HNSW и IVFFlat работают только с 2 000. Приведение типа halfvec позволяет увеличить это число до 4 000. При превышении этого значения документация pgvector рекомендует бинарную квантование: индексируйте выражение binary_quantize(embedding)::bit(N) с помощью bit_hamming_ops, что поддерживает до 64 000 измерений, а затем выполняйте переранжирование лучших кандидатов относительно исходных векторов.
Что означают m и ef_construction во время сборки
HNSW (hierarchical navigable small world) — это индекс, который используют и pgvector, и Qdrant. Он представляет собой многоуровневый граф. Каждый вектор является узлом со связями с соседними узлами, и поиск перемещается по этим связям в сторону запроса, вместо того чтобы считывать всё содержимое.
m определяет количество связей, которое сохраняет каждый узел. В документации Faiss объем памяти для HNSW указан как (d * 4 + m * 2 * 4) байт на вектор, и рекомендуется поддерживать m в диапазоне от 4 до 64. Запустите это для 768 измерений на миллионе векторов.
Данные, на которых построен график
[
{
"label": "m = 8",
"link_bytes_per_vector": 64,
"total_gib": 2.92
},
{
"label": "m = 16 (default)",
"link_bytes_per_vector": 128,
"total_gib": 2.98
},
{
"label": "m = 32",
"link_bytes_per_vector": 256,
"total_gib": 3.1
},
{
"label": "m = 64",
"link_bytes_per_vector": 512,
"total_gib": 3.34
}
]Полезным открытием является размер этой разницы. Переход от значения по умолчанию m = 16 к m = 64 увеличивает количество связей с 128 до 512 байт на вектор, что дает дополнительные 384 байта к 3072 байтам данных вектора, поэтому общий объем меняется с 2.98 ГБ до 3.34 ГБ. Это около 12 процентов. При таких размерностях m не является тем параметром, который расходует вашу память. Память расходуют векторы.
На что действительно влияет m, так это на время сборки и время вставки, поскольку размещение узла означает поиск и установку связей с таким количеством соседей. ef_construction — это размер списка кандидатов, который рассматривает сборщик при размещении каждого узла. Увеличение этого значения создает более качественный граф и замедляет сборку, при этом размер готового индекса не меняется вовсе.
SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 7;
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);maintenance_work_mem — это настройка, которая определяет, займет ли сборка минуты или часы, поскольку pgvector собирает граф в оперативной памяти, если он туда помещается. Когда память заканчивается, pgvector сообщает об этом:
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 100000 tuples
DETAIL: Building will take significantly more time.
HINT: Increase maintenance_work_mem to speed up builds.Это уведомление — самая полезная строка, которую выводит pgvector. Оно означает, что сборка перешла на гораздо более медленный путь выполнения, поэтому отмените её, увеличьте значение настройки выше рассчитанного вами объема ОЗУ и начните заново. Отслеживайте прогресс из второго сеанса:
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS pct
FROM pg_stat_progress_create_index;HNSW сообщает initializing, а затем loading tuples. Если сборка долгое время находится на низком проценте выполнения при неактивном диске, это проблема maintenance_work_mem, а не зависший запрос.
Два факта, которые стоит учитывать при планировании. В README для pgvector указано, что HNSW «имеет более медленное время сборки и использует больше памяти», чем IVFFlat, но взамен обеспечивает лучший баланс между скоростью и полнотой поиска (recall). Кроме того, HNSW можно создать на пустой таблице, в то время как для IVFFlat сначала необходимо выполнить k-means на репрезентативных данных, поэтому создание IVFFlat на пустой таблице дает низкую полноту поиска. В новой схеме HNSW — это тот индекс, который можно создать заранее.
ef_search: параметр, настраиваемый после сборки
m и ef_construction фиксируются в индексе при его создании. ef_search — нет. Этот параметр определяет количество кандидатов, которое поиск сохраняет при обходе графа, и его можно изменять для каждой сессии или запроса.
SET hnsw.ef_search = 100;
SELECT id, body FROM chunks ORDER BY embedding <=> $1 LIMIT 5;Значение по умолчанию — 40. Увеличение этого параметра повышает полноту поиска (recall), но одновременно увеличивает задержку (latency). Уменьшение приводит к снижению обоих показателей. Это единственный способ управления полнотой поиска без пересборки индекса, поэтому настраивайте его на фиксированном наборе запросов с известными правильными ответами и останавливайтесь, когда полнота перестает расти.
Важный нюанс. ef_search плохо сочетается с избирательным условием WHERE, так как индекс возвращает фиксированное количество кандидатов, а фильтр применяется уже после этого. Фильтр, отсеивающий большинство строк, может оставить вас с количеством результатов меньше LIMIT, даже если подходящие строки присутствуют в таблице. В pgvector 0.8 эта проблема решена с помощью итеративного сканирования:
SET hnsw.iterative_scan = relaxed_order;В этом случае индекс сканируется повторно для поиска дополнительных кандидатов, пока не будет достигнут лимит, вплоть до значения hnsw.max_scan_tuples, которое по умолчанию равно 20000. strict_order сохраняет точный порядок по расстоянию, но требует больше ресурсов. Именно этой функции нет в пакете версии 0.6.0 для Ubuntu, и пропуск строк при использовании фильтра — это первый признак того, что вы столкнулись с данным ограничением.
Почему индекс должен помещаться в RAM
Поиск HNSW — это обход графа. Каждый переход считывает узел, расположенный в памяти независимо от предыдущего, поэтому характер обращений близок к случайному, и механизм упреждающего чтения (read-ahead) не помогает. Пока граф находится в RAM, каждый переход — это обращение к оперативной памяти. Если граф не помещается в RAM, переход может превратиться в чтение с диска, и поиск, затрагивающий несколько сотен узлов, превращается в несколько сотен операций чтения.
В документации Qdrant это сформулировано так: «если вы храните в RAM в два раза меньше векторов, задержка поиска примерно удвоится». Учитывайте это утверждение при планировании.
Если индекс заведомо не помещается в память, любой вариант — это компромисс, на который вы должны пойти осознанно.
- Используйте memory-mapping для векторов, чтобы операционная система кэшировала «горячие» страницы, а «холодные» оставляла на диске. Для приемлемой работы под этим должен находиться быстрый накопитель NVMe.
- Применяйте квантование, сохраняя каждое измерение в виде одного байта вместо четырех. Это сокращает объем данных вектора в четыре раза при небольшом, измеримом снижении точности (recall).
- Выполняйте приведение к
halfvecв pgvector, что уменьшает объем данных вдвое с меньшими потерями точности, чем при однобайтовом квантовании. - Используйте для эмбеддингов модель меньшего размера. Это самое дешевое решение, которое часто игнорируют, так как оно требует повторного создания эмбеддингов для всего корпуса данных.
Типичные сбои и сообщения об ошибках
could not open extension control file. Пакет pgvector для используемой мажорной версии Postgres не установлен. Выведите версию с помощью sudo -u postgres psql -tAc 'SHOW server_version' и установите соответствующий postgresql-NN-pgvector.
ERROR: expected 768 dimensions, not 1536. Тип столбца и модель не согласуются. Вы сменили модели встраивания, но не выполнили повторное встраивание. Частичного исправления здесь нет, так как векторы от двух разных моделей вообще не сопоставимы, поэтому каждую строку необходимо перегенерировать.
Запрос выполняется медленно, и EXPLAIN показывает последовательное сканирование (sequential scan). Класс операторов индекса и оператор запроса не совпадают. vector_cosine_ops поддерживает только <=>. Запустите EXPLAIN ANALYZE для запроса и найдите Index Scan using ... on chunks. Если вместо этого вы видите Seq Scan on chunks, пересоздайте индекс с классом операторов, который соответствует оператору, используемому в вашем запросе.
Строк меньше, чем LIMIT, и присутствует предложение WHERE. Это ловушка фильтрации, описанная выше. Увеличьте hnsw.ef_search или перейдите на pgvector 0.8 и настройте hnsw.iterative_scan.
Построение индекса завершается исчезновением процесса без ошибки в psql. Параметр maintenance_work_mem установлен на значение, занимающее значительную часть памяти сервера, в то время как shared_buffers и ваше приложение также требуют память, что приводит к срабатыванию OOM-killer ядра. sudo dmesg -T | grep -i 'killed process' показывает строку с упоминанием postgres. Уменьшите значение настройки или создайте индекс на более мощном плане и восстановите дамп.
Выбор решения
Если вы уже используете Postgres и у вас менее нескольких миллионов векторов, применяйте pgvector. Индекс располагается рядом с данными, фильтрация выполняется через предложение WHERE, а существующая система резервного копирования уже охватывает эти данные. Если индекс становится настолько большим, что требует собственного ограничения по памяти, или вам необходима сложная фильтрация полезной нагрузки, запустите Qdrant параллельно и будьте готовы к обслуживанию второго сервиса.
При объеме менее ста тысяч векторов оцените производительность полного перебора (brute-force scan), прежде чем что-либо устанавливать. Исчерпывающий поиск с идеальной полнотой и отсутствием этапа построения индекса не является компромиссом при таком размере данных. Это оптимальное решение, а переход к аппроксимированному индексу означает дополнительные затраты на настройку и нагрузку на оперативную память ради экономии миллисекунд, которые вы и так не тратили.
FAQ
Нужна ли мне специализированная векторная база данных или достаточно Postgres?
Если ваши данные уже находятся в Postgres, расширения pgvector будет достаточно дольше, чем предполагает большинство сравнений. Оно хранит векторы в обычном столбце, поэтому поиск с фильтрацией — это просто предложение WHERE, а ваши существующие резервные копии охватывают и индекс. Переходите на специализированное хранилище, например Qdrant, когда векторная нагрузка требует собственного ограничения по памяти или когда вам нужны фильтрация полезной нагрузки и скалярная или продуктовая квантование, которые не предоставляет pgvector.
Сколько векторов может вместить один VPS?
Рассчитайте это, а не гадайте, используя number_of_vectors * dimensions * 4 bytes * 1.5. Один миллион векторов размерностью 768 занимает примерно 4.3 GiB, поэтому тарифный план на 8 GB вместит их с запасом для Postgres. Один миллион векторов размерностью 3072 занимает примерно 17 GiB и требует гораздо более мощного плана. Число, которое сильнее всего влияет на это — размерность вашей модели эмбеддингов, поэтому выбирайте модель с учетом затрат на память.
Почему мой векторный поиск работает медленно, если индекс находится на той же машине?
Сеть не является вашей проблемой на одном узле, поэтому обратите внимание на два других фактора. Во-первых, замерьте время выполнения вызова эмбеддинга отдельно, так как генерация вектора запроса на CPU часто занимает гораздо больше времени, чем сам поиск. Во-вторых, проверьте, находится ли индекс в RAM. Поиск HNSW совершает случайные переходы по графу, поэтому, как только граф вытесняется на диск, каждый переход может превратиться в чтение с диска. Согласно рекомендациям Qdrant, сокращение количества векторов в RAM вдвое примерно удваивает задержку поиска.
Стоит ли вообще строить индекс HNSW?
Не стоит, если векторов меньше ста тысяч. Полное сканирование считывает n * d * 4 байт за запрос, что составляет 307 MB при 100,000 векторов размерностью 768. Современный CPU обрабатывает такой объем за десятки миллисекунд с идеальной полнотой и без этапа построения индекса. Сначала измерьте скорость сканирования на вашем оборудовании. Стройте индекс, когда измеренное время сканирования действительно слишком велико, а не потому, что так сказано в статье с бенчмарками.
Во что мне на самом деле обходится увеличение m?
В время построения и время вставки, гораздо больше, чем в память. При размерности 768 увеличение значения с m = 16 по умолчанию до m = 64 увеличивает размер ссылок графа со 128 до 512 байт на вектор. Это дополнительные 384 байта к 3072 байтам векторных данных, поэтому общий объем памяти возрастает примерно на 12 процентов. Однако каждая вставка при этом должна находить и связывать в четыре раза больше соседей. Сначала настройте ef_search, так как его изменение ничего не стоит и не требует перестроения индекса.