Graph Over Tables: Why We Built a Context Graph Instead of a CMDB

Every service management platform starts with the same foundation: a relational database full of tickets. Rows and columns. Each ticket is a record. Each record is an island.
Fine for tracking work. Terrible for understanding anything.
When someone asks "what's the blast radius if we rotate this certificate?" The answer lives in relationships between systems, not in any single row. Which services use that cert? Which applications depend on those services? Which teams are affected? You could technically answer this with a relational model and enough joins, but nobody builds it that way, and if they did the query would be horrifying.
We needed a different foundation. So we built one.
The CMDB problem
CMDBs were supposed to solve this. The authoritative map of your IT environment: every asset, every dependency, every relationship, all in one place.
In practice, they're graveyards.
You've seen the failure mode. Someone sets up the CMDB. A team spends weeks populating it. For a month or two, it's accurate-ish. Then someone provisions a new server and doesn't update the CMDB. Someone else decommissions a service but forgets to remove it. A team migrates from one identity provider to another and nobody tells the CMDB team. Within a quarter, the thing is fiction. Everyone knows it's fiction. It sticks around because someone paid for it, but nobody trusts it when it actually matters.
And the fundamental unit — the Configuration Item, the CI — sounds rigorous until you look at it closely. It's a row in a table that someone has to manually classify, categorize, and keep current. Is a Kubernetes namespace a CI? Is a SaaS tenant? What about the Slack channel your on-call team uses for incident coordination? The CI model forces you to answer these questions upfront, then punishes you when the answer changes. Teams either over-classify everything into meaningless taxonomy or just give up and let the data rot. I've seen both, sometimes at the same company.
The root cause isn't that people are lazy. It's that CMDBs are designed as systems of record that humans maintain, and humans don't maintain things that aren't in their critical path. If updating the CMDB isn't a natural side effect of doing the actual work, it won't happen. It never is, and it never does.
What we actually needed
We kept coming back to three things:
First, zero manual data entry. If a human has to type something into the system for it to stay accurate, it will eventually be inaccurate. Every fact in our model had to come from an authoritative source automatically.
Second, relationships as real objects, not foreign keys in a join table. "This person belongs to this team, uses this laptop, which runs this agent, which reports to this MDM, which enforces this compliance policy." That's a path through connected entities. In a relational model it's a five-table join that someone had to anticipate when designing the schema.
Third, merging without losing provenance. Jamf calls a device JAMF-4821. CrowdStrike knows it as CS-A7F3E. Intune has a third name. Same laptop, three identities. The system has to figure that out, merge the data, and still let you look things up by any of those original IDs. In a graph, the laptop is one node that carries all three identifiers. Query by any of them and you land in the right place. In a relational model, you'd need alias tables or lookup joins that break every time a new source shows up.
You can do all of this in Postgres. You can also drive a nail with a wrench. We chose a graph.
Why a graph
A graph database stores entities as nodes and relationships as edges. That's the textbook definition. Here's why it matters.
In a relational database, figuring out that an expiring certificate affects 47 people across 12 teams who depend on 8 services that terminate TLS with that cert means recursive self-joins on a relationships table, filtering by type at each hop. You can model it. The query to answer it is where it gets painful.
In a graph, you start at the certificate node. Walk outward along secures edges to service nodes. Walk powers to applications. Walk used_by to teams, member_of to people. The query follows the shape of the question. No joins, no pre-planned schema. The relationships are the data.
That alone would have been enough. But it gets more interesting with AI in the picture.
Where relational models hit a ceiling
To be fair, you can build a good CMDB on a relational database. Plenty of people have. Generic entity models, JSONB columns, flexible typing. You can even make relationships generic — a single relationships table with source_id, target_id, type, and a JSONB properties column. The schema problem is solvable.
The performance problem isn't. Think about a mid-size company: 10,000 employees, hundreds of applications, hundreds of teams, dozens of databases, hundreds of servers. Every employee uses dozens of apps, every app depends on services, every service talks to databases and runs on infrastructure. The relationships between those entities outnumber the entities themselves by orders of magnitude, and they all land in that one table. Now traverse it: "what's downstream of this certificate?" is a recursive self-join on that table, filtering by type at each hop, across four or five levels of depth. Every hop multiplies the join cost. At any serious scale, these queries either time out or require so much precomputation that you've lost the flexibility you designed for.
In a graph, traversal is the native operation. Four hops through four different edge types costs the same as one. The data structure is built for the question. And relationships aren't just pointers — they carry meaning. A dependency between two services might be critical or incidental. An ownership might be primary or secondary. These distinctions live as properties on edges, and they change over time. As ticket data flows in, the architecture supports weighting those edges — if every outage of Service A cascades to Service B within minutes, that dependency can get heavier over time.
A good relational CMDB can model your environment. A graph can keep up with it.
Why AI works better on graphs
Most AI-powered IT tools do the same thing: embed tickets and docs into vectors, retrieve the nearest matches, feed them to an LLM. That's RAG, and it works for semantic lookups — "how do I connect to the VPN?" or "what's the policy on laptop replacements?" But service management is full of questions that aren't about finding similar text. They're about following connections. "What's affected by this network change?" is a chain: switch → VLAN → servers → services → teams. Vector search can't traverse that. It's solving a different problem.
What we actually need is both. Vectors for semantic understanding — natural language queries, sentiment, similarity. The graph for structural reasoning — traversal, dependencies, blast radius. The graph is what turns RAG from "find me something similar" into "find me something relevant, then show me everything it touches." And because graph traversal is cheap, we can do this on every ticket, every workflow, every agent decision.
Before an agent runs a workflow, it walks the graph to understand what's downstream. Resetting a service account is fine if nothing depends on it. It's a very bad idea if twelve production services authenticate through it. Without the graph, the agent is just guessing and hoping for the best.
Thirty tickets about different symptoms — "Salesforce is slow," "can't reach the file share," "VPN keeps dropping" — look unrelated in a ticket database. In the graph, all three trace back to the same firewall rule change that went out twenty minutes ago. Thirty tickets, one root cause.
The better models get, the more the graph matters. A more capable model can reason over deeper traversals and make better judgment calls — but only if the data underneath gives it structure to work with. Flat tables put a ceiling on what the AI can reason about. The graph removes it.
Provenance and source merging
When ten integrations write to the same graph, the hard problem isn't ingestion. It's knowing who said what, and merging correctly when sources overlap.
Every fact in our graph is a timestamped assertion with provenance. Instead of laptop.owner = Alice, we store:
(Laptop-X) --[assigned_to]--> (Alice)
source: Jamf
observed_at: 2026-03-10T14:47:00Z
confidence: authoritative
Every edge and property carries its source. When you're looking at the graph and something looks wrong, you can always trace it back: this fact came from Jamf at 2:47pm, that one came from CrowdStrike's last sync. This is why provenance matters: when sources eventually disagree (and they will), you need to know who said what before you can reason about which is correct.
Identity resolution is the other half of this problem. A user might be alice@company.com in Okta and employee #4821 in Workday. Both IDs need to resolve to the same node in the graph, and both need to remain queryable. You don't lose the ability to look someone up by a system's native ID just because the entity got merged with data from another source.
Self-maintaining by design
The graph populates itself through integrations. Connect your MDM and we ingest every device, its compliance state, installed software, assigned user. Connect your cloud provider and we get resources, permissions, network paths. Plug in your ticketing system and historical resolutions become part of the graph too.
Here's the thing that took us a while to articulate: the graph is a side effect of your tools already doing their jobs. Your MDM already knows which laptop runs which agent. Your cloud provider already knows which services talk to which databases. Your ticketing system already has years of resolution data connecting problems to systems. We're not asking anyone to maintain a separate system of record. We're assembling one from sources that already exist and are already being kept current.
The tradeoff
Graphs aren't free. They're harder to run than Postgres. Tooling is less mature. The talent pool is smaller. And the flexibility that makes them powerful also makes them easy to model badly. Without discipline, you end up with a tangle instead of a topology.
We took that bet because the alternative, building AI on top of flat tables, is a ceiling we'd hit on every feature we shipped. The data model is the thing everything else stands on. If it can't represent relationships, nothing built on top of it will be able to reason about them either.
If you're building in a domain where relationships matter more than records, it's worth asking whether your data model reflects that.