Fala pessoal! Tudo certo?

Espero que sim! 🙂

Vamos continuar avançando com os conceitos do Elasticsearch.

Caso não tenha acompanhado a primeira postagem, você pode ir no link abaixo, pois ela servirá de base para esta postagem. 😉

https://thedataengineer.com.br/2021/05/14/instalacao-e-utilizacao-elasticsearch-basico/

Os pontos que quero abordar aqui são:

  • Criação de mapping
  • Analyzers
  • Id do index (documento)
  • Bulk

Bora começar então!


Criação de Mapping

Para fazer um paralelo com o mundo relacional e facilitar o entendimento do Mapping, podemos dizer que o Mapping é o “schema” de uma tabela, onde definimos os campos, tipos e regras aplicadas.

No post anterior, deixamos que o próprio Elasticsearch definisse nosso Mapping, isso é legal do ponto de vista de facilidade, mas, pode ser um problema quando um tipo indesejado é atribuído ao campo.

Para verificarmos nosso Mapping atual, podemos utilizar o comando abaixo:

GET /meu-primeiro-index/_mapping

O nosso mapping é retornado como reposta:

{
   "meu-primeiro-index" : {
     "mappings" : {
       "properties" : {
         "avaliacoes" : {
           "type" : "long"
         },
         "direcao" : {
           "type" : "text",
           "fields" : {
             "keyword" : {
               "type" : "keyword",
               "ignore_above" : 256
             }
           }
         },
         "elenco" : {
           "type" : "text",
           "fields" : {
             "keyword" : {
               "type" : "keyword",
               "ignore_above" : 256
             }
           }
         },
         "link_adorocinema" : {
           "type" : "text",
           "fields" : {
             "keyword" : {
               "type" : "keyword",
               "ignore_above" : 256
             }
           }
         },
         "nota" : {
           "type" : "float"
         },
         "roteiro" : {
           "type" : "text",
           "fields" : {
             "keyword" : {
               "type" : "keyword",
               "ignore_above" : 256
             }
           }
         },
         "titulo" : {
           "type" : "text",
           "fields" : {
             "keyword" : {
               "type" : "keyword",
               "ignore_above" : 256
             }
           }
         }
       }
     }
   }
 }

Agora, para definirmos nosso próprio mapping, devemos utilizar a maneira explicita:

PUT /meu-segundo-index
 {
   "mappings": {
    "properties": {
     "titulo"  : {"type" : "text"},
     "direcao" : {"type" : "text"},
     "roteiro" : {"type" : "text"},
     "elenco" : {"type" : "text"},
     "nota" : {"type" : "float"},
     "avaliacoes" : {"type" : "integer"},
     "link_adorocinema" : {"type": "text"}
    } 
   }
 }

Como retorno temos a seguinte saída no Kibana:

{
   "acknowledged" : true,
   "shards_acknowledged" : true,
   "index" : "meu-segundo-index"
 }

Um ponto importante para destacar aqui. Temos alguns campos que tem o tipo “text” que receberão listas no documento, o Elasticsearch não tem um tipo “list” ou “array”, mas todos os campos podem conter mais de um valor por definição, desde que tenham o mesmo tipo.

Para saber mais, veja a documentação: https://www.elastic.co/guide/en/elasticsearch/reference/current/array.html

Analyzers

Analyzers permitem que o Elasticseach realize a indexação do texto e a busca dos mesmos, utilizando analise de texto. Esse recurso é útil para resolver um problema da busca não exata.

Da forma que realizamos a busca na primeira parte, tivemos que incluir a correspondência exata do nome do titulo, caso contrario não teríamos obtido resultado.

Com o analizer adequado, podemos transformar aquele texto em um token que melhor se adequá à nossa indexação e então realizar uma busca não exata pelo termo. Isso é útil quando temos acentos, pontos ou erros de digitação na busca.

Esse é um dos motivos para o Elasticsearch retornar o score de uma busca.

Antes de iniciarmos a pratica, cabe aqui ressaltar que já estamos utilizando um analyzer, ele é o default. Não tivemos que nos preocupar com isso na criação do nosso mapping. Esse analyzer padrão apenas quebra as palavras de um texto por espaço e remove pontos. Digamos então tenhamos os seguintes textos para inserir:

  • Olá, mundo (doc 1)
  • Olá, Elasticsearch (doc 2)
  • Esse mundo é grande (doc 3)

O analyzer padrão irá criar o que se chama de índice invertido, associando a palavra ao documento. O índice gerado do exemplo acima ficaria da seguinte forma:

"olá" : {1, 2}
"mundo" : {1, 3}
"elasticsearch" : {2}
"é" : {3}
"grande" : {3}

Como puderam ver, os acentos são armazenados no índice invertido que é onde o Elasticsearch realiza a busca, logo, se não houver uma correspondência exata na pesquisa não localizaremos o documento.

Os tipos mais comuns de analyzers são:

  • Padrão (deixa o texto em caixa baixa e retira virgulas e pontos).
  • Simples (deixa o texto em caixa baixa e remove algarismos).
  • Idioma (Portuguese, English e etc…).
    • Esse é um dos mais importantes, onde podemos remover acentuação, deixar as palavras no singular e permite que adicionemos sinônimos dentro da nossa pesquisa em recursos mais avançados.

Podemos utilizar o analisador do Elasticsearch para verificar como a analise de um certo texto é feita, vejamos no exemplo abaixo:

GET /meu-segundo-index/_analyze
 {
   "text" : "O Poderoso Chefão"
 }

Resposta:

{
   "tokens" : [
     {
       "token" : "o",
       "start_offset" : 0,
       "end_offset" : 1,
       "type" : "",
       "position" : 0
     },
     {
       "token" : "poderoso",
       "start_offset" : 2,
       "end_offset" : 10,
       "type" : "",
       "position" : 1
     },
     {
       "token" : "chefão",
       "start_offset" : 11,
       "end_offset" : 17,
       "type" : "",
       "position" : 2
     }
   ]
 }

Agora, vejamos como o token seria criado caso utilizássemos o analyzer portuguese:

GET /meu-segundo-index/_analyze
 {
   "analyzer": "portuguese", 
   "text" : "O Poderoso Chefão"
 }

Resposta:

{
   "tokens" : [
     {
       "token" : "poderos",
       "start_offset" : 2,
       "end_offset" : 10,
       "type" : "",
       "position" : 1
     },
     {
       "token" : "chefa",
       "start_offset" : 11,
       "end_offset" : 17,
       "type" : "",
       "position" : 2
     }
   ]
 }

Como podem ver, foram retirados os acentos e realizado um ajuste no texto para evitarmos “poderoso, poderosa, poderosos, etc.”. Isso ajuda o Elasticsearch na busca não exata.

Vamos adicionar o analyzer portuguese em nossos títulos, no momento da criação do índice.

PUT /meu-terceiro-index
  {
    "mappings": {
     "properties": {
      "titulo"  : {
          "type" : "text"
         ,"analyzer": "portuguese"
       },
      "direcao" : {"type" : "text"},
      "roteiro" : {"type" : "text"},
      "elenco" : {"type" : "text"},
      "nota" : {"type" : "float"},
      "avaliacoes" : {"type" : "integer"},
      "link_adorocinema" : {"type": "text"}
     } 
    }
  }

Depois de adicionar o documento abaixo nos dois índices, podemos tentar realizar a busca não exata.

# Documentos
POST meu-segundo-index/_doc/
 {
   "titulo" : "O Poderoso Chefão",
   "direcao" : "Francis Ford Coppola",
   "roteiro" : ["Francis Ford Coppola","Mario Puzo"],
   "elenco" : ["Marlon Brando", "Al Pacino", "James Caan"],
   "nota" : 4.8,
   "avaliacoes" : 6112,
   "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
 }

POST meu-terceiro-index/_doc/
 {
   "titulo" : "O Poderoso Chefão",
   "direcao" : "Francis Ford Coppola",
   "roteiro" : ["Francis Ford Coppola","Mario Puzo"],
   "elenco" : ["Marlon Brando", "Al Pacino", "James Caan"],
   "nota" : 4.8,
   "avaliacoes" : 6112,
   "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
 }

Vamos buscar a palavra “chefao” (sem acento) no “meu-segundo-index”.

GET /meu-segundo-index/_search?q=chefao

# Resultado:

{
   "took" : 0,
   "timed_out" : false,
   "_shards" : {
     "total" : 1,
     "successful" : 1,
     "skipped" : 0,
     "failed" : 0
   },
   "hits" : {
     "total" : {
       "value" : 0,
       "relation" : "eq"
     },
     "max_score" : null,
     "hits" : [ ]
   }
 }

Agora, veja a mesma busca no índice “meu-terceiro-index”.

GET /meu-terceiro-index/_search?q=chefao

# Resultado:

{
   "took" : 0,
   "timed_out" : false,
   "_shards" : {
     "total" : 1,
     "successful" : 1,
     "skipped" : 0,
     "failed" : 0
   },
   "hits" : {
     "total" : {
       "value" : 1,
       "relation" : "eq"
     },
     "max_score" : 0.2876821,
     "hits" : [
       {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "8_s1KXoBlL3R1jOB_LjG",
         "_score" : 0.2876821,
         "_source" : {
           "titulo" : "O Poderoso Chefão",
           "direcao" : "Francis Ford Coppola",
           "roteiro" : [
             "Francis Ford Coppola",
             "Mario Puzo"
           ],
           "elenco" : [
             "Marlon Brando",
             "Al Pacino",
             "James Caan"
           ],
           "nota" : 4.8,
           "avaliacoes" : 6112,
           "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
         }
       }
     ]
   }
 }

Bem legal, né?

Ainda poderíamos adicionar novos elementos dentro do analisador, como por exemplo, sinônimos de palavras, associando uma palavra especifica à outras. Com por exemplo:

Caso buscássemos NoSQL, poderíamos associar essa palavra à outros termos como por exemplo: MongoDB, Redis, Elasticsearch e outros.

Abordarei essa prática em uma outra postagem, creio que o analyzer portuguese já serve de inicio para buscas um pouco mais avançadas que a padrão.

Id do index

Note que quando inserimos um novo documento o Elasticsearch cria o campo “_id” automaticamente. Isso não é muito bom, pois, imagine que eu queira atualizar o mesmo documento?

Se eu tentasse reinserir um documento que já existe, o Elasticsearch criaria dois ids diferentes para cada um deles.

Podemos corrigir esse comportamento de maneira bem simples. Vamos lá? 🙂

Estamos realizando o POST do documento da seguinte forma:

POST meu-terceiro-index/_doc/
 {
   "titulo" : "O Poderoso Chefão",
   "direcao" : "Francis Ford Coppola",
   "roteiro" : ["Francis Ford Coppola","Mario Puzo"],
   "elenco" : ["Marlon Brando", "Al Pacino", "James Caan"],
   "nota" : 4.8,
   "avaliacoes" : 6112,
   "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
 }

Devemos passar nesse POST, também o valor “/1” (id do documento que queremos atualizar) e apenas isso já é o suficiente para atribuirmos o valor que quisermos no campo “_id”

POST meu-terceiro-index/_doc/1
 {
   "titulo" : "O Poderoso Chefão",
   "direcao" : "Francis Ford Coppola",
   "roteiro" : ["Francis Ford Coppola","Mario Puzo"],
   "elenco" : ["Marlon Brando", "Al Pacino", "James Caan"],
   "nota" : 4.8,
   "avaliacoes" : 6112,
   "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
 }

Agora podemos realizar o GET desse documento, conforme a sintaxe abaixo:

GET meu-terceiro-index/_doc/1

#Resultado:
{
   "_index" : "meu-terceiro-index",
   "_type" : "_doc",
   "_id" : "1",
   "_version" : 1,
   "_seq_no" : 1,
   "_primary_term" : 2,
   "found" : true,
   "_source" : {
     "titulo" : "O Poderoso Chefão",
     "direcao" : "Francis Ford Coppola",
     "roteiro" : [
       "Francis Ford Coppola",
       "Mario Puzo"
     ],
     "elenco" : [
       "Marlon Brando",
       "Al Pacino",
       "James Caan"
     ],
     "nota" : 4.8,
     "avaliacoes" : 6112,
     "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
   }
 }

Podemos notar que além do valor “_id” estar com “1” (valor que atribuímos), ele também tem a propriedade “_version“, com o valor 1.

Esse campo corresponde a versão do documento, caso eu realize um novo POST esse numero mudará, veja:

POST meu-terceiro-index/_doc/1
 {
   "titulo" : "O Poderoso Chefão",
   "direcao" : "Francis Ford Coppola",
   "roteiro" : ["Francis Ford Coppola","Mario Puzo"],
   "elenco" : ["Marlon Brando", "Al Pacino", "James Caan"],
   "nota" : 4.8,
   "avaliacoes" : 6112,
   "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
 }

GET meu-terceiro-index/_doc/1

# Resultado:
{
   "_index" : "meu-terceiro-index",
   "_type" : "_doc",
   "_id" : "1",
   "_version" : 2,
   "_seq_no" : 2,
   "_primary_term" : 2,
   "found" : true,
   "_source" : {
     "titulo" : "O Poderoso Chefão",
     "direcao" : "Francis Ford Coppola",
     "roteiro" : [
       "Francis Ford Coppola",
       "Mario Puzo"
     ],
     "elenco" : [
       "Marlon Brando",
       "Al Pacino",
       "James Caan"
     ],
     "nota" : 4.8,
     "avaliacoes" : 6112,
     "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
   }
 }

Não é possível recuperar versões anteriores do documento, caso você queira realizar esse tipo de ação, terá que implementar você mesmo. Esse campo serve apenas para verificarmos quantas vezes o documento foi sobrescrito.

Bulk

O Bulk é utilizado quando queremos realizar uma tarefa massiva dentro do Elasticsearch, para essa postagem, irei demostrar como inserir dados de forma massiva.

Podemos executar muitas outras ações, como create, update e delete.

Para mais informações veja a documentação: https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html

Agora, vamos inserir 3 documentos de uma só vez utilizando o bulk:

POST meu-terceiro-index/_bulk
 {"index":{"_id":2}}
 {"titulo":"O Auto da Compadecida","direcao":"Guel Arraes","roteiro":["Guel Arraes","João Falcão"],"elenco":["Matheus Nachtergaele","Selton Mello","Denise Fraga"],"nota":4.7,"avaliacoes":3026,"link_adorocinema":"https://www.adorocinema.com/filmes/filme-120824/"}
 {"index":{"_id":3}}
 {"titulo":"A Lista de Schindler","direcao":"Steven Spielberg","roteiro":"Steven Spielberg","elenco":["Liam Neeson","Ben Kingsley","Ralph Fiennes"],"nota":4.8,"avaliacoes":4744,"link_adorocinema":"https://www.adorocinema.com/filmes/filme-9393/"}
 {"index":{"_id":4}}
 {"titulo":"O Senhor dos Anéis - O Retorno do Rei","direcao":"Peter Jackson","roteiro":["Peter Jackson","Philippa Boyens"],"elenco":["Sean Astin","Elijah Wood","Viggo Mortensen"],"nota":4.7,"avaliacoes":7726,"link_adorocinema":"https://www.adorocinema.com/filmes/filme-39187/"}

# Resultado:
{
   "took" : 27,
   "errors" : false,
   "items" : [
     {
       "index" : {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "2",
         "_version" : 1,
         "result" : "created",
         "_shards" : {
           "total" : 2,
           "successful" : 1,
           "failed" : 0
         },
         "_seq_no" : 3,
         "_primary_term" : 2,
         "status" : 201
       }
     },
     {
       "index" : {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "3",
         "_version" : 1,
         "result" : "created",
         "_shards" : {
           "total" : 2,
           "successful" : 1,
           "failed" : 0
         },
         "_seq_no" : 4,
         "_primary_term" : 2,
         "status" : 201
       }
     },
     {
       "index" : {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "4",
         "_version" : 1,
         "result" : "created",
         "_shards" : {
           "total" : 2,
           "successful" : 1,
           "failed" : 0
         },
         "_seq_no" : 5,
         "_primary_term" : 2,
         "status" : 201
       }
     }
   ]
 }

Uma coisa que deve-se notar, é que a indentação importa no caso da inserção. Cada dado deve estar na sua linha.

Depois, podemos realizar um GET para verificar os resultados.

GET meu-terceiro-index/_search
 {
   "query": {
     "match_all": {}
   }
 }

# Resultado:

{
   "took" : 3,
   "timed_out" : false,
   "_shards" : {
     "total" : 1,
     "successful" : 1,
     "skipped" : 0,
     "failed" : 0
   },
   "hits" : {
     "total" : {
       "value" : 5,
       "relation" : "eq"
     },
     "max_score" : 1.0,
     "hits" : [
       {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "8_s1KXoBlL3R1jOB_LjG",
         "_score" : 1.0,
         "_source" : {
           "titulo" : "O Poderoso Chefão",
           "direcao" : "Francis Ford Coppola",
           "roteiro" : [
             "Francis Ford Coppola",
             "Mario Puzo"
           ],
           "elenco" : [
             "Marlon Brando",
             "Al Pacino",
             "James Caan"
           ],
           "nota" : 4.8,
           "avaliacoes" : 6112,
           "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
         }
       },
       {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "1",
         "_score" : 1.0,
         "_source" : {
           "titulo" : "O Poderoso Chefão",
           "direcao" : "Francis Ford Coppola",
           "roteiro" : [
             "Francis Ford Coppola",
             "Mario Puzo"
           ],
           "elenco" : [
             "Marlon Brando",
             "Al Pacino",
             "James Caan"
           ],
           "nota" : 4.8,
           "avaliacoes" : 6112,
           "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-1628/"
         }
       },
       {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "2",
         "_score" : 1.0,
         "_source" : {
           "titulo" : "O Auto da Compadecida",
           "direcao" : "Guel Arraes",
           "roteiro" : [
             "Guel Arraes",
             "João Falcão"
           ],
           "elenco" : [
             "Matheus Nachtergaele",
             "Selton Mello",
             "Denise Fraga"
           ],
           "nota" : 4.7,
           "avaliacoes" : 3026,
           "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-120824/"
         }
       },
       {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "3",
         "_score" : 1.0,
         "_source" : {
           "titulo" : "A Lista de Schindler",
           "direcao" : "Steven Spielberg",
           "roteiro" : "Steven Spielberg",
           "elenco" : [
             "Liam Neeson",
             "Ben Kingsley",
             "Ralph Fiennes"
           ],
           "nota" : 4.8,
           "avaliacoes" : 4744,
           "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-9393/"
         }
       },
       {
         "_index" : "meu-terceiro-index",
         "_type" : "_doc",
         "_id" : "4",
         "_score" : 1.0,
         "_source" : {
           "titulo" : "O Senhor dos Anéis - O Retorno do Rei",
           "direcao" : "Peter Jackson",
           "roteiro" : [
             "Peter Jackson",
             "Philippa Boyens"
           ],
           "elenco" : [
             "Sean Astin",
             "Elijah Wood",
             "Viggo Mortensen"
           ],
           "nota" : 4.7,
           "avaliacoes" : 7726,
           "link_adorocinema" : "https://www.adorocinema.com/filmes/filme-39187/"
         }
       }
     ]
   }
 }

Notem que até o primeiro índice inserido sem passar o _id está entre os resultados. 😀

Essa é uma ótima maneira de realizar operações massivas, recomento a leitura da documentação, lá temos todas as informações necessárias para performar esse tipo de ação.

Conclusão

Bom, espero ter contribuído nem que seja um pouquinho com a melhora do conhecimento sobre o Elasticsearch.

Eu gosto bastante da ferramenta, ela pode se encaixar em diversos cenários, e como puderam ver, ela é de simples instalação.

Eu ainda me aprofundarei em como implementa-lá em um cluster, visto que uma das vantagens é a escalabilidade.

Também trarei em uma outra postagem o aprofundamento dos analyzers que é um recurso fundamental da tecnologia.

Espero que tenham gostado! Até logo! 🙂

Categorias: NoSQL

Jefferson Soares

Olá! Sou Jefferson. Trabalho com: Dados, Dashboards, SQL, SAS, Python e muito mais! Criei esse cantinho para postar alguns conhecimentos. :)

5 2 votes
Article Rating
Subscribe
Notify of
guest
0 Comentários
Inline Feedbacks
View all comments