Blog / Engineering

Why we rebuilt our query engine around a single live graph

For two years we shipped answers from nightly snapshots. They were fast, wrong by morning, and quietly eroding trust. Here's how we moved to one continuously-updated graph, and what it taught us about latency, freshness, and the cost of "good enough."

Abstract 3D render representing a connected graph

The first version of Veeta was a beautiful lie. Every dashboard loaded in under a second because none of the data was actually live; we precomputed everything overnight and served it from a cache. It demoed perfectly. It also told a sales leader on a Tuesday morning that a deal had closed when it had quietly slipped three hours earlier.

That gap between "what the screen says" and "what is true right now" is the entire problem we set out to solve. So in late 2025 we did the unreasonable thing: we tore out the snapshot pipeline and rebuilt the query engine around a single, continuously-updated graph. This is the story of why, and what broke along the way.

Snapshots were never the product

Nightly batch jobs are seductive. They're simple to reason about, cheap to run, and they make read latency somebody else's problem. But they encode an assumption that turns out to be false for most teams: that yesterday's answer is close enough to today's.

It isn't. The questions people actually ask Veeta, which accounts are at risk, what changed since standup, who needs a response in the next hour, are all questions about the present tense. A snapshot answers them in the past tense and hopes you won't notice.

If the answer can be wrong by the time someone reads it, you haven't built an intelligence layer. You've built a very expensive screenshot.

The shape of the new engine

We replaced the warehouse-of-record model with three moving parts that work together:

That last piece is the one that made the whole thing viable. Freshness is easy if you're willing to be slow; speed is easy if you're willing to be stale. The interesting engineering is refusing both.

Rows of servers
Streaming ingestion meant rethinking everything downstream of the connector layer.

What broke (and what we learned)

Moving to live data surfaced a class of bugs that snapshots had been politely hiding. Two stand out.

Ordering is a feature, not a detail

When events arrive out of order (and across dozens of sources, they always do), a naive graph will briefly show a customer as both churned and active. We added per-entity logical clocks so the graph converges on the correct state regardless of arrival order. Users never see the flicker.

Permissions have to live in the graph

A live graph that leaks is worse than a stale one. We moved authorization out of the application layer and into the graph itself, so every node carries the policy that governs it. A query can only ever traverse edges the asker is allowed to see.

The rule we landed on: the system should never return an answer it can't also explain: which source it came from, how fresh it is, and why you were allowed to see it.

Was it worth it?

Median query latency landed at 312ms against data that is, in the worst case, a few seconds old. More importantly, the questions changed. Once people trusted that the number on the screen was the number right now, they stopped exporting to spreadsheets to "double-check," and started asking the kind of in-the-moment questions the product was always meant to answer.

We're not done. Sub-second is good; we want the graph to feel like it's reading your mind by the time you finish typing. But we'll never go back to serving yesterday with a smile.


Curious how the live graph would map onto your stack? Talk to our team. We love a gnarly data model.

EngineeringArchitectureReal-time

Keep reading

Get started

Bring every system into focus.

Spin up a guided pilot on your real data in under a week.