Taha @ MJTHD
/
GitHub
Blog
·2 min read

Why a bad ingest can't break Dokki

Immutable versions, snapshots as a manifest, and rollback as a mere pointer flip — how Dokki protects the production corpus.

airagversioningdokki

The obvious way to maintain a RAG corpus: re-import the document, overwrite the old chunks, done. The problem — if the new import is broken (mangled PDF layout, half the text missing), you've just damaged production live, and the old good state is gone.

Dokki does it the other way around. The production state is untouchable until you actively activate something else.

Versions are immutable

Every re-ingest of a document produces a new document_version with its own version_no and its own chunks — never an update of the old one.

arabic-grammar.pdf
├── version_no = 1   (ready)   → 500 chunks   [in the active snapshot]
└── version_no = 2   (ready)   → 512 chunks   [in NO active snapshot]

The chunks of version_no = 2 already sit in the database — but are invisible to queries until they're deliberately added to a snapshot and that snapshot is activated. A version moves through a simple state machine:

pending → ingesting → ready → failed

A snapshot is just a manifest

A snapshot stores no data. It is a list: "which version of each document belongs to this state?"

snapshot "lughaty-2026-06-20"  (active)
└── snapshot_members
    ├── grammar.pdf       → version_no 1
    ├── vocabulary.epub   → version_no 3
    └── dialects.pdf      → version_no 2

Per collection, exactly one active_pointer row points at the production snapshot. Queries run only against its members — everything else is in the DB but out of view.

Promotion: build, check, switch

A draft snapshot is built from the desired versions. The quality gate checks the candidate. Only on pass does the pointer flip and snapshot.activated inform the RAG service. The old state stays live untouched throughout the entire check.

Rollback is a pointer flip

Because all old vectors still live in the DB, a rollback is instant — no recomputation, no re-embedding:

UPDATE knowledge.active_pointer
SET active_snapshot_id = $previousSnapshot
WHERE collection_id = $c;
-- then: NATS event snapshot.activated → RAG service invalidates cache

A garbage-collector job later clears away versions no longer referenced by any of the last N snapshots — the only place anything is deleted at all.

What this means in practice

Rollback is the safety net, not the normal case.

A bad ingest never goes active in the first place thanks to the quality gate — rollback only kicks in if a regression surfaces during operation. The combination of immutable versions and "live only after promotion" makes production structurally robust: you can import, experiment, and build snapshots at any time without ever risking the running state.

Exactly how the gate decides whether a candidate is better or worse than the active snapshot is in the next post.