29 октября 2026 года Евгений Патлан выложил на блоге Percona один прогон векторного поиска. В шесть баз загрузили 990 000 эмбеддингов OpenAI, у каждого 1 536 измерений, и в каждой собрали свой индекс ближайших соседей (HNSW). Мерили пять вещей: сколько ждать, пока по индексу уже можно искать, сколько он занимает места и памяти, как полнота (recall) связана со скоростью, что будет на нескольких клиентах сразу и что останется, если переписать 10% корпуса. Хост один, форма контейнера одна, набор запросов один.
Разброс по сборке вышел шире, чем в Percona ждали. До первого запроса одна база дошла за 1,9 минуты, другая за 6,7 часа. Индекс занял от 3,9 до 15,1 ГиБ, и самый маленький собирался дольше всех. На recall 0,99 поле, по их оценке, сжимается: четыре из шести укладываются в двукратный разброс. На 32 клиентах оно снова расходится. pgvector даёт в 21,7 раза больше запросов, чем на одном клиенте. Три движка на InnoDB остаются примерно на том же уровне.
Это один прогон на одной машине. Percona убрала панель эффективности для MariaDB 11.8, MariaDB 12.3 и AliSQL: стенд перестал делить часть окон на число клиентов. Код, конфиг и сырые записи лежат в Percona-Lab/vector-bench. Прогон подписан full-20260825, профиль tuned-complete.
Xeon Gold 6230 и шесть сборок
Машина не похожа на VPS, где крутится сайт. Intel Xeon Gold 6230, 2,10 ГГц, 80 логических ядер, AVX-512, 187,5 ГБ памяти. База работает в одном контейнере: ядра 0-63, лимит памяти 120,8 ГБ, /dev/shm 13 ГБ. Стенд в другом: ядра 64-79 и 8 ГБ. Так несколько сотен мегабайт NumPy с набором запросов не попадают в лимит сервера и не портят замер памяти. Стенд сервер не настраивает. Это делает оркестратор, и тот же бюджет записан в манифест рядом с результатами.
Корпус называется dbpedia-openai-1000k-angular: 990 000 векторов float32, косинусная близость. HNSW, M = 16 у всех, k = 10. Проход по точности гоняет ef_search от 10 до 800. Проход по операциям фиксирует ef_search 100. Клиентов берут 1, 2, 4, 8, 16 и 32, на каждую точку 60 секунд и три повтора. Фильтр: 500 запросов на точку, когда условию удовлетворяют 10% строк и когда 1%. Потом удаляют и заново вставляют 10% корпуса.
Бюджет общий. Буферный пул InnoDB 42,3 ГБ. Для сборки pgvector maintenance_work_mem 12,1 ГБ. Потоков ввода-вывода 8, потоков сборки 8. Двум движкам с кэшем графа дали по 30,2 ГБ этого кэша. У обоих значение по умолчанию 16 МиБ, и мерить его значило бы оценивать настройку, которую сами авторы индекса просят поменять. Потоки зажали с 64: дальше они уже мешают друг другу, а большое число вовсе останавливало запись в уже заполненный индекс. /dev/shm подняли с 8 до 13 ГБ, чтобы параллельная сборка pgvector удержала сегмент размером с maintenance_work_mem.
Четыре движка собраны из тега, RelWithDebInfo, флаг -march=x86-64-v3. Два поставлены пакетами Percona. Смешивать этот флаг между движками не стали: иначе сравнивались бы компиляторы, а не базы. Полноту считают как в ann-benchmarks, по порогу расстояния, чтобы цифры стояли рядом с уже опубликованными результатами ANN. Для запроса с фильтром правильный ответ пересчитывают полным перебором по строкам, которые условие пропустило. Иначе все движки оценивались бы по чужим соседям.
- MariaDB 11.8.8, индекс MHNSW, сборка из тега.
- MariaDB 12.3.2, MHNSW, из тега.
- AliSQL 8.0.44-2, индекс VIDX, тоже InnoDB, из тега.
- PostgreSQL с pgvector 0.8.6, HNSW, из тега.
- Valkey 9.1.1 с valkey-search, HNSW, пакеты Percona.
- Percona Search for MongoDB 1.70.3-1 поверх PSMDB 8.3.8-2, HNSW, пакеты Percona.
Percona Search для MongoDB помечен как техническое превью. В документации сказано, что для продакшена он не предназначен. Цифры сняты со скалярно квантованного индекса: MongoDB советует его, когда векторный индекс больше 3 ГБ. Здесь он больше.
От 1,9 минуты до 6,7 часа
MHNSW и VIDX дописывают граф на каждом INSERT. Отдельной фазы «собрать индекс» у них нет: загрузка и есть сборка. Поэтому у MariaDB и AliSQL в таблице сборки стоит «внутри загрузки».
- Percona Search: загрузка 14 с, сборка 100 с, до запроса 1,9 минуты, 69 018 строк/с. Индекс 15,1 ГиБ, таблица 5,8 ГиБ, пик RSS 48,8 ГиБ.
- Valkey: 33 с и 226 с, всего 4,3 минуты, 30 170 строк/с. Индекс 11,3 ГиБ, таблица 6,9 ГиБ, RSS 14,3 ГиБ.
- PostgreSQL: COPY 26,9 минуты плюс 14,0 минут параллельной сборки на 8 воркерах, вместе 40,9 минуты, 613 строк/с. Индекс и таблица по 7,6 ГиБ, RSS 43,5 ГиБ.
- MariaDB 11.8: 3,6 часа, 75,9 строк/с. Индекс 3,9 ГиБ, таблица 14,3 ГиБ, RSS 28,6 ГиБ.
- MariaDB 12.3: 5,4 часа, 50,7 строк/с. Индекс 3,9 ГиБ, таблица 14,4 ГиБ, RSS 27,0 ГиБ.
- AliSQL: 6,7 часа, 41,0 строк/с. Индекс 3,9 ГиБ, таблица 10,5 ГиБ, RSS 21,9 ГиБ.
Сырые float32 сами по себе занимают 5,7 ГиБ. Индекс MariaDB ужимается до 3,9 ГиБ. В этом прогоне это единственное место, где MariaDB впереди, и оно решает, если индекс должен помещаться в память узла. Таблица при этом толстая: 14,3 ГиБ против 5,8 ГиБ у Percona Search. Скорость загрузки другая: 41-76 строк в секунду у тройки на InnoDB, 613 у pgvector и 30 170 у Valkey.
Смена модели эмбеддингов для Percona выглядит как окно переиндексации: полдня на pgvector и выходные на AliSQL.
Recall 0,99 на одном клиенте
Ниже лучшая скорость, у которой измеренный recall ещё проходит порог. Один клиент, запросов в секунду. Valkey даёт 1 191 при пороге 0,90, 825 при 0,95 и 264 при 0,99. PostgreSQL: 679, 462 и 210. MariaDB 12.3: 475, те же 475 и 277 на самом жёстком пороге. AliSQL: 424, 424 и 126. MariaDB 11.8: 415, 415 и 46. Percona Search: 138, 124 и 88.
У MariaDB и AliSQL число повторяется в двух колонках, потому что самая быстрая настройка уже даёт recall от 0,956 до 0,989. Она закрывает оба порога сразу. Повтор в таблице как раз про это.
На пороге 0,99 быстрее всех MariaDB 12.3: 277 запросов в секунду. Это в шесть раз больше, чем 46 у 11.8, при сборке на 50% длиннее, 5,4 часа против 3,6. Percona объясняет разницу графом, а не более быстрым поиском по тому же графу. Если нужна полнота выше 0,99, смотреть стоит 12.3, а не оставаться на 11.8 ради более короткой загрузки.
Подъём порога с 0,90 до 0,95 забирает у pgvector и Valkey около трети скорости. У MariaDB и AliSQL не забирает ничего: их быстрая точка и так выше обоих порогов. Ни MHNSW, ни VIDX не дают ручки, которая при фиксированном M меняет полноту на скорость. У pgvector и Valkey есть и ef_construction, и ручка на время запроса.
Одна точка AliSQL в прогоне не объясняется. При M = 16 скорость растёт с 2,6 до 4,4 запроса в секунду между ef_search 400 и 800. Больше работы на том же индексе быстрее быть не может. Рядом обрыв задержки: медиана 6,6 мс при ef_search 80, 50,8 мс при 120 и 384 мс дальше. Это повторилось на трёх прогонах, двух размерах корпуса и обеих степенях графа, без посторонней нагрузки на хосте.
32 клиента и блокировка интерпретатора
Дальше ef_search зафиксирован на 100. Valkey растёт с 470 запросов в секунду на одном клиенте до 5 683 на тридцати двух, p99 10,2 мс. Внутри движка в среднем 30,7 запроса из 32. PostgreSQL растёт с 221 до 4 798, p99 10,8 мс, внутри 31,9 из 32. Percona Search с 168 до 1 844, p99 43,5 мс, внутри 28,8 из 32. Здесь ось клиентов значит то, что на ней написано.
Тройка на InnoDB почти плоская. AliSQL: 148 на одном клиенте и 127 на тридцати двух, p99 1 494 мс, внутри 16,9 из 32. MariaDB 11.8: 165 и 148, p99 1 364 мс, внутри 14,4. MariaDB 12.3: 199 и 210, p99 1 043 мс, внутри 7,1. «В работе» у Percona это скорость, умноженная на медианную задержку. По закону Литтла так получается среднее число запросов внутри движка за окно.
Часть клиентов ждала не базу. Параллельные клиенты стенда были потоками одного интерпретатора Python, у каждого своё соединение. MariaDB Connector/C держит блокировку интерпретатора всё время ожидания сокета. Поздний короткий прогон показал это прямо: 4 клиента, маленький корпус, MariaDB отдала 2 927 запросов в секунду при медиане 0,325 мс. Это один запрос в полёте.
Стенд теперь считает «в работе» на каждом окне и отказывается делить скорость на число клиентов, если внутри меньше половины. На этом прогоне отказ падает на MariaDB 11.8 при 16 и 32 клиентах, на MariaDB 12.3 при 2, 16 и 32 и на AliSQL при 2. Шесть точек из тридцати шести. У pgvector, Valkey и Percona Search не отказано ни в одной. Насыщенный сервер так не отбрасывается: настоящая очередь поднимает всю задержку, включая медиану, число запросов внутри остаётся на числе клиентов, а скорость перестаёт расти.
После отказа остаётся вот что. Скорость не выросла, медиана выросла примерно в двадцать раз. У MariaDB 11.8 медиана ушла с 4,6 мс до 97 мс. У AliSQL с 5,1 мс до 133 мс при той же плоской скорости. Больше работы, та же скорость, задержка в двадцать раз выше. Percona относит эту очередь на сторону сервера. Клиент, который ещё ждёт блокировку интерпретатора, запрос не отправил и в эти перцентили не входит. Три движка измерены на 7-17 одновременных запросах, не на 32.
Три плоских движка это три базы на InnoDB. И они же три базы, которые стенд гонял через MariaDB Connector/C. Остальные идут через psycopg3, PyMongo и клиент Valkey. Две переменные, три движка, один рисунок. Разделить их этот прогон не может, профилирования нет. MHNSW и VIDX писали разные команды, поэтому интересная половина здесь как раз движок хранения.
На одном клиенте есть ещё поправка, и она про задержку, не про скорость. pgvector, Valkey и Percona Search проводят внутри вызова запроса 99-101% окна. MariaDB 11.8, 12.3 и AliSQL проводят 74-75%. Около 1,5 мс на запрос на этом пути принадлежит стенду, не серверу. Сравнивать медианы между двумя группами в лоб уже нельзя. Запросы в секунду эта добавка не двигает.
Фильтр, который оставляет 1% строк
Все шесть отвечают на один вопрос: ближайшие соседи вектора среди строк, которые прошли обычное условие. Способов три, и они расходятся, когда условие становится жёстче. Запросов на точку 500, клиент один.
На 10% отобранных строк Valkey планирует запрос отдельно: recall 0,9952, 53,5 запроса/с, p99 36 мс. MariaDB 12.3 фильтрует после обхода и продолжает искать: 0,9896, 16,4 запроса/с, p99 322 мс. AliSQL так же: 0,9866, 10,6 запроса/с, p99 447 мс. MariaDB 11.8: 0,9646, 13,0 запроса/с, p99 366 мс. Percona Search фильтрует заранее: 0,8582, 71,9 запроса/с, p99 25 мс. pgvector фильтрует после обхода и останавливается: 0,8594, 227,5 запроса/с, p99 6,4 мс.
На 1% строк Valkey остаётся точным: recall 0,9996 при 8,2 запроса/с. MariaDB 12.3 даёт 0,9796 и 0,5 запроса/с, AliSQL 0,9884 и 0,3, MariaDB 11.8 0,9724 и 0,4. Percona Search даёт 0,8666 и уже 117,5 запроса/с. pgvector даёт 0,1034 и 237,1 запроса/с.
У pgvector оба recall помечены: запрос вернул меньше k строк. На 10% так вышло в 224 запросах из 500, на 1% во всех 500. Движок идёт по графу, набирает ef_search кандидатов и выбрасывает тех, кто не прошёл условие. На 1% не проходит почти никто, и наружу выходят одна-две строки вместо десяти. Ошибки нет, предупреждения нет, скорость полная. Recall 0,1034 это короткий ответ, а не плохие соседи. Iterative scan в pgvector 0.8 как раз для этого случая есть, в прогоне его не включали. В приложение попадает то, что стоит по умолчанию, если про настройку никто не знает.
MariaDB и AliSQL тоже фильтруют после обхода, но ищут, пока выживших строк не наберётся достаточно. Полнота держится выше 0,97, скорость падает ниже одного запроса в секунду. Percona называет p99 десять-тринадцать секунд. Percona Search единственный, кто от жёсткого фильтра ускоряется: 71,9 запроса/с на 10% и 117,5 на 1%. Полнота у него самая низкая из пяти, кто отвечает полностью, 0,858-0,867, и это скалярное квантование, не сам фильтр. Без квантования на том же корпусе и при том же размере индекса полнота доходит до 0,997. На 1% и точным, и ещё пригодным к работе остаётся Valkey.
Рядом с сайтом это развилка, если поиск идёт не по всем документам, а по одной рубрике. Узкое условие на pgvector 0.8.6 по умолчанию вернёт короткий список на полной скорости, и это легко прочитать как пустую выдачу. MariaDB десятку соседей доберёт, но на 1% строк уходит в доли запроса в секунду.
Десять процентов корпуса заново
Дальше удалили 99 000 строк и вставили те же 99 000. PostgreSQL почти не сдвинулся: 244 запроса/с до, 245 после, плюс 0,4%, recall 0,9816, вставка 128 строк/с. Ровно, потому что после вставки отработал VACUUM (ANALYZE). Из шести движков только у PostgreSQL после такой перезаписи вообще запускают обслуживание. Valkey: 479 и 460, минус 4,0%, recall 0,9742, 137 строк/с. Percona Search: 186 и 113, минус 39,2%, recall после 0,8416. До перезаписи было 0,8432. Индекс не разваливается, падает скорость. Вставка при этом 20 223 строк/с, самое большое падение в таблице.
У трёх движков на InnoDB замера после перезаписи нет. MariaDB 12.3 до неё давала 156 запросов/с и вставляла 28,1 строк/с. MariaDB 11.8: 152 и 22,8. AliSQL: 138 и 16,1. Прочерки в таблице это ошибка стенда, не результат базы. На каждую повторную вставку поставили лимит по часам, чтобы зависшая запись отпустила машину. Лимит взяли тридцать минут, под дефект Valkey: конвейер записи в уже проиндексированный префикс не возвращался. Для движка, который пишет медленно и правильно, тридцати минут мало. MariaDB 12.3 успела 51 480 строк из 99 000, MariaDB 11.8 успела 41 580, AliSQL 29 700. Сейчас лимит два часа. Вернуть три клетки стоит около 24 часов, и почти всё это повторная загрузка корпуса.
Неполный кусок на свой вопрос всё же отвечает. Запись в уже построенный граф идёт со скоростью 16-28 строк в секунду против 41-76 на первой загрузке. На всех трёх движках это в два-три раза дороже, чем строить граф с нуля. Потоки-читатели в интерпретаторе в этот момент не работали, записи прогона это подтверждают. История с 32 клиентами к этим строкам в секунду не примешивается.
Что оставлять рядом с WordPress
Сайт на WordPress эти индексы сам не строит. Посты лежат обычными строками в MariaDB или MySQL. Цифры нужны, если эмбеддинги живут в той же MariaDB, в соседнем PostgreSQL или в Valkey рядом с nginx.
Percona по этому прогону раскладывает выбор так. Параллельный трафик отдавать pgvector: 4 798 запросов в секунду при p99 10,8 мс, на порядок дальше семейства MySQL. До выкладки запроса с фильтром включить iterative scan, иначе в ответ уедет короткий список. Valkey, если рабочий набор помещается в память и фильтр должен быть и точным, и быстрым. Сначала проверить запись в уже проиндексированный префикс: на ней у Valkey в стенде ловили зависание. MariaDB 12.3, если индекс должен быть маленьким и точным, а клиентов одновременно горстка. На recall 0,99 она здесь самая быстрая, индекс примерно вчетверо меньше, чем 15,1 ГиБ у Percona Search. Ветка 11.8 в этом прогоне не делает ничего, что 12.3 не делает лучше. Percona Search, если типичный запрос это узкое условие и техническое превью устраивает. AliSQL собирается дольше всех и упирается в обрыв задержки выше ef_search 80.
Сборки взяты из тегов и из пакетов Percona, не из репозитория Ubuntu или Debian. apt update на VPS с сайтом эти бинарники не поставит. Смотреть стоит ту версию, которая реально запущена.
mariadb --version
Полный профиль tuned-complete Percona оценивает примерно в 40 часов, почти всё это пошаговые сборки индексов. Перед тем как занять машину на двое суток, в том же репозитории есть --profile dev, около минуты на движок на синтетическом корпусе, и --profile smoke, около 45 минут на проход, с теми же графиками. На коробку, которая отдаёт сайт, этот прогон ставить некуда: серверу в замере отдали 64 ядра и 120,8 ГБ. Команды полного прогона Патлан приводит там же, включая ./run-benchmark.sh run --profile tuned-complete.
Легко промахнуться в двух местах. Колонку «32 клиента» у MariaDB прочитать как 32 одновременных запроса, хотя стенд сам оставил там 7-17. И включить поиск с фильтром на pgvector 0.8.6, не трогая iterative scan: скорость будет красивая, список короткий.
Дальше сам предел прогона. Один корпус, одна размерность 1 536, у половины поля одна степень графа, одна машина, один запуск, потолок параллелизма не профилировали. Сильнее всего при повторе могут сдвинуться три пустые клетки перезаписи и шесть отказанных точек по клиентам. Слабее всего время сборки, размер индекса и recall: они совпали с более ранними прогонами на двух размерах корпуса. M = 6 для MHNSW и VIDX был хуже M = 16 везде, где recall выше 0,88. Остальные три движка гоняли только на M = 16.