Evitando problemas com ElasticSearch - Parte 1: Análise

Publicado por Thiago von Sydow no dia dev

Aqui na Resultados Digitais utilizamos frequentemente o ElasticSearch para pesquisar documentos e para a função de autocomplete. Com o passar do tempo, descobrimos falhas na forma como indexamos nossos documentos, portanto tornou-se necessário reestruturá-los. Este post é o primeiro de uma sequência que tem como objetivo expor o aprendizado do time para que você não cometa os mesmos erros.

Vamos começar com a Análise. Ela define como seu texto será dividido em tokens, que são partes pesquisáveis do texto. Basta uma configuração errada para que a pesquisa que você quer realizar não seja possível.

1. Analyzer

Analyzer é apenas um container que aceita um Tokenizer e múltiplos TokenFilters (mais sobre eles abaixo). Você pode definir o seu próprio, ou utilizar algum dos padrões.

2. Tokenizer

O Tokenizer permite criar tokens a partir do seu texto. Eles serão utilizados para encontrar o seu documento. Por exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
curl -XPUT 'localhost:9200/post_elasticsearch' -d '
{
  "settings" : {
    "analysis" : {
      "analyzer" : {
        "email_analyzer" : {
          "tokenizer" : "email_analyzer_tokenizer"
        }
      },
      "tokenizer" : {
        "email_analyzer_tokenizer" : {
          "type" : "uax_url_email"
        }
      }
    }
  },
  "mappings" : {
    "pessoa" : {
      "properties" : {
        "email" : {
          "type" : "String",
          "analyzer" : "email_analyzer"
        }
      }
    }
  }
}'

Aqui criamos um Analyzer email_analyzer com o token uax_url_email, utilizado para URLs e e-mails. Agora vamos analisar uma palavra com nosso Analyzer email_analyzer:

1
GET '/_analyze?analyzer=email_analyzer' -d 'thiago.sydow@resultadosdigitais.com.br'

O resultado é:

1
{ "token" : "thiago.sydow@resultadosdigitais.com.br " }

Até aqui, tudo bem. Vamos pesquisar esse email no nosso index:

1
GET /pessoa/_search -d '{ "query": { "match": { "email": "thiago.sydow@resultadosdigitais.com.br" } } }'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"hits": {
  "total": 1,
  "max_score": 0.028130025,
  "hits": [
     {
        "_index": "post_elasticsearch",
        "_type": "pessoa",
        "_id": "X9M1_X9sQ2SwmybcEJP5NA",
        "_score": 0.028130025,
        "_source": {
           "email": "thiago.sydow@resultadosdigitais.com.br"
        }
     }
  ]
}

Agora vamos pesquisar todas as pessoas com email @resultadosdigitais.com.br:

1
GET /pessoa/_search -d '{ "query": { "match": { "email": "@resultadosdigitais.com.br" } } }'
1
2
3
4
5
"hits": {
  "total": 0,
  "max_score": null,
  "hits": []
}

So close

Nosso primeiro #Fail. O token gerado não permite pesquisas parciais. Como podemos resolver isso? É aí que entra o TokenFilter.

3. TokenFilter

O TokenFilter permite manipular os tokens gerados pelo Tokenizer. Vamos adicionar um no nosso Analyzer email_analyzer. Vou utilizar o Pattern Capture Filter, que permite gerar tokens a partir de uma expressão regular.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
curl -XPUT 'localhost:9200/post_elasticsearch' -d '
{
  "settings" : {
    "analysis" : {
      "analyzer" : {
        "email_analyzer" : {
          "tokenizer" : "uax_url_email",
          "filter": [
            "email_pattern_filter"
          ]
        }
      },
      "filter": {
        "email_pattern_filter": {
          "type": "pattern_capture",
          "preserve_original": "true",
          "patterns": [
            "([^@]+)",
            "(\\p{L}+)",
            "(\\d+)",
            "@(.+)"
          ]
        }
      }
    }
  },
  "mappings" : {
    "pessoa" : {
      "properties" : {
        "email" : {
          "type" : "string",
          "analyzer" : "email_analyzer"
        }
      }
    }
  }
}'

Vamos ver como ficaram os tokens agora:

1
GET '/_analyze?analyzer=email_analyzer' -d 'thiago.sydow@resultadosdigitais.com.br'
1
2
3
4
5
6
7
8
9
{ "token" : "thiago.sydow@resultadosdigitais.com.br" },
{ "token" : "thiago.sydow" },
{ "token" : "thiago" },
{ "token" : "sydow" },
{ "token" : "resultadosdigitais.com.br" },
{ "token" : "resultadosdigitais" },
{ "token" : "resultadosdigitais.com.br" },
{ "token" : "com" },
{ "token" : "br" },

Vários tokens! Todos eles poderão ser utilizados para pesquisar nosso documento. Vamos pesquisar novamente todos os emails @resultadodigitais.com.br e todos os usuários thiago.

1
2
GET /pessoa/_search -d '{ "query": { "match": { "email": "@resultadosdigitais.com.br" } } }'
GET /pessoa/_search -d '{ "query": { "match": { "email": "thiago" } } }'

As pesquisas retornam:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"hits": {
  "total": 1,
  "max_score": 0.028130025,
  "hits": [
     {
        "_index": "post_elasticsearch",
        "_type": "pessoa",
        "_id": "X9M1_X9sQ2SwmybcEJP5NA",
        "_score": 0.028130025,
        "_source": {
           "email": "thiago.sydow@resultadosdigitais.com.br"
        }
     }
  ]
}

Victory

Viu como é fácil errar na hora de indexar seu documento? Um filter a menos – ou a mais – pode te privar de realizar a pesquisa de maneiras diferentes. Por isso vale a pena investir um tempo lendo a documentação completa do ElasticSearch para se certificar que qualquer pesquisa que você oferece aos seus usuários funcione perfeitamente.

Chegamos ao fim da primeira parte. Fique atento para os próximos posts!

Referências

Thiago von Sydow

Thiago von Sydow

Full Stack Developer

Comentários