Apache AGE : faire du Cypher dans PostgreSQL

Twelve-factor, cloud-native, Kubernetes-native, on empile les concepts comme des Legos. Mais au final, on a toujours des données relationnelles ET des données graphe. Et personne veut installer Neo4j à côté, parce que c’est une DB de plus à backup, monitorer, sécuriser.
C’est là qu’Apache AGE (A Graph Extension) rentre en jeu : une extension PostgreSQL qui implémente openCypher. Tu fais du MATCH (a)-[:KNOWS]->(b) directement dans PostgreSQL. Les vertices et les edges sont des tables Postgres. Cypher est compilé en SQL. Le planner Postgres optimise tout.
Le problème que AGE résout
Scénario classique : t’as un inventaire d’infra. Des serveurs. Des services. Des dépendances. Des relations “ce serveur run ce service, qui dépend de cette DB, qui est répliquée sur ce cluster”. En SQL relationnel, c’est une série de JOINs sur des tables de liaison. Ça marche, mais pour des traversées comme “trouver tous les services impactés si ce serveur crashe”, t’écris une query récursive en CTE.
Avec AGE, tu fais :
SELECT * FROM cypher('infra_graph', $$
MATCH (s:Server {name: 'nats-1'})-[:RUNS]->(svc:Service)<-[:DEPENDS_ON]-(other)
RETURN other.name
$$) AS (name agtype);
Une ligne. Pas de CTE. Pas de jointures. Pas de changement de base.
Architecture en 2 minutes
Quand tu créés un graph, AGE :
- Crée un schema PostgreSQL dédié
- Ajoute des entrées dans
ag_catalog.ag_graphetag_catalog.ag_label - Crée deux tables parentes :
_ag_label_vertexet_ag_label_edge - Chaque label devient une table fille avec héritage PostgreSQL
Le résultat : tes Person, Server, FRIEND_OF, RUNS sont des vraies tables Postgres, indexables avec B-tree et GIN, dumpables avec pg_dump.
Le type agtype (similaire à jsonb) stocke les propriétés. C’est aussi le type de retour des requêtes Cypher.
Le killer feature : hybride Cypher + SQL
Tu peux faire des CTE avec Cypher, des JOINs entre une table SQL et un résultat Cypher, des sous-requêtes EXISTS.
-- CTE avec Cypher
WITH infra_friends AS (
SELECT * FROM cypher('devlab', $$
MATCH (s:Server)-[:CLUSTERED_WITH]->(peer)
RETURN s.name AS server, peer.name AS peer
$$) AS (server agtype, peer agtype)
)
SELECT server::text, peer::text
FROM infra_friends;
Les résultats Cypher sont en agtype, tu les castes en text/int selon le besoin.
Ce que AGE n’est pas
AGE n’est pas Neo4j. C’est important à comprendre.
| Neo4j | AGE |
|---|---|
| Store graphe natif | Tables Postgres avec héritage |
| APOC (300+ algo) | Rien, t’implémentes ou tu fais du SQL |
| Distribué (causal cluster) | Un nœud Postgres |
| Traversées profondes rapides | Traversées profondes = jointures multi-niveaux |
| Cypher complet | openCypher partiel |
Si ton use case c’est “analyser un réseau social de 10 millions de nœuds avec PageRank et détection de communauté”, prends Neo4j. Si ton use case c’est “j’ai déjà PostgreSQL et je veux naviguer des relations d’infrastructure sur 50 000 nœuds”, AGE est parfait.
Déploiement
docker pull apache/age
docker run --name age -p 5455:5432 \
-e POSTGRES_USER=ageuser \
-e POSTGRES_PASSWORD=agepass \
-e POSTGRES_DB=agedb \
-d apache/age
docker exec -it age psql -d agedb -U ageuser
agedb=# LOAD 'age';
agedb=# SET search_path = ag_catalog, "$user", public;
agedb=# SELECT create_graph('devlab');
C’est tout. Pas de service à configurer, pas de port à ouvrir, pas de healthcheck custom. pg_dump backup tout.
Le mot de la fin
Apache AGE, c’est le couteau suisse des graph databases opérationnelles. Pas le plus fort pour les workloads analytiques lourds, pas le plus rapide pour les traversées profondes — mais le plus pratique dans 80% des cas où t’as déjà PostgreSQL et que tu veux naviguer des relations sans changer de base.
Et avec SQL/PGQ (SQL:2023) qui arrive dans PostgreSQL 19, le support natif du multi-modèle arrive. Mais en attendant, AGE fait le taf.
See ya space-cowboy!