Visão geral sobre a pesquisa vetorial - Amazon MemoryDB

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Visão geral sobre a pesquisa vetorial

A pesquisa vetorial baseia-se na criação, na manutenção e no uso de índices. Cada operação de pesquisa vetorial especifica um único índice e está confinada a esse índice, ou seja, as operações em um índice não são afetadas pelas operações em nenhum outro índice. Com exceção das operações para criar e destruir índices, qualquer número de operações pode ser emitido em qualquer índice a qualquer momento, o que significa que, no nível do cluster, várias operações em vários índices podem estar em andamento simultaneamente.

Índices individuais são objetos nomeados que existem em um namespace exclusivo, separado dos outros namespaces do Redis OSS: chaves, funções etc. Cada índice é conceitualmente semelhante a uma tabela de banco de dados convencional, pois está estruturado em duas dimensões: coluna e linhas. Cada linha na tabela corresponde a uma chave Redis OSS. Cada coluna no índice corresponde a um membro ou a uma parte dessa chave. Neste documento, os termos chave, linha e registro são idênticos e usados de maneira intercambiável. Da mesma forma, os termos coluna, campo, caminho e membro são essencialmente idênticos e também são usados de maneira intercambiável.

Não há comandos especiais para adicionar, excluir ou modificar dados indexados. Em vez disso, os comandos HASH ou JSON existentes que modificam uma chave em um índice também atualizam automaticamente esse índice.

Índices e o espaço de teclas Redis OSS

Os índices são construídos e mantidos em um subconjunto do espaço de teclas do Redis OSS. Vários índices podem escolher subconjuntos separados ou sobrepostos do espaço de teclas do Redis OSS, sem limitação. O espaço de chaves para cada índice é definido por uma lista de prefixos de chave que são fornecidos quando esse índice é criado. A lista de prefixos é opcional e, se omitida, todo o espaço de teclas do Redis OSS fará parte desse índice. Os índices também são digitados, pois cobrem apenas as chaves que têm um tipo correspondente. Atualmente, apenas há suporte para índices JSON e HASH. Um índice HASH indexa somente as chaves HASH cobertas por sua lista de prefixos e, da mesma maneira, um índice JSON indexa somente as chaves JSON cobertas por sua lista de prefixos. As chaves na lista de prefixos do espaço de chaves de um índice que não têm o tipo designado são ignoradas e não afetam as operações de pesquisa.

Quando um comando HASH ou JSON modifica uma chave que está dentro de um espaço de chaves de um índice, esse índice é atualizado. Esse processo envolve a extração dos campos declarados para cada índice e a atualização do índice com o novo valor. O processo de atualização é feito em um thread em segundo plano, o que significa que os índices são consistentes apenas eventualmente com o conteúdo do espaço de chaves. Assim, a inserção ou atualização de uma chave não ficará visível nos resultados da pesquisa por um curto período. Durante períodos de grande carga do sistema e/ou forte mutação de dados, o atraso na visibilidade pode se tornar maior.

A criação de um índice é um processo em várias etapas. A primeira etapa é executar o comando FT.CREATE que define o índice. A execução bem-sucedida de uma criação inicia automaticamente a segunda etapa: o preenchimento. O processo de preenchimento é executado em um thread em segundo plano e examina o espaço de chaves do Redis OSS procurando por chaves que estejam na lista de prefixos do novo índice. Cada chave encontrada é adicionada ao índice. Por fim, todo o espaço de chaves é verificado, concluindo o processo de criação do índice. Enquanto o processo de preenchimento está em execução, são permitidas mutações das chaves indexadas, não há restrições, e o processo de preenchimento do índice não será concluído até que todas as chaves estejam devidamente indexadas. Tentativas de operações de consulta feitas enquanto um índice está sendo preenchido não são permitidas e serão encerradas com um erro. A conclusão do processo de preenchimento pode ser determinada pela saída do comando FT.INFO desse índice ('backfill_status').

Tipos de campos de índice

Cada campo (coluna) de um índice tem um tipo específico que é declarado quando esse índice é criado e um local dentro de uma chave. Para chaves HASH, a localização é o nome do campo dentro do HASH. Para chaves JSON, a localização é uma descrição do caminho do JSON. Quando uma chave é modificada, os dados associados aos campos declarados são extraídos, convertidos no tipo declarado e armazenados no índice. Se os dados estiverem ausentes ou não puderem ser convertidos com êxito no tipo declarado, esse campo será omitido do índice. Há quatro tipos de campos, conforme explicado a seguir:

  • Campos numéricos contêm um único número. Para campos JSON, devem ser seguidas as regras numéricas de números JSON. Para HASH, espera-se que o campo contenha o texto ASCII de um número escrito no formato padrão para números fixos ou de pontos flutuantes. Independentemente da representação na chave, esse campo é convertido em um número de pontos flutuantes de 64 bits para armazenamento no índice. Campos numéricos podem ser usados com o operador de pesquisa de intervalo. Como os números subjacentes são armazenados em ponto flutuante com suas limitações de precisão, as regras comuns sobre comparações numéricas para números de pontos flutuantes são aplicáveis.

  • Campos de etiqueta contêm zero ou mais valores de etiqueta codificados como uma única string UTF-8. A string é analisada em valores de etiquetas usando um caractere separador (o padrão é uma vírgula, mas pode ser substituído) com espaços em branco à esquerda e à direita removidos. Qualquer número de valores de etiquetas pode estar contido em um único campo de etiqueta. Campos de etiquetas podem ser usados para filtrar consultas de equivalência de valores de etiquetas com comparação com ou sem distinção entre maiúsculas e minúsculas.

  • Campos de texto contêm um blob de bytes que não precisa ser compatível com UTF-8. Esses campos podem ser usados para decorar resultados de consultas com valores significativos para a aplicação. Por exemplo, um URL ou o conteúdo de um documento etc.

  • Campos vetoriais contêm um vetor de números, também conhecido como incorporação. Esses campos oferecem suporte à pesquisa do vizinho mais próximo (KNN) de vetores com tamanho fixo usando um algoritmo e uma métrica de distância especificados. No caso de índices HASH, o campo deve conter todo o vetor codificado em formato binário (little-endian IEEE 754). No caso de chaves JSON, o caminho deve fazer referência a uma matriz do tamanho correto preenchida com números. Quando uma matriz JSON é usada como um campo vetorial, a representação interna dessa matriz na chave JSON é convertida no formato exigido pelo algoritmo selecionado, reduzindo o consumo e a precisão da memória. Operações de leitura subsequentes usando os comandos JSON produzirão o valor de precisão reduzido.

Algoritmos de índice vetorial

São fornecidos dois algoritmos de índice vetorial:

  • Flat: o algoritmo Flat é um processamento linear por força bruta de cada vetor no índice, produzindo respostas exatas dentro dos limites da precisão dos cálculos de distância. Devido ao processamento linear do índice, os tempos de execução desse algoritmo podem ser muito altos para índices grandes.

  • HNSW (Hierarchical Navigable Small Worlds) — O algoritmo HNSW é uma alternativa que fornece uma aproximação da resposta correta em troca de tempos de execução substancialmente menores. O algoritmo é controlado por três parâmetros: M, EF_CONSTRUCTION e EF_RUNTIME. Os dois primeiros parâmetros são especificados no momento da criação do índice e não podem ser alterados. O parâmetro EF_RUNTIME tem um valor padrão que é especificado no momento da criação do índice, mas pode ser substituído mais tarde em qualquer operação de consulta individual. Esses três parâmetros interagem para equilibrar o consumo de memória e CPU durante operações de ingestão e consulta, bem como para controlar a qualidade da aproximação de uma pesquisa KNN exata (conhecida como taxa de recall).

Ambos os algoritmos de pesquisa vetorial (Flat e HNSW) aceitam um parâmetro opcional INITIAL_CAP. Quando especificado, esse parâmetro pré-aloca memória para os índices, resultando na redução da sobrecarga de gerenciamento de memória e no aumento das taxas de ingestão de vetores.

Algoritmos de pesquisa vetorial, como o HNSW, podem não lidar de maneira eficiente com a exclusão ou substituição de vetores inseridos anteriormente. O uso dessas operações pode resultar no consumo excessivo de memória de índice e/ou degradação na qualidade do recall. A reindexação é um dos métodos para restaurar o uso e/ou a recuperação ideais da memória.

Expressão de consulta de pesquisa vetorial

Os comandos FT.SEARCH e FT.AGGREGATE exigem uma expressão de consulta. Essa expressão é um único parâmetro de cadeia de caracteres que é composto por um ou mais operadores. Cada operador usa um campo no índice para identificar um subconjunto das chaves no índice. Vários operadores podem ser combinados usando combinadores boolianos e parênteses para aprimorar ou restringir ainda mais o conjunto coletado de chaves (ou o conjunto de resultados).

Curinga

O operador curinga, o asterisco ('*'), corresponde a todas as chaves no índice.

Intervalo numérico

O operador de intervalo numérico tem a sintaxe a seguir.

<range-search> ::= '@' <numeric-field-name> ':' '[' <bound> <bound> ']' <bound> ::= <number> | '(' <number> <number> ::= <integer> | <fixed-point> | <floating-point> | 'Inf' | '-Inf' | '+Inf'

O < numeric-field-name > deve ser um campo do tipo declaradoNUMERIC. O limite é inclusivo por padrão, mas um parêntese de abertura inicial ['('] pode ser usado para tornar um limite exclusivo. A pesquisa de intervalo pode ser convertida em uma única comparação relacional (<, <=, >, >=) usando Inf, +Inf ou -Inf como um dos limites. Independentemente do formato numérico especificado (inteiro, ponto fixo, ponto flutuante, infinito), o número é convertido em ponto flutuante de 64 bits para realizar comparações, reduzindo a precisão de acordo.

exemplo Exemplos
@numeric-field:[0 10] // 0 <= <value> <= 10 @numeric-field:[(0 10] // 0 < <value> <= 10 @numeric-field:[0 (10] // 0 <= <value> < 10 @numeric-field:[(0 (10] // 0 < <value> < 10 @numeric-field:[1.5 (Inf] // 1.5 <= value

Comparação de etiquetas

O operador de comparação de etiquetas tem a seguinte sintaxe.

<tag-search> ::= '@' <tag-field-name> ':' '{' <tag> [ '|' <tag> ]* '}'

Se alguma das etiquetas no operador corresponder a qualquer uma das etiquetas no campo de etiqueta do registro, este último será incluído no conjunto de resultados. O campo criado por <tag-field-name> deve ser um campo do índice declarado com o tipo TAG. Exemplos de comparação de etiquetas são:

@tag-field:{ atag } @tag-field: { tag1 | tag2 }

Combinações boolianas

Os conjuntos de resultados de um operador numérico ou de etiqueta podem ser combinados usando a lógica booliana: e/ou. Parênteses podem ser usados para agrupar operadores e/ou alterar a ordem de avaliação. A sintaxe dos operadores lógicos boolianos é:

<expression> ::= <phrase> | <phrase> '|' <expression> | '(' <expression> ')' <phrase> ::= <term> | <term> <phrase> <term> ::= <range-search> | <tag-search> | '*'

Vários termos combinados em uma frase são indicados com “e”. Várias frases combinadas com a barra vertical ('|') são indicadas com “ou”.

Os índices vetoriais oferecem suporte a dois métodos de pesquisa diferentes: vizinho mais próximo e alcance. Uma pesquisa do vizinho mais próximo localiza um número, K, dos vetores no índice que estão mais próximos do vetor fornecido (de referência) — isso é coloquialmente chamado de KNN para “K” vizinhos mais próximos. A sintaxe para uma pesquisa KNN é:

<vector-knn-search> ::= <expression> '=>[KNN' <k> '@' <vector-field-name> '$' <parameter-name> <modifiers> ']' <modifiers> ::= [ 'EF_RUNTIME' <integer> ] [ 'AS' <distance-field-name>]

Uma pesquisa vetorial KNN só é aplicada aos vetores que satisfazem o <expression> que pode ser qualquer combinação dos operadores definidos acima: curinga, pesquisa por intervalo, pesquisa por tag e/ou combinações booleanas dos mesmos.

  • <k> é um número inteiro que especifica o número de vetores vizinhos mais próximos a serem retornados.

  • <vector-field-name> deve especificar um campo de tipo declarado VECTOR.

  • O campo <parameter-name> especifica uma das entradas para a tabela PARAM do comando FT.SEARCH ou FT.AGGREGATE. Esse parâmetro é o valor vetorial de referência para cálculos de distância. O valor do vetor é codificado no valor PARAM no formato binário little-endian IEEE 754 (a mesma codificação de um campo vetorial HASH)

  • Para índices vetoriais do tipo HNSW, a cláusula EF_RUNTIME opcional pode ser usada para substituir o valor padrão do parâmetro EF_RUNTIME que foi estabelecido quando o índice foi criado.

  • O <distance-field-name> opcional fornece um nome de campo para o conjunto de resultados a fim de conter a distância calculada entre o vetor de referência e a chave localizada.

Uma pesquisa de intervalo localiza todos os vetores dentro de uma distância especificada (raio) de um vetor de referência. A sintaxe para uma pesquisa de intervalo é:

<vector-range-search> ::= ‘@’ <vector-field-name> ‘:’ ‘[’ ‘VECTOR_RANGE’ ( <radius> | ‘$’ <radius-parameter> ) $<reference-vector-parameter> ‘]’ [ ‘=’ ‘>’ ‘{’ <modifiers> ‘}’ ] <modifiers> ::= <modifier> | <modifiers>, <modifier> <modifer> ::= [ ‘$yield_distance_as’ ‘:’ <distance-field-name> ] [ ‘$epsilon’ ‘:’ <epsilon-value> ]

Em que:

  • <vector-field-name>é o nome do campo vetorial a ser pesquisado.

  • <radius> or $<radius-parameter>é o limite numérico de distância para pesquisa.

  • $<reference-vector-parameter> é o nome do parâmetro que contém o vetor de referência. O valor do vetor é codificado no valor PARAM no formato binário little-endian IEEE 754 (mesma codificação de um campo vetorial HASH)

  • O opcional <distance-field-name> fornece um nome de campo para o conjunto de resultados para conter a distância calculada entre o vetor de referência e cada chave.

  • O opcional <epsilon-value> controla o limite da operação de pesquisa, vetores dentro da distância <radius> * (1.0 + <epsilon-value>) são percorridos em busca de resultados candidatos. O padrão é 0,1.

Comando INFO

A pesquisa vetorial aumenta o comando Redis OSS INFO com várias seções adicionais de estatísticas e contadores. Uma solicitação para recuperar a seção SEARCH recuperará todas as seções a seguir:

Seção search_memory

Nome Descrição
search_used_memory_bytes Número de bytes de memória consumidos em todas as estruturas de dados de pesquisa
search_used_memory_human Versão legível por humanos do valor acima

Seção search_index_stats

Nome Descrição
search_number_of_indexes Número de índices criados
search_num_fulltext_indexes Número de campos não vetoriais em todos os índices
search_num_vector_indexes Número de campos vetoriais em todos os índices
search_num_hash_indexes Número de índices em chaves de tipo HASH
search_num_json_indexes Número de índices em chaves de tipo JSON
search_total_indexed_keys Número total de chaves em todos os índices
vetores_indexados totais de pesquisa Número total de vetores em todos os índices
search_total_indexed_hash_keys Número total de chaves de tipo HASH em todos os índices
search_total_indexed_json_keys Número total de chaves de tipo JSON em todos os índices
search_total_index_size Bytes usados por todos os índices
search_total_fulltext_index_size Bytes usados por estruturas de índices não vetoriais
search_total_vector_index_size Bytes usados por estruturas de índices vetoriais
search_max_index_lag_ms Atraso na ingestão durante a última atualização do lote de ingestão

Seção search_ingestion

Nome Descrição
search_background_indexing_status Status da ingestão. NO_ACTIVITY significa ocioso. Outros valores indicam que há chaves em processo de ingestão.
search_ingestion_paused Exceto durante a reinicialização, sempre deve ser “no”.

Seção search_backfill

nota

Alguns dos campos documentados nesta seção apenas são visíveis quando um preenchimento está em andamento.

Nome Descrição
search_num_active_backfills Número de atividades atuais de preenchimento
search_backfills_paused Exceto quando estiver sem memória, sempre deve ser “no”.
search_current_backfill_progress_percentage % de conclusão (0-100) do preenchimento atual

Seção search_query

Nome Descrição
search_num_active_queries Número de comandos FT.SEARCH e FT.AGGREGATE em andamento

Segurança da pesquisa vetorial

Os mecanismos de segurança Redis OSS ACL (Access Control Lists) para acesso a comandos e dados são estendidos para controlar o recurso de pesquisa. Há suporte total para o controle de ACL de comandos de pesquisa individuais. É fornecida uma nova categoria de ACL, @search, e muitas das categorias existentes (@fast, @read, @write etc.) são atualizadas para incluir os novos comandos. Os comandos de pesquisa não modificam os dados de chaves, o que significa que o mecanismo de ACL existente para acesso de gravação é preservado. As regras de acesso para operações HASH e JSON não são modificadas pela presença de um índice. O controle normal de acesso em nível de chave ainda é aplicado a esses comandos.

Os comandos de pesquisa com um índice também têm seu acesso controlado por meio do Redis OSS ACL. Verificações de acesso são realizadas no nível do índice inteiro, e não no nível por chave. Isso significa que o acesso a um índice será concedido a um usuário somente se este tiver permissão para acessar todas as chaves possíveis na lista de prefixos do espaço de chaves desse índice. Em outras palavras, o conteúdo real de um índice não controla o acesso. Pelo contrário, é o conteúdo teórico de um índice, conforme definido pela lista de prefixos, que é usado para a verificação de segurança. Pode ser fácil criar uma situação em que um usuário tem acesso de leitura e/ou gravação a uma chave, mas não consegue acessar um índice contendo essa chave. Observe que somente o acesso para leitura ao espaço de chaves é necessário para criar ou usar um índice: a presença ou ausência do acesso para gravação não é levada em consideração.

Para obter mais informações sobre o uso de ACLs com o MemoryDB, consulte Autenticação de usuários com listas de controle de acesso (ACLs).