Rails + Elasticsearch 101 (parte 1)
Recentemente participei da implementação de um sistema para análise de comportamento de usuários. A stack escolhida foi Rails + Elastic (antigo Elasticsearch) para coleta/persistência, e posterior análise via Kibana. Como os usos mais comuns do Elastic são Full Text Search e armazenamento de logs (na stack ELK), encontrei pouquíssimos exemplos de uso que se aplicavam a minha necessidade.
Quem trabalha com Rails está acostumado com as facilidades de seu ecossistema, onde com poucas linhas de código (e algumas Gems), é possível fazer muita coisa, sem muito trabalho.
Durante o projeto, percebi que a documentação das Gems principais, voltadas para Elastic não respondia alguns questionamentos básicos, comuns a todo iniciante. Depois de quebrar a cabeça entendendo como elas funcionam, decidi criar uma série de posts com algumas informações que aprendi nesse meio tempo, e que teriam me ajudado muito no início. Vamos lá!
Existem 3 gems principais para se trabalhar com Elastic em aplicações Ruby, para 3 situações distintas:
- elasticsearch-persistence: permite a integração básica entre uma aplicação Ruby e Elastic, através dos padrões Repository ou ActiveRecord.
- elasticsearch-model: responsável pela integração com as models do Rails, permitindo trabalhar com callbacks, sincronia automática, etc.
- elasticsearch-rails: fornece algumas facilidades para aplicações Rails, como gerador de aplicações de busca e integração com ActiveSupport.
O modo mais fácil de integrar o Elastic a sua aplicação, é adicionando as seguintes linhas em suas models:
A linha Elasticsearch::Model é responsável pelo vínculo entre as estruturas do Rails e Elastic. Já a linha Elasticsearch::Model::Callbacks mantém os dados atualizados no Elastic (com um hook after_commit nas ações create, save, update e destroy) de modo automático.
Caso seja necessário ter um tratamento específico, é possível criar estes mesmos callbacks de modo manual (removendo o include dos Callbacks):
O modo como o Elastic persiste os dados funciona em cima dos conceitos de Index e Type, onde um Index é referente a uma estrutura semelhante a uma “tabela”, e um Type é referente a um tipo/agrupamento de dados dentro desta mesma “tabela”. Note as aspas ao redor de tabela: as estruturas internas de persistência (entre Elastic e um banco relacional) são bem diferentes, sendo que a comparação é apenas para facilitar a compreensão.
O Index e Type são definidos automaticamente a partir do nome do seu objeto. No caso da model Article acima, o Index gerado será articles, e o Type article.
Cada coluna/atributo de sua model é mapeado automaticamente para um campo no Elastic, com seus tipos respectivos. A estrutura na qual um documento (registro) é replicado no Elastic é chamado de Mapping, e cada campo possui um tipo distinto. Por padrão, a gem elasticsearch-model “entende” sua estrutura de dados, e cria os mesmos tipos no Elastic. Fácil, fácil :)
Porém, existem casos onde pode ser necessário que Elastic guarde os dados numa estrutura diferente do seu banco relacional, como por exemplo geo_point, geo_mapping (para usar os recursos de busca geográfica do Elastic). Nestes casos, é possível forçar o tipo de dado a ser mapeado pelo Elastic:
Um ponto importante a ser mencionado, é que o Elastic não trabalha bem com dados esparsos e/ou não normalizados (Avoid Sparsity). Algumas boas práticas são:
- Definir o esquema estrutural previamente, pois alterações em campos são custosas ao Elastic, ainda mais quando a quantidade de dados já for significativa.
- “Forçar” o Mapping de todos os campos, conforme o exemplo acima. O Elastic não faz o mapeamento de um campo nulo até receber o primeiro registro com ele preenchido. Se seus dados não são consistentes, isso pode gerar perda de performance na indexação.
Caso a sua aplicação já possua dados, você pode forçar o Elastic a importar todos os registros (via console ou na inicialização da sua aplicação):
O metodo import possui diversos parâmetros que auxiliam a rotina de importação. Alguns exemplos:
Neste post, expliquei algumas características básicas de integração, e como o Elastic grava estes dados. Existem vários outros detalhes e recursos disponívels, que explicarei nos próximos capítulos :)
Gostou da leitura? Clique no coração abaixo, assim você me ajuda a transmitir este conhecimento a mais pessoas!
Além disso, dúvidas, críticas e elogios são bem vindos! :D
Obrigado e até a próxima!