Data

19 ago, 2016

Elastic Search + MongoDB + Node.js

Publicidade

Olá a todos! Hoje fazendo um artigo bem rápido, uma sugestão do meu colega Rafael Ferreira: utilizar o Elastic Search + MongoDB para melhorar os resultados de busca em texto. Lancei recentemente para testes o projeto http://itssimple.com.br/, e a performance é muito boa, mas vamos melhorar a pesquisa, pois é fundamental para o projeto. Vamos aos testes com Elastic Search e comparar com o $text do MongoDB.

Instalando o Elastic Search

Nada muito complexo, vamos aos comandos:

sudo apt-get install openjdk-7-jre
wget https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.7.2.deb
sudo dpkg -i elasticsearch-1.7.2.deb
sudo update-rc.d elasticsearch defaults
sudo service elasticsearch start

Para testar se o servidor está ativo, vamos usar o comando:

curl http://localhost:9200
{
  "status" : 200,
  "name" : "Earth Lord",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "1.7.2",
    "build_hash" : "e43676b1385b8125d647f593f7202acbd816e8ec",
    "build_timestamp" : "2015-09-14T09:49:53Z",
    "build_snapshot" : false,
    "lucene_version" : "4.10.4"
  },
  "tagline" : "You Know, for Search"
}

Instalando plugin do MongoDB para Elastic Search

cd /usr/share/elasticsearch
sudo bin/plugin --install com.github.richardwilly98.elasticsearch/elasticsearch-river-mongodb/1.6.0
sudo bin/plugin --install elasticsearch/elasticsearch-mapper-attachments/1.6.0
sudo bin/plugin --install mobz/elasticsearch-head
sudo bin/plugin --install lukas-vlcek/bigdesk
sudo service elasticsearch restart

Criando índice

O Elastic Search é uma ferramenta de pesquisa, logo ele cria seus próprios registros. Caso já possua uma base no MongoDB, será necessário enviar para o Elastic. É possível ainda instalar plugins para sincronizar sua base MongoDB e o Elastic, porém, no meu caso específico, que é sincronizar uma base com mais de 4 milhões de registros e subindo, não sei ainda se vale apena, por isso o envio para o índice somente ocorre ao realizar a migração de dados. A criação do índice é muito importante, pois há uma série de parâmetros que devem ser configurados. O índice padrão, por exemplo, não me atendeu, pois ele realiza análise comparativa de todo o texto e atribui um peso por paridade que, no caso de produtos, não retorna uma pesquisa aceitável, e só fui descobrir isso depois de migrar minha base toda. Assim, tive que recriar o índice utilizando parâmetros mais coerentes com a minha necessidade, por isso vou deixar links explicando sobre os índices e suas configurações:

Criando índices simples

O ElasticSearch utiliza uma API de integração na porta 9200 na qual serão enviados comandos via HTTP REST para realizar ações. Podemos fazer isso por meio de terminal ou por módulos no Node.js como https://www.npmjs.com/package/elasticsearch. Inicialmente, vamos criar manualmente para entender o processo.

curl -XPUT 'http://localhost:9200/meuindice/'

Ao enviar o comando acima, será criado um índice chamando ‘meuindice’ utilizando as configurações padrões. Para verificar informações sobre os índices criados e realizar alguns testes, vamos utilizar o plugin head já instalado acima. O acesso é via navegador pelo endereço http://localhost:9200/_plugin/head/ Captura de tela de 2016-03-15 11:28:17

Perceba que ele criou o índice e, por padrão, ele cria 5 replicadores. Os replicadores são um cluster para manter a performance das pesquisas. Para pesquisas simples sem necessidade de precisão do retorno, esse índice já basta; no meu caso, foi necessário configurar como devem ser analisados os resultados. Quando estamos falando de produtos, por exemplo, as informações que estão no começo do título são mais relevantes do que o restante, por isso a configuração prevê a análise de palavras dando mais força. Caso os termos sejam encontrados no começo da string, o código de criação de índices desse tipo requer um analisador personalizado:

curl -XPUT 'http://localhost:9200/meuindice2/' -d '{
    "settings":{
        "index":{
            "number_of_replicas" : 5,
            "analysis":{
                "analyzer":{
                    "analyzer_startswith":{
                        "tokenizer":"keyword",
                        "filter":"lowercase"
                    }
                }
            }
        }
    },
    "mappings":{
        "products":{
            "properties":{
                "title":{
                    "search_analyzer":"analyzer_startswith",
                    "index_analyzer":"analyzer_startswith",
                    "type":"string"
                }
            }
        }
    } 
}'

Nas configurações do índice, eu informo que eu quero criar um analisado chamado ‘analyzer_startswitch’, no qual serão analisadas palavras e não o texto completo, e mais abaixo eu informo que no campo ‘title’ será testado usando meu analisador criado nas configurações. Ainda ignoro case sensitive, defino ainda 5 replicadores para o índice, mas até então nosso índice é padrão do Elastic Search. Precisamos criar um índice com integração ao MongoDB, assim, quando qualquer registro for inserido/alterado no banco, será automaticamente replicado no Elastic Search:

curl -XPUT localhost:9200/meuindice -d '{
    "mongodb": {
      "servers": [
        { "host": "127.0.0.1", "port": 27017 }
      ],
      "db": "",
      "collection": "",
      "options": { "secondary_read_preference": true },
      "gridfs": false
    },
    "settings":{
        "index":{
            "number_of_shards" : 5,
            "number_of_replicas" : 1,
            "analysis":{
                "analyzer":{
                    "analyzer_startswith":{
                        "tokenizer":"keyword",
                        "filter":"lowercase"
                    }
                }
            }
        }
    },
    "mappings":{
        "groups":{
            "properties":{
                "title":{
                    "search_analyzer":"analyzer_startswith",
                    "index_analyzer":"analyzer_startswith",
                    "type":"string"
                }
            }
        }
    } 
}'

Inserindo dados no índice

Só para testes, vamos enviar alguns registros ao índice para ver o que ele retorna na pesquisa:

curl -XPUT http://localhost:9200/meuindice/groups/9788572835886 -d '{"_id":9788572835886,"title":"Livro - Ato Administrativo - Alfredo Enéias G. D'abril","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788572833523 -d '{"_id":9788572833523,"title":"Livro - Abc do Comunismo - Nikolai Ivanovitch Bukharin","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9798572831627 -d '{"_id":9798572831627,"title":"Livro - Código de Águas - 3ª Edição 2001 - Jair Lot Vieira","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529007908 -d '{"_id":9788529007908,"title":"Livro - Trem di Versos: Estações Rimadas - Renato Diniz Santos","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529007892 -d '{"_id":9788529007892,"title":"Livro - Tempo-Será - Nilza Azzí","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529003344 -d '{"_id":9788529003344,"title":"Livro - O Tribunal de Impostos e Taxa - Ademar Pereira","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529007830 -d '{"_id":9788529007830,"title":"Livro - Sauna Finlandesa: Historia, Rituais e Construção - Timo Aaltonen","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529002835 -d '{"_id":9788529002835,"title":"Livro - Proposta para Reunião de Pais - Carmen Silvia Penha Galluzzi","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529004327 -d '{"_id":9788529004327,"title":"Livro - Só a Pessoa Sabe o Que Tem Por Dentro","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529004938 -d '{"_id":9788529004938,"title":"Livro - Pais e Filhos: Entre Erros e Acertos - Helyete dos Santos","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529003368 -d '{"_id":9788529003368,"title":"Livro - Ordem e Sociedade - João Barcellos","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529004464 -d '{"_id":9788529004464,"title":"Livro - Passarela da Vida - Beatriz de Freitas Fonseca","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529004259 -d '{"_id":9788529004259,"title":"Livro - Metodologia: o Caminho da Ciência","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529003993 -d '{"_id":9788529003993,"title":"Livro - As Mercadorias como Objeto de Desejo: Insanidade Capitalista - Maria Cristina Garcia","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529004662 -d '{"_id":9788529004662,"title":"Livro - Oásis de Curimataú - Joaquim Cavalcanti de Oliveira Neto","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529002705 -d '{"_id":9788529002705,"title":"Livro - Mudanças e Inovações na Educação","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9798529003794 -d '{"_id":9798529003794,"title":"Livro - Manual De Correspondencia Del Mercosur - Ángel Rodrígues","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529001555 -d '{"_id":9788529001555,"title":"Livro - Memórias de um Descasado - Antonio Luiz Fontela","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9788529004105 -d '{"_id":9788529004105,"title":"Livro - Gente da Terra - João Barcellos","type":"group"}'
curl -XPUT http://localhost:9200/meuindice/groups/9780000652744 -d '{"_id":9780000652744,"title":"Livro - Jânio de Fio a Pavio - Nelson Valente","type":"group"}'

Pesquisando

Agora vem a hora da verdade, vamos brincar com algumas buscas:

curl -XGET 'http://localhost:9200/searchproduct/_search' -d '{
    "query": {
        "match": {
            "_all": "bom"
        }
    }
}' | grep title

Acima, vamos fazer uma busca simples por tempo ‘bom’, e o resultado é:

 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   372  100   293  100    79  83642  22552 --:--:-- --:--:-- --:--:-- 97666
{"took":1,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":1,"max_score":1.444881,"hits":[{"_index":"searchproduct","_type":"groups","_id":"9788576353379","_score":1.444881,"_source":{"_id":9788576353379,"title":"Livro - Bom Menino. Junto.","type":"group"}}]}}

Agora vamos fazer uma pesquisa utilizando o analisador que criamos:

curl -XGET 'http://localhost:9200/meuindice/groups/_search?pretty' -d '{
    "query": {
        "match_phrase_prefix": {
            "title": {
                "query": "bom",
                "max_expansions": 5
            }
        }
    }
}'

Acima, definimos que a pesquisa vai ser feita no índice pelo mapeador ‘groups’, usando ‘match_phrease_prefix’. Assim, os resultados que possuem o tempo no começo serão melhor classificados; perceba a diferença: realizei uma pesquisa na base do It’s Simple com os termo ‘note’, e o resultado com a pesquisa simples foi:

{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 12,
        "max_score": 3.414377,
        "hits": [
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "7898023250457",
                "_score": 3.414377,
                "_source": {
                    "_id": 7898023250457,
                    "title": "DVD - Death Note – Volume 1",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "7898925037774",
                "_score": 3.1283908,
                "_source": {
                    "_id": 7898925037774,
                    "title": "Skin I Stick p/ Galaxy Note Keep Calm",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "7898925037828",
                "_score": 3.0015497,
                "_source": {
                    "_id": 7898925037828,
                    "title": "Skin I Stick p/ Galaxy Note Tuco Tuco",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "7891301198441",
                "_score": 3.0015497,
                "_source": {
                    "_id": 7891301198441,
                    "title": "Mochila Unissex Acolchoada Note Red Bull Brasil - Cinza",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "7898023250440",
                "_score": 2.845314,
                "_source": {
                    "_id": 7898023250440,
                    "title": "DVD - Death Note: Box 1 - 3 Discos",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "8806085022898",
                "_score": 2.7894506,
                "_source": {
                    "_id": 8806085022898,
                    "title": "Capa Flip Cover Samsung para Galaxy Note - Marrom",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "7898439186982",
                "_score": 2.2762513,
                "_source": {
                    "_id": 7898439186982,
                    "title": "Capa Flip Cover Samsung S-EFC1J9FAEGSTDI para Galaxy Note II - Marrom",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "7898439186470",
                "_score": 2.2762513,
                "_source": {
                    "_id": 7898439186470,
                    "title": "Capa Flip Cover Samsung S-EFC1J9FPEGSTDI para Galaxy Note II - Rosa",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "7898439185817",
                "_score": 2.2315605,
                "_source": {
                    "_id": 7898439185817,
                    "title": "Capa Flip Cover Samsung EFC1J9WEGSTD para Galaxy Note II - Branco",
                    "type": "group"
                }
            },
            {
                "_index": "searchproduct",
                "_type": "groups",
                "_id": "8809176620836",
                "_score": 2.2315605,
                "_source": {
                    "_id": 8809176620836,
                    "title": "Capa Aquática DiCAPac WP-C2 para Samsung Galaxy Note e Galaxy S4 - Rosa",
                    "type": "group"
                }
            }
        ]
    }
}

Agora vamos ao resultado da segunda pesquisa utilizando analisador:

{
  "took" : 26,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 25,
    "max_score" : 4.623839,
    "hits" : [ {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "190151182790",
      "_score" : 4.623839,
      "_source":{"_id":190151182790,"title":"Notebook 2 em 1 Touch Lenovo Yoga 500 com Intel® Core™ i5-5200U, 4GB, 1TB, Leitor de Cartões, HDMI, Wireless, Bluetooth, Webcam, LED 14\" e Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "4713392253355",
      "_score" : 4.623839,
      "_source":{"_id":4713392253355,"title":"Notebook Acer Aspire E5-573-707B com Intel® Core™ i7-5500U, 8GB, 1TB, Gravador de DVD, Leitor de Cartões, HDMI, Bluetooth, LED 15.6\" e Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "4713392253324",
      "_score" : 4.623839,
      "_source":{"_id":4713392253324,"title":"Notebook Acer Aspire E5-573G-74Q5 com Intel® Core™ i7-5500U, 8GB, 1TB, Gravador de DVD, HDMI, Placa Gráfica de 2GB, Bluetooth, LED 15.6\" e Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "889955752321",
      "_score" : 4.623839,
      "_source":{"_id":889955752321,"title":"Notebook 2 em 1 Touch Lenovo Yoga 900 com Intel® Core™ i7-6500U, 8GB, 256GB SSD, Leitor de Cartões, Wireless, Bluetooth, Webcam, LED 13.3\", Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "7899864905209",
      "_score" : 4.623839,
      "_source":{"_id":7899864905209,"title":"Notebook Dell Inspiron I15-3542-B40 com Intel® Core™ i5-4210U, 8GB, 1TB, Gravador de DVD, HDMI, Bluetooth, Placa Gráfica 2GB, LED 15.6\" e Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "190151182813",
      "_score" : 4.6211176,
      "_source":{"_id":190151182813,"title":"Notebook 2 em 1 Touch Lenovo Yoga 500 com Intel® Core™ i5-5200U, 8GB, 1TB, Leitor de Cartões, HDMI, Wireless, Bluetooth, Webcam, LED 14\" e Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "190151182806",
      "_score" : 4.6211176,
      "_source":{"_id":190151182806,"title":"Notebook 2 em 1 Touch Lenovo Yoga 500 com Intel® Core™ i5-5200U, 4GB, 1TB, Leitor de Cartões, HDMI, Wireless, Bluetooth, Webcam, LED 14\" e Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "889894405104",
      "_score" : 4.6211176,
      "_source":{"_id":889894405104,"title":"Notebook 2 em 1 Touch HP Pavilion X360 13-S103BR com Intel® Core™ i5-6200U, 4GB, 500GB, Leitor de Cartões, HDMI, Bluetooth, LED 13.3\" e Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "7899864904974",
      "_score" : 4.6211176,
      "_source":{"_id":7899864904974,"title":"Notebook 2 em 1 Touch Dell Inspiron I13-7348-C10 com Intel® Core i3-5010U, 4GB, 500GB, Leitor de Cartões, HDMI, Caneta Digital, LED 13.3\" e Windows 10","type":"group"}
    }, {
      "_index" : "searchproduct",
      "_type" : "groups",
      "_id" : "7899864904998",
      "_score" : 4.6211176,
      "_source":{"_id":7899864904998,"title":"Notebook 2 em 1 Touch Dell Inspiron I13-7348-C40 com Intel® Core™ i7-5500U, 8GB, 500GB, 8GB SSD, HDMI, Caneta Digital, LED Full HD 13.3\" e Windows 10","type":"group"}
    } ]
  }
}

Bibliografia