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.


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.


Quand tu créés un graph, AGE :

  1. Crée un schema PostgreSQL dédié
  2. Ajoute des entrées dans ag_catalog.ag_graph et ag_catalog.ag_label
  3. Crée deux tables parentes : _ag_label_vertex et _ag_label_edge
  4. 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.


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.


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.


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.


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!