
From ArangoDB to NebulaGraph: Why Cost Forced the Move, and Performance Made It Permanent
When ArangoDB's October 2025 licensing change put our production cluster out of compliance overnight, we had 90 days to migrate a live graph workload to NebulaGraph. Here is exactly what we changed, what broke, and what we would do differently.
A Year on ArangoDB: What We Built and Why We Chose It
Our recommendation engine runs on a property graph: users, products, categories, purchase events and behavioural signals, all connected by typed edges. Twelve months ago ArangoDB 3.11 was an easy choice. One database handled JSON documents and graph traversals under a single query language (AQL), the Apache 2.0 licence was clean, and the .NET driver (ArangoDBNetStandard) was mature enough for production. We had about 60 GB of vertex and edge data, comfortably inside the Community Edition limits.
Our .NET 8 worker service polled a queue, built AQL queries, and wrote the traversal results into a Redis cache. Life was simple.
// ArangoDB traversal: finding 2-hop product recommendations via AQL
public async Task<IEnumerable<string>> GetRecommendationsAsync(
string userId, int maxDepth, CancellationToken ct)
{
var query = $"""
FOR v, e, p IN 1..{maxDepth} OUTBOUND @startVertex
GRAPH 'recommendation_graph'
OPTIONS {{ uniqueVertices: 'path', bfs: true }}
FILTER v.active == true
LIMIT 50
RETURN DISTINCT v._key
""";
var bindVars = new Dictionary<string, object>
{
["startVertex"] = $"users/{userId}"
};
var cursor = await _db.ExecuteAqlQueryAsync<string>(
new AqlQuery { Query = query, BindVars = bindVars }, ct);
return cursor.Result;
}This worked well until the dataset passed 80 GB. Traversal latency went from 40 ms to over 300 ms on 3 hop queries. We had run into the performance ceiling of the Community Edition. Without SmartGraph and Intelligent Sharding, both Enterprise only features, ArangoDB cannot spread traversal work across shards efficiently, and query times can get 4 to 10 times slower as the graph grows.
The Breaking Point: When the Cost Stopped Adding Up
On 18 October 2025 ArangoDB changed the licence for future Community Edition releases from Apache 2.0 to BSL 1.1, and introduced a new ArangoDB Community License that caps data at 100 GB per cluster and forbids commercial use. We were at 87 GB and adding 8 GB a month. The clock was running.
Enterprise pricing, which is what unlocks SmartGraph and removes the cap, would have cost roughly three to four times our whole infrastructure spend. The managed platform (AMP) is priced per node and per SLA, you cannot see the number until you ask for a quote, and it is aimed at companies with dedicated DBAs. Neither option was a good fit for a mid sized engineering team.
The licence change also made ArangoDB Community 3.11 end of life, with no patches and no security updates. We were on 3.11.5, the last stable release before the change. Staying put was not an option.
Why NebulaGraph: What We Wanted from an Alternative
Our requirements were specific:
- Apache 2.0 or equivalent. No commercial use restriction in the community edition.
- A native distributed graph architecture. Not a graph layer sitting on top of a document store.
- Traversal latency in milliseconds at hundreds of millions of edges.
- A usable .NET client, or a REST API we could wrap ourselves.
- A realistic migration path from the ArangoDB document and edge model.
NebulaGraph 3.7.0 (Community, released March 2024) met all five. It is licensed Apache 2.0 plus Common Clause 1.0. The Common Clause stops you from selling NebulaGraph as a managed service, but it does not restrict commercial use inside your own company, which is the opposite of where ArangoDB ended up. The Enterprise and Cloud track (v5.2, January 2026) adds features we may want later, but Community was enough to start.
Snapchat evaluated nine graph databases, Neo4j included, and picked NebulaGraph for their friend recommendation engine based on SLOs, QPS and data scale. That was a useful signal for our shortlist.
Data Model Migration: Documents and Edge Collections to Vertices and Edges
ArangoDB is flexible JSON. Collections do not require a schema, and _id, _from and _to are generated for you. NebulaGraph requires a schema. Every vertex belongs to one or more Tags, every edge belongs to an Edge Type, and every vertex needs an explicit Vertex ID (VID).
VID design is the most consequential decision in the whole migration. There is no auto generated _id. A bad VID scheme, sequential integers for example, creates storage hotspots in NebulaGraph's hash partitioned storage layer. We used a composite key, {entity_type}:{uuid}, hashed with FNV-1a before insertion, which spread the data evenly across partitions.
| ArangoDB concept | NebulaGraph equivalent |
|---|---|
| Document collection | Vertex Tag (typed schema) |
| Edge collection | Edge Type (typed schema) |
_id (automatic) |
VID (you design it) |
| Schema optional JSON | Fixed tag and edge type properties |
| AQL (multi model) | nGQL or openCypher (graph only) |
We wrote a one off .NET migration tool that streamed the ArangoDB export JSONL files, mapped each document to a CREATE TAG schema, and bulk imported through NebulaGraph's SST ingest API. About 62 million vertices and 400 million edges moved over a weekend.
Query Migration: From AQL to nGQL
AQL's FOR ... IN ... FILTER ... RETURN is document iteration syntax and has no direct equivalent in nGQL. Graph traversals translate much more cleanly.
The AQL query from the top of this article becomes this:
// NebulaGraph traversal using nGQL via the nebula-net client (unofficial)
public async Task<IEnumerable<string>> GetRecommendationsAsync(
string userId, int maxDepth, CancellationToken ct)
{
// NebulaGraph uses GO FROM ... OVER ... for traversal
// VIDs are typed; ours are strings prefixed with entity type
var vid = $"user:{userId}";
var ngql = $"""
GO 1 TO {maxDepth} STEPS FROM "{vid}" OVER purchased, viewed
WHERE $$.product.active == true
YIELD DISTINCT $$.product.id AS product_id
LIMIT 50
""";
// Using NebulaGraph's HTTP2 session interface wrapped in a typed client
var result = await _nebulaSession.ExecuteAsync(ngql, ct);
return result.GetValuesByColName("product_id")
.Select(v => v.GetSVal().ToString());
}GO FROM ... OVER ... STEPS does the same job as AQL's FOR v, e, p IN 1..N OUTBOUND, except you name the edge types instead of naming a graph. If your team prefers openCypher, NebulaGraph v3.x supports MATCH alongside nGQL, and you can use both against the same cluster.
Performance: Where NebulaGraph Was Faster
Once the data was loaded and the indexes were built, the numbers were clear. NebulaGraph's own LDBC-SNB SF100 benchmarks (282 million vertices, 1.775 billion edges) show FIND ALL PATH improving by 50 to 500 percent depending on depth, and around 600 percent on 1 to 5 hop traversals in v3.5.0.
On our workload, 62 million vertices and 400 million edges, 3 hop recommendation traversals went from 280 to 320 ms on ArangoDB CE down to 35 to 60 ms on NebulaGraph Community 3.7, with no query tuning at all. Two hop MATCH QPS improved noticeably once we added native indexes on the tag properties we filter on most.
One caveat worth knowing. Past 10 hops the Community Edition degrades and the Enterprise Edition is the recommended path. Our workload stops at 5 hops, so it never came up.
Features That Won Us Over
Separating storage from compute is the architectural reason NebulaGraph scales traversals better than ArangoDB CE. Compute nodes scale on their own, so a heavy traversal job does not fight with write throughput in the same process.
The SAMPLE clause, added in v5.x, is worth calling out. In a large graph, a very well connected vertex, a super node, can stall a traversal while the engine tries to walk every edge. SAMPLE lets you cap how wide each hop goes, declaratively, without reshaping the graph.
Unified multi modal querying in Enterprise and Cloud v5.0 and later lets you put graph traversal, vector similarity search and full text search in one statement. For a recommendation engine that also wants semantic similarity between products, that is an interesting direction.
The Hard Parts
VID design cannot be undone. Once 400 million edges are in with a given VID scheme, changing it means a full re import. We got it right, but only because a colleague read the architecture docs properly before we started.
No schema flexibility. In ArangoDB you add a field to a document and move on. In NebulaGraph you need ALTER TAG and a rolling schema propagation. Plan the schema the way you would plan a relational one, not a document store.
Tooling gaps in Community Edition. The .NET driver story is thinner than ArangoDB's. We wrapped the HTTP2 Bolt style interface ourselves, which is not hard, but budget the time. The Java and Python clients are first class. .NET is not, at least today.
A regression in v3.5.0. The published benchmarks show a small performance drop on some Go1-3 StepEdge and Go1-3 StepEdge_count patterns compared to v3.4.0. Pin your version and benchmark your own workload before you upgrade.
Licence detail. Apache 2.0 plus Common Clause 1.0 is not plain Apache 2.0. If your business involves hosting NebulaGraph as a managed service for other people, you need a commercial agreement. Internal production use has no restriction.
The Results, and Whether We Would Do It Again
Six months after the migration: p95 traversal latency is under 65 ms, we store 104 GB with no licence alarm going off, and the infrastructure bill is lower, because separating storage from compute let us size each tier for what it actually needs.
The migration took one engineer eight weeks end to end. One week for VID design and schema mapping, one weekend for the bulk import, four weeks for rewriting queries and integration testing, and two weeks for the production cutover with a shadow read validation period.
Would we do it again? Yes, with one change. If the domain is graph first, we would prototype on NebulaGraph from day one. ArangoDB's multi model convenience is genuinely useful early on, but it comes with a performance ceiling and now a licensing cliff, and both of them arrive at exactly the scale where a graph workload gets interesting. A purpose built architecture starts paying off the moment traversal depth and dataset size grow together.
Keep reading

October 16, 2026 · 16 min
Why Agents Need a Knowledge Base: The Gaps an LLM Can't Fill by Itself
A model knows only what was in its training data, up to its cutoff, and very little about specialised fields or your own systems. Here are the four gaps a knowledge base fills in an agentic system, why a 1M token context window doesn't close them, and how to expose one to an agent in C#.
Read
October 13, 2026 · 11 min
Why the Agile Coach Is Not a Luxury in a Software Team
Why the work of an agile coach can't simply be handed to the tech lead or the CTO, why agile is about results and not comfort, and why retrospectives and other ceremonies are the glue of a team, especially a remote one.
Read
October 6, 2026 · 6 min
Keeping Your Knowledge Graph Current Without a Dedicated Team
A knowledge graph that nobody updates becomes a lie with good syntax. This article builds the automated maintenance loop — deriving edges from git history, deployment manifests, and OpenTelemetry service maps, reconciling on a schedule, and handling conflicts with provenance on every edge.
Read