Installation
snkvctl is bundled inside the snkv Python package.
Installing the package also installs the snkvctl executable via
a console_scripts entry point — no separate install step needed.
pip install snkv snkvctl --help
Build from source:
git clone https://github.com/hash-anu/snkv pip install -e snkv/python/
snkvctl uses only Python's standard library (argparse, json,
os, sys) plus the snkv C extension that is already installed.
Global Flags
All commands share these flags. They must appear before the subcommand name.
snkvctl --db PATH [global flags] COMMAND [command args]
| Flag | Default | Description |
|---|---|---|
--db PATH | — | Database file path (required) |
--cf NAME | default CF | Target column family for all read/write commands |
--password PASS | — | Open as encrypted store; required for decrypt and rekey |
--timeout MS | 3000 | Busy-retry timeout in milliseconds before returning an error |
--format text|json | text | Output format; JSON is newline-delimited (one object per line) |
KVStoreConfig Flags
These flags map directly to KVStoreConfig fields and are forwarded to
kvstore_open_v2 on every invocation. Defaults match kvstore_open.
| Flag | Default | KVStoreConfig field | Description |
|---|---|---|---|
--journal wal|delete | wal | journalMode | WAL (recommended) or rollback-journal mode |
--sync off|normal|full | normal | syncLevel | fsync aggressiveness; full = power-safe, off = fastest |
--cache-size N | 2000 | cacheSize | Page cache in pages (~4 KB each); 2000 ≈ 8 MB |
--page-size N | 4096 | pageSize | Database page size in bytes; new databases only |
--read-only | off | readOnly | Open read-only; any write command will error |
--wal-limit N | 0 | walSizeLimit | Auto-checkpoint every N committed write transactions (0 = disabled) |
--full-mutex | off | fullMutex | Recursive mutex around every API call; only needed for shared-handle multi-threading |
CRUD
put
Insert or update a key. With --ttl, the key expires after the given number of seconds (float precision).
snkvctl --db mydb.db put hello world snkvctl --db mydb.db put session:abc tok123 --ttl 300 snkvctl --db mydb.db --cf users put alice admin
get
Print the value for KEY. Exits 2 if the key is missing or expired. Text mode prints the raw value; JSON mode prints {"value": "…"}.
# capture value in a shell variable VAL=$(snkvctl --db mydb.db get hello) echo "$VAL" # world # check exit code snkvctl --db mydb.db get missing_key || echo "not found"
del
Delete a single KEY (exits 2 if missing), or delete all keys whose names start with the given prefix. Prefix deletes run inside a transaction and commit every 10,000 keys to keep WAL size bounded — not a single atomic operation for very large prefix sets.
snkvctl --db mydb.db del user:alice snkvctl --db mydb.db del --prefix tmp: # → 42 key(s) deleted snkvctl --db mydb.db del --prefix tmp: --format json # → {"deleted": 42, "prefix": "tmp:"}
exists
Check whether KEY exists. Exits 0 if found, 2 if not found. Useful in shell if statements.
if snkvctl --db mydb.db exists flag; then echo "flag is set" fi
list
Print keys only (no values), one per line. Supports prefix filtering, seek (start at first key ≥ KEY), reverse (descending) order, and an entry limit.
snkvctl --db mydb.db list --prefix user: --limit 50
snkvctl --db mydb.db list --reverse
snkvctl --db mydb.db list --seek user:bob # starts at user:bob inclusive
scan
Print key+value pairs. Text mode prints alternating key/value lines; JSON mode prints one {"key":…,"value":…} object per line. --seek KEY positions the iterator at the first key ≥ KEY before streaming begins.
snkvctl --db mydb.db scan --prefix order: --format json | jq .
snkvctl --db mydb.db --cf sessions scan --limit 10
snkvctl --db mydb.db scan --seek user:bob # start from user:bob onwards
count
Print the number of entries. With --prefix, counts only matching keys (iterates; O(matches)). Without prefix, uses the O(pages) native counter.
snkvctl --db mydb.db count snkvctl --db mydb.db count --prefix user: --format json
clear
Delete every key in the store or column family (including TTL index entries) in O(pages). Non-reversible.
set-if-absent
Atomically insert KEY only if it does not already exist. Prints inserted or already exists (not modified). Safe for distributed locks and deduplication.
snkvctl --db mydb.db set-if-absent lock owner:proc1 --ttl 30
Native Key Expiry
ttl
Print the remaining lifetime of KEY in seconds. Outputs no ttl if the key has no expiry, expired if it has already passed, or N.NNNs remaining.
snkvctl --db mydb.db ttl session:abc # → 284.312s remaining snkvctl --db mydb.db ttl no_expiry_key # → no ttl snkvctl --db mydb.db ttl session:abc --format json # → {"key": "session:abc", "ttl": 284.312, "status": "284.312s remaining"}
purge
Scan and permanently delete all expired keys. Returns the count of keys removed. Call before count for an accurate live-key tally.
snkvctl --db mydb.db purge # → 12 expired key(s) purged snkvctl --db mydb.db purge --format json # → {"purged": 12}
Atomic Batch (txn)
txn
Read put KEY VALUE and del KEY lines from stdin.
All operations are executed inside a single begin → commit transaction.
Any parse error or write failure rolls back the entire batch. Lines starting with
# are comments and are ignored.
--dry-run validates all operations but issues a rollback instead of a
commit — useful for testing batch scripts.
# Inline heredoc snkvctl --db mydb.db txn <<EOF put counter 42 put flag enabled del old_key EOF # → 3 op(s) committed # Pipeline from another command generate_ops.sh | snkvctl --db mydb.db txn # Target a column family printf 'put alice 30\nput bob 25\n' | snkvctl --db mydb.db --cf users txn # Dry run — validate without persisting printf 'put x 1\ndel y\n' | snkvctl --db mydb.db txn --dry-run # → dry-run: 2 op(s) validated, rolled back # JSON output printf 'put a 1\nput b 2\n' | snkvctl --db mydb.db txn --format json # → {"committed": 2}
Supported op keywords: put KEY VALUE, del KEY
(aliases: delete, rm).
sync
Flush all pending writes to disk (fsync). Equivalent to calling db.sync(). If a write transaction is active, performs a commit-and-reopen cycle first.
snkvctl --db mydb.db sync
# → sync complete
Column Families
Column families are logical namespaces within a single .db file.
Manage them with the cf subcommand; target one for CRUD operations
with the global --cf NAME flag.
list — print all user column family names.
create — create a new column family.
drop — permanently delete a column family and all its data.
snkvctl --db mydb.db cf list snkvctl --db mydb.db cf create users snkvctl --db mydb.db --cf users put alice admin snkvctl --db mydb.db --cf users scan snkvctl --db mydb.db cf drop users
cf list and cannot be targeted with --cf.
Maintenance & Stats
stats
Print 12 operation counters: puts, gets, deletes, iterations, errors, bytes_read, bytes_written, wal_commits, checkpoints, ttl_expired, ttl_purged, db_pages. With --reset, clears cumulative counters after printing (db_pages is always live).
snkvctl --db mydb.db stats snkvctl --db mydb.db stats --reset --format json
checkpoint
Copy WAL frames back to the main database file. Prints nlog (total WAL frames) and nckpt (frames written to the DB file).
| Mode | Description |
|---|---|
passive (default) | Copy frames without blocking readers or writers |
full | Wait for all readers then flush all frames |
restart | Like full, then reset the WAL write position |
truncate | Like restart, then truncate the WAL file to zero bytes |
snkvctl --db mydb.db checkpoint --mode full
vacuum
Incrementally reclaim unused pages. --pages 0 (default) frees all available pages. Runs inside an automatic transaction.
snkvctl --db mydb.db vacuum snkvctl --db mydb.db vacuum --pages 100
check
Run a full integrity check. Exits 0 on success; exits 1 and prints the corruption detail on failure.
snkvctl --db mydb.db check || echo "database is corrupt!"
info
Print a summary: file path, encryption status, column family count and names, page count, and file size in bytes.
snkvctl --db mydb.db info
# path mydb.db
# encrypted no
# cf_count 2
# cfs users, sessions
# db_pages 128
# size_bytes 524288
snkvctl --db mydb.db info --format json
Encryption
SNKV uses XChaCha20-Poly1305 per-value encryption with an Argon2id KDF.
Encrypted stores are transparent to all read/write commands when
--password is provided.
encrypt
Migrate a plaintext store to encrypted in-place. Creates a temporary encrypted copy, copies all default-CF and user-CF data, then atomically replaces the original file.
snkvctl --db mydb.db encrypt --new-password s3cr3t
# All subsequent access requires --password
snkvctl --db mydb.db --password s3cr3t get hello
decrypt
Remove encryption from a store. Re-writes all values as plaintext. After this operation the store can be opened without --password.
snkvctl --db mydb.db --password s3cr3t decrypt
snkvctl --db mydb.db get hello # works without --password now
rekey
Change the encryption password. Re-encrypts all values atomically inside a single transaction. The old password no longer works after this call.
snkvctl --db mydb.db --password old_pass rekey --new-password new_pass
Output Formats & Exit Codes
Text format (default)
Human-readable output. Keys and values are printed as UTF-8 text.
Non-UTF-8 bytes are shown as <hex:AABBCC>.
# get — bare value world # scan — alternating key / value lines user:alice admin user:bob viewer # list — one key per line user:alice user:bob # stats — aligned key: value puts 1024 gets 8192 bytes_written 102400
JSON format (--format json)
Newline-delimited JSON (NDJSON). Each record is one JSON object per line,
suitable for piping to jq.
# scan {"key": "user:alice", "value": "admin"} {"key": "user:bob", "value": "viewer"} # get {"value": "world"} # stats {"puts": 1024, "gets": 8192, "bytes_written": 102400, ...} # info {"path": "mydb.db", "encrypted": "no", "cf_count": 2, ...}
Exit codes
| Code | Meaning | Triggered by |
|---|---|---|
| 0 | Success | All commands on success |
| 1 | Error | I/O failure, bad arguments, wrong password, database corruption, busy timeout exceeded |
| 2 | Not found | get, del, exists, ttl when the key is absent or expired |
# Shell script pattern using exit codes if ! snkvctl --db mydb.db get config_key > /dev/null 2>&1; then snkvctl --db mydb.db put config_key default_value fi