Skip to main content

Versioning Records Without Losing Queryability

Any mutable record in your system has a versioning question: what do you do when it changes and you still need to answer questions about the past?

Three approaches work in RushDB:

  1. In-place mutation — update the record; accept that history is lost
  2. Append-only versions — create a new VERSION record on every change; link with CURRENT_VERSION and PREVIOUS_VERSION
  3. Hybrid — in-place mutation for queryable mutable state plus an append-only EVENT log for history

This tutorial shows all three and explains when to use each.


Approach 1: In-place mutation (PATCH)

db.records.update sends a PATCH — it merges your new fields over the existing record. This is the simplest approach and the one to default to when history is not required.

from rushdb import RushDB
import os
from datetime import datetime, timezone

db = RushDB(os.environ["RUSHDB_API_KEY"], base_url="https://api.rushdb.com/api/v1")

doc = db.records.create("DOCUMENT", {
"title": "System Design Guide",
"body": "Initial draft content.",
"version": 1,
"updatedAt": datetime.now(timezone.utc).isoformat()
})

# Partial update (PATCH)
db.records.update(doc.id, {
"body": "Revised content with better examples.",
"version": 2,
"updatedAt": datetime.now(timezone.utc).isoformat()
})

# Full replace (PUT)
db.records.set(doc.id, {
"title": "System Design Guide v2",
"body": "Complete rewrite.",
"version": 3,
"updatedAt": datetime.now(timezone.utc).isoformat()
})

When to use: simple records with no history requirement — settings, profiles, catalog items.


Approach 2: Append-only versions (VERSION chain)

Every change creates a new VERSION record. A CURRENT_VERSION edge from the root DOCUMENT always points to the latest version. SUPERSEDED_BY links the chain.

def create_document_with_version(title: str, body: str, author_id: str):
tx = db.transactions.begin()
try:
doc = db.records.create("DOCUMENT", {"title": title, "authorId": author_id}, transaction=tx)
v1 = db.records.create("VERSION", {
"versionNumber": 1,
"body": body,
"authorId": author_id,
"createdAt": datetime.now(timezone.utc).isoformat(),
"isCurrent": True
}, transaction=tx)
db.records.attach(doc.id, v1.id, {"type": "CURRENT_VERSION", "direction": "out"}, transaction=tx)
db.transactions.commit(tx)
return doc, v1
except Exception:
db.transactions.rollback(tx)
raise


def add_version(doc_id: str, new_body: str, author_id: str):
current_result = db.records.find({
"labels": ["VERSION"],
"where": {
"DOCUMENT": {
"$relation": {"type": "CURRENT_VERSION", "direction": "in"},
"__id": doc_id
},
"isCurrent": True
}
})
current = current_result.data[0]

tx = db.transactions.begin()
try:
db.records.update(current.id, {"isCurrent": False}, transaction=tx)
new_version = db.records.create("VERSION", {
"versionNumber": current.data["versionNumber"] + 1,
"body": new_body,
"authorId": author_id,
"createdAt": datetime.now(timezone.utc).isoformat(),
"isCurrent": True
}, transaction=tx)

doc_result = db.records.find({"labels": ["DOCUMENT"], "where": {"__id": doc_id}})
db.records.detach(doc_result.data[0].id, current.id, {"type": "CURRENT_VERSION"}, transaction=tx)
db.records.attach(doc_result.data[0].id, new_version.id, {"type": "CURRENT_VERSION", "direction": "out"}, transaction=tx)
db.records.attach(new_version.id, current.id, {"type": "SUPERSEDED_BY", "direction": "out"}, transaction=tx)

db.transactions.commit(tx)
return new_version
except Exception:
db.transactions.rollback(tx)
raise

When to use: documents, configurations, contracts, or any record where historical content must be retrievable and comparable.


Approach 3: Hybrid (mutable state + immutable events)

Keep the entity's current state queryable in one record while logging all changes as immutable EVENT records. This is the pattern from Audit Trails and Temporal Graphs. Choose this when:

  • You need efficient current-state queries (no traversal to find latest version)
  • You need a history log (who changed what, when)
  • You do not need to serve the full historical content on demand

Choosing the right approach

RequirementBest approach
No history neededIn-place mutation (PATCH/PUT)
Full historical content retrievalAppend-only VERSION chain
Audit log only (who / when / what changed)Hybrid: mutable state + EVENT log
Point-in-time query (what was the state at T)Append-only VERSION or Temporal STATE chain
High write throughputIn-place mutation

Production caveat

Append-only VERSION chains grow linearly with edit frequency. If documents are edited frequently (wiki-style), consider capping the chain at N versions and archiving older ones to a separate project or record-level storage. Always benchmark query performance against version count: traversals over a 500-version chain behave differently than traversals over a 10-version chain.


Next steps