Operations and cleanup¶
What to watch and what to run when kv0 holds data you care about.
Where data lives¶
| What | Where | Lost when |
|---|---|---|
| Committed data and every version | The dataset (path or URI) | You delete it, or cleanup removes old versions |
| Writes not yet committed | The write-ahead log under wal_dir (~/.cache/kv0/wal) |
The machine is lost |
View snapshots (view.snap) |
Next to the write-ahead log | Never needed: a missing or stale snapshot means a full scan on open |
In containers, point wal_dir (or KV0_WAL_DIR) at a persistent volume, or accept that a crash
of the container loses the writes of its last few seconds.
Flush and close¶
close() commits buffered writes, removes the handle's write-ahead log and saves a snapshot. A
process that never calls close() still commits: Python flushes open handles at exit, Rust on the
last drop. A killed process loses nothing: the next writable open on the machine replays its log.
Compaction¶
Each commit adds a file. kv0 compacts automatically above max_fragments small files. Run it on
demand with compact_range() (Python) / compact() (Rust) for the key-value store and
optimize() for tracking, which also sorts metric points by series and step and builds the
series index.
Cleanup safety¶
Compaction rewrites rows into new files; the old ones stay until their versions are cleaned up,
automatically after version_retention (7 days) or on cleanup_old_versions(age).
Other handles read values over lazy_value_threshold from the version they last loaded. If a
cleanup deletes that version's files, the handle's next read refreshes and reads the value again
from the new version: correct, but it costs a refresh, and it fails if the handle cannot refresh
(a handle opened at a fixed version). Keep the cleanup age above the longest a reader goes
without refreshing: its refresh_interval, or the life of an iterator. Time-travel opens of a
cleaned-up version fail.
Tagged versions are never cleaned up:
db.create_tag('release-3') # this handle's version
db.tags() # [('release-3', 42)]
old = kv0.DB(path, version=db.tag_version('release-3'))
Monitoring¶
db.stats() returns the version, live keys, buffered operations and bytes, the number of files
and stored rows, and view entries. Stored rows far above live keys means compaction will rewrite
soon; many files means commits are frequent relative to max_fragments.
Several machines¶
Writers on different machines need no coordination: Lance commits with conditional writes, which AWS S3 and current S3-compatible servers support. Each machine has its own write-ahead log. A machine that dies with uncommitted writes keeps them in its log until it opens the database again.
Deletion and erasure¶
Deleting and erasing are different steps.
Delete (delete_runs, DB.delete, delete_range) makes data invisible at once. The bytes
stay: deleted rows are marked in deletion files, older versions still reference the files that hold
them, and time travel (DB(path, version=n), At::Version, tags) can read them back.
Purge removes them:
report = repo.purge() # tracking: every run deleted so far
report = db.purge() # key-value: deleted and overwritten values
# {'runs': 1, 'fragments_rewritten': 4, 'versions_removed': 37, 'bytes_removed': 1834562,
# 'tags_deleted': [], 'snapshots_removed': 1, 'tags_left': []}
It rewrites every data file that holds deleted rows (the key-value store rewrites the database as
its live view), then removes every version but the latest, the way cleanup_old_versions(0) would.
For tracking, it first deletes any rows a writer racing the delete committed afterwards.
Tags. A tag keeps its version through cleanup. If a tag keeps a version that still holds data
purge would remove (rows of a deleted run; in the key-value store, any value that is not the live
one now, deleted or overwritten), purge stops with an error naming the tag and changes nothing. Delete the tag, or call purge(force=True), which deletes such tags and
lists them in tags_deleted. Records tables are tagged one by one, so a tag on an evaluation
dataset's table does not block erasing a run's scores. A write that overwrites a tagged key while a purge runs can
leave such a tag behind; the report lists it in tags_left, and a second purge handles it.
What purge does not reach.
- Write-ahead logs. A write is in the local WAL until its batch is committed; committed segments are deleted. A WAL left by a process that crashed before committing still holds its writes until the next open on that machine replays and removes it; replay drops writes to deleted runs. Purge only sees the machine and handle it runs on: open (and close) the database on each machine that ran a crashed writer, or delete that machine's WAL directory.
- View snapshots.
view.snapholds a key-value database's live view as of the last close.db.purge()removes this machine's snapshot of the database; other machines' snapshots are rejected on their next open (the data files changed) and replaced on close, or can be deleted by hand (<wal_dir>/<hash>/view.snap). - Delete markers. The key-value store keeps delete markers younger than
tombstone_grace(1 h) so a slower writer cannot bring a key back; they hold the key, not its value. Tracking keeps a tombstone row per deleted run (its id only) untilmark_ttl. - Copies elsewhere: object-store versioning or replication, backups, caches outside kv0.
- Disk blocks. Purge deletes files; it does not overwrite them. On a local disk or SSD the blocks may stay readable to someone with access to the raw device until reused. Where that matters, use disk-level encryption (and destroy the key with the disk) or secure deletion at the storage layer; on object stores, deletion is the provider's.
- Memory of other handles. Other open handles keep values they have read or cached until they refresh or close.
Concurrency. Writers can keep writing during a purge: compaction and cleanup commit like any
other writer, and Lance's cleanup never removes a file that no version references until it is
7 days old, so a file a concurrent writer is about to commit is safe. (Such an unreferenced file,
left by a writer that crashed before committing, is removed only by a cleanup after those 7 days.) Readers that hold an
older version lose it: a key-value handle reading a value whose file is gone refreshes and reads it
again; a tracking query in flight may fail and should be retried. Run purge when a short burst of
reader refreshes is acceptable.