Trabalhando com Relacionamentos: bancos de dados baseados em grafos e o Neo4j

Ao desenvolvermos uma aplicação, geralmente nos deparamos com a situação de trabalharmos com o banco de dados relacional, realizando diversas queries complexas. Tais queries podem possuir essa complexidade devido ao fato de estarmos modelando dados que não são naturais ao paradigma relacional, em um banco relacional.

Imagine uma aplicação que deve manter as informações relativas a viagens e locais onde pessoas moraram. Com isso, deve ser possível saber quando uma pessoa viajou ou qual o período em que ela viveu em determinada cidade. Modelando esse problema em um banco de dados relacional, podemos chegar à seguinte estrutura de tabelas e seus respectivos dados:

Tabela

Tabelas com dados de viagens e residências (datas abreviadas por efeito de simplicidade)

Com essa estrutura de tabelas, podemos realizar consultas, como por exemplo, procurar quais foram as pessoas que viajaram para o mesmo lugar que o Ricardo. Uma possibilidade para buscar essas informações do banco de dados seria a seguinte query SQL:

select distinct(v.id_pessoa),  p.nome
from viagens v 
   inner join pessoas p on (
      v.id_pessoa = p.id
   )
where
   v.id_cidade in (
      select  viagens_sub.id_cidade 
      from viagens viagens_sub
      where viagens_sub.id_pessoa = 2
   ) and v.id_pessoa <> 2;

Nesse caso, como não devemos retornar a próprio Ricardo, que é o ponto de partida da nossa consulta, o ignoramos com o filtro id <> 2.

Apesar de ser uma consulta realizada em dois passos (pois contém sub-selects), primeiro buscando as cidades as quais ele passou e em seguida descobrindo quem foi para essas cidades, a pesquisa não é difícil de ser realizada.

No entanto, quando o requisito é saber “por quais cidades passaram (seja morando ou viajando) as pessoas que passaram por Roma a consulta já não é mais trivial de ser desenvolvida, podendo exigir diversos joins e subqueries que poderão tornar a consulta de extrema complexidade e muito pouco performática.

Modelando o mesmo problema de maneira diferente: Grafos

A aplicação modelada anteriormente pode ser remodelada através de grafos, onde teríamos um similar à imagem abaixo:

Grafo

Dados modelados através de grafos

Analisando o grafo, se quisermos saber quem viajou para o mesmo lugar que o Ricardo, basta descobrirmos os relacionamentos de saída dele, que sejam do tipo VIAJOU, com isso, temos as cidades pelas quais o Ricardo viajou.

Cidades que o Ricardo viajou

Cidades que o Ricardo viajou

Em seguida, a partir de cada cidade, precisamos dos relacionamentos de entrada, que também sejam do tipo VIAJOU e com isso temos as pessoas que viajaram para os mesmos lugares que o Ricardo.

Quem viajou para os mesmos lugares que o Ricardo

Quem viajou para os mesmos lugares que o Ricardo

Podemos também realizar a complexa pesquisa sugerida anteriormente, para saber por onde passaram (morando ou viajando) as pessoas que passaram por Nova Iorque. Novamente, podemos percorrer o grafo tendo como ponto de partida Nova Iorque, porém, agora percorrendo os dois tipos de relacionamentos (MOROU e VIAJOU) de chegada à cidade. Com isso, teremos as pessoas.

Pessoas que passaram por Nova Iorque

Pessoas que passaram por Nova Iorque

A partir das pessoas, precisamos dos seus relacionamentos de saída. Neste momento, teremos todos os lugares que foram viajados ou morados por quem já passou por Nova Iorque.

Por onde passaram as pessoas que já foram em Nova Iorque

Por onde passaram as pessoas que já foram em Nova Iorque

Banco de dados baseados em grafos e o Neo4j

Os bancos de dados relacionais não permitem que os dados sejam representados através de grafos de uma maneira natural, com isso, algumas pesquisas se tornam extremamente complexas, ou até mesmo impraticáveis nos bancos de dados relacionais, que é justamente o caso da consulta exemplificada anteriormente. Navegando pela estrutura do grafo, conseguimos facilmente os resultados os quais queríamos.

Com a popularização dos bancos de dados não relacionais e as diferentes formas de persistir dados, começam a surgir alternativas para a situação de dados que devem ser persistidos e consultados através de uma representação em grafo.

Esse é justamente o papel do banco de dados Neo4j, que permite que dados sejam persistidos e percorridos da mesma forma que os grafos e mantendo algumas características que se tornaram bastante comuns em bancos relacionais, como controle de transações e seguindo as propriedades ACID, além de suporte a pesquisas textuais, através da integração com o Lucene.

Consultas como as demonstradas anteriormente, quando realizadas no Neo4j, poderão ter performance significativamente superiores à mesma consulta em banco de dados relacional.

A API do Neo4j e como percorrer grafos

Uma vez que fizemos toda a configuração do Neo4j, podemos utilizar sua API para realizarmos a pesquisa no grafo, também chamado de Traversal.

Com isso, basta utilizarmos a classe Traversal invocando o método description() para indicarmos como o grafo deve ser percorrido e quais informações serão retornadas.

   Node lugarDeOrigem = db.getNodeById( id );
		
   Iterable<Node> nos = Traversal.description()
				.relationships(Relacionamentos.MOROU_EM, Direction.BOTH)
				.relationships(Relacionamentos.VIAJOU_PARA, Direction.BOTH)
				.evaluator(Evaluators.atDepth(2))
				.traverse(lugarDeOrigem).nodes();

Acima, as invocações a relationships indicam que vamos percorrer os relacionamentos MOROU_EM e VIAJOU_PARA em ambos os sentidos, ou seja, tanto entrada como saída. Enquanto que no método evaluator indicamos quais os nós devem ser devolvidos, que no caso são os estiverem na profundidade 2 do traverse do grafo.

Por fim, indicamos qual é o nó de origem para realizar o traverse e invocamos o método nodes() para termos acesso aos nós devolvidos pelo Neo4j.

A melhor forma de persistir seus dados

Ao escolher um banco de dados, seja relacional ou não relacional, é necessário sempre analisar os tradeoffs entre as tecnologias e modelos diferentes. Alguns bancos de dados podem suportar modelos mais ricos e complexos, enquanto outros podem possuir estratégias de escalabilidade interessantes, pagando um preço em consistência ou disponibilidade.

Da mesma maneira que atualmente temos o conceito de programação poliglota, onde é importante conhecer bem a linguagem para escolher a mais adequada para cada aplicação, com os bancos de dados não é diferente. Cunharam até mesmo o termo persistência poliglota.

Situações práticas vão exigir o uso de mais de um tipo de banco de dados. Isso já ocorre quando queremos, além de guardar os dados em um banco relacional, poder pesquisar textualmente (isso se você considerar o Lucene como um banco de dados NoSQL). Um sistema de recomendação de produtos de um e-commerce deve guardar todos os itens comprados por cada usuário numa base relacional, e pode utilizar o Neo4J para também armazenar esses dados, com o único intuito de facilitar determinadas consultas. Dessa maneira, teríamos em uma mesma aplicação duas formas diferentes de persistir dados, utilizando a melhor tecnologia para cada situação, o que nos lembra o já desgastado “não existe bala de prata“.

12 Comentários

  1. Ranieri 30/05/2011 at 19:38 #

    Olá Adriano, excelente post!
    Não sei se você lembra, mas tive o prazer de ter uma explicação sua sobre BD em grafos. Procurei mais sobre o assunto ao chegar em casa, mas nada como este seu post.
    Parabéns!

  2. Loiane 30/05/2011 at 21:04 #

    Excelente o artigo!

  3. Deivid Martins 31/05/2011 at 08:19 #

    Muito bom!

  4. Carlos 31/05/2011 at 12:01 #

    Muito bom o artigo….indo o embalo existe também a base LDAP que possui uma estrutura que lembra uma arvore. Se possivel gostaria de ver um artigo falando sobre LDAP

  5. Norberto 01/06/2011 at 16:28 #

    Excelente post sobre um dos assuntos mais comentados no momento.

  6. Leandrolopesp 15/06/2011 at 08:48 #

    Pergunta: Isto só se aplica a bancos pequenos, não? Ou ele resolve isto de maneira elegante também neste caso?

    Abraço

  7. Adriano Almeida 15/06/2011 at 08:56 #

    Oi Leandro,

    Com o Neo4j você consegue modelar qualquer tipo de aplicação, com um modelo maior (mais complexo) ou menor (mais simples).

    Com relação à quantidade dos dados, dependendo da configuração do seu servidor, é possível ter até bilhões de nós e relacionamentos. Tem toda uma estratégia para tuná-lo também, para que aguente mais, ou para que a execução do Garbage Collector não se torne um gargalo e assim por diante. Mas eu considero bem elegante sim e até mesmo, transparente para o desenvolvedor.

    Vale dar uma olhada na documentação oficial deles: http://docs.neo4j.org/chunked/1.3/

    Qualquer dúvida posta aí.

    Abraço

  8. Suissa 24/09/2012 at 11:06 #

    Ótimo artigo parabéns, vou referenciar o artigo la no nosqlbr.com.br

  9. Neto 14/11/2013 at 10:05 #

    Excelente post, claro e objetivo, parabéns. Apenas uma dúvida: se no caso a cima tivéssemos o tipo de transporte, assim
    Maria Viajou a Toquio e a Nova iorque e ambas as cidades podem ser conectadas por trem ou avião, Como mapear o transporte que maria usou na quela data (Problema clássico do relacionamento Triplo)??
    Obrigado pelo Post
    Neto

  10. SERGIO 23/01/2014 at 02:04 #

    Olá, já estamos em 2014, mas também curti é interessante pois estou pesquisando para iniciar o estudo, é uma excelente postagem.
    Um abraço.

  11. Gregorio 13/10/2014 at 14:15 #

    Olá, estive estudando um pouco sobre o Neo4J e tenho uma dúvida: qual é a linguagem utilizada para mostrar essas relações de forma gráfica? Existem exemplos de como o Neo4J tem sido utilizado na indústria?
    Abraço!

Deixe uma resposta