Introdução

O Couchbase Full Text Search (FTS) é uma ótima opção para indexar várias matrizes e executar consultas com vários predicados de filtro em matrizes. Neste artigo, demonstrarei as vantagens de usar o FTS em vez do GSI (Índice Secundário Global) para indexação de matrizes, enquanto trabalho em um caso de uso de exemplo que exige a consulta de várias matrizes. Criaremos um índice FTS de várias matrizes e consultaremos o índice com N1QL usando a nova função SEARCH() introduzida no Couchbase Server 6.5.  

Balde de amostras de viagem

Neste artigo, faremos referência ao Conjunto de dados de amostra de viagens disponível para instalação em qualquer instância do Couchbase Server. O bucket de amostra de viagem tem vários tipos de documentos distintos: companhia aérea, rota, aeroporto, ponto de referência e hotel. O modelo de documento para cada tipo de documento contém:

  • Uma chave que atua como uma chave primária
  • Um campo de id que identifica o documento
  • Um campo de tipo que identifica o tipo de documento

 

Os exemplos deste artigo usarão os documentos de hotel. O exemplo de documento abaixo lhe dá uma ideia da estrutura de um documento de hotel: 

Figura 1 - Exemplo de documento de hotel

O problema

Nosso exemplo é um caso de uso em que um usuário pode pesquisar hotéis que tenham sido avaliados ou curtidos por uma pessoa com um nome específico. Para isso, é necessário consultar os documentos do hotel em curtidas e avaliações públicas, que são matrizes no modelo de documento do hotel: 

Figura 2 - As matrizes "public_likes" e "reviews" no documento de exemplo de hotel

 

Primeiro, vamos dar uma olhada na implementação desse caso de uso com N1QL e GSI (Global Secondary Index). Para encontrar hotéis que alguém chamado Ozella tenha gostado ou avaliado, a consulta poderia ser assim: 

 

Precisamos criar um índice apropriado para essa consulta. Talvez algo como isto, que indexa ambas as matrizes de interesse para documentos de hotéis: 

 

Isso não funciona, e obtemos o erro mostrado na Figura 3:

Figura 3 - Erro ao criar índice com várias matrizes

 

Como Keshav Murthy escreveu em sua postagem no blog Busca e resgate: 7 razões para os desenvolvedores de N1QL (SQL) usarem o Search (problema #6), com N1QL no Couchbase, "Para obter o melhor desempenho ao pesquisar dentro de matrizes, você precisa criar índices com chaves de matriz. O índice de matriz vem com uma limitação: cada índice de matriz só pode ter uma chave de matriz por índice. Portanto, quando você tem um objeto de cliente com vários campos de matriz, não é possível pesquisar todos eles usando um único índice... o que causa consultas caras." Como Keshav observa nesse artigo, essa é uma limitação dos índices b-tree nos bancos de dados em geral. 

 

Então, agora vamos tentar dois índices de matriz separados. Os índices para dar suporte a essa consulta podem se parecer com estes, que foram criados usando o comando Consultor de índice N1QL do Couchbaseum novo recurso (DP) no Couchbase 6.5:

 

 

Com esses dois índices implementados, nossa consulta é executada com sucesso com 5 resultados (hotel_26020, hotel_10025, hotel_5081, hotel_20425, hotel_25327) e o seguinte plano de execução: 

Figura 4 - Plano de execução usando vários índices (GSI)

O mesmo plano em JSON:

 

No cluster de nó único que está sendo usado para esses exemplos, o tempo decorrido da consulta é de cerca de 190-200 milissegundos para retornar os 5 documentos resultantes. Como você pode ver no plano, há dois operadores IndexScan3 que usam cada um dos dois índices de matriz que criamos, seguidos por um DistinctScan para os resultados de cada varredura de índice e, em seguida, um UnionScan. O UnionScan mostra um valor #itemsIn de 1646 documentos e um valor #itemsOut de 904 documentos, o operador Fetch também obtém 904 documentos e, finalmente, com o operador Filter, obtemos um valor #ItemsOut de 5. A busca de 904 documentos é um desperdício, considerando que acabamos com 5 documentos retornados pela consulta e, de fato, cerca de 170 milissegundos do tempo total decorrido são gastos na busca de 905 documentos quando apenas 5 são necessários.  

A solução

Por outro lado, um índice invertido FTS pode ser facilmente criado para várias matrizes e é adequado para casos em que você precisa pesquisar campos em várias matrizes. Criaremos um índice FTS nos documentos do hotel para a matriz public_likes e o campo author na matriz reviews. 

 

Etapas de criação do índice: 

  1. Na interface de usuário da Pesquisa de texto completo, clique em "Add Index" (Adicionar índice).
  2. Especifique um nome de índice, por exemplo, "hotel_mult_arrays", e selecione o bucket de amostra de viagem. 
  3. Como cada documento no bucket de amostra de viagem tem um campo "type" que indica o tipo de documento, deixe "JSON type field" definido como "type".
  4. Em mapeamentos de tipos:  
    1. Clique em "+ Add Type Mapping" e especifique "hotel" como o nome do tipo, pois o requisito é pesquisar todos os documentos de hotéis.  
    2. Uma lista de analisadores disponíveis pode ser acessada por meio do menu suspenso à direita do campo de nome do tipo. Para esse caso de uso, deixe a opção "inherit" selecionada para que o mapeamento de tipos herde o analisador padrão do índice.
    3. Como o requisito é pesquisar os gostos públicos do hotel e revisar os campos do autor, marque "indexar somente campos especificados". Com essa opção marcada, somente os campos especificados pelo usuário do documento serão incluídos no índice para o mapeamento do tipo de hotel (o mapeamento não será dinâmico, o que significa que todos os campos serão considerados disponíveis para indexação). 
    4. Clique em OK.  
    5. Passe o mouse sobre a linha com o mapeamento do tipo de hotel, clique no botão + e, em seguida, clique em "inserir campo filho". Isso permitirá que a matriz public_likes seja incluída individualmente no índice. Especifique o seguinte: 
      1. campo: Digite o nome do campo a ser indexado, "public_likes".
      2. type (tipo): Deixe essa opção definida como text para a matriz public_likes.
      3. pesquisável como: Deixe-o igual ao nome do campo para o caso de uso atual. Pode ser usado para indicar um nome de campo alternativo. 
      4. analisador: Como foi feito para o mapeamento de tipos, para esse caso de uso, deixe a opção "herdar" selecionada para que que o mapeamento de tipos herda o analisador padrão.
      5. caixa de seleção do índice: Deixe essa opção marcada para que o campo seja incluído no índice. Desmarcar a caixa removeria explicitamente o campo do índice.
      6. caixa de seleção da loja: Marque essa configuração para incluir o conteúdo do campo nos resultados da pesquisa, o que permite destacar as expressões correspondentes nos resultados. Isso é útil para testar o índice, mas não é recomendado no Prod se o destaque não for necessário, pois aumenta o tamanho do índice.
      7. Caixa de seleção "incluir no campo _all": Deixe-a marcada, pois o requisito do caso de uso é pesquisar vários campos. 
      8. Caixa de seleção "incluir vetores de termos": Deixe-a marcada também durante o desenvolvimento e teste do nosso índice para permitir o destaque dos resultados. 
      9. caixa de seleção docvalues: Desmarque essa configuração. Essa configuração armazena os valores de campo no índice, o que oferece suporte para as Facetas de pesquisa e para a classificação dos resultados de pesquisa com base nos valores de campo, nenhum dos quais precisamos neste caso de uso. 
      10. Clique em OK.
    6. Passe o mouse sobre a linha com o mapeamento do tipo de hotel, clique no botão + e, em seguida, clique em "inserir mapeamento de filhos". Isso permitirá que a matriz de subdocumentos de revisão seja incluída no índice. Digite o nome da propriedade "reviews", deixe a opção "inherit" selecionada no menu suspenso do analisador, marque a opção "only index specified fields" e clique em OK. 
    7. Passe o mouse sobre a linha com o mapeamento filho das revisões, clique no botão + e, em seguida, clique em "inserir campo filho". Isso permitirá que o campo de autor da matriz de subdocumentos de revisão seja incluído no índice. Especifique o seguinte: 
      1. campo: Digite o nome do campo a ser indexado, "author" (autor).
      2. type (tipo): Deixe essa opção definida como texto para o campo de autor.
      3. pesquisável como: Deixe-o igual ao nome do campo para o caso de uso atual. Pode ser usado para indicar um nome de campo alternativo. 
      4. analisador: Como foi feito para o mapeamento de tipos, para esse caso de uso, deixe a opção "herdar" selecionada para que que o mapeamento de tipos herda o analisador padrão.
      5. caixa de seleção do índice: Deixe essa opção marcada para que o campo seja incluído no índice. Desmarcar a caixa removeria explicitamente o campo do índice.
      6. caixa de seleção da loja: Marque essa configuração para incluir o conteúdo do campo nos resultados da pesquisa, o que permite destacar as expressões correspondentes nos resultados. Isso é útil para testar o índice, mas não é recomendado no Prod se o destaque não for necessário, pois aumenta o tamanho do índice.
      7. Caixa de seleção "incluir no campo _all": Deixe-a marcada, pois o requisito do caso de uso é pesquisar vários campos. 
      8. Caixa de seleção "incluir vetores de termos": Deixe-a marcada também durante o desenvolvimento e teste do nosso índice para permitir o destaque dos resultados.  
      9. caixa de seleção docvalues: Desmarque essa configuração. Essa configuração armazena os valores de campo no índice, o que oferece suporte para as Facetas de pesquisa e para a classificação dos resultados de pesquisa com base nos valores de campo, nenhum dos quais precisamos neste caso de uso. 
      10. Clique em OK.
    8. Por fim, desmarque a caixa de seleção ao lado do mapeamento de tipo "padrão". Se o mapeamento padrão for deixado ativado, todos os documentos no intervalo serão incluídos no índice, independentemente de o usuário especificar ativamente os mapeamentos de tipo. Somente os documentos do hotel são necessários, e eles são incluídos pelo mapeamento de tipo de hotel adicionado anteriormente. 
  5. Os valores padrão são suficientes para os painéis recolhidos restantes (Analyzers, Custom Filters, Date/Time Parsers e Advanced). 
  6. As réplicas de índice podem ser definidas como 1, 2 ou 3, desde que o cluster esteja executando o serviço Search em n+1 nós. Com um ambiente de desenvolvimento de nó único, mantenha o valor padrão de 0. 
  7. Para Tipo de índice, o valor padrão de "Versão 6.0 (Scorch)" é apropriado para todos os índices recém-criados. O Scorch reduz o tamanho do índice no disco e oferece desempenho aprimorado do MongoDB no operador para indexação e tratamento de mutação.
  8. As partições de índice podem ser deixadas com o valor padrão de 6. 
  9. Nesse ponto, a página de índice de criação deve ter a seguinte aparência o último quadro capturado em Figura 5. Clique em "Create Index" para concluir o processo. 

 

Figura 5 - Criação de índice FTS com várias matrizes

 

Observação: consulte o Apêndice para ver a carga útil JSON usada para criar esse índice por meio da API REST.

 

Teste de consultas no índice: 

  1. Na UI de pesquisa de texto completo, aguarde até que o progresso da indexação mostre 100% e, em seguida, clique no nome do índice "hotel_mult_arrays". 
  2. Para pesquisar todos os hotéis com curtidas ou avaliações de alguém chamado "Ozella", na caixa de texto "pesquisar este índice...", digite "Ozella" e clique em Pesquisar. O escopo de campo da pesquisa não é necessário porque ambos os campos indexados estão incluídos no campo padrão "_all".
  3. Os resultados são mostrados (semelhante à Figura 6) com a chave de cada documento correspondente e os campos correspondentes destacados. Os IDs de documentos retornados são os mesmos da nossa consulta N1QL anterior.  

Figura 6 - Resultados da pesquisa do índice "hotel_mult_arrays" para "Ozella"

 

Esse é um índice único em 2 chaves de matriz, o que, conforme mencionado anteriormente, é algo que nunca poderia ser feito em um índice baseado em b-tree. Então, agora vamos aproveitar esse índice FTS em uma consulta N1QL usando a função SEARCH(). Nossa consulta poderia ter a seguinte aparência: 

 

Alguns aspectos a serem observados sobre a consulta: 

  • A cláusula USE INDEX...USING FTS especifica que o índice FTS deve ser usado em vez de um índice GSI, portanto, essa consulta não usa o serviço de índice. (Documentação)
  • Como nosso índice FTS usa um mapeamento de tipo personalizado, a consulta precisa ter o tipo correspondente especificado na cláusula WHERE (t.type="hotel").  
  • O nome do índice FTS é especificado no campo "index" na função SEARCH() como uma dica, mas isso é opcional, pois a cláusula USE INDEX tem precedência sobre uma dica fornecida no campo "index". (Documentação)  

Usando o índice FTS que criamos, nossa consulta N1QL é executada com êxito e retorna 5 resultados (hotel_5081, hotel_26020, hotel_10025, hotel_20425, hotel_25327) e o seguinte plano de execução: 

Figura 7 - Plano de execução usando vários índices (FTS)

O mesmo plano em JSON:

 

No cluster de nó único que está sendo usado para esses exemplos, o tempo decorrido da consulta é de cerca de 20 milissegundos para retornar os mesmos 5 documentos. Como você pode ver no plano, há um operador IndexFtsSearch, mas não há operadores IndexScan3, DistinctScan, UnionScan ou IntersectScan. A consulta geral é muito mais eficiente sem esses caros operadores GSI. O operador IndexFtsSearch envia os 5 documentos correspondentes do índice FTS para o operador Fetch, que obtém apenas esses 5 documentos. A busca é muito mais eficiente aqui do que na consulta anterior, pois está buscando apenas 5 contra 904 documentos, e isso também pode ser observado na comparação dos tempos totais decorridos (e o servTime para os operadores de busca: 170ms na consulta 1 e 1,5ms na consulta 2) entre as consultas.  

 

Conclusão

Com o GSI, é possível misturar e combinar vários índices de matriz em uma única consulta, mas com o FTS é possível misturar e combinar várias matrizes em um único índice FTS (e com o FTS não há problema de chave principal como no GSI em relação à ordem dos campos no índice). Como mostramos neste exemplo simples de consulta a 2 matrizes nos documentos do hotel, a utilização da nova função SEARCH() no N1QL pode resultar em consultas a matrizes mais simples e de melhor desempenho. O mesmo conceito poderia ser aplicado a consultas que utilizam várias matrizes, o que teria resultados ainda mais favoráveis em relação às consultas N1QL que utilizam vários índices de matriz GSI. Essa abordagem usa menos recursos do sistema e oferece maior rendimento, o que resulta em um aumento na eficiência geral do sistema.  

Esse é apenas um exemplo dos benefícios da integração entre o N1QL e o FTS, e outros benefícios estão documentados nas publicações do blog na seção de referências abaixo. 

 

Referências

 

Apêndice

Definição do índice JSON: hotel_mult_arrays

 

 

Autor

Postado por Brian Kane, engenheiro de soluções, Couchbase

Brian Kane é engenheiro de soluções da Couchbase e trabalha com desenvolvimento de aplicativos e tecnologias de banco de dados desde 1996. Ele mora na região de Houston, Texas.

Deixar uma resposta