Atualmente existem diversas características dos bancos NOSQL, em diferentes arquiteturas, formas de armazenamento de informação e estruturas de dados. No entanto apesar dessa grande variedade no número e variedades eles compartilham uma coisa em comum. Eles normalmente buscam pela chave primária. Apesar de conseguir-se manter altamente disponível, inserir e recuperar informações de forma bastante rápida, o fato de que a maioria dos bancos NOSQL somente recuperem pela chave, torna um pouco difícil adaptar a aplicação para o seu uso, já que nem sempre você consegue buscar apenas pela chave no banco. Para não abrir mão da alta disponibilidade e recuperar informações não apenas em chave, uma boa opção certamente é “terceirizar” esse serviço. Por esse motivo será apresentado o Lucene trabalhando em conjunto com um banco nosql, mais precisamente o Cassandra, juntando o bom de dois mundos em sua aplicação.
O Apache Lucene é uma API de busca e indexação de documentos, escrito em Java. Ele é composto por basicamente duas etapas: indexação e pesquisa. Dado o texto primeiro passo é a indexação que processa os dados originais e gera uma estrutura que facilita a busca e gera palavras-chaves, em seguida vem à busca que visa estar buscando a partir das palavras-chaves indexadas e retorna pela semelhança do texto com a consulta. A vantagem é que o Lucene abstrai ao ponto que não é necessário que o desenvolvedor saiba algoritmo de indexação. Os índices podem ser criados em ambientes distribuídos, aumentando o desempenho e a escalabilidade da ferramenta.
Apresentado um pouco da ferramenta o objetivo agora será apresentar uma prática envolvendo os dois mundos: banco nosql e o Lucene. Para essa parte prática será feita uma aplicação com o objetivo de estar cadastrando currículos. A idéia é bastante simples:
- O usuário estará cadastrando o currículo com suas informações.
- A partir dessas informações a analista de recursos humanos estará buscando o profissional.
- O profissional poderá ser buscado pela palavra chave de uma qualificação técnica, localização, Profissão.
Como o objetivo da aplicação será web, estaremos utilizando a plataforma Java EE em sua versão mais recente, a versão 6.0.
No Lucene os índices são armazenados a partir da interface Direcoty que no momento que eu escrevo possui basicamente duas implementações: Uma para armazenar na memória RAM e para armazenar no disco rígido. Como o objetivo é garantir uma alta disponibilidade estaremos utilizando a opção de estar trabalhando com os índices na memória RAM, mas para não perdemos tais índices estaremos fazendo um backup no disco rígido. Para fazer tal procedimento, usaremos o recurso schedule do EJB 3.1 para de tempos em temos, jogar o que está na memória para o HD.
@Schedule(minute = "*/1", hour = "*") public void reindex() { try { Directory disco = FSDirectory.open(new File(Constantes.getIndexDirectory())); luceneManager.backup(directory, disco); } catch (Exception e) { Logger.getLogger(ScheduleService.class.getName()).log(Level.SEVERE, null, e); } }
Desse modo quando a aplicação cair e levantar novamente basta estar carregando as informações do disco para a memória principal novamente. Vale salientar que o diretório precisa ser único para toda a aplicação.
@ApplicationScoped public class LuceneManager implements Serializable{ private static final long serialVersionUID = -8280220793266559394L; @Produces private Directory directory; @Inject public void init() { directory = new RAMDirectory(); try { levantarServico(); } catch (IOException e) { Logger.getLogger(LuceneManager.class.getName()).log(Level.SEVERE, null, e); } } public void levantarServico() throws IOException { Directory disco = FSDirectory.open(new File(Constantes.getIndexDirectory())); backup(disco, directory); } public void backup(Directory deDiretorio, Directory paraDiretoria) throws IOException { for (String file : deDiretorio.listAll()) { deDiretorio.copy(paraDiretoria, file, file); // newFile can be either file, or a new name } } }
Uma vez criado o diretório e definido onde estarão as informações o próximo passo é criar os índices, conforme falado anteriormente ele será a chave tanto para a inserção quando a busca das informações. Agora o que falta é a criação do Document que representa para a troca de informações entre o Lucene e sua aplicação. Ele é composto por campos que por sua vez por informações. A relação entre o Lucene e a aplicação se dará da seguinte forma:
- A chave será armazenada e não indexada.
- O estado não armazenado e não indexado.
- O conteúdo do currículo será não armazenado e indexado.
private Document criarDocumento(Pessoa pessoa) throws IOException { Document document = new Document(); document.add(new Field(Constantes.ESTADO_INDICE,pessoa.getEndereco().getEstado(), Store.YES, Index.NOT_ANALYZED_NO_NORMS)); document.add(new Field(Constantes.ID_INDICE,pessoa.getNickName(), Store.YES, Index.NOT_ANALYZED_NO_NORMS,TermVector.WITH_POSITIONS_OFFSETS)); document.add(new Field(Constantes.TUDO, getConteudoCurriculo(pessoa), Store.NO, Index.ANALYZED)); return document; }
Uma vez essa informação armazenada no Cassandra e indexada no Lucene, o ciclo da aplicação se dará da seguinte forma:
- A seqüência segue um processo simples:
- O usuário adiciona as informações e envia ao servidor
- A informação é persistida no Cassandra
- A informação é indexada e é armazenado o id no Lucene
- Quando o analista de RH fizer a busca é recuperado um Document que por sua vez retorna a informação da chave, assim poderá se recuperada todas as informações no Cassandra.
Pronto! Dessa forma quanto a inserção quanto a busca serão feitas de maneiras rápidas, o Cassandra possui o recurso de índices secundários que permite que existam campos de busca além da chave, mas isso faz com que o banco perca velocidade com o crescimento desses campos especiais. Uma outra forma de deixar a busca ainda mais rápida seria deixar os resultados dessa busca em um cachê, mas isso serão cenas para um próximo capítulo.
Falando um pouco mais sobre o Lucene existem alguns frameworks que já se integram com o Lucene. É o caso do hibernate search que é o hiberante, para banco relacional, utilizando o Lucene, solr é um servidor de busca que usa o Lucene como motor de busca e o solandra que é um solr para o Cassandra.
Nesse artigo foi apresentado um problema existente na maioria dos bancos NOSQL, a busca de campos além da chave. Para resolver tal problema foi demonstrado um trabalho em conjunto com o Lucene, ferramenta de indexação de documentos, além de apresentar alguns conceitos da ferramenta.
Referências:
- Solr
- Hibernate Search
- Conhecendo o Lucene, PAULO SIGRIST e WILSON AKIO HIGASHINO, java Magazine 104, devmedia.