Перейти к основному содержимому

Использование AI-инструмента vector

Администрирование

  1. Администратор должен обеспечивать корректную настройку и эксплуатацию СУБД Pangolin с AI-функциями строго в соответствии с документацией на Продукт, чтобы исключить возможность раскрытия конфиденциальной информации при их использовании.
  2. При выявлении аномалий или сбоев в работе СУБД Pangolin с AI-функциями Администратор должен предоставлять необходимую информацию для анализа и реагирования Администратору безопасности.
  3. Администратор должен производить настройку и поддерживать в рабочем состоянии систему логирования всех операций с AI-функциями, и обеспечивать хранение журналов не менее установленного внутренними регламентами срока.
  4. Администратор должен своевременно устанавливать обновления программного обеспечения, включая патчи безопасности, обновления моделей AI и компонентов фильтрации, выпущенные разработчиком СУБД Pangolin.

Правила работы с AI-функциями

При использовании AI-инструментов СУБД Pangolin соблюдайте следующие правила:

  1. Не используйте AI-функции СУБД Pangolin для обработки персональных или конфиденциальных данных во избежание утечки чувствительной информации.
  2. Не передавайте пароли, токены, криптографические ключи и другие чувствительные данные в строки ввода, загружаемые в Продукт файлы, буферы или сторонние сервисы при использовании AI-функций СУБД Pangolin.
  3. Используйте AI-функции СУБД Pangolin только для законных и разрешенных сценариев в соответствии с эксплуатационной документацией продукта, корпоративной политикой и регуляторными требованиями.
  4. Не применяйте AI-функции для принятия критических решений без проверки специалистом.
  5. Проверяйте корректность и актуальность ответов AI перед использованием или распространением информации.
  6. Сообщайте об аномальной активности, некорректных результатах или подозрении на инциденты безопасности ответственным лицам или в службу ИБ.

Правила эксплуатации

При эксплуатации расширения vector в составе СУБД Pangolin соблюдайте следующие требования:

Изоляция и управление доступом:

  • Разворачивать СУБД Pangolin с AI-функциями в изолированной среде с ограниченными сетевыми соединениями;
  • Выполнять авторизацию вызывающей стороны (пользовательский вызов, межсистемный вызов, вызов со стороны другого AI-агента);
  • Реализовывать аутентификацию и авторизацию AI-модуля/сервиса/агента при выполнении запросов к ресурсной системе;
  • Применять принцип минимальных привилегий для всех сервисных и агентских учетных записей.

Контроль ресурсов:

  • Установить ограничения на вычислительные ресурсы для предотвращения перегрузок;
  • Контролировать время выполнения запросов к AI-модели с автоматическим прерыванием;
  • Ограничить количество параллельных запросов от одного пользователя;
  • Ограничить размер и сложность входных данных в соответствии с требованиями модели.

Защита данных:

  • Обеспечивать логическое разделение векторных данных разных пользователей;
  • Предотвращать смешивание векторных данных из разных источников без соответствующих разрешений;
  • Использовать дополнительную базу данных для AI-модуля, ограничивая доступ к основной базе данных Продукта;
  • Шифровать данные при передаче с использованием TLS 1.2 или выше.

Быстрый старт

Создание таблицы с вектором в трех измерениях:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));

Вставка данных:

INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');

Поиск ближайшего соседа по расстоянию L2:

SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;

Также поддерживается:

  • внутреннее произведение <#>;
  • косинусное расстояние <=>;
  • расстояние L1 <+>.
примечание

<#> возвращает отрицательное внутреннее произведение, поскольку Pangolin поддерживает только порядок ASC для сканирования индекса по оператору.

Хранение

Создание новой таблицы с векторным полем:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));

Также возможно добавление векторного поля к существующей таблице:

ALTER TABLE items ADD COLUMN embedding vector(3);

Также поддерживаются другие векторные типы:

  • с половинной точностью;
  • двоичные векторы;
  • разреженные векторы.

Вставка данных:

INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');

Возможна загрузка векторов пачкой с использованием команды COPY:

COPY items (embedding) FROM STDIN WITH (FORMAT BINARY);

Вставка/обновление векторов:

INSERT INTO items (id, embedding) VALUES (1, '[1,2,3]'), (2, '[4,5,6]')
ON CONFLICT (id) DO UPDATE SET embedding = EXCLUDED.embedding;

Обновление векторов:

UPDATE items SET embedding = '[1,2,3]' WHERE id = 1;

Удаление векторов:

DELETE FROM items WHERE id = 1;

Выполнение запросов

Поиск ближайшего соседа для вектора:

SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;

Поддерживаются следующие функции поиска:

  • <-> - расстояние L2;
  • <#> - (негативное) внутреннее произведение;
  • <=> - косинусное расстояние;
  • <+> - расстояние L1;
  • <~> - расстояние Хэмминга (для двоичного типа);
  • <%> - расстояние Жаккарда (для двоичного типа).

Поиск ближайшего соседа для строки:

SELECT * FROM items WHERE id != 1 ORDER BY embedding <-> (SELECT embedding FROM items WHERE id = 1) LIMIT 5;

Получение строк на определенном расстоянии (необходимо использовать совместно с ORDER BY и LIMIT для использования индекса):

SELECT * FROM items WHERE embedding <-> '[3,1,2]' < 5;

Расстояния

Вычисление расстояний:

SELECT embedding <-> '[3,1,2]' AS distance FROM items;

Для внутреннего произведения необходимо умножать на -1, поскольку результат вычисления внутреннего произведения возвращает отрицательный результат:

SELECT (embedding <#> '[3,1,2]') * -1 AS inner_product FROM items;

Для косинусного расстояния необходимо результат вычисления вычитать из 1:

SELECT 1 - (embedding <=> '[3,1,2]') AS cosine_similarity FROM items;

Агрегатные функции

Вычисление средних значений:

SELECT AVG(embedding) FROM items;

Вычисление средних значений с группировкой:

SELECT category_id, AVG(embedding) FROM items GROUP BY category_id;

Индексирование

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

Возможно добавить индекс, чтобы использовать приблизительный поиск по ближайшему соседу, который ускоряет выполнение. В отличие от обычных индексов, можно увидеть отличающиеся результаты для одинаковых запросов после добавления приблизительного индекса.

Поддерживаются следующие типы индексов:

  • HNSW;
  • IVFFlat.

HNSW

Индекс HNSW создает многослойный граф. Он обеспечивает лучшую производительность запросов, по сравнению с IVFFlat (с точки зрения соотношения скорости и полноты поиска), но медленнее строится и использует больше памяти. Кроме того, данный индекс может быть создан без каких-либо данных в таблице, поскольку в нем нет шага обучения, как в IVFFlat.

Примеры добавления индекса для каждой функции расстояния, которую возможно использовать:

  • расстояние L2:

    CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
    примечание

    Используйте halfvec_l2_ops для halfvec (и аналогично с другими типами и функциями расстояния).

  • внутреннее произведение:

    CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
  • косинусное расстояние:

    CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
  • расстояние L1:

    CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);
  • расстояние Хэмминга (только для двоичного типа векторов):

    CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);
  • расстояние Жаккарда (только для двоичного типа векторов):

    CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);

Поддерживаемые типы для данного индекса:

  • vector - до 2000 измерений (2000-мерное пространство);
  • halfvec - до 4000 измерений (4000-мерное пространство);
  • bit - до 64000 измерений (64000-мерное пространство);
  • sparsevec - до 1000 ненулевых координат.

Опции индексирования

Указание параметров HNSW:

  • m - максимальное количество подключений на слой (по умолчанию 16);
  • ef_construction - размер динамического списка кандидатов для построения графа (по умолчанию 64).
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);

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

Опции запросов

Размер динамического списка кандидатов для поиска (по умолчанию 40):

SET hnsw.ef_search = 100;

Более высокое значение обеспечивает лучшую полноту поиска в обмен на скорость.

Использование SET LOCAL внутри блока транзакции позволяет установить параметр для одного запроса:

BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT -- КОД ПРОПУЩЕН
COMMIT;

Время построения индекса

Индексы строятся значительно быстрее, когда граф помещается в maintenance_work_mem:

SET maintenance_work_mem = '8GB';

Когда граф больше не помещается, отображается уведомление:

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.
примечание

Не устанавливайте значение maintenance_work_mem слишком высоким, чтобы не исчерпать свободную память на сервере.

Как и в случае с другими типами индексов, быстрее будет создать индекс после загрузки исходных данных. Также можно ускорить создание индекса, увеличив количество параллельных рабочих процессов (по умолчанию 2):

SET max_parallel_maintenance_workers = 7; -- плюс лидер

При большом количестве рабочих процессов также может потребоваться увеличить значение max_parallel_workers (по умолчанию 8).

Отображение прогресса индексирования

Начиная с версии ядра PostgreSQL 12+, возможно смотреть прогресс индексирования следующим запросом:

SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;

Построение индекса HNSW состоит из фаз:

  1. Инициализация;
  2. Загрузка кортежей.

IVFFlat

Индекс IVFFlat разбивает векторы на списки, а затем выполняет поиск в подмножестве тех списков, которые ближе всего расположены к вектору запроса. Он обеспечивает более быстрое построение и использует меньше памяти, чем HNSW, но обладает более низкой производительностью запроса (с точки зрения соотношения скорости и полноты поиска).

Ключи к достижению хорошей полноты поиска:

  • создание индекса после того, как в таблице появятся данные;
  • подходящее количество списков - лучше всего начать с rows/1000 для 1 млн строк и sqrt(rows) для более чем 1 млн строк;
  • при выполнении запроса укажите подходящее количество проверок (чем больше, тем лучше полнота, чем меньше — скорость) — хорошей отправной точкой будет sqrt(lists).

Ниже приведены примеры добавления индексов для каждой функции определения расстояния, которую планируется использовать:

  • расстояние L2:

    CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
    примечание

    Используйте halfvec_l2_ops для halfvec (и аналогично с другими типами и функциями расстояния).

  • внутреннее произведение:

    CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);
  • косинусное расстояние:

    CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
  • расстояние Хэмминга (только для двоичного типа):

    CREATE INDEX ON items USING ivfflat (embedding bit_hamming_ops) WITH (lists = 100);

Поддерживаемые типы для данного индекса:

  • vector - до 2000 измерений (2000-мерное пространство);
  • halfvec - до 4000 измерений (4000-мерное пространство);
  • bit - до 64000 измерений (64000-мерное пространство).

Опции запросов

Указание количества проб (по умолчанию 1):

SET ivfflat.probes = 10;

Более высокое значение обеспечивает лучшее запоминание в ущерб скорости, и его можно установить на количество списков для точного поиска ближайших соседей (в этот момент планировщик не будет использовать индекс).

Использование SET LOCAL внутри транзакции позволяет установить параметр для одного запроса:

BEGIN;
SET LOCAL ivfflat.probes = 10;
SELECT -- КОД ПРОПУЩЕН
COMMIT;

Время построения индекса

Возможно ускорить создание индекса в больших таблицах за счет увеличения числа параллельных обработчиков (по умолчанию 2):

SET max_parallel_maintenance_workers = 7; -- плюс лидер

При большом количестве рабочих процессов также может потребоваться увеличить значение max_parallel_workers (по умолчанию 8).

Отображение прогресса индексирования

Начиная с версии ядра PostgreSQL 12+ возможно смотреть прогресс индексирования следующим запросом:

SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;

Фазы построения индекса IVFFlat:

  1. Инициализация;
  2. Выполнение k-means (или алгоритма k-средних);
  3. Распределение кортежей;
  4. Загрузка кортежей.
примечание

% заполняется только на этапе загрузки кортежей.

Фильтрация

Существует несколько способов индексировать запросы ближайшего соседа с помощью предложения WHERE:

SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;

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

CREATE INDEX ON items (category_id);

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

CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WHERE (category_id = 123);

Использование партиционирования для приблизительного поиска по множеству различных значений столбцов, используемых в WHERE:

CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);

Типы векторов

Векторы с половинной точностью

Для хранения векторов с половинной точностью используется тип halfvec:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding halfvec(3));

Индекс вектора с половинной точностью, для уменьшения размера индекса:

CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);

Получение ближайшего соседа:

SELECT * FROM items ORDER BY embedding::halfvec(3) <-> '[1,2,3]' LIMIT 5;

Двоичные векторы

Для хранения векторов в двоичном формате используется тип bit:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
INSERT INTO items (embedding) VALUES ('000'), ('111');

Получение ближайшего соседа по расстоянию Хэмминга:

SELECT * FROM items ORDER BY embedding <~> '101' LIMIT 5;

Еще один способ:

SELECT * FROM items ORDER BY bit_count(embedding # '101') LIMIT 5;

Также поддерживается поиск по расстоянию Жаккарда (<%>).

Двоичное квантование

Используйте выражение индексации для двоичного квантования:

CREATE INDEX ON items USING hnsw ((binary_quantize(embedding)::bit(3)) bit_hamming_ops);

Получение ближайшего соседа по расстоянию Хэмминга:

SELECT * FROM items ORDER BY binary_quantize(embedding)::bit(3) <~> binary_quantize('[1,-2,3]') LIMIT 5;

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

SELECT * FROM (
SELECT * FROM items ORDER BY binary_quantize(embedding)::bit(3) <~> binary_quantize('[1,-2,3]') LIMIT 20
) ORDER BY embedding <=> '[1,-2,3]' LIMIT 5;

Разреженные векторы

Для хранения разреженных векторов используется тип sparsevec:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding sparsevec(5));

Вставка векторов:

INSERT INTO items (embedding) VALUES ('{1:1,3:2,5:3}/5'), ('{1:4,3:5,5:6}/5');

Разреженные векторы хранятся в формате {index1:value1,index2:value2}/dimensions и отсчет начинается с 1 аналогично SQL-массивам. Данный формат векторов нужен, чтобы не тратить место на диске для хранения нулей.

Получение ближайшего соседа по расстоянию L2:

SELECT * FROM items ORDER BY embedding <-> '{1:3,3:1,5:2}/5' LIMIT 5;

Гибридный поиск

Использование совместно с полнотекстовым поиском Pangolin для гибридного поиска:

SELECT id, content FROM items, plainto_tsquery('hello search') query
WHERE textsearch @@ query ORDER BY ts_rank_cd(textsearch, query) DESC LIMIT 5;

Возможно использовать взаимное ранжирование или перекрестный кодер для объединения результатов.

Индексирование подвекторов

Использование выражения индексирования для индексирования подвекторов:

CREATE INDEX ON items USING hnsw ((subvector(embedding, 1, 3)::vector(3)) vector_cosine_ops);

Получение ближайшего соседа по косинусному расстоянию:

SELECT * FROM items ORDER BY subvector(embedding, 1, 3)::vector(3) <=> subvector('[1,2,3,4,5]'::vector, 1, 3) LIMIT 5;

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

SELECT * FROM (
SELECT * FROM items ORDER BY subvector(embedding, 1, 3)::vector(3) <=> subvector('[1,2,3,4,5]'::vector, 1, 3) LIMIT 20
) ORDER BY embedding <=> '[1,2,3,4,5]' LIMIT 5;

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

Тюнинг

примечание

Изменение параметров maintenance_work_mem, max_parallel_maintenance_workers и других конфигурационных значений должно выполняться только авторизованными администраторами. Не устанавливайте значение maintenance_work_mem слишком высоким, чтобы не исчерпать свободную память на сервере.

Используйте инструмент, подобный PgTune, для установки начальных значений параметров сервера Pangolin. Например, shared_buffers обычно должны занимать 25% памяти сервера. Можно найти конфигурационный файл с помощью команды:

SHOW config_file;

И проверить конкретную настройку:

SHOW shared_buffers;

Иногда необходимо рестартовать Pangolin для применения настройки.

Загрузка данных

Использование команды COPY для загрузки данных порциями (пример):

COPY items (embedding) FROM STDIN WITH (FORMAT BINARY);

После загрузки данных возможно добавление индексов для улучшения производительности.

Индексирование

Обратите внимание на время построения индексов HNSW и IVFFlat.

В промышленных средах имеет смысл использовать конкурентное построение индекса для того, чтобы избежать блокировок:

CREATE INDEX CONCURRENTLY -- КОД ПРОПУЩЕН

Выполнение запросов

Используйте EXPLAIN ANALYZE чтобы оценить производительность. Например:

EXPLAIN ANALYZE SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;

Точный поиск

Чтобы ускорить выполнение запросов без индекса, увеличьте значение max_parallel_workers_per_gather:

SET max_parallel_workers_per_gather = 4;

Если векторы нормализованы по длине (как эмбеддинги в OpenAI), используйте внутреннее произведение для достижения наилучшей производительности:

SELECT * FROM items ORDER BY embedding <#> '[3,1,2]' LIMIT 5;

Приблизительный поиск

Чтобы ускорить выполнение запросов с индексом IVFFlat, увеличьте количество инвертированных списков (за счет полноты поиска):

CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);

Очистка

Очистка может ожидать индексы HNSW. Для ускорения процесса вакуума имеет смысл сперва выполнить REINDEX:

REINDEX INDEX CONCURRENTLY index_name;
VACUUM table_name;

Мониторинг

Контролировать производительность возможно с помощью pg_stat_statements (обязательно добавьте его в shared_preload_libraries):

CREATE EXTENSION pg_stat_statements;

Получение запросов, которые выполнялись дольше всего:

SELECT query, calls, ROUND((total_plan_time + total_exec_time) / calls) AS avg_time_ms,
ROUND((total_plan_time + total_exec_time) / 60000) AS total_time_min
FROM pg_stat_statements ORDER BY total_plan_time + total_exec_time DESC LIMIT 20;

Контролируйте полноту, сравнивая результаты приблизительного поиска с точным поиском:

BEGIN;
SET LOCAL enable_indexscan = off; -- использовать точный поиск
SELECT -- КОД ПРОПУЩЕН
COMMIT;

Также возможно использование инструментов накопления статистики, таких как pg_profile или performance_insight.

Аудит операций с векторными данными

В рамках аудита работы расширения vector рекомендуется фиксировать:

  • Изменение конфигурационных параметров (hnsw.ef_search, ivfflat.probes, maintenance_work_mem и др.);
  • Создание, удаление и перестроение векторных индексов;
  • Отключение или обновление функций безопасности;
  • Входные и выходные данные запросов в случаях обнаружения инцидентов.
warning

При выявлении подозрительной или аномальной активности, связанной с векторными запросами, Администратор должен немедленно предоставить информацию Администратору безопасности.

Масштабирование

Расширение vector масштабируется также, как Pangolin.

Вертикальное масштабирование достигается за счет увеличения объема памяти, процессора и хранилища в одном экземпляре. Используйте существующие инструменты для настройки параметров и мониторинга производительности.

Горизонтальное масштабирование достигается с помощью реплик или специальных библиотек (например, Citus) или другого подхода для сегментирования (пример - https://github.com/pgvector/pgvector-python/blob/master/examples/citus/example.py).

Языки программирования

Используйте vector из различных языков программирования через драйвер Pangolin. Возможно даже генерировать и сохранять вектора из приложения на одном языке, а использовать из другого.

ЯзыкМодульСсылка
Cpgvector-chttps://github.com/pgvector/pgvector-c
C++pgvector-cpphttps://github.com/pgvector/pgvector-cpp
C#, F#, Visual Basicpgvector-dotnethttps://github.com/pgvector/pgvector-dotnet
Crystalpgvector-crystalhttps://github.com/pgvector/pgvector-crystal
Dartpgvector-darthttps://github.com/pgvector/pgvector-dart
Elixirpgvector-elixirhttps://github.com/pgvector/pgvector-elixir
Gopgvector-gohttps://github.com/pgvector/pgvector-go
Haskellpgvector-haskellhttps://github.com/pgvector/pgvector-haskell
Java, Kotlin, Groovy, Scalapgvector-javahttps://github.com/pgvector/pgvector-java
JavaScript, TypeScriptpgvector-nodehttps://github.com/pgvector/pgvector-node
Juliapgvector-juliahttps://github.com/pgvector/pgvector-julia
Lisppgvector-lisphttps://github.com/pgvector/pgvector-lisp
Luapgvector-luahttps://github.com/pgvector/pgvector-lua
Nimpgvector-nimhttps://github.com/pgvector/pgvector-nim
OCamlpgvector-ocamlhttps://github.com/pgvector/pgvector-ocaml
Perlpgvector-perlhttps://github.com/pgvector/pgvector-perl
PHPpgvector-phphttps://github.com/pgvector/pgvector-php
Pythonpgvector-pythonhttps://github.com/pgvector/pgvector-python
Rpgvector-rhttps://github.com/pgvector/pgvector-r
RubyNeighborhttps://github.com/ankane/neighbor
Rubypgvector-rubyhttps://github.com/pgvector/pgvector-ruby
Rustpgvector-rusthttps://github.com/pgvector/pgvector-rust
Swiftpgvector-swifthttps://github.com/pgvector/pgvector-swift
Zigpgvector-zighttps://github.com/pgvector/pgvector-zig

Часто задаваемые вопросы

Как много векторов может быть сохранено в одной таблице?

Непартиционированные таблицы имеют ограничение 32 TB по умолчанию в Pangolin. Партиционированные таблицы могут иметь тысячи партиций такого размера.

Поддерживается ли репликация?

Да, pgvector использует журнал предзаписи (WAL), который позволяет выполнять репликацию и восстановление на точку во времени.

Что делать, если я хочу индексировать векторы с более чем 2000 измерений?

Возможно использовать векторы с половинной точностью для увеличения до 4 000 измерений или двоичное квантование для увеличения до 64 000 измерений. Другой вариант — уменьшение размерности вектора.

Могу ли я сохранять векторы различной размерности в одной колонке?

Можно использовать тип без размерности, например vector вместо vector(3):

CREATE TABLE embeddings (model_id bigint, item_id bigint, embedding vector, PRIMARY KEY (model_id, item_id));

Однако использовать индекс возможно только на строках с одинаковой размерностью векторов (используйте условия и частичное индексирование):

CREATE INDEX ON embeddings USING hnsw ((embedding::vector(3)) vector_l2_ops) WHERE (model_id = 123);

И пример выполнения такого запроса:

SELECT * FROM embeddings WHERE model_id = 123 ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;

Могу ли я хранить векторы с большей точностью?

Возможно использование типов double precision[] или numeric[] для сохранения векторов с большей точностью:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding double precision[]);

-- use {} instead of [] for Postgres arrays
INSERT INTO items (embedding) VALUES ('{1,2,3}'), ('{4,5,6}');

При необходимости добавьте ограничение, контролирующее, что данные могут быть преобразованы в векторный тип и имеют ожидаемые размеры.

ALTER TABLE items ADD CHECK (vector_dims(embedding::vector) = 3);

Используйте индексирование по условию (для небольших размерностей):

CREATE INDEX ON items USING hnsw ((embedding::vector(3)) vector_l2_ops);

и выполняйте запросы:

SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;

Должен ли индекс умещаться в памяти?

Нет, но, как и в случае с другими типами индексов, в этом случае производительность, скорее всего, повысится. Можно получить размер индекса с помощью:

SELECT pg_size_pretty(pg_relation_size('index_name'));

Почему запрос не использует индексы?

Запрос должен содержать ORDER BY и LIMIT, а ORDER BY должен быть результатом выполнения оператора вычисления дистанции (не выражения) в порядке ASC:

-- index
ORDER BY embedding <=> '[3,1,2]' LIMIT 5;

-- no index
ORDER BY 1 - (embedding <=> '[3,1,2]') DESC LIMIT 5;

Можно предложить планировщику использовать индекс для запроса с помощью:

BEGIN;
SET LOCAL enable_seqscan = off;
SELECT -- КОД ПРОПУЩЕН
COMMIT;

Также, если таблица имеет небольшие размеры, полное сканирование может быть быстрее.

Почему в запросе не используется параллельное сканирование таблицы?

Планировщик не учитывает внешнее хранение (TOAST) в оценке стоимости, что может удешевлять последовательное сканирование. Можно снизить стоимость параллельного сканирования запроса с помощью:

BEGIN;
SET LOCAL min_parallel_table_scan_size = 1;
SET LOCAL parallel_setup_cost = 1;
SELECT -- КОД ПРОПУЩЕН
COMMIT;

Или выберите встроенное хранение векторов:

ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;

Почему после добавления индекса HNSW результатов по запросу становится меньше?

Результаты ограничены размером динамического списка кандидатов (hnsw.ef_search). Результатов может быть еще меньше из-за неактивных кортежей или условий фильтрации в запросе. Рекомендуется задать для параметра hnsw.ef_search значение, как минимум в два раза превышающее значение LIMIT в запросе. Если нужно более 500 результатов, лучше использовать индекс IVFFlat.

Также обратите внимание, что векторы со значением NULL не индексируются (как и нулевые векторы для косинусного расстояния).

Почему после добавления индекса IVFFlat результатов по запросу становится меньше?

Вероятно, индекс был создан с использованием слишком малого количества данных для такого количества списков. Удалите индекс до тех пор, пока в таблице не будет больше данных.

DROP INDEX index_name;

Результаты также могут быть ограничены количеством проб (параметр ivfflat.probes).

Также стоить иметь в виду, что векторы со значением NULL не индексируются (так же, как нулевые векторы для косинусного расстояния).

Техническое описание типов векторов

Тип vector

Каждый вектор занимает (4 * размерность + 8) байт в памяти. Каждый элемент представляет собой число с плавающей запятой одинарной точности (как тип real в Postgres), и все элементы должны быть конечными (без NaN, Infinity или -Infinity). Векторы могут иметь до 16 000 измерений.

Доступные операторы:

  • + - поэлементное сложение;
  • - - поэлементное вычитание;
  • * - поэлементное умножение;
  • || - конкатенация;
  • <-> - Евклидово расстояние (расстояние L2);
  • <#> - отрицательное внутреннее произведение;
  • <=> - косинусное расстояние;
  • <+> - расстояние городских кварталов (расстояние L1).

Доступные функции:

  • binary_quantize(vector) RETURN bit - двоичное квантование;
  • cosine_distance(vector, vector) RETURN double precision - косинусное расстояние;
  • inner_product(vector, vector) RETURN double precision - внутреннее произведение;
  • l1_distance(vector, vector) RETURN double precision - расстояние L1;
  • l2_distance(vector, vector) RETURN double precision - расстояние L2;
  • l2_normalize(vector) RETURN vector - нормализация по Евклиду;
  • subvector(vector, integer, integer) RETURN vector - получение подвектора;
  • vector_dims(vector) RETURN integer - размерность вектора;
  • vector_norm(vector) RETURN double precision - Евклидова норма.

Доступные агрегатные функции:

  • avg(vector) RETURN vector - среднее;
  • sum(vector) RETURN vector - сумма.

Тип halfvec

Каждый вектор с половинной точностью занимает (2 * размерность + 8) байт в памяти. Каждый элемент представляет собой число с плавающей запятой половинной точности, и все элементы должны быть конечными (без NaN, Infinity или -Infinity). Векторы с половинной точностью могут иметь до 16 000 измерений.

Доступные операторы:

  • + - поэлементное сложение;
  • - - поэлементное вычитание;
  • * - поэлементное умножение;
  • || - конкатенация;
  • <-> - Евклидово расстояние (расстояние L2);
  • <#> - отрицательное внутреннее произведение;
  • <=> - косинусное расстояние;
  • <+> - расстояние городских кварталов (расстояние L1).

Доступные функции:

  • binary_quantize(halfvec) RETURN bit - двоичное квантование;
  • cosine_distance(halfvec, halfvec) RETURN double precision - косинусное расстояние;
  • inner_product(halfvec, halfvec) RETURN double precision - внутреннее произведение;
  • l1_distance(halfvec, halfvec) RETURN double precision - расстояние L1;
  • l2_distance(halfvec, halfvec) RETURN double precision - расстояние L2;
  • l2_normalize(halfvec) RETURN halfvec - нормализация по Евклиду;
  • subvector(halfvec, integer, integer) RETURN halfvec - получение подвектора;
  • vector_dims(halfvec) RETURN integer - размерность вектора;
  • l2_norm(halfvec) RETURN double precision - Евклидова норма.

Доступные агрегатные функции:

  • avg(halfvec) RETURN halfvec - среднее;
  • sum(halfvec) RETURN halfvec - сумма.

Тип bit

Каждый двоичный вектор занимает (размерность/8 + 8) байт в памяти. Смотрите документацию на стандартный тип bit https://www.postgresql.org/docs/current/datatype-bit.html для получения подробностей.

Доступные операторы:

  • <~> - расстояние Хэмминга;
  • <%> - расстояние Жаккарда.

Доступные функции:

  • hamming_distance(bit, bit) RETURN double precision - расстояние Хэмминга;
  • jaccard_distance(bit, bit) RETURN double precision - расстояние Жаккарда.

Тип sparsevec

Каждый разреженный вектор занимает (8 * количество ненулевых элементов + 16) байт в памяти. Каждый элемент представляет собой число с плавающей запятой одинарной точности, и все элементы должны быть конечными (без NaN, Infinity или -Infinity). Разреженные векторы могут содержать до 16 000 ненулевых элементов.

Доступные операторы:

  • <-> - Евклидово расстояние(расстояние L2);
  • <#> - отрицательное внутреннее произведение;
  • <=> - косинусное расстояние;
  • <+> - расстояние городских кварталов(расстояние L1).

Доступные функции:

  • cosine_distance(sparsevec, sparsevec) RETURN double precision - косинусное расстояние;
  • inner_product(sparsevec, sparsevec) RETURN double precision - внутреннее произведение;
  • l1_distance(sparsevec, sparsevec) RETURN double precision - расстояние L1;
  • l2_distance(sparsevec, sparsevec) RETURN double precision - расстояние L2;
  • l2_normalize(sparsevec) RETURN sparsevec - нормализация по Евклиду;
  • l2_norm(sparsevec) RETURN double precision - Евклидова норма.