Opinion

Vector Search vs. Knowledge Graph: What It Really Costs to Build and Maintain a GraphRAG

Vector RAG solves the similar-fragment lookup problem ("find where X was mentioned"), while a knowledge graph is needed for multi-hop relationship questions and corpus-wide summarization ("who used which tools on project X over a year and a half"). Ditching one-off CLI calls with a 117,000-token system prompt in favor of batch extraction by subagents inside a single session cut the cost of building a graph from 10,019 records from a projected $1,500 down to a few dollars. The real cost of a graph lies in operating it: canonicalizing 2,542 raw tool-name variants and 1,213 project-name spellings (compressed down to 81 actual projects) requires ongoing deduplication and protection against auto-linking blowups.

Vector Search vs. Knowledge Graph: What It Really Costs to Build and Maintain a GraphRAG
Hamidun News.
Listen to article
Article Fact Sheet & Key Figures
ParameterDetails
Editor-in-Chief
Source corpus and graph size10,019 knowledge cards expanded into 5,237 nodes and 19,803 edges (a 15 MB SQLite file)
Savings at the build stageextraction cost dropped from a projected $1,500 (at 117K prefix tokens per call) to a few dollars
Entity compression via a canon dictionary1,213 project name variants collapsed into 81 canonical projects (a 15x reduction)
Hybrid pipeline speed13 ms for a ranked FTS5 result over 538,956 messages, and 87 ms for a two-hop graph JOIN
  • Source corpus and graph size: 10,019 knowledge cards expanded into 5,237 nodes and 19,803 edges (a 15 MB SQLite file)
  • Savings at the build stage: extraction cost dropped from a projected $1,500 (at 117K prefix tokens per call) to a few dollars
  • Entity compression via a canon dictionary: 1,213 project name variants collapsed into 81 canonical projects (a 15x reduction)
  • Hybrid pipeline speed: 13 ms for a ranked FTS5 result over 538,956 messages, and 87 ms for a two-hop graph JOIN

After a year of running my own memory system, I've distilled the difference between the two approaches into one sentence: vector search answers "find me a quote about X," while a graph answers "tell me how X is structured and who's connected to it." When you ask "which tools did we use on this project over a year and a half, and who was involved," no single text chunk contains the right answer in full — vector search returns three random fragments, and the model confidently makes up the missing 80% of the picture.

The oft-cited Microsoft Research paper (`arXiv:2404.16130`), which claims GraphRAG wins 72–83% of pairwise comparisons on answer completeness, is accurate but comes with strict boundary conditions. The benchmark was run on a global corpus-summarization task (`query-focused summarization` over 1.0–1.7 million tokens), and the 72–83% range includes the `TS` configuration, which doesn't use a graph at all — it does `map-reduce` over the raw source texts.

How do you avoid burning $1,500 on a system prompt when extracting

entities from 10,019 records?

When I sat down to build a graph on top of 10,019 knowledge cards (5,658 learning notes, 2,657 code snippets, 1,209 discussions, and 155 error breakdowns), the first naive implementation nearly bankrupted the project. It seemed natural to chop the corpus into batches of 200 records and spawn a CLI agent as a subprocess for each batch. The very first test call cost 15 cents: the CLI client reloaded the system prompt, rules, and skill descriptions from scratch on every startup — 117,000 tokens of overhead per call. Across 49 batches, that added up to more than $7 of pure waste, and parsing all 10,019 records one by one would have run the bill past $1,500.

We rebuilt the pipeline: we pre-filtered out 287 junk records and ran waves of 5–7 parallel subagents inside a single live parent session, where the prefix was already cached. Trying to spin up 20 workers at once ran into server-side throttling after 411 of 672 tasks completed, so we settled on an optimum: stable batch IDs in a manifest and waves of up to 6–8 parallel agents.

Why does running a graph cost more than building it, and how does a

canon dictionary compress projects 15x?

Every new wave of entity extraction brings in dozens of spelling variants for the same names and brands. If you just dump them into the database as-is, within a month one project turns into fifteen isolated islands in the graph. The only cure is a deterministic canon dictionary (`_MAPS`) applied before writing to the database:

```python def canon(entity_kind: str, raw_name: str) -> str: # Normalizes a raw entity name to its canonical form, or discards noise (empty string) cleaned = (str(raw_name) if raw_name is not None else "").strip() return _MAPS.get(entity_kind, {}).get(cleaned, cleaned) ```

After running the normalization maps, 1,213 raw project spellings collapsed into 81 real projects (a 15x reduction), 2,542 tool variants shrank to 1,068, companies went from 1,427 to 1,115, and people from 783 to 590. Another 66 pseudo-tools and 11 abstract "companies" ("Holding," "Library") were mapped to an empty string. Along the way, we fixed two classic bugs: a `for t in stack` loop over a string field instead of a list, which had spawned 96,000 junk edges pointing to individual letters of the alphabet (fixed with an `isinstance(v, list)` check), and an auto-linking blowup from substring-matching on titles that produced 27,000 false connections (fixed with a 5-character minimum length filter and a rule that a key must appear in no more than 4 projects, yielding 543 precise `about` edges).

When are two SQLite tables enough instead of a dedicated graph server?

The first version of our graph lived on a server running FalkorDB (built on Redis and GraphBLAS). There we discovered that the Cypher dialect doesn't support regex `=~` or undirected `shortestPath()`, and because the MCP connector was disabled and an SSH tunnel had to be set up manually, a graph with 4,743 nodes sat completely disconnected from the agents for 7 weeks.

We moved the entire graph into a local SQLite file, 15 MB in size, made of two tables (`nodes` and `edges`). At a scale of 5,237 nodes and 19,803 edges, a plain SQL query with two `JOIN`s finds the top projects by number of tools and sessions (the leader has 277 sessions and 285 tools) in 87 ms, while full-text FTS5 search over half a million messages responds in 13 ms.

This kind of hybrid search, with no extra daemons running, is baked into the architecture of [Nomad AI](https://nomadnet.ai) and the cloud platform [agent.nomadnet.ai](https://agent.nomadnet.ai). You can learn to build production-grade RAG systems and knowledge graphs at [Hamidun Academy](https://academy.hamidun.com) (a 72-hour trial is open now), and the canonicalization scripts are available in [Vibe Club](https://hamidun.com/club).

Story

Key Questions Answered

How do you avoid burning $1,500 on a system prompt when extracting entities from 10,019 records?

When I sat down to build a graph on top of 10,019 knowledge cards (5,658 learning notes, 2,657 code snippets, 1,209 discussions, and 155 error breakdowns), the first naive implementation nearly bankrupted the project. It seemed natural to chop the corpus into batches of 200 records and spawn a CLI agent as a subprocess for each batch. The very first test call cost 15 cents: the CLI client reloaded the system prompt, rules, and skill descriptions from scratch on every startup — 117,000 tokens of overhead per call. Across 49 batches, that added up to more than $7 of pure wast

Why does running a graph cost more than building it, and how does a canon dictionary compress projects 15x?

Every new wave of entity extraction brings in dozens of spelling variants for the same names and brands. If you just dump them into the database as-is, within a month one project turns into fifteen isolated islands in the graph. The only cure is a deterministic canon dictionary (`_MAPS`) applied before writing to the database: ```python def canon(entity_kind: str, raw_name: str) -> str: # Normalizes a raw entity name to its canonical form, or discards noise (empty string) cleaned = (str(raw_name) if raw_name is not None else "").strip() return _MAPS.get(entity_kind, {}).get

When are two SQLite tables enough instead of a dedicated graph server?

The first version of our graph lived on a server running FalkorDB (built on Redis and GraphBLAS). There we discovered that the Cypher dialect doesn't support regex `=~` or undirected `shortestPath()`, and because the MCP connector was disabled and an SSH tunnel had to be set up manually, a graph with 4,743 nodes sat completely disconnected from the agents for 7 weeks. We moved the entire graph into a local SQLite file, 15 MB in size, made of two tables (`nodes` and `edges`). At a scale of 5,237 nodes and 19,803 edges, a plain SQL query with two `JOIN`s finds the top project

ZK
Zhemal Khamidun — CPO at Alpina Digital, product manager of AlpinaGPT. Building AI products since 2019. Hamidun News is a pet project that grew into a production aggregator with 6,000+ readers.
✦ Hands-On AI Engineering · Hamidun Academy

Build & Ship Production AI Agents Without Boilerplate Code

Start your free 72-hour trial at Hamidun Academy (academy.hamidun.com) — interactive lessons on Claude Code & Cursor, live Tech Radar, and production agent templates.

Start 72h Free Trial →Vibe-Coding Intensive
What do you think?
Loading comments…