Preparação dos índices antes da atualização para 2.5.1#
Este procedimento se aplica à atualização de 2.5.0 para 2.5.1. Para outra compilação com um esquema de banco de dados particionado equivalente, use-o somente depois que o administrador e a equipe de suporte confirmarem que ele se aplica. Se a versão atual for anterior à 2.5.0, por exemplo 2.4.10, não execute os comandos SQL abaixo sem combinar antes o caminho de atualização.
Para esta atualização, prepare os índices antes de executar update.sh ou as migrações manuais da versão 2.5.1. O script de atualização inicia as migrações, mas não faz essa preparação. A migração verifica se os índices estão prontos e não será concluída sem eles.
Esta tarefa deve ser realizada por um administrador de banco de dados. A criação de índices em um banco grande pode levar tempo; faça um backup e programe uma janela de atualização. É necessário usar psql conectado ao mesmo banco de dados usado pelo Sherpa AI Server e uma função com permissão para criar índices nas tabelas. Crie os arquivos SQL no computador onde executará o psql: o pacote de instalação do cliente não os contém.
1. Verifique a conexão com o banco de dados#
Substitua DB_HOST, DB_OWNER e SHERPA_DB pelas configurações reais do banco de dados. Se executar psql dentro do contêiner do banco, use as configurações de conexão disponíveis nele. Confirme o banco de dados e o usuário antes de continuar:
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;'
Em uma instalação padrão a partir do pacote do cliente, você pode usar o psql dentro do contêiner PostgreSQL. No diretório com docker-compose.yml e .env, verifique a conexão sem instalar o psql no host:
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;"'
Verifique se public.embeddings é uma tabela particionada com uma coluna account_id. Use o comando correspondente ao seu modo de conexão:
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;"
Na instalação padrão do cliente, use o contêiner do banco de dados:
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 -'
O resultado esperado é t. Se aparecer f, um erro ou nenhum resultado, não execute os arquivos SQL abaixo nem update.sh. Combine primeiro as próximas etapas com a equipe de suporte.
2. Salve o SQL para a busca vetorial#
Copie todo o conteúdo do bloco a seguir para o arquivo prepare_dimension_hnsw.sql sem alterações:
-- 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. Salve o SQL para verificar fragmentos duplicados#
Copie todo o conteúdo do bloco a seguir para o arquivo prepare_documents_dedup_index.sql sem alterações:
-- 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;
Confira os arquivos salvos com estes hashes SHA-256 dos blocos acima (incluindo a quebra de linha final):
sha256sum prepare_dimension_hnsw.sql prepare_documents_dedup_index.sql
fe7e626f0bfb5d8d6b8a8147ee128f4ae6b830ca7753c44648ab812229c17521 prepare_dimension_hnsw.sql
dfde574e8a213c32b2e2cb16f713701d29d1991de905f1e16d414e9b0dbe9401 prepare_documents_dedup_index.sql
Se os hashes forem diferentes, corrija os arquivos antes de executá-los.
4. Execute os dois arquivos antes da atualização#
Execute estes comandos um de cada vez no computador que contém os arquivos. Cada comando deve terminar com código 0. Não use psql -1 nem BEGIN: CREATE INDEX CONCURRENTLY deve ser executado fora de uma transação compartilhada.
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
Se o psql não estiver disponível no host, use estes comandos no diretório com docker-compose.yml. Os arquivos SQL permanecem no host e são enviados ao contêiner pela entrada padrão:
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. Verifique o resultado#
Depois que os dois arquivos forem executados com sucesso, esta consulta não deve retornar nenhuma linha com índices inválidos:
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');"
Se usar o contêiner PostgreSQL, faça a mesma verificação assim:
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 -'
Se algum comando falhar ou a consulta mostrar um índice inválido, interrompa o processo e resolva o problema com o administrador do banco antes de atualizar. Após as verificações, execute update.sh ou o procedimento de migração manual. Não é necessário reindexar os documentos apenas para esta preparação: os índices são criados nas tabelas existentes e abrangem os registros que já estiverem presentes.
6. Dimensões adicionais após a atualização #
Use esta seção após as migrações da versão 2.5.1 se o novo modelo produzir vetores com dimensão diferente de 384 e 1024. Prepare um índice para cada conta afetada antes de salvar o novo modelo nas configurações de indexação. Descubra o account_id numérico no banco de dados do Sherpa AI Server e a dimensão real do vetor (dimension, de 32 a 2000). Execute a preparação separadamente para cada conta. As dimensões padrão 384 e 1024 foram preparadas nas etapas anteriores, antes da atualização.
Copie todo o conteúdo do bloco a seguir para o arquivo prepare_additional_dimension_hnsw.sql sem alterações:
-- 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);
Hash SHA-256 do arquivo, incluindo a quebra de linha final:
sha256sum prepare_additional_dimension_hnsw.sql
94e18e038e02371a0859d60ca8f8e6345d0d5091e04f1659a8beecba034c4702 prepare_additional_dimension_hnsw.sql
Substitua ACCOUNT_ID e DIMENSION pelos números reais. Execute este comando fora de uma transação compartilhada com as mesmas configurações de conexão do psql:
psql -X -v ON_ERROR_STOP=1 -v account_id=ACCOUNT_ID -v dimension=DIMENSION -f prepare_additional_dimension_hnsw.sql
Para a instalação padrão do cliente, execute o seguinte no diretório com docker-compose.yml para usar o contêiner do banco de dados:
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
O comando deve terminar com código 0: o próprio script verifica se a conta existe, se a dimensão é permitida e se os dois índices estão prontos. Em caso de falha, não salve o novo modelo de indexação até resolver a causa. Após a preparação, volte ao procedimento para alterar o modelo de indexação.