Article
Do You Need a Graph Database, or Do You Need Graph Queries?
MongoDB's $graphLookup, Atlas Vector Search, and GraphRAG cover most graph workloads in production. A few genuinely need Neo4j. Here's how to tell which one you have.
01
The conversation I keep having
Every few weeks a customer team tells me they’re “evaluating Neo4j.” When I ask why, the answer is some variant of:
- “We have a knowledge graph use case.”
- “We need GraphRAG.”
- “Our data is highly connected.”
- “We’re modelling relationships.”
These are not the same problem. They sound the same in a slide deck. They diverge sharply once you write the queries.
So I always reframe the question. Do you need a graph database, or do you need graph queries? Those are different decisions, and the right answer drives wildly different architectures.
Two questions, two architectures
Graph database
An engine built around traversing edges. Storage, indexing, and planning all assume the graph.
Index-free adjacency
O(1) per hop
Graph queries
Any query that traverses relationships. Expressible on a document, relational, or property-graph engine.
One aggregation pipeline
One result set
Which problem do you have?
Do you need a graph database, or do you need graph queries?
A graph database is a system whose entire engine — storage, indexing, query planning — is built around traversing edges. Native graph storage uses index-free adjacency: each node holds direct memory pointers to its neighbours, so each hop is an O(1) pointer dereference rather than an O(log n) index lookup. That is a real and durable advantage for the workloads it targets.
Graph queries are a different thing. A graph query is any query that traverses relationships — friends-of-friends, recursive category hierarchies, money-flow paths through a transaction network, multi-hop knowledge-graph lookups for an LLM. You can express these on top of a document database, a relational database, or a property-graph engine. The expressiveness, ergonomics, and performance vary, but the queries are not exclusive to graph engines.
Most teams I talk to need graph queries on a dataset that has many other shapes. They have transactional reads and writes. They have full-text search. They have vector search for RAG. They have analytics and time-series. The graph is one shape among several — important, but not dominant.
A few teams genuinely need a graph database. Their workload is graph algorithms. Their team thinks in patterns. Their queries are 6 hops deep over dense networks. For them, Neo4j is the right answer and I’ll happily say so.
This article is about telling the difference.
02
What MongoDB gives you for graph workloads
MongoDB Atlas is not a graph database. It does not have index-free adjacency, native graph storage, or a Cypher dialect. Saying otherwise would be dishonest. But it has a substantial set of primitives that cover most graph queries you’ll write in production.
$graphLookup — recursive traversal as an aggregation stage
$graphLookup is a recursive $lookup. You give it a starting value, a connectFromField, a connectToField, and an optional maxDepth. It walks the graph one wave at a time, following edges defined by your fields, until it runs out of matches or hits the depth cap.
The mental model is the adjacency-list property graph: each document is a node; an array field holds outbound edges (typically references to _ids on the same or another collection). $graphLookup recurses across that adjacency list and returns every visited document as an array.
Here’s a friends-of-friends query — the canonical graph query, and the one customers ask about first:
Friends-of-friends with $graphLookup
db.users.aggregate([
{ $match: { _id: ObjectId("...alice") } },
{
$graphLookup: {
from: "users",
startWith: "$friends", // Alice's direct friends
connectFromField: "friends", // expand each friend's friend list
connectToField: "_id",
as: "network",
maxDepth: 2, // friends-of-friends-of-friends
depthField: "hops",
restrictSearchWithMatch: { active: true }
}
},
{ $project: { name: 1, "network.name": 1, "network.hops": 1 } }
])$graphLookup expansion
Starting at one document, each wave follows connectFromField → connectToField until maxDepth caps the traversal.
Wave 1 — depth 0
u2
hops: 0
u3
hops: 0
u4
hops: 0
Wave 2 — depth 1
u5
hops: 1
u6
hops: 1
u7
hops: 1
u8
hops: 1
Wave 3 — depth 2
u9
hops: 2
u10
hops: 2
u11
hops: 2
u12
hops: 2
u13
hops: 2
maxDepth: 2 — cap reached
Stage parameters
startWith
Initial value(s) the recursion begins with — typically a field on the input document.
connectFromField
The field on each visited document whose value(s) feed the next wave.
connectToField
The field on the target collection that connectFromField is matched against. Index this.
maxDepth
Caps recursion depth. maxDepth: 0 ≡ a single non-recursive lookup.
depthField
Tags each result with the hop at which it was discovered.
restrictSearchWithMatch
Filter applied at every wave — express typed edges without separate collections.
A few things to note that matter in production:
- Depth control is built in.
maxDepth: 2caps the traversal at three hops. Without it, broad networks blow up. - Edge filtering.
restrictSearchWithMatchruns at every wave — so you can express typed edges (only followKNOWS, notBLOCKED) without storing them in separate collections. - Hop annotation.
depthField: "hops"tags each result with the hop count it was discovered at. This is what lets you weight results by distance. - An index on
connectToFieldis not optional. Without it, every expansion wave scans the entirefromcollection. With it, each wave is index-backed and the traversal stays bounded. - The 100 MB stage memory limit applies.
$graphLookupacceptsallowDiskUsefor spilling, but very wide traversals — high fan-out, many hops — can still error. The mitigation is the same as Neo4j’s: filter aggressively, cap depth, scope the starting set. - Sharded
fromcollections have been supported since MongoDB 5.1 (Nov 2021). Earlier versions required unsharded targets.
For 1- to 3-hop traversals on indexed adjacency lists, $graphLookup is fast and predictable. It is not designed for 7-hop traversals over dense graphs — Neo4j’s index-free adjacency wins there, and it isn’t close.
Atlas Vector Search — semantic retrieval as an aggregation stage
$vectorSearch runs approximate nearest-neighbour search over an HNSW index — the same graph-based ANN structure used by Pinecone, Weaviate, and Qdrant. It supports pre-filtering with the standard MongoDB Query API, exact (ENN) mode for small collections, and integrates natively with the rest of the aggregation pipeline.
If you want the ground-up explanation of why embeddings work, see What Are Vector Embeddings? and RAG From the Ground Up.
The relevant point here is that vector search and graph traversal compose. You can run $vectorSearch to find the top-K semantically similar entities, then $graphLookup from each of them to expand the neighbourhood, all in one pipeline. No federation, no cross-store join.
Atlas Search — lexical search and $rankFusion
$search is BM25-style full-text search built on Apache Lucene. $rankFusion, GA in MongoDB 8.0, combines N input pipelines into a single ranked output using Reciprocal Rank Fusion with a fixed rank_constant = 60: each document’s score is the weighted sum of 1 / (60 + rank) across pipelines.
In practice this means a single query can blend semantic similarity, lexical match, and graph proximity:
Hybrid retrieval — vector + lexical + graph
db.docs.aggregate([
{
$rankFusion: {
input: {
pipelines: {
semantic: [{ $vectorSearch: { /* embedding query */ } }],
lexical: [{ $search: { /* keyword query */ } }],
graph: [{ $graphLookup: { /* neighbourhood expansion */ } }]
}
},
combination: { weights: { semantic: 0.5, lexical: 0.3, graph: 0.2 } }
}
}
])This is the move that makes Atlas a credible substrate for graph-flavoured AI workloads. Three retrieval primitives, one pipeline, one cluster, one auth domain.
The document model maps cleanly to property graphs
A property graph has nodes with properties, edges with properties and types, and a set of labels. A MongoDB document with an edges array — each entry containing a target reference, a type, and properties — is a property-graph node + adjacency list expressed in BSON.
Property graph ↔ Document
A node with a typed adjacency list is a property-graph vertex expressed in BSON. Click an edge to see the matching document entry.
Property graph view
alice
User
bob
User
carol
User
acme
Company
post_42
Post
Edges typed by label, properties on each edge.
BSON document view (users collection)
Adjacency list embedded in BSON. The same data — different view.
This is not an accident. The document model and the property-graph model are isomorphic for a wide class of graphs. Where they diverge is at the engine level: MongoDB’s planner doesn’t know about edges as first-class objects, so it can’t optimise traversals the way Neo4j can. The data model is fine. The query optimiser is the difference.
03
GraphRAG on Atlas: the strongest case
This is where the thesis becomes hardest to argue against. GraphRAG is a pattern that needs what Atlas already gives you: a vector index, a graph traversal, a lexical index, and a transactional document store, all sharing a single query path.
What GraphRAG actually is
The term comes from Edge, Trinh, et al., From Local to Global: A Graph RAG Approach to Query-Focused Summarization (Microsoft Research, arXiv:2404.16130, April 2024).
Vanilla RAG retrieves the top-K chunks most similar to the query embedding, stuffs them into the prompt, and lets the LLM compose an answer. It works well for local questions — “What does the contract say about termination?” — where the answer lives in a small number of chunks. It fails on global questions — “What are the main themes across our entire knowledge base?” — because there is no single chunk to retrieve. The answer requires synthesis across the corpus.
The Microsoft paper’s pipeline:
- Entity and relationship extraction — an LLM reads each document and emits structured triples
(entity_a, relationship, entity_b). - Graph construction — entities become nodes, relationships become edges, properties are attached.
- Community detection with the Leiden algorithm, run hierarchically. Leiden is chosen over Louvain because Leiden’s refinement phase guarantees each community is internally well-connected — critical because the next step assumes each community represents a coherent topic.
- Pre-generated community summaries at multiple levels of the hierarchy.
- Query time — map-reduce over relevant community summaries: each community generates a partial answer, partial answers are reduced into a global one.
The reported gain on 1M-token corpora is “substantial improvements over a conventional RAG baseline for both the comprehensiveness and diversity of generated answers.”
Where independent benchmarks land
GraphRAG is not strictly better than vector RAG. A 2025 systematic evaluation (Han et al., arXiv:2502.11371) reports F1 scores on Natural Questions where vanilla RAG hits 64.78% with Llama 3.1-8B, the KG-GraphRAG (Triplets+Text) variant drops to 50.27% — a ~14-point F1 regression on simple factoid QA — and the strongest GraphRAG variant (Community-Local) only ties RAG to within ~2 points. Latency goes the wrong way too: graph indexing and community summarisation add overhead that compounds at query time.
The honest read: GraphRAG is a tool for queries that require multi-hop synthesis. For simple lookups, vanilla RAG is faster and at least as accurate.
The implication for production systems: most “GraphRAG” deployments are actually hybrid GraphRAG — they route simple queries to vector retrieval, complex queries through the graph. Hybrid is also where Atlas shines, because both paths share the same store.
What GraphRAG looks like on Atlas
The langchain-mongodb package added MongoDBGraphStore in version 0.5.0 and announced GA in April 2025. The integration is also covered in the Atlas documentation.
Polyglot stack vs Atlas stack
Same workload — four specialised stores wired together, or one cluster with three retrieval primitives in a single pipeline.
Polyglot stack
Several ms per cross-store hop, compounding across stages
Origin
Client
Request
Hop 1
Postgres
OLTP / docs
Hop 2
Pinecone
Vector index
Hop 3
Neo4j
Entity graph
Hop 4
Elasticsearch
Lexical / summaries
Origin
Client
Request
Hop 1
Postgres
OLTP / docs
Hop 2
Pinecone
Vector index
Hop 3
Neo4j
Entity graph
Hop 4
Elasticsearch
Lexical / summaries
Cross-store integration cost
Atlas stack
In-process, single cluster, single auth boundary
$vectorSearch
HNSW vector index
$graphLookup
Recursive traversal
$search
Lucene full-text
One pipeline, one transactional boundary
GraphRAG build pipeline
Microsoft’s 2024 GraphRAG pipeline expressed end-to-end on Atlas — each stage writes back into the same cluster.
01
Documents
Source corpus chunked and stored with embeddings.
02
Entity extraction
LLM emits (entity_a, relationship, entity_b) triples.
03
Graph construction
Entities → nodes, relationships → edges, in MongoDB.
04
Leiden communities
Hierarchical community detection with refinement guarantees.
05
Community summaries
LLM-generated summaries per community, embedded for retrieval.
01
Documents
Source corpus chunked and stored with embeddings.
02
Entity extraction
LLM emits (entity_a, relationship, entity_b) triples.
03
Graph construction
Entities → nodes, relationships → edges, in MongoDB.
04
Leiden communities
Hierarchical community detection with refinement guarantees.
05
Community summaries
LLM-generated summaries per community, embedded for retrieval.
At query time, retrieval is map-reduce over relevant community summaries — run as a single aggregation pipeline with $vectorSearch + $graphLookup + $lookup.
The pattern:
- Source documents → one Atlas collection. Vector index on a
chunk_embeddingfield for vanilla RAG fallback. - Entities → another collection. Each entity document contains its name, type, properties, an
embeddingfield (so vector search hits entity nodes directly), and anedgesarray of typed relationships pointing to other entity_ids. - Community summaries → a third collection, with
level,member_entities, and a generatedsummarytext field that is itself embedded for retrieval.
Query time, in one aggregation pipeline:
GraphRAG retrieval on Atlas — single aggregation pipeline
db.entities.aggregate([
// 1. Find entities semantically related to the query
{
$vectorSearch: {
queryVector: $$query_embedding,
path: "embedding",
numCandidates: 100,
limit: 10
}
},
// 2. Expand each match's neighbourhood up to N hops
{
$graphLookup: {
from: "entities",
startWith: "$edges.target",
connectFromField: "edges.target",
connectToField: "_id",
as: "neighbourhood",
maxDepth: 2,
restrictSearchWithMatch: { active: true }
}
},
// 3. Pull the community summaries those entities belong to
{
$lookup: {
from: "community_summaries",
localField: "community_id",
foreignField: "_id",
as: "summary"
}
},
{ $project: { name: 1, neighbourhood: 1, summary: 1 } }
])One cluster. One auth surface. One transactional consistency boundary. The same indexes that serve your OLTP traffic serve your GraphRAG retrieval.
The polyglot alternative looks like this: source docs in PostgreSQL, embeddings in Pinecone, entity graph in Neo4j, summaries in Elasticsearch. Four stores, four sync pipelines, four monitoring surfaces, four eventual-consistency windows between them. Every query path is now a federation problem.
Atlas does not eliminate the work — entity extraction and community detection are still LLM-heavy preprocessing steps. It eliminates the integration cost, which is the part that tends to dominate the operational budget over time.
04
The other 90% of the workload
Here is the question I never hear asked at a Neo4j evaluation: what else is running on this dataset?
Almost always, the answer is a long list. The graph is one access pattern in a system that also serves:
- Transactional CRUD — user profiles, content, sessions, orders, accounts. Reads and writes per request, with ACID expectations on multi-document updates.
- Full-text search — the same data needs lexical retrieval with stemming, synonyms, faceting.
- Vector search for RAG — embeddings on the same documents that are nodes in your graph.
- Time-series telemetry — events, metrics, audit logs alongside the entities they describe.
- Aggregations and analytics — group-bys, rollups, dashboards.
- Sometimes geospatial — locations of entities, routing, geo-fencing.
If your dataset has graph structure, it almost always has these other shapes too. A social network has the social graph and user profiles, posts, likes, search, recommendations. A fraud-detection system has the transaction graph and per-account ledgers, behavioural events, model features. A knowledge graph has the entity-relationship graph and the source documents, vector embeddings, full-text indexes.
You have two architectural options.
Option A: specialised store per shape. Neo4j for the graph, Postgres or DynamoDB for OLTP, Elasticsearch for search, Pinecone for vectors, ClickHouse for analytics. Each store is best-in-class on its slice. You pay for it in operational surface area, and the bill is concrete:
- N sync pipelines — CDC streams, dual-writes, or scheduled batch jobs between every pair of stores that needs to agree on the same data. The number scales worse than linearly as you add stores.
- N auth surfaces — IAM, SCIM, audit logging, secret rotation, and access reviews multiplied by the number of systems. Each one is a compliance artefact.
- A network hop for every cross-store read — AWS quotes single-digit-millisecond round-trip latency between Availability Zones in the same region, and sub-millisecond intra-AZ. Several ms per hop sounds cheap until you fan out across four stores in a single request path; the cost compounds with every dependent stage.
- Dual schema-evolution discipline — every entity that exists in two stores has to migrate in lockstep, with rollback paths planned in both. Schema drift is the most common cause of late-night incidents in polyglot stacks.
- On-call rotations spanning multiple stores — engineers have to stay current on the operational quirks of every system in the path, or the rotation becomes a tier-1-vs-tier-2 escalation graph.
Microsoft’s Azure SQL team has written about this at length under the banner of “the polyglot tax” — vendor-aligned, but a useful articulation of the operational pattern from one engineering team’s perspective.
Polyglot tax counter
Five workload shapes that almost always coexist on the same dataset. Toggle each between a specialised store and a unified platform — watch what happens to the integration cost.
OLTP
Postgres / DynamoDB
Full-text search
Elasticsearch
Vector / RAG
Pinecone / Weaviate
Graph traversal
Neo4j
Time-series / analytics
ClickHouse
Integration cost (live)
Distinct stores
5stores
Each specialised store is its own system.
Cross-store sync pipelines
10pipelines
Pairs of stores that must agree on the same data — n(n−1)/2.
Network hops per query
5hops
Each cross-store read pays a network round trip.
The sync-pipeline count grows quadratically. Two specialised stores → one pipeline. Five → ten. The integration tax compounds long after the initial architecture decision.
Option B: unified platform. One cluster, one auth surface, one backup, one monitoring stack. You give up best-in-class on each individual shape. You gain transactional consistency across the workloads, simpler ops, and a much smaller integration cost.
The pattern I see in the field: teams in mode A spend the first year shipping fast, the next two years building integration plumbing, and most year-3+ engineering effort on keeping the stores in sync. Teams in mode B ship slower on the graph slice and faster on everything else.
For database choice frameworks more broadly — including the four-layer model that separates data model from engine from operational platform — see Relational, Document, or Platform?.
05
Where Neo4j (genuinely) wins
I work at MongoDB. I’m going to be careful here, because the temptation is to soft-pedal the comparison. I won’t.
Neo4j is excellent technology. There are workloads where it is the right answer and $graphLookup is not. If I’m advising a customer and I see one of these patterns, I tell them to seriously evaluate Neo4j:
Graph algorithms in the runtime path
The Graph Data Science library ships production implementations of:
- PageRank, Personalized PageRank, ArticleRank — for ranking, recommendation, influence scoring.
- Louvain and Leiden community detection — for clustering, segment discovery, knowledge-graph hierarchies.
- Betweenness centrality, closeness, eigenvector — for identifying bridges and influencers.
- Shortest path algorithms — Dijkstra, A*, Yen’s k-shortest, with weighted edges.
- Node embeddings — Node2Vec, FastRP, GraphSAGE.
- Link prediction and graph similarity.
These are not exposed as MongoDB primitives. If you need them, your options on Atlas are: ETL the graph out to NetworkX, igraph, or GraphFrames; or move the graph to Neo4j and use the GDS library directly. For an application whose runtime path includes “compute PageRank over the current graph state” or “detect communities every hour,” Neo4j is the right answer.
Deep traversals on dense graphs
Index-free adjacency means each hop is an O(1) pointer dereference. $graphLookup does each wave with an index-backed lookup against connectToField. The mechanisms diverge as depth grows: Neo4j’s per-hop cost stays roughly constant because the next neighbour is already a pointer in memory, while $graphLookup pays an index lookup per wave whose constant factor compounds with depth × fan-out.
That mechanism difference is the basis of the comparison — not a published head-to-head benchmark. There is no widely cited apples-to-apples $graphLookup vs Neo4j benchmark on identical hardware, datasets, and query mixes that I’d point a customer to. So treat what follows as engineering judgement derived from how the two engines work, not as a measured result:
- 1-3 hops on adjacency lists with proper indexing: the gap is small.
$graphLookupis fine. - 5+ hops on dense graphs (high average degree): the index-lookup constant factor stacks up wave after wave; index-free adjacency wins, often decisively. For fraud-ring detection, deep citation networks, and multi-hop authorisation paths, this is where Neo4j earns its keep.
If you have these workloads, profile them rather than trusting the framing. The crossover depth depends on average degree, index selectivity, and how aggressively restrictSearchWithMatch narrows each wave.
Cypher ergonomics for graph-native teams
Cypher is a pattern-matching language. MATCH (a:User {id: $alice})-[:FRIEND*1..3]-(b:User)-[:LIKES]->(p:Post) WHERE p.topic = "graph" reads like a graph drawing. For teams whose mental model is graphs first, the productivity advantage over expressing the same thing as nested aggregation stages is real.
Cypher was standardised in 2024 as GQL (ISO/IEC 39075:2024) — the first new ISO database query language since SQL. That matters for procurement, training, and multi-vendor portability.
OLAP-style graph workloads
Repeated algorithm runs over a stable graph projection — what GDS calls “in-memory graph projections” — are a category Atlas isn’t built for. If your workload is “load 100M nodes into memory, run PageRank, run community detection, run link prediction, write results back, repeat hourly,” Neo4j has the right primitives.
Graph-first applications
If the graph is the product — a fraud-investigation UI built around exploring connections, a knowledge-graph product where users browse by relationship, a Bloom-style graph visualiser — the entire application is shaped by graph operations. Atlas’s other strengths are irrelevant; Neo4j’s graph-native everything is the right fit.
Honest summary
Neo4j wins where graph traversal and graph algorithms are the dominant workload, the team thinks in graphs, and the depth and density justify the operational cost of running a specialised store.
It does not win — and it is not the cheapest option — when the graph is one shape among several on the same data. There the polyglot tax makes the trade unattractive even when Neo4j is technically superior on the graph slice.
Capability matrix — Atlas vs Neo4j
Ten workload-relevant axes, scored from each engine’s documentation and what the engine is built around. Don’t add the rows up — see the caveat below the matrix.
Capability
MongoDB Atlas
Neo4j
1–3 hop traversal
$graphLookup with indexed connectToField
Index-free adjacency, native
5+ hop traversal on dense graphs
Index-lookup constant factor compounds
Pointer dereference per hop
Graph algorithms (PageRank, Louvain, Leiden)
ETL to NetworkX/igraph required
GDS library, in-engine
Vector search
$vectorSearch on HNSW, native
Vector indexes (GA 2025.10)
Full-text search
Atlas Search on Lucene
Lucene full-text indexes
Hybrid retrieval ($rankFusion / RRF)
Native in MongoDB 8.0
Manual RRF in app code
OLTP CRUD at scale
Document model, native sharding
Optimised for graph, not OLTP
Time-series / analytics
Time-series collections, aggregation
Not the engine's strength
Multi-document ACID
Since 4.0 (replica sets), 4.2 (sharded)
ACID native
Cypher / GQL ergonomics
Aggregation pipeline, not Cypher
Cypher native, GQL standard
Atlas (unweighted)
14/ 20
Neo4j (unweighted)
13/ 20
06
A decision framework
The honest test is not “which database is better.” It’s “which workload do you actually have?” Five questions, framed by what you’ll observe in production rather than by abstract characteristics.
01. Does your application’s request path include running graph algorithms?
If a user request triggers PageRank, community detection, betweenness centrality, or shortest-path computation — and the result must be returned within request-budget latency — you need a graph engine. Atlas can store and traverse the graph, but the algorithms aren’t first-class.
If you compute these offline (batch, daily, into a feature store), Atlas plus an external library is fine.
02. How deep are your typical traversals, and how dense is the graph?
Profile your real queries. For each, count the maximum hop depth and the average degree (edges per node) at each depth.
- 1-3 hops on adjacency lists with average degree <50:
$graphLookupis fine. - 4+ hops, especially on dense graphs: index-free adjacency starts to matter; favour Neo4j.
- Highly variable hop depth driven by user input: you need the depth-cap discipline of
maxDepthplus aggressiverestrictSearchWithMatchfiltering. Both engines can handle this, but Neo4j’s optimiser handles unbounded patterns more gracefully.
03. What other workloads run on the same dataset?
List them honestly. Transactional CRUD, search, vector retrieval, analytics, time-series, geospatial. Then ask: if you add a specialised graph store, what’s the cost of keeping every other shape in sync with it?
If the graph dominates and the others are minor, the polyglot cost is acceptable. If the graph is one of several substantial workloads, the integration cost likely exceeds the per-query advantage of a specialised store.
04. Is your team graph-native or document-native?
This sounds soft. It isn’t. Tooling fluency drives architecture for years.
- A team that has shipped Cypher for years, thinks in patterns, and has Neo4j ops experience: lean Neo4j.
- A team fluent in MongoDB aggregation pipelines, with platform standardisation on Atlas: lean Atlas.
- A new team with no incumbent: pick by workload (questions 1-3), not by hype.
05. Are you doing GraphRAG, and what kind?
- Pure global summarisation over a structured ontology (the Microsoft paper case): either store works; pick on the rest of the stack.
- Hybrid GraphRAG with vector and lexical fallback (the typical production case): Atlas wins on integration. The whole pattern is
$vectorSearch+$graphLookup+$rankFusionin a single aggregation. - Graph-first knowledge product where the graph itself is the user-facing artefact: Neo4j’s tooling (Bloom, GDS) is hard to beat.
Five-question framework
Each question is framed by what you’d observe in production — not by abstract characteristics. Profile your real queries before answering hop-depth questions.
Does your application's request path include running graph algorithms (PageRank, community detection, betweenness, shortest path)?
Algorithms inside the request budget vs. computed offline into a feature store.
When you profile your real graph queries, what's the typical hop depth and average degree?
Don't guess. Profile production traffic — the answer is usually shallower than expected.
What other workloads run on the same dataset (OLTP, search, vector, analytics, time-series)?
List them honestly. The graph is one shape — what's the rest of the system shape?
How does your team write queries today — aggregation pipelines or Cypher patterns?
Tooling fluency drives architecture for years. New teams should pick by workload, not preference.
Are you doing GraphRAG, and what flavour?
Pure global summarisation, hybrid retrieval, or graph-first knowledge product?
Running tally (0/5 answered)
07
The honest take
If you’ve made it this far, the conclusion shouldn’t surprise you.
Most teams I work with need graph queries on a dataset that has many other shapes. For them, MongoDB Atlas is the right answer — $graphLookup covers the traversal, Atlas Vector Search covers the retrieval, $rankFusion covers hybrid retrieval, the document model maps cleanly to the property-graph model, and everything runs in one cluster with one auth surface and one transactional boundary. GraphRAG fits this shape particularly well, which is why MongoDB’s LangChain GraphRAG integration has been GA since April 2025.
A few teams genuinely need a graph database. Their workload is graph algorithms, deep traversals, or graph-first products. Their team is graph-native. The graph is the dominant access pattern, not one shape among several. For them, Neo4j is excellent and I’ll say so.
The mistake is the middle case — teams adding a specialised graph store for queries that $graphLookup would have handled, then carrying the polyglot tax for years. The trigger is usually “we have a knowledge graph use case” without anyone testing whether the queries actually need a graph engine.
So the question I started with is the question I always finish with. Not do you have graph data? — almost everyone does. Not do you have a knowledge graph use case? — that’s a pattern, not a database choice. The question is:
Do you need a graph database, or do you need graph queries?
Profile the workload. Count the hops. List the other shapes on the same data. Answer honestly. The architecture follows.
The honest read
Three workload shapes, three answers.
MongoDB Atlas
Graph queries on a mixed dataset
- $graphLookup for recursive traversal
- $vectorSearch for RAG / GraphRAG
- $rankFusion for hybrid retrieval
- Document model carries the rest
One cluster, one auth surface, one transactional boundary.
Neo4j
Graph-first workload, deep traversals
- GDS algorithms in the request path
- 5+ hops on dense graphs
- Cypher / GQL fluency in-team
- Bloom-style graph products
Pay the polyglot tax for the per-query advantage.
Don't know which?
Profile your real queries
- 01 — Algorithms in the request path?
- 02 — Hop depth and graph density?
- 03 — What other workloads on the data?
- 04 — Team graph-native or document-native?
- 05 — GraphRAG, and which flavour?
Profile the workload. Count the hops. List the other shapes.