Graph databases in 2026: revisit Neo4j Community, Memgraph and Apache AGE before you model another relationship in SQL joins

Most teams do not decide to adopt a graph database. They arrive at it through a query. Somebody asks “which customers are connected to that blocked customer through a shared phone number, bank account or address?”, and the SQL grows from two joins to six, then into a recursive query nobody wants to touch. A conference talk shows the same question in four lines of Cypher, and the team wonders if they chose the wrong database.

Sometimes the answer is yes. More often, the answer is that PostgreSQL could already do it, and the real problem was the data model. This post compares the three graph options that Indian teams most often look at for self-hosting in 2026: Neo4j Community Edition, Memgraph and Apache AGE. We also look briefly at FalkorDB, ArangoDB, JanusGraph and Kuzu, and at the query language story around openCypher and the ISO GQL standard. We cover versions, licences, storage models, high availability by edition, backups, memory sizing, performance claims, upgrades, security history, and, most importantly, when not to add a graph database at all.

Several earlier posts sit close to this one. Vector search in 2026 asked the same “do you need a new database?” question for embeddings, and graph vendors now sell themselves for GraphRAG too. Distributed SQL in 2026 is about scaling relational data, which is a different problem from traversing it. Authorization in 2026 covers OpenFGA and SpiceDB, which are relationship graphs built for one job. Data catalogs in 2026 shows lineage, another graph that usually lives inside a product. MongoDB alternatives in 2026 and in-process analytics in 2026 are about picking the right engine for the shape of the question. Since Apache AGE runs inside PostgreSQL, PostgreSQL high availability and PostgreSQL backups in 2026 apply to it directly.

We end with a small drill: the same “who is connected within N hops” question asked as a recursive CTE in SQLite and as Cypher in the embedded Kuzu engine, on synthetic data. We say clearly what we did not run.

A scenario to keep us honest

Imagine a textile wholesale trader in Surat that sells sarees, dress material and synthetic fabric on credit to retailers across Gujarat, Maharashtra, Rajasthan and Madhya Pradesh. The company is invented for this post. It has about 6,000 active retailer accounts, around 140 field agents who bring in new retailers and earn a commission, a warehouse near the Ring Road textile markets, and an engineering team of six, one of whom looks after databases.

The setup today:

  • A Django application on PostgreSQL 16, one primary and one streaming replica in a cloud region in Mumbai.
  • Tables for retailers, agents, orders, invoices, payments, delivery addresses, phone numbers, bank accounts and GSTINs.
  • Credit limits per retailer based on payment history.

Over the last two quarters the company has lost money to a pattern the finance team calls “new shop, big order, no payment”. A new retailer account is opened by an agent, places two or three small orders that are paid on time, then places a large credit order and disappears. When the finance team looked closely, several of these “new shops” shared a phone number, a bank account for refunds, a GSTIN that had been cancelled, or a delivery address with older accounts that had already defaulted. Some were brought in by the same two agents.

Three things push the team to act. The finance head wants a check before any large credit limit is approved: “show me every account connected to this one through any shared detail, up to four steps away, and flag any that defaulted”. The SQL one developer wrote takes several seconds on the replica and nobody else can read it. And three engineers have proposals: Neo4j, Memgraph because “it is faster”, or “we already have PostgreSQL, just add Apache AGE”.

We will come back to Surat in the worked plan.

The short version

  • Neo4j Community Edition is the best-known native graph database. It is GPLv3, runs on the JVM, stores graphs on disk with a page cache, and uses Cypher. The current release is 2026.10.1 (tagged around 20 October 2026), with 5.26 as the long-term support line. Community is single instance only: no clustering, no online backup, no role-based access control. Those are in the commercial Enterprise Edition.
  • Memgraph is an in-memory graph database in C++ that speaks Cypher and the Bolt protocol, so most Neo4j drivers work with it. The current release is 3.14.0 (21 October 2026). Its source is under the Business Source License 1.1 with an Additional Use Grant for internal business purposes, plus the Memgraph Enterprise License for enterprise code. Replication is in Community; automatic failover, RBAC, audit logs and multi-tenancy need an Enterprise licence.
  • Apache AGE is an Apache-2.0 PostgreSQL extension that adds openCypher queries over graph data stored in ordinary PostgreSQL tables. It has a separate release branch for each PostgreSQL major version: 1.8.0 for PostgreSQL 18 (July 2026) and 1.7.0 for PostgreSQL 17 (February 2026) are the newest final releases. High availability and backups are whatever your PostgreSQL already has.
  • Before any of them, try a recursive CTE in the database you already run. For bounded hops it is often enough, and in our small drill it was faster than an embedded graph engine.
  • Choose a graph database when the question is about variable-depth paths and patterns, asked often, by people who must read and change the query. Then choose by storage model and licence: on-disk native (Neo4j), in-memory (Memgraph) or inside PostgreSQL (AGE).

Where things stand on 30 October 2026

Neo4j. Since January 2025 the server uses calendar versioning in the form YYYY.MM.Patch. The project publishes tags on GitHub, not GitHub releases. The newest tags are 2026.10.0 (around 9 October) and 2026.10.1 (around 20 October 2026), based on the commit dates of the tags. The 5.26 LTS line still gets patches: 5.26.32 was tagged around 6 October 2026. The operations manual lists Java 21 and 25 as supported JDKs for the current version. Since 2025.06, the Cypher language is versioned separately from the server: Cypher 5 is frozen apart from fixes and performance work, and new features go only into Cypher 25. From 2026.02, the shipped neo4j.conf sets Cypher 25 as the default for new databases.

Memgraph. Version 3.14.0 was published on GitHub on 21 October 2026 at 4:35 PM IST, after 3.13.2 on 9 October and 3.13.0 on 9 September 2026. Releases come roughly monthly. The 3.14.0 notes list many breaking changes, mostly Cypher semantics moving closer to the standard: comparing lists or maps that contain null now returns null, NaN > x returns false, and custom C++ query modules must be recompiled.

Apache AGE. The GitHub releases are named per PostgreSQL branch, and every tag carries an -rc0 suffix even for final releases, so read the release title and the pre-release flag, not the tag. When we checked: 1.8.0 for PostgreSQL 18 on 9 July 2026, 1.7.0 for PostgreSQL 17 on 11 February 2026 and for PostgreSQL 18 on 21 January 2026, 1.6.0 for PostgreSQL 16 on 4 September 2025 and for PostgreSQL 14 and 15 on 20 November 2025. A 1.8.0 candidate for PostgreSQL 15 was published on 19 September 2026, marked as a pre-release and “pending Apache AGE PMC approval”. The README says AGE supports PostgreSQL 11 to 18. In practice, check which AGE version exists for your exact PostgreSQL major before you plan an upgrade, because the branches do not move together.

The others. FalkorDB 6.0.0 (29 September 2026) introduced a Rust engine, with 6.0.3 on 22 October; the 4.x C line continues with 4.22.3 on 28 October 2026. JanusGraph 1.2.0 came on 25 October 2026, its first release since November 2024. ArangoDB’s newest tag is v3.12.13. Kuzu’s repository is archived; its last release was 0.11.3 on 10 October 2025.

The first question: is this really a graph problem?

Before comparing databases, write the question down in plain words and look at its shape. There are four common shapes, and only some of them need a graph engine.

Shape 1: fixed, shallow relationships

“Show the retailer, their agent and their last five orders.” Two or three joins of known depth. A graph database adds nothing here except a second copy of your data.

“Show every account that shares a phone number with this one, and every account that shares a phone number with those, up to three steps.” The depth is variable but bounded and the graph is fairly sparse. A recursive CTE in PostgreSQL or SQLite handles this, especially with good indexes and a cycle guard. This is where most “we need a graph database” conversations should start and often end.

“Find rings where A shares a bank account with B, B shares an address with C, C was brought in by A’s agent, and any of them defaulted, up to six steps.” The question is about patterns, link types vary, and the analyst will change it next week. SQL can express it, but it becomes hard to read. This is where a graph query language earns its place.

Shape 4: whole-graph analytics

“Which groups of accounts form tight communities?” This is graph algorithms such as connected components, PageRank and community detection. It is batch work, which you can run in a graph database with an algorithm library or on an exported graph in a notebook.

A quick test

Ask the person who wants the answer a direct question: “Will you change the shape of this question every few weeks, and does it go beyond three or four steps?” If the answer is no, write a good recursive CTE and stop. If the answer is yes, and the question is asked many times a day, a graph database is worth a trial.

What each database is

Neo4j Community Edition

Neo4j is the database that made the property graph model popular: nodes with labels, relationships with types, and properties on both. Its GitHub repository dates from November 2012, but the project is older. It is written in Java and runs on the JVM. It uses Cypher, which Neo4j created and later opened up through the openCypher project.

Neo4j stores data on disk in its own native graph format, where each relationship record points directly to its start and end nodes. Traversal follows those pointers instead of looking up an index for every hop. The operations manual calls this native graph processing and storage. Hot data is kept in a page cache in memory, and query work happens on the JVM heap.

The operations manual describes two self-managed editions:

  • Community Edition is GPLv3 and “suitable for single-instance deployments”. It has ACID transactions, Cypher, the slotted runtime, indexes (including full-text and vector indexes), uniqueness and existence constraints, the Bolt protocol, official drivers for .NET, Go, Java, JavaScript and Python, APOC core procedures, offline import and offline backup through a dump.
  • Enterprise Edition is commercial. It adds clustering, online backup, multiple user databases, role-based and fine-grained access control, LDAP and Kerberos, and the pipelined and parallel runtimes. Infinigraph, a further edition, adds sharding.

Neo4j’s Enterprise source used to be published under an open licence; with version 3.5 in 2018 the company stopped doing that, and Enterprise has been closed source since. Community stayed GPLv3.

The default login is user neo4j with password neo4j, and Neo4j forces a password change on first login. By default, Neo4j listens on 7474 for HTTP and 7687 for Bolt.

A minimal single-server configuration for a private network looks like this:

# neo4j.conf (sketch for 2026.x Community; not run in our drill)
server.default_listen_address=10.20.0.12
server.bolt.listen_address=:7687
server.http.listen_address=:7474
dbms.security.auth_enabled=true

# memory: set explicitly, do not rely on start-up heuristics
server.memory.heap.initial_size=4g
server.memory.heap.max_size=4g
server.memory.pagecache.size=8g
db.memory.transaction.total.max=2g

# new databases default to Cypher 25 from 2026.02
db.query.default_language=CYPHER_25

And the Surat question in Cypher, with a uniqueness constraint so the lookup is fast:

CREATE CONSTRAINT retailer_id IF NOT EXISTS
FOR (r:Retailer) REQUIRE r.id IS UNIQUE;

// accounts linked to R1042 through shared identifiers, up to 4 retailer-to-retailer steps
MATCH (a:Retailer {id: $id})-[:USES*1..8]-(b:Retailer)
WHERE b <> a
RETURN DISTINCT b.id, b.status;

Each retailer-to-retailer step passes through an identifier node (phone, bank account, GSTIN or address), so four steps are eight hops. Always bound a variable-length pattern. An unbounded [:USES*] on a graph with one popular identifier, such as the warehouse phone number entered on many accounts, can explore most of the graph.

Memgraph

Memgraph is an in-memory graph database written in C++. Its GitHub repository dates from September 2020. It implements Cypher and the Bolt protocol, so most Neo4j drivers and many tools work with it, but it is a separate engine with its own extensions and its own differences in semantics. Its own description today leans on GraphRAG, AI memory and real-time analytics.

Storage is in RAM. The documentation describes three storage modes:

  • IN_MEMORY_TRANSACTIONAL, the default, with ACID transactions, durability through write-ahead logs and periodic snapshots on disk.
  • IN_MEMORY_ANALYTICAL, which speeds up import and analysis but “offers no ACID guarantees besides manually created snapshots”. It is meant for bulk loads and batch analysis.
  • ON_DISK_TRANSACTIONAL, which keeps data on disk; the documentation says it is still experimental and recommends in-memory modes where possible.

The whole graph, plus indexes and query working memory, must fit in RAM in the default mode. Memgraph lets you cap memory for the instance with --memory-limit, and per query or per procedure with QUERY MEMORY and PROCEDURE MEMORY clauses.

Memgraph ships the MAGE library of graph algorithms (community detection, PageRank, centrality and more) as query modules, and supports Kafka streams, triggers and Python query modules.

Licensing needs a careful reading. The top-level LICENSE file says source code is “variously licensed” under the Business Source License 1.1 and the Memgraph Enterprise License. The BSL file is dated 1 January 2026 and names Memgraph Community Edition as the licensed work. Its Additional Use Grant allows production use “solely for any Authorised Purpose”, defined as your internal business purposes, provided you do not embed or distribute it to third parties, do not host it for third parties as a database service, and do not build a competing product. Each version changes to Apache-2.0 on the change date or four years after its first public distribution, whichever comes first; the change date field in the file is printed as “2030-21-10”, which is not a valid date in that format, so rely on the four-year rule and ask Memgraph if it matters to you.

The Memgraph Enterprise page lists these as Enterprise only: role-based, label-based and property-based access control, LDAP, SAML and OIDC, the audit log, Prometheus metrics, automatic failover, multi-tenancy, CRON snapshot schedules, dynamic algorithms, TTL and parallel execution. Enterprise licences are sized by memory; at the limit, writes are blocked. Replication from MAIN to REPLICA instances (SYNC, ASYNC or STRICT_SYNC) is in Community, with manual failover.

There is one default that deserves attention. Memgraph starts with no users, and the documentation describes a “userless log-in”: until you create a user, anyone who can reach the Bolt port can run any query. Create an admin user before the instance is reachable, and bind it to a private address.

A configuration sketch:

# /etc/memgraph/memgraph.conf (sketch for 3.14; not run in our drill)
--bolt-address=10.20.0.13
--bolt-port=7687
--memory-limit=24576          # MiB, keep headroom below the VM's RAM
--storage-wal-enabled=true
--storage-snapshot-interval-sec=3600
--storage-snapshot-retention-count=5
--storage-snapshot-on-exit=true
--log-level=WARNING

The same question in Memgraph, using its breadth-first expansion:

MATCH (a:Retailer {id: $id})-[:USES *BFS ..8]-(b:Retailer)
WHERE b <> a
RETURN DISTINCT b.id, b.status;

Check the flag names against the configuration page for your exact version; Memgraph renames and deprecates flags across releases.

Apache AGE

Apache AGE (A Graph Extension) adds graph storage and openCypher queries to PostgreSQL. It grew out of AgensGraph, a graph database fork of PostgreSQL made by the Korean company Bitnine. AGE entered the Apache Incubator in April 2020, released 1.0.0 in April 2022 and graduated to a top-level project in May 2022. It is Apache-2.0.

AGE is not a separate server. You install the extension, and graphs live as schemas inside your database. Each vertex label and edge label becomes a table, with properties in a JSON-like type called agtype. You run Cypher through a SQL function, which means you can mix graph patterns with ordinary SQL joins, CTEs and aggregates in one statement, and the graph is covered by the same transactions, roles, replication and backups as the rest of the database.

-- once per database (sketch; not run in our drill)
CREATE EXTENSION age;
LOAD 'age';
SET search_path = ag_catalog, "$user", public;
SELECT create_graph('trade');

-- the Surat question
SELECT * FROM cypher('trade', $$
  MATCH (a:Retailer {id: 'R1042'})-[:USES*1..8]-(b:Retailer)
  WHERE b <> a
  RETURN DISTINCT b.id, b.status
$$) AS (id agtype, status agtype);

Some things to know:

  • The cypher() call needs a column definition list, because PostgreSQL must know the result types before it plans the query.
  • AGE functions that create graphs and labels write to the catalog. The README warns that with clients that do not autocommit, such as psycopg 3 or JDBC, a graph or label can look created but be invisible to other sessions until you commit. That is normal PostgreSQL behaviour, but it surprises people.
  • Use parameters for anything that comes from users. CVE-2022-45786 was an SQL injection issue in the AGE drivers for Go and Python up to 1.1.0, fixed by a parameterised form of cypher() and updated drivers.
  • Upgrades use extension update scripts. The release notes carry a clear warning to back up the database before running them.
  • The 1.8.0 candidate notes for PostgreSQL 15 describe faster variable-length traversal, smaller graph caches and automatic indexes on vertex IDs and edge endpoints. That is the project’s description, not our measurement.

AGE has no clustering of its own. A graph in AGE is as available as the PostgreSQL it runs in: Patroni or CloudNativePG for failover, pgBackRest or WAL-G for backups, streaming replicas for reads. On managed PostgreSQL services, check whether the provider allows the AGE extension at all; many do not.

FalkorDB, ArangoDB, JanusGraph and Kuzu, briefly

FalkorDB is a Redis module that holds the graph in memory as sparse matrices (GraphBLAS) and speaks Cypher through GRAPH.* commands, continuing the RedisGraph design after Redis ended RedisGraph. It is under the Server Side Public License v1, which is not OSI-approved and has strong conditions for service use. The 6.0 line has a new Rust engine, and its notes warn not to mix 4.x and 6.0 nodes in one replication set. It targets GraphRAG.

ArangoDB is a multi-model database (documents, key-value and graphs) with its own language, AQL. From 3.12 (March 2024) it is under BSL 1.1 with a grant for internal production use. It had six GitHub security advisories in August and September 2026, four rated critical, including unauthenticated superuser access. If you run it, read them now.

JanusGraph is an Apache-2.0 distributed graph database on top of a storage backend such as Apache Cassandra, ScyllaDB or HBase, with an index backend such as Elasticsearch, OpenSearch or Solr. It uses Gremlin from Apache TinkerPop, not Cypher. Version 1.2.0 (25 October 2026) moves to TinkerPop 3.8.2, which changes some Gremlin semantics, requires Java 11 or later, and adds support for Cassandra 5.0 and Elasticsearch 9. It suits very large graphs where a team already runs Cassandra well, and is heavy for anything smaller.

Kuzu was an embedded, columnar graph database (MIT licence) that ran inside your process, like SQLite or DuckDB, with Cypher. Its GitHub repository is archived, and 0.11.3 from 10 October 2025 is the last release. It still installs and runs, which is why we used it in the drill, but do not start a new production system on an archived project.

Query languages: Cypher, openCypher, GQL and Gremlin

Cypher is a declarative pattern language: you draw the shape of the data with ASCII art, such as (a)-[:USES]->(i)<-[:USES]-(b), and the database finds matches. Neo4j created it and started the openCypher project in 2015 to let other databases implement it. Memgraph, AGE, FalkorDB and Kuzu all implement Cypher or openCypher, each with gaps and extensions. A query that runs on Neo4j will often run elsewhere, but not always, and the differences show up in edge cases like null handling, path semantics and functions. Memgraph’s 3.14 breaking changes are a good example.

GQL is the ISO standard graph query language, published as ISO/IEC 39075:2024 on 12 April 2024. It drew heavily on Cypher, along with PGQL, GSQL and G-CORE, and SQL itself gained property graph queries (SQL/PGQ) in SQL:2023. Neo4j publishes GQL conformance notes for its Cypher. For a buyer in 2026, GQL is less a feature to check and more a signal: Cypher’s pattern style is the standard direction, so time spent learning it is not wasted.

Gremlin, from Apache TinkerPop, is a traversal language: you write the steps the traversal takes. JanusGraph uses it.

Side by side

Licence, editions and governance

  • Neo4j Community: GPLv3, company governance by Neo4j Inc. Running it as a server for your own application does not oblige you to publish your application code, but modifying and distributing Neo4j itself brings GPL obligations; discuss distribution plans with your legal team. Neo4j Enterprise: commercial licence, closed source since 3.5.
  • Memgraph: BSL 1.1 with an Additional Use Grant for internal business purposes, plus the Memgraph Enterprise License for enterprise features, which need a paid or trial licence key. Not open source by the OSI definition until each version converts to Apache-2.0.
  • Apache AGE: Apache-2.0, Apache Software Foundation governance with a PMC. The most permissive of the three, and the only one with foundation governance.

For internal use on your own servers, all three are usable without paying. If you embed the database in a product or host it for clients, Memgraph’s grant does not cover you, Neo4j Community’s GPL needs legal review, and AGE is the simplest.

Storage model

  • Neo4j: native graph storage on disk with a page cache. The graph can be larger than RAM, though cache misses make traversals disk-bound.
  • Memgraph: in RAM, with WAL and snapshots. Fast while it fits; a hard limit when it does not.
  • Apache AGE: ordinary PostgreSQL tables and indexes, where each hop is effectively an index lookup or join. You get PostgreSQL’s maturity, and pay with speed on deep patterns.

Clustering and high availability by edition

  • Neo4j Community: single instance. No clustering, no read replicas. If the server is down, the graph is down. Enterprise: clustering with Raft for primaries, asynchronous secondaries for read scaling, and causal consistency through bookmarks.
  • Memgraph Community: replication from MAIN to REPLICA instances, with manual promotion. Enterprise: high availability with coordinators and automatic failover.
  • Apache AGE: whatever your PostgreSQL has. Streaming replicas carry the graph tables, and Patroni or CloudNativePG handle failover.

If the graph is a read-side copy that can be rebuilt from the system of record, a single instance with a tested rebuild script is a reasonable availability plan, and the licence question becomes much simpler.

Backups

  • Neo4j Community: offline only. The manual says neo4j-admin database dump runs only on a stopped DBMS in Community, so every backup is planned downtime, unless the graph is rebuilt from the source. Online backup is Enterprise.
  • Memgraph: CREATE SNAPSHOT, then LOCK DATA DIRECTORY so durability files are not removed while you copy the snapshot and WAL files, then unlock. CRON snapshot schedules are Enterprise.
  • Apache AGE: pgBackRest, WAL-G, Barman or pg_dump, with point-in-time recovery. Test that a restore brings back the ag_catalog metadata with the label tables.

Memory sizing

We do not give sizing numbers we did not measure, but the method is clear for each.

  • Neo4j: size the page cache for the store files you query (ideally the whole store plus growth) and the heap for concurrent queries, and set both explicitly. neo4j-admin server memory-recommendation gives a starting point. Leave memory for the OS and for vector indexes, which the manual says live outside the page cache, and turn swap off on a dedicated server, as the manual recommends.
  • Memgraph: graph, indexes and query memory all live in RAM. Load a realistic sample, read SHOW STORAGE INFO, and extrapolate with care. Set --memory-limit below the machine’s RAM so a large query fails cleanly instead of the kernel killing the process.
  • Apache AGE: normal PostgreSQL sizing: shared_buffers, work_mem, and enough OS cache for hot label tables and indexes.

Performance

Performance claims in the graph world are loud, so be strict about where a number comes from.

  • Memgraph publishes its own benchmark comparisons against Neo4j, called Benchgraph. Those are the vendor’s own runs, on workloads and settings the vendor chose. Use them to decide what to test, not as a result.
  • The AGE 1.8.0 notes describe faster variable-length traversal and smaller caches. That is the project’s description; we did not measure it.

The only benchmark that matters is your own: your graph shape, your degree distribution (a few very popular nodes change everything), your query and your concurrency. In our small drill, a SQL recursive CTE answered faster than the embedded graph engine at every depth we tried. On a larger graph with wider patterns, the result could be the opposite. Measure.

Security model

  • Neo4j Community: authentication on by default with a forced password change, but no role-based access control, so every user can read and write everything.
  • Memgraph Community: userless login until you create a user; users and passwords work, but RBAC, SSO and audit logs are Enterprise. Configure SSL on Bolt.
  • Apache AGE: PostgreSQL roles, grants, row-level security, pg_hba.conf and TLS, and you can grant on label tables like any other table. The strongest free access control of the three.

Security history

Read this section for patterns, not for a score.

  • Neo4j has a steady NVD history. Recent server issues include CVE-2025-11602 (October 2025, a one-byte information leak from earlier connections in the Bolt handshake, Community and Enterprise), CVE-2026-1622 (February 2026, the obfuscate_literals query log option did not redact error information, fixed in 2026.01.3 and 5.26.21), and CVE-2026-14587 (August 2026, an unauthenticated client can make the Bolt handshake decoder hold a connection open by sending an overlong capability mask). Several 2026 issues, such as CVE-2026-1524 (an SSO edge case rated 9.8) and CVE-2026-1497 (composite database namespaces), affect Enterprise only. Older issues in the APOC procedures library, such as CVE-2021-42767 (directory traversal), are a reminder that plugins are part of the attack surface.
  • A wider pattern: Cypher injection in applications. In 2026, NVD lists several CVEs in other software that built Cypher by joining strings, including AI agent frameworks that passed LLM-generated Cypher straight to Neo4j (CVE-2026-55615 in Langroid and CVE-2026-32247 in Graphiti), and integration components in Apache Camel (CVE-2026-46591) and Apache Syncope (CVE-2026-86460). None of these is a bug in the database. All of them are the old SQL injection lesson in a new language. Use parameters, and give LLM-driven tools a read-only user.
  • Memgraph had no GitHub security advisories and no NVD entries matching “Memgraph” when we checked. That does not mean it has no bugs; the userless login default is the bigger practical risk.
  • Apache AGE: CVE-2022-45786, SQL injection through the Go and Python drivers when queries were built without parameters, for AGE up to 1.1.0 on PostgreSQL 11 and 12. No GitHub security advisories were published in the repository when we checked. AGE also inherits PostgreSQL’s security record, so patch PostgreSQL minors.
  • FalkorDB had a large batch of CVEs published in September and October 2026, mostly memory-safety bugs: out-of-bounds writes in the Bolt and WebSocket handlers reachable without authentication before 4.20.0 (CVE-2026-107908, CVE-2026-107909), an authentication bypass in the Bolt handler before 4.20.0 (CVE-2026-107910), crashes and overflows in RDB loading and query validation, and a path traversal in FalkorDB Browser 1.9.3 (CVE-2026-6057). If you run FalkorDB, stay on a current release and keep the Bolt port private.

Operations and upgrades

  • Neo4j: monthly calendar releases, or the 5.26 LTS line for fewer upgrades. Moving from 5.26 to 2025 or 2026 releases is a real migration; test on a copy of the store and choose your Cypher version deliberately. In Community, every upgrade restart is downtime.
  • Memgraph: frequent releases with breaking changes at the top of each note. Snapshot recovery time grows with data, so measure restart time with your data.
  • Apache AGE: ALTER EXTENSION age UPDATE after a backup. PostgreSQL major upgrades need an AGE build for the new major, and branches release at different times, so AGE can hold back a PostgreSQL upgrade.

When you do not need a graph database

The relational answer deserves a fair hearing first. PostgreSQL has had WITH RECURSIVE since 8.4, and SQLite since 3.8.3. Since PostgreSQL 14 there are SEARCH and CYCLE clauses that make breadth-first ordering and cycle detection explicit. For the Surat question, a model with one link table makes the SQL short:

-- one row per (retailer, identifier) pair: phone, bank account, GSTIN, address
CREATE TABLE retailer_identifier (
  retailer_id  bigint NOT NULL,
  ident_kind   text   NOT NULL,
  ident_value  text   NOT NULL,
  PRIMARY KEY (retailer_id, ident_kind, ident_value)
);
CREATE INDEX ON retailer_identifier (ident_kind, ident_value);

WITH RECURSIVE reach(retailer_id, depth) AS (
  SELECT 1042::bigint, 0
  UNION
  SELECT b.retailer_id, r.depth + 1
  FROM reach r
  JOIN retailer_identifier a ON a.retailer_id = r.retailer_id
  JOIN retailer_identifier b ON b.ident_kind = a.ident_kind
                            AND b.ident_value = a.ident_value
                            AND b.retailer_id <> r.retailer_id
  WHERE r.depth < 4
)
SELECT retailer_id, min(depth) AS steps
FROM reach
WHERE retailer_id <> 1042
GROUP BY retailer_id
ORDER BY steps, retailer_id;

The trick is the model, not the database. Instead of five tables with phone, bank and address columns, there is one table of “this retailer uses this identifier”. That turns a many-join query into a single self-join repeated by the recursion. UNION (not UNION ALL) removes duplicate rows of retailer and depth, which keeps the work bounded on a graph with cycles.

You probably do not need a graph database when:

  • The depth is fixed and small. Two or three known joins are SQL’s home ground.
  • The question is bounded reachability on one or two link types. A recursive CTE with a depth limit and the right index is enough, and it runs on the replica you already have.
  • The graph is a feature inside another product. Permissions belong in an authorisation system such as OpenFGA or SpiceDB; lineage belongs in a data catalog. Do not build those on a general graph database unless you have a strong reason.
  • You need graph algorithms once a month. Export and run them in a notebook with NetworkX or igraph.
  • Nobody will own a second database. Another stateful system means backups, upgrades, monitoring, access control and someone on call. That cost is real for a six-person team.

The signs that SQL is no longer enough: the recursive query becomes a tree of special cases per link type, analysts want patterns rather than reachability, depth keeps growing, or you need the paths themselves, not only the endpoints.

A worked plan for Surat

Here is how the Surat team could go from “the query is slow and unreadable” to something the finance head can trust.

  1. Write the question down. With the finance head, write it in one sentence: “for a retailer asking for credit above the threshold, list accounts connected through a shared phone, bank account, GSTIN or delivery address, up to four steps away, and flag any that have defaulted or were opened by the same agent.” Agree that the answer is advisory, and a person approves the credit.
  2. Fix the model in PostgreSQL first. Create the retailer_identifier table, fill it from the existing tables with a nightly job and triggers for new rows, and normalise values on the way in (phone numbers to one format, GSTINs uppercase, addresses through a simple cleaning step). Most of the “missed” links in the original SQL were formatting differences, not depth.
  3. Handle popular identifiers. A few identifiers are shared by hundreds of accounts for innocent reasons: a market association’s address, a transport company’s phone number, an agent’s own mobile entered for convenience. Keep a small list of such “hub” identifiers and exclude them from the traversal, or the answer becomes “everyone”.
  4. Run the recursive CTE on the replica. With the link table and index, the four-step query should be measured on real data. If it returns in well under a second for typical retailers, ship it behind the credit approval screen and stop here for now.
  5. Measure the worst cases. Retailers with many identifiers, and depth five or six. Keep the numbers.
  6. Decide whether a graph engine is justified. The trigger is not speed alone. It is the finance team asking for patterns (“same agent, shared bank account, defaulted within 90 days”) or for the paths themselves. Then run a two-week trial.
  7. Trial on the read side, not as the system of record. Load the retailer, identifier, agent and order-status graph from PostgreSQL into a graph engine every night, with an incremental feed later if needed. The system of record stays PostgreSQL. This keeps high availability simple: if the graph server is down, rebuild it, and the credit screen falls back to the SQL query.
  8. Pick the trial engine by constraints. AGE on a separate PostgreSQL 17 or 18 instance keeps everything in PostgreSQL with a simple licence; they would not put it on the PostgreSQL 16 primary because of the upgrade path. Memgraph Community fits a read-side graph: it fits in RAM, internal use is covered by the BSL grant, and MAGE gives community detection for the “rings” question. Neo4j Community is the most familiar to new hires; its offline backups and missing RBAC matter little for a rebuildable copy. They trial Memgraph Community, with Neo4j Community as the comparison on the same Cypher.
  9. Lock down access. One service account for the nightly load, a read-only user for the credit screen, Bolt bound to the private network, TLS on Bolt, and parameters in every query. No LLM tool gets write access.
  10. Review after a quarter. If the graph trial did not change any credit decision, retire it and keep the SQL. If it did, budget for running it properly, including whether automatic failover (Memgraph Enterprise or Neo4j Enterprise) is worth paying for or whether “rebuild in 20 minutes” is good enough.

When each is better

  • Neo4j Community is better when you want the most familiar graph database, the largest set of drivers, tools, training and hiring options, a graph larger than RAM on one server, and you can accept single-instance operation with offline backups, or you plan to pay for Enterprise later.
  • Memgraph is better when the graph fits in memory, low-latency traversal or streaming updates matter, you want built-in graph algorithms, and your use is internal. Budget for Enterprise if you need automatic failover, RBAC or audit logs.
  • Apache AGE is better when your data and your team already live in PostgreSQL, you want graph queries mixed with SQL in one transaction, you want PostgreSQL’s access control, backups and HA, and your graph patterns are moderate in depth. It is the most open licence of the three.
  • A recursive CTE is better more often than graph vendors would like.

When not to adopt a graph database

  • Your questions are fixed and shallow. You would be copying data to answer questions SQL already answers.
  • You want it as the only database for a transactional business system. Graph databases can be systems of record, but most business teams get more from keeping orders, invoices and payments in a relational database and putting a graph beside it.
  • The graph will not fit your edition’s limits. An in-memory engine for a graph that grows past RAM, or a single-instance Community edition for a workload that cannot tolerate downtime.
  • You are choosing it for AI because of a blog post. GraphRAG is real, but test whether a vector index and a few joins already answer your questions.
  • Nobody owns it.

A short history

Graph-shaped data is older than relational databases: the network databases of the 1960s were graphs of records, and the relational model replaced them because joins made data independent of access paths. Property graph databases of the 2000s, led by Neo4j, brought back pointer-style traversal for connected data. Neo4j opened Cypher through openCypher in 2015, which let Memgraph, AGE, RedisGraph, FalkorDB and Kuzu reuse the language. ISO published GQL in April 2024, and SQL:2023 added property graph queries to SQL. The licensing story moved the usual way: Neo4j closed its Enterprise source in 2018, Memgraph and ArangoDB use BSL, FalkorDB uses SSPL, and the fully open options are Neo4j Community under GPLv3, Apache AGE and JanusGraph under Apache-2.0. Kuzu, one of the more interesting new engines, was archived in 2025.

Decision guide

Start with the question

  • Fixed joins: stay in SQL.
  • Bounded reachability on one or two link types: recursive CTE first, with a link table and a depth limit.
  • Patterns across many link types, variable depth, changing often: a graph engine.
  • Whole-graph algorithms, occasionally: export and use a library.

Then choose the storage model

  • Data already in PostgreSQL and moderate patterns: Apache AGE, in a database where you control the extension and the PostgreSQL major version.
  • Graph fits in RAM, traversal latency matters: Memgraph.
  • Graph larger than RAM on one server, or you value the widest ecosystem: Neo4j.
  • Very large and distributed on Cassandra: JanusGraph.

Then check the licence and edition for your use

  • Internal, single instance, rebuildable: all three are fine without paying.
  • Automatic failover, RBAC or online backup: Neo4j or Memgraph Enterprise, or AGE on a well-run PostgreSQL.
  • Selling or hosting a product with the database inside: AGE is simplest; Neo4j Community needs GPL review; Memgraph’s grant does not cover it.

Checklist

  • The business question is written in one sentence, with the maximum depth.
  • A recursive CTE on a clean link table has been tried and measured.
  • Popular “hub” identifiers are handled explicitly.
  • Every variable-length pattern has an upper bound.
  • Every query from user input or an LLM uses parameters, and LLM tools have read-only accounts.
  • The graph is either the system of record with a tested backup, or a read-side copy with a tested rebuild.
  • Default credentials are gone (Neo4j forces this; Memgraph needs a user created before exposure).
  • Bolt and HTTP ports are on private addresses, with TLS.
  • Memory is set explicitly: page cache and heap for Neo4j, --memory-limit for Memgraph, PostgreSQL settings for AGE.
  • Restart and recovery time have been measured with real data.
  • The licence and edition have been checked for your deployment, including any plan to sell or host.
  • For AGE, an AGE build exists for your next PostgreSQL major before you plan the upgrade.
  • Release notes and advisories are followed for your exact version.

Common mistakes

  • Treating a modelling problem as a database problem. Five tables with identifier columns make every query hard. One link table makes SQL and graph queries simple.
  • Unbounded variable-length patterns. [*] on a graph with a popular node explores the world.
  • Ignoring degree skew. One identifier shared by 500 accounts dominates both the run time and the answer.
  • Assuming Cypher is portable. It mostly is. Null handling, path semantics and function names differ, and Memgraph 3.14 changed several behaviours in one release.
  • Exposing Memgraph before creating a user. Userless login is convenient on a laptop and dangerous on a network.
  • Installing AGE on the primary without checking the upgrade path. It can block your next PostgreSQL major upgrade.
  • Building Cypher strings from user or LLM input. It is SQL injection again.

The drill we ran

The drill was small and local, and ran on 30 October 2026 between about 6:25 AM and 6:30 AM IST, entirely inside a user folder. No server was started, nothing listened on the network, and we did not install packages system-wide or touch system directories.

  1. We created a Python 3.13 virtual environment and installed Kuzu 0.11.3, the last Kuzu release, as an embedded Cypher engine. SQLite 3.46.1 came with Python.
  2. We generated invented, synthetic data with a fixed seed: accounts and identifiers (standing for phones, bank accounts, GSTINs and addresses), with a “uses” link from accounts to identifiers. A sparse set had 20,000 accounts, 26,011 identifiers and 26,826 links, with a planted chain of 12 accounts linked through shared identifiers. A denser set had 20,000 accounts, 20,011 identifiers and 40,180 links.
  3. We asked the same question in both engines: every account reachable from one starting account within N account-to-account steps, with the minimum number of steps. In SQLite we used a recursive CTE much like the PostgreSQL one above. In Kuzu we used a Cypher shortest-path pattern with an upper bound of 2N hops.
  4. Both engines returned identical answers in every run: 16 accounts on the sparse set at four steps, and 27, 81, 233, 664 and 1,771 accounts on the denser set at three to seven steps.
  5. Timings from one run each on this box, not a benchmark: SQLite’s in-memory recursive CTE took between about 0.2 ms and 9.5 ms, growing with depth. Kuzu took between about 22 ms and 29 ms at every depth, which looks like fixed per-query overhead at this size. At this scale, plain SQL was faster.
  6. One snag: Kuzu 0.11 stores a database as a single file, and our clean-up assumed a folder, so reruns failed with “already exists in catalog”. We fixed it and reran everything.
  7. Everything was embedded, and a pgrep check at the end showed no drill process.

What we did not run: Neo4j Community (the box had no Java, and Neo4j needs JDK 21 or 25), Memgraph (it ships as distribution packages and containers, and we did not install packages system-wide), Apache AGE (it needs a PostgreSQL server built with development files), FalkorDB, ArangoDB and JanusGraph. We did not test large graphs, concurrency, server modes, backups, replication or upgrades. A useful next step is the same comparison with Memgraph and Neo4j Community on a graph with a realistic hub identifier and a million links.

Unlearn, re-learn

  • Unlearn “relationships need a graph database”. Re-learn that a link table and a recursive CTE answer many relationship questions, on the database you already run.
  • Unlearn “Cypher is Neo4j’s language”. Re-learn that Cypher’s pattern style is now the basis of the ISO GQL standard, and that several engines implement it with real differences.
  • Unlearn “Community edition means the same product, free”. Re-learn to read the edition table: no clustering or online backup in Neo4j Community, no failover or RBAC in Memgraph Community.
  • Unlearn “source available is open source”. Re-learn to read BSL grants and SSPL terms against your deployment, especially if you sell or host software.
  • Unlearn “graph databases are always faster for connected data”. Re-learn to measure on your graph; in our drill, SQL won at small scale.
  • Unlearn “injection is a SQL problem”. Re-learn to parameterise Cypher, and give LLM tools read-only access.

Sources

comments powered by Disqus

Releted Posts

MySQL-compatible databases in 2026: revisit MySQL, MariaDB and Percona Server before you upgrade another database

Most teams that run MySQL do not choose their database every year. They chose it once, long ago, and since then they have only upgraded it, or not upgraded it.

Read more

In-process analytics in 2026: revisit DuckDB, Polars and chDB before you spin up another warehouse for a small question

Think of a knitwear exporter in Tiruppur that makes T-shirts, polos and kids’ wear for buyers in Europe, the US, the Gulf and Japan (an invented example, not a real client).

Read more

Log storage in 2026: revisit Grafana Loki, VictoriaLogs and OpenObserve before you keep another month of logs

Picture a platform team in Pune that runs a B2B lending stack for NBFCs across Maharashtra and Gujarat (an invented example, not a real client).

Read more