Boas práticas
Leia a chave de erro de cada instrução, agrupe instruções relacionadas, indexe uma coluna de vetor e nomeie um banco de dados pelo que ele guarda.
Uma instrução que falha nem sempre se anuncia. A requisição que a carregou pode relatar sucesso enquanto a instrução dentro dela foi rejeitada. Uma consulta que retorna dez linhas pode ter lido um milhão para encontrá-las, e a conta é medida pelo que foi lido. A busca por vizinhos mais próximos lê um índice pelo nome, então o índice precisa existir antes de a consulta ser escrita. Cada um desses pontos é decidido onde a instrução é escrita, e cada um é barato de acertar ali e caro de descobrir depois.
As práticas abaixo tratam de como um cliente lê o resultado de cada instrução, de quando as instruções viajam em uma única chamada e de onde o custo de uma consulta é informado. As demais tratam do índice que uma busca vetorial lê, do tipo que uma coluna de embedding carrega, da espera antes de um banco de dados novo responder à primeira consulta, do nome que ele mantém por toda a sua existência e da cópia dos dados que só você pode fazer.
Leia a chave de erro de cada instrução, qualquer que seja o status HTTP
Decida o fluxo do código a partir de data[].error em toda resposta que o endpoint de consulta retorna, e leia o status HTTP como um veredito sobre a requisição, não sobre as instruções dentro dela.
POST /databases/{database_id}/query responde 200 assim que a requisição em si está bem formada. Cada instrução recebe sua própria entrada em data, e uma entrada cuja instrução falhou carrega error no lugar de results. Um cliente que verifica apenas o código de status registra a falha como sucesso e segue com o que estava fazendo. A resposta abaixo é o que SELECT * FROM nope; retorna em um banco de dados sem nenhuma tabela com esse nome:
O código de status continua carregando os erros que a própria requisição levanta. Um corpo sem a chave statements responde 400 com o erro 10059 Required Field, e instruções que a plataforma não conseguiu executar de forma alguma respondem 422 com o erro 14005 Execute SQL Exception. Ambos são falhas da chamada; um 200 com uma entrada error é a falha de uma instrução dentro de uma chamada que funcionou no resto.
O custo é que o código de status sozinho não diz mais a um cliente o que aconteceu. Toda resposta é percorrida entrada por entrada, e o cliente decide o que um resultado parcial significa para o trabalho que estava fazendo.
Envie as instruções relacionadas em uma única chamada
Coloque as instruções que pertencem a uma mesma unidade de trabalho em um único array statements, em vez de enviar uma requisição para cada uma.
O endpoint de consulta recebe o array e executa as instruções em ordem. Cada instrução retorna uma entrada em data, na mesma ordem, então a entrada na posição 2 pertence à instrução na posição 2. Pelo menos 100 instruções em uma chamada são bem-sucedidas, o que cobre uma tabela e as linhas que a inicializam. Um schema e suas primeiras linhas viajam em um único corpo:
Um array vazio é aceito: responde 200 com "data":[].
O custo é que uma chamada é tão rápida quanto sua instrução mais lenta, e uma falha dentro dela é parcial. As entradas anteriores à que falhou carregam seus próprios results, e nenhum campo da resposta informa um rollback. Ordene o array de modo que o trabalho possa continuar a partir do que já foi executado.
Meça o custo de uma consulta pela resposta que ela retorna
Leia rows_read e rows_written em cada entrada de resultado, porque é por esses dois números que a conta é medida.
Toda entrada que carrega results carrega três números ao lado de columns e rows: rows_read, rows_written e query_duration_ms. Eles são informados por instrução, não por chamada, então um array de instruções informa o custo de cada uma separadamente. rows_read conta as linhas que uma instrução leu, não as linhas que ela retornou, então um filtro que varre uma tabela custa mais do que o tamanho do seu conjunto de resultados sugere. Um resultado de uma única linha informa os cinco campos:
A biblioteca SQL da Azion informa menos. Suas entradas de resultado carregam statement, columns e rows, e descartam os três números, então um cliente que precisa das contagens medidas as lê na resposta da API. Para as linhas que cada plano inclui, consulte Limites do SQL Database.
O custo é que os números precisam ser extraídos da entrada inteira, em vez de retirados diretamente de rows. Um cliente escrito para retornar apenas as linhas descarta o único relato do que a consulta custou.
Indexe uma coluna de vetor antes da primeira consulta de vizinhos mais próximos
Crie o índice que vector_top_k lê antes de executar uma busca por vizinhos mais próximos na coluna.
vector_top_k('index', vector, k) recebe um nome de índice, não um nome de coluna, e retorna as k linhas mais próximas como valores id que fazem join em rowid. O índice é criado ao envolver a coluna em libsql_vector_idx dentro de uma instrução CREATE INDEX, e a função recebe uma métrica opcional, como em libsql_vector_idx(stats_embedding, 'metric=cosine'). A indexação usa o algoritmo DiskANN. Um índice de vetor é suportado em uma tabela com um ROWID ou uma PRIMARY KEY de coluna única, e uma PRIMARY KEY composta sem ROWID não é suportada. O índice e a busca que o lê nomeiam a mesma string:
As funções de distância são um caminho diferente. vector_distance_cos(a, b) e vector_distance_l2(a, b) comparam dois vetores e não leem nenhum índice, então respondem a uma pergunta sobre um par, não sobre uma tabela. A distância de cosseno vai de 0 a 2, em que 0 é quase idêntico, 1 é ortogonal e 2 é oposto.
O custo é uma segunda tabela. Criar um índice de vetor adiciona uma shadow table chamada <index>_shadow. Tanto .tables no EdgeSQL Shell quanto getTables na biblioteca azion a listam, então um código que itera por todas as tabelas encontra uma tabela que ninguém criou.
Declare uma nova coluna de embedding como FLOAT32
Declare uma coluna de embedding como F32_BLOB(<dimensions>), e passe para outro tipo somente quando os dados derem um motivo.
Uma coluna de vetor é um tipo blob que carrega a quantidade de dimensões na instrução CREATE TABLE, então F32_BLOB(3) guarda vetores de três dimensões. Seis tipos estão disponíveis, cada um com um alias: FLOAT1BIT e F1BIT_BLOB, FLOAT8 e F8_BLOB, FLOATB16 e FB16_BLOB, FLOAT16 e F16_BLOB, FLOAT32 e F32_BLOB, e FLOAT64 e F64_BLOB. FLOAT32 é o ponto de partida recomendado, e o tipo mais estreito não é uma economia gratuita: uma coluna FLOAT1BIT não suporta vector_distance_l2, então ela responde a menos perguntas que os outros. Por exemplo, text-embedding-3-small produz vetores de 1.536 dimensões, que F32_BLOB(1536) guarda.
A quantidade de dimensões é verificada quando um vetor é construído, não quando a coluna é declarada. CREATE TABLE t (v F32_BLOB(65537)); é aceito, e vector() então rejeita o valor com vector: max size exceeded 65536 dentro de um HTTP 200.
O custo é que tanto o tipo quanto a quantidade de dimensões ficam fixos na instrução que declara a coluna. A decisão é tomada antes de o primeiro embedding ser armazenado, e uma tabela mantém o tipo com que foi criada.
Verifique o status antes da primeira consulta em um banco de dados novo
Espere até status indicar created antes de enviar a primeira instrução a um banco de dados criado na mesma execução.
POST /databases responde 202. O state do envelope é pending enquanto o status do próprio banco de dados é creating, e o provisionamento leva cerca de 15 segundos. GET /databases/{database_id} responde 200, não carrega a chave state e informa o valor atual de status em data, que é creating, created ou deleting. Repita essa chamada até o valor indicar created:
O mesmo campo aparece em Azion Console, na coluna Status da lista de bancos de dados, e a ação Delete da linha permanece desabilitada enquanto o status é creating ou deleting.
O custo é um laço de espera em qualquer fluxo que cria um banco de dados e o consulta na mesma execução. A chamada de criação retorna antes de o banco de dados conseguir responder a uma instrução, então o código que vem depois dela consulta o status repetidamente em vez de seguir adiante.
Nomeie um banco de dados pelo que ele guarda
Escolha um nome que ainda descreva os dados daqui a alguns meses, porque nada renomeia um banco de dados depois que ele existe.
name é obrigatório na criação, vai de 6 a 50 caracteres, aceita letras, números e o hífen, e é único dentro da conta. PATCH e PUT em um banco de dados respondem 405 com o erro 10007 Method Not Allowed, então o nome é somente leitura desde o momento em que o banco de dados existe. active é aceito apenas na criação, pela mesma razão. Um nome fora do comprimento ou do conjunto de caracteres é rejeitado com o erro 14000 Invalid Database Name Format, e um nome com menos de 6 caracteres retorna 10048 Min Length junto dele. O erro 14001, cujo título é Name Already In Use., rejeita um nome que a conta já tem.
Um nome que declara o que o banco de dados guarda diz ao próximo leitor da lista de bancos de dados qual deles abrir. Para os limites dentro dos quais esta prática funciona, consulte Limites do SQL Database.
O custo é que a decisão é permanente. Um nome que deixa de descrever o seu conteúdo é substituído criando um segundo banco de dados com outro nome, copiando os dados para ele e apagando o primeiro.
Faça seu próprio backup a partir da instância principal
Exporte os dados você mesmo, em um cronograma que é seu, e leia a exportação na instância principal, não em uma réplica.
A Azion não oferece endpoint de backup nem comando de backup, então uma cópia de um banco de dados existe apenas quando algo a cria. A plataforma executa uma instância principal com réplicas de leitura, e uma réplica alcança o estado da instância principal após um tempo de propagação que difere de uma réplica para outra. Uma exportação lida em uma réplica pode, portanto, estar atrás do que a instância principal guarda, e é por isso que a instância principal é a que deve ser copiada. Dois caminhos produzem uma exportação: .dump no EdgeSQL Shell escreve o schema de uma tabela, os dados dela ou ambos, e Azion Console oferece Export all to .csv, Export all to .json e Export all to .xlsx na aba Tables.
Para a instância principal e as réplicas que leem dela, consulte Como o SQL Database funciona.
O custo é que o cronograma, o armazenamento e a restauração são todos seus. Nenhum endpoint informa quando a última exportação foi feita, e uma restauração é o conjunto de instruções exportadas executado novamente em um banco de dados que você cria.