AI Briefing/Blog
graphrag·October 6, 2026·8 min read

GraphRAG: three Postgres tables before you buy a graph DB

GraphRAG without a graph DB, in six steps: a 20-question eval, an ontology, three Postgres tables, and a single retrieval query.

Most teams that say “we need GraphRAG” need three things: an ontology, an edges table, and one query that can hop twice. They don’t need a graph DB or community detection. They certainly don’t need Microsoft’s full pipeline.

Some background first. Plain RAG came out of a Meta AI paper in May 2020. You split documents into chunks, embed them, and return the top-k most similar. In 2024 Microsoft Research published GraphRAG. An LLM extracts entities and relationships from the documents, you build a knowledge graph from them, and at query time you walk the graph instead of searching for similar text. The name caught on fast, and every graph DB vendor has been selling it since.

The problem it solves is real. Memgraph calls it the “one-hop wall”. Vector search finds a chunk that looks like the question, but it can’t connect evidence in one document to evidence in another. The cost is real too. Paul Nickerson ran Tencent’s Youtu-GraphRAG (accepted to ICLR 2026) on 27,378 aviation accident reports and ended up with a 313K-node graph. All the extraction ran on a local Qwen2.5-72B, on a GH200 with 96GB of HBM3. At that volume, API calls get, in his words, “prohibitively expensive”. FalkorDB writes that an enterprise corpus can generate millions of extraction calls. And Jai from QX, who builds a “company brain” for clients, wrote on July 20 that in practice GraphRAG is “complete overkill”.

So here is how I would do it, in order. Every step is also an exit. If a step doesn’t justify the next one, stop.

01 · Twenty questions before a line of code

Write 20 real questions, ones users have already asked in Slack or in support. Next to each one, note how many documents it takes to answer and what relationship links them.

Nickerson’s example is classic multi-hop: “What aircraft make appears most frequently in carburetor icing accidents during winter approaches?” To answer it you have to connect accident report → aircraft make → failure → conditions → season. No single chunk holds the answer.

What you’ll usually see: most of the list is single-hop. Some of the multi-hop ones are really aggregations that a GROUP BY handles with no LLM at all. My rule: fewer than five multi-hop questions out of 20, stay with vector RAG and improve your chunking.

eval/questions.yaml
- id: q07
q: "Which services are affected by this CVE through dependencies?"
hops: 2
path: [cve, package, service]
expected_docs: [sbom/payments.json, sbom/auth.json]
- id: q08
q: "What is the log retention policy?"
hops: 1
expected_docs: [policies/logging.md]

The path field isn’t decoration. It’s what we build the ontology from in the next step.

The question takes four hops between entities, and vector search stops after the first. That's the whole case for a graph, and also its limit (Nickerson; Memgraph)
The question takes four hops between entities, and vector search stops after the first. That’s the whole case for a graph, and also its limit (Nickerson; Memgraph)

02 · Ontology first, extraction second

The most common mistake is asking the LLM to extract “all the entities”. What you get is a graph where Person, person, Employee and CEO are four different types, each connected to half the world. Paul Iusztin gave this as a talk twice in one month, and he put it best:

GraphRAG isn’t a retrieval algorithm, it’s a data modeling problem. — Paul Iusztin, Decoding AI

Youtu-GraphRAG does exactly this, just with a fancier name: schema-guided extraction. The graph is limited up front to the types that matter for the analysis. In practice: 5–8 entity types, 6–10 edge types, and every edge has a direction. Anything that doesn’t appear in some path in the questions file stays out.

Take time seriously. Iusztin’s example: Arthur tells Felix about his favorite café in Timișoara, and two months later he moves to Lisbon. If you overwrite the edge, you’ve lost history. Append-only with valid_from and valid_to, and that’s it.

How the mechanism works
How the mechanism works

03 · Three tables in Postgres

Before you stand up Neo4j or FalkorDB, try it on the Postgres you already have. Polygres hit Show HN on July 7 with exactly this pitch, and the engines behind it, pgGraph and pgContext, are open source and past 1K GitHub stars. You don’t need their product to start. You need pgvector and three tables:

graph.sql
create extension if not exists vector;
create table chunks (
id bigserial primary key,
doc_id text not null,
content_hash text not null unique,
body text not null,
embedding vector(1024)
);
create table entities (
id bigserial primary key,
type text not null check (type in ('person','team','service','package','cve','incident')),
name text not null,
norm_name text generated always as (lower(trim(name))) stored,
embedding vector(1024),
unique (type, norm_name)
);
create table edges (
src bigint not null references entities(id),
dst bigint not null references entities(id),
rel text not null check (rel in ('owns','used_by','affects','caused','on_call_for')),
chunk_id bigint not null references chunks(id),
valid_from timestamptz not null default now(),
valid_to timestamptz,
primary key (src, rel, dst, valid_from)
);
create index on chunks using hnsw (embedding vector_cosine_ops);
create index on entities using hnsw (embedding vector_cosine_ops);
create index on edges (src) where valid_to is null;

Set the 1024 to match your embedding model. The unique on (type, norm_name) does exactly what FalkorDB describes as schema constraints: no separate nodes for “Acme” and “acme “. The checks on type and rel are the ontology from step 02, enforced in the database. An LLM that returns a type not on the list just fails the insert.

GraphRAG on Postgres already exists as a product, and the open engines behind it are past 1K stars. You don't need a graph DB to start (credit: Polygres)
GraphRAG on Postgres already exists as a product, and the open engines behind it are past 1K stars. You don’t need a graph DB to start (credit: Polygres)

04 · Cheap extraction: hash, small model, incremental

This is where most of the money burns. Three rules.

First, hash every chunk. Only a content_hash that isn’t already in the table goes to extraction. FalkorDB say it plainly: re-indexing the whole corpus on every document change “is not viable at scale”.

Second, use a small model for extraction. You’re asking for JSON against a closed schema, not poetry. FalkorDB recommend batching, caching recurring entities, and small models for the first pass. Nickerson went further: an open 72B model at 4-bit, about 40GB, on his own hardware, because otherwise the economics didn’t work.

Finally, count how many inserts failed on the check constraints and log them. That’s the cheapest eval of extraction quality you’ll ever get. If the rejection rate climbs, your extraction prompt and your ontology have drifted apart, and you want to find that out before the graph fills up with junk.

27,378 reports became 313K nodes, but only on a local GH200. Through an API, this extraction wouldn't have made economic sense (credit: Paul Nickerson)
27,378 reports became 313K nodes, but only on a local GH200. Through an API, this extraction wouldn’t have made economic sense (credit: Paul Nickerson)

05 · One query, not a chain of calls

Memgraph calls this Atomic GraphRAG: all of retrieval should be one database query, not a chain of steps in the application. I agree, even without a graph DB. Every round-trip between Python and the DB is more latency and one more place for a bug. The vector gives you an entry point, the edges give you the hops:

retrieve.sql
with seeds as (
select id from entities
order by embedding <=> $1::vector
limit 5
),
hop1 as (
select e.dst as id, e.chunk_id from edges e
join seeds s on e.src = s.id
where e.valid_to is null
),
hop2 as (
select e.dst as id, e.chunk_id from edges e
join hop1 h on e.src = h.id
where e.valid_to is null
)
select c.id, c.doc_id, c.body
from chunks c
where c.id in (select chunk_id from hop1 union select chunk_id from hop2)
limit 20;

What you get: up to 20 chunks, each of which arrived through a specific edge. That’s the explainability Memgraph talks about. You can show the user why a given chunk made it into the context. Iusztin adds a hybrid step: merge the vector, text and graph results with Reciprocal Rank Fusion. It’s a few lines and worth it.

06 · Measure against plain vector RAG, and be ready to throw it out

Run the 20 questions from step 01 twice: vector only, and vector plus graph. For each version, three numbers: recall@10 against expected_docs, p50 latency, and extraction cost per 1,000 documents. Just for a sense of latency: Polygres report 91% recall@10 at 2.44ms p50 on GloVe with 1.18M vectors. That’s a vector-only benchmark, not GraphRAG, but it shows how little of the budget retrieval should take.

The decision is simple. The graph has to win on the multi-hop questions and not lose on the single-hop ones. If it doesn’t, throw it out and keep a few JOINs.

When should you move to a real graph DB, Memgraph or FalkorDB or Neo4j? When you have variable-length paths, more than three hops, or global questions across the whole corpus. That’s where a recursive CTE in Postgres starts to hurt.

My take: community detection and community summaries, the best-known part of Microsoft’s GraphRAG, are also the most expensive and least needed part in most enterprise systems. Your users don’t ask “what are the main themes across the whole corpus”. They ask who owns the service that just went down.

What to take away

Sources

  1. Show HN: Instant GraphRAG over any Postgres database · 2026-07-07
  2. GraphRAG is overkill. This is what we do instead for knowledge graphs · 2026-07-20
  3. Atomic GraphRAG Explained: The Case for a Single-Query Pipeline · 2026-02-27
  4. GraphRAG, 27,000 Aviation Accidents, and What OpenClaw Found · 2026-02-21
  5. Graph Database AI Agents: GraphRAG & Memory Guide
  6. Building Agentic GraphRAG: Unified Memory With MCP

How this was made: drafted by our agent from 6 sources, edited and approved by Koby Almog.

More terms

All terms →
3/4

ChatGPT up close: the features engineers leave switched off

Projects, memory, canvas, deep research, agent and connectors in ChatGPT: what each one does, where it breaks, and what to turn on this week.

1/5

Claude Code in Practice, Part 1: Short CLAUDE.md, Strict Permissions

The three CLAUDE.md files, auto memory, settings.json permissions, and the commands worth memorizing. Step by step, for a real team.

2/5

Claude Code in practice, part 2: hooks, MCP and skills, and what goes where

CLAUDE.md says what's required, a skill says how, MCP what's possible, a hook enforces. A PreToolUse block on main, auto-formatting, MCP, a first SKILL.md.