Produto

Entendendo "Query Then Fetch" vs "DFS Query Then Fetch"

Em nosso último artigo sobre correspondência de frase "starts-with", executamos uma situação em que as pontuações retornadas eram suspeitas. Como uma recapitulação, aqui está a consulta em questão:

$ curl -XGET localhost:9200/startswith/test/_search?pretty -d '{
        "query": {
        "match_phrase_prefix": {
           "title": {
             "query": "d",
             "max_expansions": 5
           }
         }
       }
     }' | grep title

      "_score" : 1,0, "_source" : {"title":"drunk"}
      "_score" : 0,30685282, "_source" : {"title":"dzone"}
      "_score" : 0,30685282, "_source" : {"title":"data"}
      "_score" : 0,30685282, "_source" : {"title":"drive"}

Veja como o documento “drunk” recebe uma pontuação de 1,0, enquanto os outros têm uma pontuação de 0,3? Esses documentos não deveriam ter todos a mesma pontuação, já que correspondem à consulta por “d” da mesma forma? A resposta é sim, mas há um motivo muito bom para essa discrepância de pontuação.

Pontuação de relevância

Parte do algoritmo de pontuação usado pelo Elasticsearch (e pelo Lucene subjacente) inclui estatísticas de “Frequência de termo – Frequência inversa de documento” (TF-IDF) para ajudar a calcular a relevância dos documentos no índice.

Muito já foi escrito sobre o assunto TF-IDF, mas basicamente diz que “quanto mais um termo aparece em um documento, mais relevante esse documento é. Mas a relevância é atenuada pela frequência com que o termo aparece em todo o índice”.

Termos raros estão presentes apenas em alguns documentos, o que significa que qualquer consulta que corresponda a um termo raro se torna altamente relevante. Por outro lado, termos comuns são encontrados em toda parte, portanto, sua relevância para a consulta é baixa.

O Elasticsearch enfrenta um dilema interessante quando você executa uma busca. Sua consulta precisa encontrar todos os documentos relevantes... mas esses documentos estão espalhados por qualquer número de shards em seu cluster.

Cada shard é basicamente um índice Lucene, que mantém suas próprias estatísticas de TF e DF. Um shard só sabe quantas vezes “pineapple” aparece dentro do shard, não em todo o cluster.

Mas o algoritmo de relevância usa TF-IDF... ele não precisa saber como são o TF e o DF de todo o índice, e não de cada shard?

Tipo de busca padrão: Query Then Fetch

A resposta é sim e não. Por padrão, o Elasticsearch usará um tipo de busca chamado “Query Then Fetch“. A maneira como funciona é a seguinte:

  1. Envie a consulta para cada shard
  2. Encontrar todos os documentos correspondentes e calcular as pontuações usando frequências locais de Termo/Documento
  3. Crie uma fila de prioridade de resultados (classificação, paginação com de/para, etc.)
  4. Retornar metadados sobre os resultados para o Node solicitante. Observe que o documento real ainda não foi enviado, apenas as pontuações
  5. As pontuações de todos os shards são mescladas e classificadas no Node solicitante, os documentos são selecionados de acordo com os critérios de consulta
  6. Finalmente, os documentos reais são recuperados de shards individuais onde residem.
  7. Os resultados são retornados ao cliente

Este sistema geralmente funciona bem. Na maioria dos casos, seu índice tem documentos "suficientes" para suavizar as estatísticas de frequência de Termo/Documento. Portanto, embora cada shard possa não ter conhecimento completo das frequências em todo o cluster, os resultados são "bons o suficiente" porque as frequências são bastante semelhantes em toda parte.

Mas no caso da nossa consulta mencionada no início deste artigo, o tipo de busca padrão às vezes falha.

DFS Query Then Fetch

No último artigo, criamos um índice sem especificar a contagem de shards – o Elasticsearch usou o padrão de 5 shards. Em seguida, inserimos apenas cinco documentos no índice e exigimos que o ES retornasse resultados relevantes e pontuações precisas. Não é muito justo, é?

As discrepâncias de pontuação foram causadas pelo tipo de busca Query Then Fetch. Cada shard continha apenas 1 ou 2 documentos (o algoritmo de hash usado pelo ES garante uma distribuição relativamente aleatória). Quando pedimos ao Elastic para calcular as pontuações, cada shard tinha apenas uma visão minúscula do índice de cinco documentos... então as pontuações estavam imprecisas.

Felizmente, o Elasticsearch não deixa você na mão. Se você tiver uma situação em que essa discrepância de pontuação seja problemática, o ES fornece um tipo de busca chamado "DFS Query Then Fetch". O procedimento é quase idêntico ao Query Then Fetch, exceto que ele realiza uma pré-consulta para calcular as frequências globais dos documentos.

  1. Fazer uma pré-consulta em cada shard perguntando sobre as frequências de Termo e Documento
  2. Envie a consulta para cada shard
  3. Encontre todos os documentos correspondentes e calcule as pontuações usando frequências globais de Termo/Documento calculadas a partir da pré-consulta.
  4. Crie uma fila de prioridade de resultados (classificação, paginação com de/para, etc.)
  5. Retornar metadados sobre os resultados para o Node solicitante. Observe que o documento real ainda não foi enviado, apenas as pontuações
  6. As pontuações de todos os shards são mescladas e classificadas no Node solicitante, os documentos são selecionados de acordo com os critérios de consulta
  7. Finalmente, os documentos reais são recuperados de shards individuais onde residem.
  8. Os resultados são retornados ao cliente

Se aplicarmos este novo tipo de buscar à nossa consulta anterior, obteremos resultados de pontuação que fazem sentido (por exemplo, todos são idênticos):

$ curl -XGET 'localhost:9200/startswith/test/_buscar?pretty=true&buscar_type=dfs_query_then_fetch' -d '{
        "query": {
        "match_phrase_prefix": {
           "title": {
             "query": "d",
             "max_expansions": 5
           }
         }
       }
     }' | grep title

      "_score" : 1,9162908, "_source" : {"title":"dzone"}
      "_score" : 1,9162908, "_source" : {"title":"data"}
      "_score" : 1,9162908, "_source" : {"title":"drunk"}
      "_score" : 1,9162908, "_source" : {"title":"drive"}

Conclusão

É claro que uma precisão melhor não vem de graça. A pré-consulta causa um round-trip extra entre os shards, o que pode causar uma queda no desempenho dependendo do tamanho do índice, número de shards, taxa de consulta, etc. E, na maioria dos casos, é totalmente desnecessário... ter dados "suficientes" resolve o problema para você.

Mas, às vezes, você encontrará situações estranhas de pontuação e, nesses casos, é útil saber como ajustar o plano de execução de buscar com DFS Query then Fetch.