Graph databases: quando usar Neo4j em vez de bancos relacionais

1. O paradigma dos grafos vs. o modelo relacional

Bancos relacionais organizam dados em tabelas com linhas e colunas, conectadas por chaves estrangeiras. Para obter informações relacionadas, é necessário executar operações de JOIN — que, em consultas complexas, tornam-se custosas e difíceis de manter. Por exemplo, para encontrar amigos de amigos em uma rede social, um banco relacional exige múltiplos JOINs recursivos.

Os bancos de grafos, como Neo4j, adotam uma abordagem fundamentalmente diferente: os dados são modelados como nós (entidades), arestas (relacionamentos) e propriedades (atributos). A navegação entre nós ocorre por travessia explícita de relacionamentos, sem necessidade de JOINs. Isso torna operações como "encontrar o caminho mais curto entre dois nós" naturais e eficientes.

-- Modelo relacional para amigos de amigos
SELECT u2.nome 
FROM usuarios u1 
JOIN amizades a1 ON u1.id = a1.usuario_id 
JOIN amizades a2 ON a1.amigo_id = a2.usuario_id 
JOIN usuarios u2 ON a2.amigo_id = u2.id 
WHERE u1.id = 1 AND a2.amigo_id != 1;
// Modelo em grafo com Cypher
MATCH (u:Usuario {id: 1})-[:AMIGO*2]->(amigo:Usuario)
WHERE NOT (u)-[:AMIGO]->(amigo)
RETURN DISTINCT amigo.nome

2. Casos de uso onde o grafo brilha (e o relacional sofre)

Redes sociais e recomendações — Sistemas de recomendação baseados em conexões sociais se beneficiam da travessia de grafos. Encontrar influenciadores, sugerir amigos ou detectar comunidades exige navegar por múltiplos níveis de relacionamento, algo que em SQL se torna exponencialmente complexo.

// Encontrar influenciadores (pessoas com muitos seguidores indiretos)
MATCH (u:Usuario)-[:SEGUE*1..3]->(influenciador:Usuario)
RETURN influenciador.nome, COUNT(DISTINCT u) AS alcance
ORDER BY alcance DESC
LIMIT 10

Detecção de fraudes — Padrões cíclicos e conexões ocultas entre contas, transações e dispositivos são naturalmente modelados como grafos. Uma fraude de cartão de crédito pode envolver múltiplas contas conectadas a um mesmo telefone ou endereço IP.

// Detectar possíveis fraudes: mesmo telefone em contas diferentes
MATCH (c1:Conta)-[:USA]->(t:Telefone)<-[:USA]-(c2:Conta)
WHERE c1.id <> c2.id
RETURN c1.id, c2.id, t.numero

Gerenciamento de permissões e hierarquias — RBAC (Role-Based Access Control) com herança de permissões em organizações profundas é complexo em SQL. Em grafos, a travessia de hierarquias é direta.

// Encontrar todas as permissões de um usuário, considerando herança de cargos
MATCH (u:Usuario {nome: "João"})-[:POSSUI_CARGO]->(c:Cargo)-[:HERDA*0..]->(:Cargo)-[:PERMITE]->(p:Permissao)
RETURN DISTINCT p.nome

Otimização de rotas e logística — Calcular a menor rota em mapas ou cadeias de suprimentos é trivial com algoritmos de grafo como Dijkstra ou A*.

// Menor caminho entre dois armazéns
MATCH (a:Armazem {codigo: "A1"}), (b:Armazem {codigo: "B3"})
CALL apoc.algo.dijkstra(a, b, 'ROTA', 'distancia_km')
YIELD path, weight
RETURN path, weight

3. Quando o banco relacional ainda é a melhor escolha

Bancos relacionais continuam superiores para dados tabulares com relacionamentos simples e previsíveis. Um catálogo de produtos com categorias fixas, por exemplo, não se beneficia de um grafo. Aplicações que exigem transações ACID complexas (como sistemas financeiros) ou relatórios agregados pesados (OLAP) funcionam melhor em bancos relacionais maduros como PostgreSQL ou MySQL.

Equipes que já dominam SQL e ferramentas de BI (Tableau, Power BI) raramente precisam migrar para grafos se o padrão de acesso for predominantemente de consultas agregadas, não de travessia de relacionamentos.

4. Neo4j na prática: modelagem e consultas essenciais

Em Neo4j, cada nó possui labels (rótulos) e propriedades. Relacionamentos têm tipo, direção e propriedades. A modelagem de um sistema de e-commerce em grafo seria:

// Criando nós e relacionamentos
CREATE (u:Usuario {id: 1, nome: "Maria", email: "maria@email.com"})
CREATE (p:Produto {id: 101, nome: "Notebook", preco: 3500})
CREATE (c:Categoria {id: 10, nome: "Eletrônicos"})
CREATE (u)-[:COMPROU {data: "2024-01-15", quantidade: 1}]->(p)
CREATE (p)-[:PERTENCE_A]->(c)

Consultas básicas em Cypher:

// Encontrar produtos comprados por usuários que também compraram o mesmo produto
MATCH (u1:Usuario)-[:COMPROU]->(p:Produto)<-[:COMPROU]-(u2:Usuario)
WHERE u1.id = 1 AND u1 <> u2
MATCH (u2)-[:COMPROU]->(outro:Produto)
WHERE outro <> p
RETURN DISTINCT outro.nome AS recomendacao
// Atualizar propriedades de relacionamento
MATCH (u:Usuario {id: 1})-[rel:COMPROU]->(p:Produto {id: 101})
SET rel.avaliacao = 5
RETURN u.nome, p.nome, rel.avaliacao

5. Performance e escalabilidade: o que muda com grafos

Neo4j utiliza index-free adjacency: cada nó armazena ponteiros diretos para seus relacionamentos. Isso elimina a necessidade de índices ou JOINs durante a travessia, tornando consultas de profundidade variável extremamente rápidas — independentemente do tamanho total do banco.

// Travessia de profundidade variável em grafo (milissegundos)
MATCH (a:Usuario {id: 1})-[:CONHECE*1..5]->(b:Usuario)
RETURN COUNT(DISTINCT b)
-- Equivalente em SQL (recursivo, segundos em grandes volumes)
WITH RECURSIVE amigos AS (
  SELECT amigo_id FROM amizades WHERE usuario_id = 1
  UNION ALL
  SELECT a.amigo_id FROM amizades a JOIN amigos ON a.usuario_id = amigos.amigo_id
)
SELECT COUNT(*) FROM amigos;

Limitações: grafos não são ideais para scans massivos de todos os nós (ex: "soma de todos os pedidos do mês") ou agregações estilo OLAP. Para esses casos, bancos relacionais ou colunares são mais adequados.

6. Estratégia híbrida: quando usar ambos (grafos + relacionais)

Uma abordagem híbrida separa domínios: dados transacionais (pedidos, pagamentos, estoque) permanecem no PostgreSQL, enquanto relacionamentos complexos (recomendações, detecção de fraudes, hierarquias) migram para Neo4j.

Exemplo real: e-commerce com catálogo em PostgreSQL e engine de recomendação em Neo4j.

-- PostgreSQL: dados transacionais
CREATE TABLE pedidos (
  id SERIAL PRIMARY KEY,
  usuario_id INT,
  produto_id INT,
  data TIMESTAMP,
  valor DECIMAL
);
// Neo4j: grafo de recomendações (sincronizado via CDC)
MATCH (u:Usuario)-[:COMPROU]->(p:Produto)
WITH u, COLLECT(p) AS produtos
MATCH (outro:Usuario)-[:COMPROU]->(p2:Produto)
WHERE outro <> u AND p2 IN produtos
RETURN outro.nome, COLLECT(DISTINCT p2.nome) AS recomendacoes

A sincronização pode ser feita com eventual consistency (Apache Kafka, Debezium) ou jobs periódicos (Apache Airflow).

7. Mitos comuns e armadilhas ao adotar Neo4j

"Graph database resolve tudo mais rápido" — Nem sempre. Se seu padrão de acesso é "todos os clientes que compraram produto X no último mês", um índice B-tree em SQL é mais eficiente. Grafos brilham em travessia de relacionamentos, não em varreduras de propriedades.

Complexidade operacional — Backup, restore e monitoria de Neo4j são menos maduros que ecossistemas relacionais. Ferramentas como Prometheus e Grafana têm integrações, mas a curva de aprendizado operacional é real.

Curva de aprendizado — Cypher é expressivo, mas exige mudança de mentalidade. Desenvolvedores acostumados com SQL podem levar semanas para pensar em termos de padrões de grafos. Investir em treinamento e provas de conceito é essencial antes de adoção em produção.


Referências