One Read, One Truth: Snapshots, Indexes, and Freshness

HydraDB's read path is built around a deceptively strict rule: one query must see one pinned storage snapshot. This chapter examines how that rule works when the durable graph is changing, the reader's local view may be behind, and the fastest traversal index was built earlier than the query. The payoff is a practical way to distinguish freshness choices from consistency guarantees and to understand why HydraDB sometimes refuses an optimization rather than return a stale answer.

A graph query is not only a request for a collection of records. It is a claim about relationships at a particular point in the graph's history. If a query reads a vertex's properties at one point, its outgoing topology at another, and a deletion marker at a third, the result may describe no real state that was ever committed. This problem becomes more serious when indexes and caches are independently refreshed. A fast index can be old; a warm cache can be local; neither should be allowed to change the meaning of a result.

HydraDB therefore separates two decisions. First, it decides how fresh the reader needs to be. Then it pins one SlateDB snapshot and performs the query inside that boundary. The query may use a compiled traversal generation, a visible tail of newer changes, or canonical snapshot adjacency. Those are different execution routes, but they are all subordinate to the same read point.

Consistency starts before execution

A storage snapshot is a stable view of the records that a query is allowed to read. In HydraDB, the snapshot carries a storage sequence, and graph operations—topology, metadata, indexes, degrees, and tombstones—are resolved against it. The sequence is not a suggestion attached to the result after the work is complete. It is chosen before the graph operation runs and remains the boundary while the operation runs.

Consider a cell whose durable sequence has advanced through several mutations. A query begins by selecting a reader view. If that reader view is acceptable for the request, HydraDB pins a snapshot at the sequence it can observe. From that point, a later write may advance the durable graph, but the query does not silently switch to the new state halfway through. The answer is a view at the pinned sequence, not an accidental mixture of “before” and “after.”

This rule applies even when the query takes a fast path. Suppose a property index identifies candidate vertices, a compiled topology matrix supplies neighbors, and canonical records provide relationship properties. Each piece must be interpreted at the same snapshot. A matrix that was compiled from a newer or older graph cannot be treated as an unrestricted authority. A property match that comes from one view cannot be joined to topology from another merely because both structures are available in memory.

HydraDB exposes the storage sequence in its internal snapshot model because that sequence explains the result's visibility. A read snapshot can answer whether an edge exists, return outgoing or incoming neighbors, report a degree, or run a sparse traversal. Those operations share the snapshot rather than each acquiring a fresh view. This shared boundary is the mechanism behind the phrase “one read, one truth.”

The rule does not mean every read sees the absolute latest write at the moment it starts. Freshness and consistency are related but not identical. A coherent snapshot can be older than the newest durable sequence. The question is whether the request accepts that reader position, or whether it asks HydraDB to make a stronger freshness effort before pinning.

Causal and strong mean different costs

HydraDB's default read mode is causal. It uses the node's current durable reader view. When a caller supplies a bookmark that names a sequence the reader has not yet observed, the reader refreshes until that sequence is visible before pinning the query snapshot. This supports read-your-writes behavior across requests or nodes without making every ordinary read pay for a remote refresh.

A bookmark is best understood as a progress requirement. A client writes a mutation, receives a durable sequence associated with that operation, and later presents the bookmark when it needs a read that includes that progress. HydraDB checks that the bookmark belongs to the same target and waits for the target cell to reach the required durable position. The later query still runs against one snapshot; the bookmark determines how far that snapshot must have advanced before it is selected.

Strong consistency makes a different trade. A strong read refreshes the SlateDB reader from object storage before pinning its snapshot. This deliberately pays the remote freshness cost so the query can observe durable writes visible at the completion of that refresh. The benefit is a more current starting point; the cost is additional object-store work and the latency that comes with it.

Neither mode should be described as “consistent versus inconsistent.” Once the snapshot is pinned, both modes enforce internal consistency. The distinction is how much effort HydraDB makes to move the reader frontier before the snapshot is chosen. Causal mode is the ordinary hot path, with a bookmark-triggered catch-up when required. Strong mode asks for an explicit refresh even without that particular bookmark condition.

The client contract also avoids turning every storage sequence into a public historical query selector. An arbitrary old epoch is not treated as a safe instruction to reconstruct a client query from retained state. The supported causal mechanism is a target-bound bookmark, which asks the system to catch up to a durable position and then execute normally. This keeps the public meaning focused on progress rather than exposing an unrestricted time-travel interface.

Base N plus visible tail through M

The difficult case appears when traversal acceleration is asynchronous. An indexer may have built an immutable compressed sparse column, or CSC, generation through storage sequence N. A query now needs a snapshot at sequence M, where M is newer than N. Throwing away the generation for every query would waste its value. Trusting it without adjustment would omit committed changes after N.

HydraDB joins the two views in a controlled way. The CSC generation supplies the stable base through N. The topology changes committed from N+1 through M form a visible tail. HydraDB reads that tail and resolves each affected source-destination pair against the query's pinned snapshot. The result is not “old index plus whatever the latest reader happens to say.” It is the indexed base corrected to the exact state visible at M.

Imagine that a generation contains an edge from vertex 1 to vertex 4, and later commits delete that edge while adding a path from 3 to 5. A query pinned after both mutations must not return 1-to-4 simply because the generation still contains it, and it must be able to traverse through 3-to-5 if the query reaches that part of the graph. The visible tail identifies the affected pairs; the pinned snapshot decides their final existence. The overlay therefore carries final edge states rather than a loose sequence of client-side guesses.

The same boundary protects metadata. Structural topology can identify candidate paths, but relationship identity, properties, labels, and other query semantics still need canonical records at the pinned view. A topology shortcut cannot be allowed to hydrate a relationship from a different sequence. The index is a way to narrow or accelerate the work; the snapshot remains the answer to “what existed for this query?”

The tail itself has an important shape. It is based on committed topology changes after the generation's base, not on every unrelated mutation in the graph. HydraDB parses immutable WAL files, filters entries for the relevant cell and edge type and sequence range, collects affected pairs, and resolves those pairs against the snapshot. That makes the overlay useful for recent change activity while retaining a clear correctness boundary.

A query may have no usable generation at all. In that case, the engine can use canonical snapshot adjacency rather than inventing a generation in the request path. A generation may also exist but be too old for the required tail to be practical. The difference is an execution decision, not a change in what the graph means.

flowchart TB REQUEST["Authorized graph query"] --> FRESHNESS["Causal reader view or strong refresh"] FRESHNESS --> SNAPSHOT["Pin one SlateDB snapshot at sequence M"] SNAPSHOT --> CHOICE{"Usable traversal base?"} CHOICE -->|"CSC generation through N"| BASE["Load immutable index base"] BASE --> TAIL["Resolve committed topology changes N+1 through M"] TAIL -->|"Available and within bounds"| OVERLAY["Base plus exact visible overlay"] OVERLAY --> RESULT["Bounded result and bookmark"] CHOICE -->|"No usable base"| FALLBACK["Canonical snapshot adjacency"] TAIL -->|"Unavailable or too large"| FALLBACK FALLBACK --> RESULT

The branch is deliberately asymmetric. The compiled route is an opportunity to save work, not a prerequisite for correctness. If the base and tail can be joined within the configured limits, the query uses them. If not, canonical snapshot adjacency is the safe route. The result remains tied to M either way.

When the fast path must decline

Remote work has more than one dimension. A tail with a few affected edges may still span many immutable WAL files, and each file can require an object-store round trip. Conversely, a short file span may contain enough affected edges to exceed a query's scan or intermediate-result budget. HydraDB bounds these dimensions rather than treating “the tail exists” as proof that it is affordable.

If the WAL tail is unavailable, the query falls back. If its file span exceeds the configured cap, the query falls back. If affected-edge work exceeds a query budget, HydraDB returns an explicit admission error or refreshes to a newer usable generation in the paths that support that recovery. The exact response depends on which bound was reached, but the invariant is stable: do not silently use an incomplete overlay and do not perform an unbounded remote walk merely to preserve the fast-path label.

This is a useful distinction between a performance fallback and a correctness failure. A missing or oversized tail does not mean the canonical graph is corrupt. It means the indexed route cannot prove the requested view within its work budget. Canonical snapshot adjacency may require more local materialization or more direct reads, but it has the right authority. In a system whose durable truth is remote, declining an optimization is one way to keep tail behavior bounded.

The same logic applies to compiled sparse kernels. HydraDB's traversal ladder includes a direct adjacency implementation, a compact CSC implementation, and a SuiteSparse GraphBLAS implementation. A query can move down that ladder when a compiled artifact is unavailable. The lower rung may be slower or lack some pushdown capabilities, but the query still works from the snapshot-scoped adjacency it can establish. The fallback is a capability or latency downgrade, not permission to read an unpinned structure.

A cache can improve the outcome without changing the rule. A cached matrix, parsed WAL file, or completed path result is useful only when its key includes the relevant cell, procedure or edge type, generation or read epoch, and other inputs that define visibility. A cache hit reduces repeated work; it does not advance the query's snapshot. A mutation that advances storage creates a different epoch for epoch-keyed results, so a previous result cannot be reused merely because the query text is identical.

The boundary in a paged result

Pagination makes snapshot discipline visible to users. If a query returns page one and the graph changes before page two, rerunning the query could produce a different set of rows, reorder results, or expose a relationship that was absent from the first page. HydraDB's server-side cursors retain the unconsumed rows from one completed result and return them under bounded, expiring state. Native path procedures use a similar page-cursor idea.

This does not make every result an unlimited durable object. Cursor memory and resident bytes are bounded, ownership is checked, and cursors expire. The important semantic point is that page two continues the result generated from the original read, rather than silently executing the procedure against a newer snapshot. The cursor preserves the result boundary while the storage graph continues to evolve.

That behavior also clarifies why query IDs and bookmarks have different roles. A query ID is an ephemeral handle for correlation, cancellation, and cursor ownership. A bookmark carries durable progress for a target cell. Neither one is itself the graph snapshot. The snapshot is the storage view created inside execution; the surrounding tokens tell the service how to manage the request or how fresh the reader must be before execution begins.

What the invariant buys

Pinning one snapshot does not eliminate the costs of distributed storage. Strong refreshes and bookmark waits can add latency. Cold readers and canonical fallback can do more remote work than a warm compiled path. An indexer that is behind may leave a larger overlay or cause a rebuild. These are real consequences, and the repository evidence supports them as design trade-offs rather than as universal performance measurements.

What the invariant buys is more fundamental: the system can choose among those costs without changing the meaning of a committed graph. It can use a generation when the generation and visible tail cover the pinned view. It can use canonical adjacency when they do not. It can retain a result for the next page without rerunning against a different state. It can offer causal progress or request a stronger refresh while keeping both reads internally coherent.

The next question is how HydraDB turns a coherent topology into useful graph semantics. Sparse traversal can identify reachable vertices, but clients may need concrete relationships, properties, weights, and bounded paths. The following discussion therefore moves from the snapshot boundary to the kernels and native path procedures that operate inside it.

References