Подготовка индексов перед обновлением до 2.5.1#
Эта процедура предназначена для перехода с версии 2.5.0 на 2.5.1. Для другой сборки с такой же разделённой схемой базы её можно применять только после подтверждения администратора и сопровождения. Если текущая версия ниже 2.5.0, например 2.4.10, не запускайте приведённые ниже SQL-команды без согласованного пути обновления.
При переходе на 2.5.1 подготовьте индексы до запуска update.sh или ручных миграций версии 2.5.1. Скрипт обновления запускает миграции, но не выполняет эту подготовку. Миграция проверяет готовность индексов и без них не завершится.
Работу выполняет администратор базы данных. Создание индексов на большой базе может занять время; заранее сделайте резервную копию и выделите окно для обновления. Нужен psql с доступом к той же базе, которую использует Sherpa AI Server, и роль с правом создавать индексы на таблицах. Файлы с SQL создайте на компьютере, где будете запускать psql: клиентский архив установки их не содержит.
1. Проверьте подключение#
Подставьте реальные параметры вашей базы вместо DB_HOST, DB_OWNER и SHERPA_DB. Если psql запускается внутри контейнера базы, укажите доступные ему параметры подключения. Проверьте имя базы и пользователя перед выполнением команд:
export PGHOST='DB_HOST'
export PGPORT='5432'
export PGUSER='DB_OWNER'
export PGDATABASE='SHERPA_DB'
psql -X -v ON_ERROR_STOP=1 -c 'SELECT current_database(), current_user;'
В стандартной установке из клиентского архива можно использовать psql внутри контейнера PostgreSQL. Из каталога с docker-compose.yml и .env проверьте подключение без установки psql на хосте:
docker compose exec -T aiserver-pg sh -c 'psql -X -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT current_database(), current_user;"'
Убедитесь, что public.embeddings — разделённая таблица со столбцом account_id. Выполните команду для вашего способа подключения:
psql -X -v ON_ERROR_STOP=1 -Atc "SELECT EXISTS (SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_attribute a ON a.attrelid = c.oid WHERE n.nspname = 'public' AND c.relname = 'embeddings' AND c.relkind = 'p' AND a.attname = 'account_id' AND a.attnum > 0 AND NOT a.attisdropped) AS schema_ready;"
В стандартной клиентской установке используйте контейнер базы данных:
printf '%s\n' "SELECT EXISTS (SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_attribute a ON a.attrelid = c.oid WHERE n.nspname = 'public' AND c.relname = 'embeddings' AND c.relkind = 'p' AND a.attname = 'account_id' AND a.attnum > 0 AND NOT a.attisdropped) AS schema_ready;" | docker compose exec -T aiserver-pg sh -c 'psql -X -v ON_ERROR_STOP=1 -At -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f -'
Ожидаемый ответ — t. Если получен f, ошибка или пустой ответ, не запускайте SQL-файлы ниже и update.sh. Сначала согласуйте дальнейшие действия с сопровождением.
2. Сохраните SQL для векторного поиска#
Скопируйте всё содержимое следующего блока в файл prepare_dimension_hnsw.sql без изменений:
-- Run with psql -X -v ON_ERROR_STOP=1 -f prepare_dimension_hnsw.sql before
-- Phinx migration 20260930160000 on a database that already has vectors.
-- Each command runs outside a transaction so PostgreSQL can build online.
-- The migration verifies these indexes before its short metadata cutover.
SELECT format(
'CREATE INDEX CONCURRENTLY IF NOT EXISTS %I ON public.%I USING hnsw '
|| '(embedding_vector public.ann_cos_ops) '
|| 'WITH (dims=384, m=8, efconstruction=64, efsearch=800) '
|| 'WHERE cardinality(embedding_vector) = 384',
child.relname || '_dim_384_hnsw_idx', child.relname
)
FROM pg_inherits inheritance
JOIN pg_class child ON child.oid = inheritance.inhrelid
WHERE inheritance.inhparent = 'public.embeddings'::regclass
ORDER BY child.relname
\gexec
SELECT format(
'CREATE INDEX CONCURRENTLY IF NOT EXISTS %I ON orchestrator.samples USING hnsw '
|| '(embedding_vector public.ann_cos_ops) '
|| 'WITH (dims=384, m=8, efconstruction=64, efsearch=64) '
|| 'WHERE cardinality(embedding_vector) = 384',
'samples_dim_384_hnsw_idx'
)
\gexec
-- Prepare the standard 384↔1024 model switch before the migration. The
-- 1024 predicates are empty on 384-only accounts but building them online
-- here prevents the first 1024 request from building HNSW on live data.
SELECT format(
'CREATE INDEX CONCURRENTLY IF NOT EXISTS %I ON public.%I USING hnsw '
|| '(embedding_vector public.ann_cos_ops) '
|| 'WITH (dims=1024, m=8, efconstruction=64, efsearch=800) '
|| 'WHERE cardinality(embedding_vector) = 1024',
child.relname || '_dim_1024_hnsw_idx', child.relname
)
FROM pg_inherits inheritance
JOIN pg_class child ON child.oid = inheritance.inhrelid
WHERE inheritance.inhparent = 'public.embeddings'::regclass
ORDER BY child.relname
\gexec
SELECT format(
'CREATE INDEX CONCURRENTLY IF NOT EXISTS %I ON orchestrator.samples USING hnsw '
|| '(embedding_vector public.ann_cos_ops) '
|| 'WITH (dims=1024, m=8, efconstruction=64, efsearch=64) '
|| 'WHERE cardinality(embedding_vector) = 1024',
'samples_dim_1024_hnsw_idx'
)
\gexec
-- Older installations might already contain other dimensions where the old
-- index was absent. Preserve them without building a blocking index in Phinx.
SELECT format(
'CREATE INDEX CONCURRENTLY IF NOT EXISTS %I ON public.%I USING hnsw '
|| '(embedding_vector public.ann_cos_ops) '
|| 'WITH (dims=%s, m=8, efconstruction=64, efsearch=800) '
|| 'WHERE cardinality(embedding_vector) = %s',
format('embeddings_account_%s_dim_%s_hnsw_idx', account_id, dimension),
format('embeddings_account_%s', account_id),
dimension, dimension
)
FROM (
SELECT account_id, cardinality(embedding_vector) AS dimension
FROM public.embeddings
WHERE cardinality(embedding_vector) NOT IN (384, 1024)
GROUP BY account_id, cardinality(embedding_vector)
) dimensions
ORDER BY account_id, dimension
\gexec
SELECT format(
'CREATE INDEX CONCURRENTLY IF NOT EXISTS %I ON orchestrator.samples USING hnsw '
|| '(embedding_vector public.ann_cos_ops) '
|| 'WITH (dims=%s, m=8, efconstruction=64, efsearch=64) '
|| 'WHERE cardinality(embedding_vector) = %s',
format('samples_dim_%s_hnsw_idx', dimension), dimension, dimension
)
FROM (
SELECT cardinality(embedding_vector) AS dimension
FROM orchestrator.samples
WHERE embedding_vector IS NOT NULL
AND cardinality(embedding_vector) NOT IN (384, 1024)
GROUP BY cardinality(embedding_vector)
) dimensions
ORDER BY dimension
\gexec
3. Сохраните SQL для проверки повторяющихся фрагментов#
Скопируйте всё содержимое следующего блока в файл prepare_documents_dedup_index.sql без изменений:
-- Run with psql -X -v ON_ERROR_STOP=1 -f before Phinx migration
-- 20260930161000 on an existing database with documents. psql must not wrap
-- this command in a transaction: the index is built without blocking writes.
CREATE INDEX CONCURRENTLY IF NOT EXISTS documents_file_text_md5_active_idx
ON public.documents (file_id, md5(text_chunk))
WHERE NOT is_deleted;
Проверьте контрольные суммы сохранённых файлов. Для блоков выше ожидаются указанные значения SHA-256 (с завершающим переводом строки):
sha256sum prepare_dimension_hnsw.sql prepare_documents_dedup_index.sql
fe7e626f0bfb5d8d6b8a8147ee128f4ae6b830ca7753c44648ab812229c17521 prepare_dimension_hnsw.sql
dfde574e8a213c32b2e2cb16f713701d29d1991de905f1e16d414e9b0dbe9401 prepare_documents_dedup_index.sql
Если контрольные суммы отличаются, исправьте файлы и не выполняйте их.
4. Выполните оба файла до обновления#
На компьютере с файлами выполните команды по очереди. Каждая должна завершиться с кодом 0. Не используйте psql -1 или BEGIN: CREATE INDEX CONCURRENTLY должен выполняться вне общей транзакции.
psql -X -v ON_ERROR_STOP=1 -f prepare_dimension_hnsw.sql
psql -X -v ON_ERROR_STOP=1 -f prepare_documents_dedup_index.sql
Если на хосте нет psql, в стандартной установке выполните вместо двух команд выше следующие команды из каталога с docker-compose.yml. Файлы SQL при этом остаются на хосте и передаются контейнеру через стандартный ввод:
docker compose exec -T aiserver-pg sh -c 'psql -X -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f -' < prepare_dimension_hnsw.sql
docker compose exec -T aiserver-pg sh -c 'psql -X -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f -' < prepare_documents_dedup_index.sql
5. Проверьте результат#
После успешного выполнения обоих файлов следующий запрос должен вернуть 0 строк с некорректными индексами:
psql -X -v ON_ERROR_STOP=1 -c "SELECT n.nspname AS schema_name, c.relname AS invalid_index FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE NOT i.indisvalid AND (c.relname ~ '_dim_[0-9]+_hnsw_idx$' OR c.relname = 'documents_file_text_md5_active_idx');"
При использовании контейнера PostgreSQL выполните ту же проверку так:
printf "%s\n" "SELECT n.nspname AS schema_name, c.relname AS invalid_index FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE NOT i.indisvalid AND (c.relname ~ '_dim_[0-9]+_hnsw_idx$' OR c.relname = 'documents_file_text_md5_active_idx');" | docker compose exec -T aiserver-pg sh -c 'psql -X -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f -'
Если команда завершилась ошибкой или запрос показал некорректный индекс, остановитесь и устраните причину с администратором БД до обновления. После успешной проверки запускайте update.sh либо предусмотренную процедуру ручных миграций. Не запускайте повторную индексацию документов только ради этой подготовки: индексы создаются для существующих таблиц и, если записи есть, для этих записей.
6. Дополнительная размерность после обновления #
Этот раздел нужен после миграций версии 2.5.1, если новая модель создаёт векторы размерности, отличной от 384 и 1024. До сохранения новой модели в настройках индексации подготовьте индекс для каждого затронутого аккаунта. Узнайте числовой account_id в базе Sherpa AI Server и фактическую размерность вектора (dimension, от 32 до 2000). Для другого аккаунта запускайте подготовку отдельно. Стандартные размерности 384 и 1024 подготовлены предыдущими шагами перед обновлением.
Скопируйте всё содержимое следующего блока в файл prepare_additional_dimension_hnsw.sql без изменений:
-- Prepare one account and the shared samples table for an additional embedding
-- dimension after migration 20260930160000. Run with psql outside a transaction:
-- psql -X -v ON_ERROR_STOP=1 -v account_id=123 -v dimension=768 \
-- -f backend/migrations/prepare_additional_dimension_hnsw.sql
-- CREATE INDEX CONCURRENTLY keeps ordinary inserts and updates available.
\set AUTOCOMMIT on
SELECT CASE WHEN EXISTS (
SELECT 1 FROM orchestrator.accounts WHERE id = :'account_id'::integer
) AND :'dimension'::integer BETWEEN 32 AND 2000
THEN 'true' ELSE 'false' END AS valid_parameters
\gset
\if :valid_parameters
\else
\echo 'Expected an existing account_id and a dimension between 32 and 2000'
SELECT 1 / 0;
\endif
-- Before the tuning migration, keep the index compatible with its migration
-- precheck. Afterwards create future dimensions with the tuned search depth.
SELECT CASE WHEN EXISTS (
SELECT 1 FROM orchestrator.phinxlog WHERE version = 20260930163000
) THEN 32000 ELSE 800 END AS hnsw_efsearch
\gset
-- The session lock serializes simultaneous preparation of the shared samples
-- index. It is released automatically if psql exits on an error.
SELECT pg_advisory_lock(2291, -1);
SELECT public.ensure_embeddings_account_partition(:'account_id'::integer);
SELECT format(
'CREATE INDEX CONCURRENTLY IF NOT EXISTS %I ON public.%I USING hnsw '
|| '(embedding_vector public.ann_cos_ops) '
|| 'WITH (dims=%s, m=8, efconstruction=64, efsearch=%s) '
|| 'WHERE cardinality(embedding_vector) = %s',
format('embeddings_account_%s_dim_%s_hnsw_idx', :'account_id'::integer, :'dimension'::integer),
format('embeddings_account_%s', :'account_id'::integer),
:'dimension'::integer,
:'hnsw_efsearch'::integer,
:'dimension'::integer
)
\gexec
SELECT format(
'CREATE INDEX CONCURRENTLY IF NOT EXISTS %I ON orchestrator.samples USING hnsw '
|| '(embedding_vector public.ann_cos_ops) '
|| 'WITH (dims=%s, m=8, efconstruction=64, efsearch=64) '
|| 'WHERE cardinality(embedding_vector) = %s',
format('samples_dim_%s_hnsw_idx', :'dimension'::integer),
:'dimension'::integer,
:'dimension'::integer
)
\gexec
SELECT public.ensure_embeddings_dimension_index(:'account_id'::integer, :'dimension'::integer);
SELECT orchestrator.ensure_samples_dimension_index(:'dimension'::integer);
SELECT pg_advisory_unlock(2291, -1);
Контрольная сумма SHA-256 файла с завершающим переводом строки:
sha256sum prepare_additional_dimension_hnsw.sql
94e18e038e02371a0859d60ca8f8e6345d0d5091e04f1659a8beecba034c4702 prepare_additional_dimension_hnsw.sql
Подставьте действительные числа вместо ACCOUNT_ID и DIMENSION. Выполните команду вне общей транзакции с теми же параметрами подключения psql:
psql -X -v ON_ERROR_STOP=1 -v account_id=ACCOUNT_ID -v dimension=DIMENSION -f prepare_additional_dimension_hnsw.sql
Для стандартной клиентской установки из каталога с docker-compose.yml используйте контейнер базы данных:
docker compose exec -T aiserver-pg sh -c 'psql -X -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v account_id=ACCOUNT_ID -v dimension=DIMENSION -f -' < prepare_additional_dimension_hnsw.sql
Команда должна завершиться с кодом 0: сам скрипт проверяет существование аккаунта, допустимую размерность и готовность обоих индексов. При ошибке не сохраняйте новую модель индексации, пока причина не устранена. После успешной подготовки вернитесь к порядку смены модели индексации.