Cassandra e Lucene

Figura 1: Cassandra e Lucene

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.

Listagem 1: Exemplo de gravação em disco


@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.

Listagem 2: Método para execução após levantamento da 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.

Listagem 3: Criando documento


  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.
Funcionamento para o usuário

Figura 2: Funcionamento para o usuário

Funcionamento para o analista

Figura 3: Funcionamento para o analista

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.