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/
No extra dependencies. 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]
FlagDefaultDescription
--db PATHDatabase file path (required)
--cf NAMEdefault CFTarget column family for all read/write commands
--password PASSOpen as encrypted store; required for decrypt and rekey
--timeout MS3000Busy-retry timeout in milliseconds before returning an error
--format text|jsontextOutput 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.

FlagDefaultKVStoreConfig fieldDescription
--journal wal|deletewaljournalModeWAL (recommended) or rollback-journal mode
--sync off|normal|fullnormalsyncLevelfsync aggressiveness; full = power-safe, off = fastest
--cache-size N2000cacheSizePage cache in pages (~4 KB each); 2000 ≈ 8 MB
--page-size N4096pageSizeDatabase page size in bytes; new databases only
--read-onlyoffreadOnlyOpen read-only; any write command will error
--wal-limit N0walSizeLimitAuto-checkpoint every N committed write transactions (0 = disabled)
--full-mutexofffullMutexRecursive mutex around every API call; only needed for shared-handle multi-threading

CRUD

put

snkvctl --db PATH put KEY VALUE [--ttl SECS]

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

snkvctl --db PATH get KEY

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

snkvctl --db PATH del KEY snkvctl --db PATH del --prefix P

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

snkvctl --db PATH exists KEY

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

snkvctl --db PATH list [--prefix P] [--seek KEY] [--reverse] [--limit N]

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

snkvctl --db PATH scan [--prefix P] [--seek KEY] [--reverse] [--limit N]

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

snkvctl --db PATH count [--prefix P]

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

snkvctl --db PATH clear

Delete every key in the store or column family (including TTL index entries) in O(pages). Non-reversible.

set-if-absent

snkvctl --db PATH set-if-absent KEY VALUE [--ttl SECS]

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

snkvctl --db PATH ttl KEY

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

snkvctl --db PATH 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

snkvctl --db PATH txn [--dry-run] [--cf NAME]

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.

Why stdin? A CLI process is stateless — each invocation opens a fresh connection. Piping operations through stdin lets a single process execute an arbitrarily large batch atomically within one open/begin/commit/close lifecycle.
# 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

snkvctl --db PATH 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.

snkvctl --db PATH cf list snkvctl --db PATH cf create NAME snkvctl --db PATH cf drop NAME

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
Note: Internal column families (TTL index, encryption metadata) are hidden from cf list and cannot be targeted with --cf.

Maintenance & Stats

stats

snkvctl --db PATH stats [--reset]

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

snkvctl --db PATH checkpoint [--mode passive|full|restart|truncate]

Copy WAL frames back to the main database file. Prints nlog (total WAL frames) and nckpt (frames written to the DB file).

ModeDescription
passive (default)Copy frames without blocking readers or writers
fullWait for all readers then flush all frames
restartLike full, then reset the WAL write position
truncateLike restart, then truncate the WAL file to zero bytes
snkvctl --db mydb.db checkpoint --mode full

vacuum

snkvctl --db PATH vacuum [--pages N]

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

snkvctl --db PATH 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

snkvctl --db PATH 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

snkvctl --db PATH encrypt --new-password PASS

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.

TTL metadata is not migrated. Key data and values are fully preserved, but per-key expiry times are lost. Keys will remain in the encrypted store but will not auto-expire.
snkvctl --db mydb.db encrypt --new-password s3cr3t
# All subsequent access requires --password
snkvctl --db mydb.db --password s3cr3t get hello

decrypt

snkvctl --db PATH --password PASS 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

snkvctl --db PATH --password OLD rekey --new-password NEW

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

CodeMeaningTriggered by
0SuccessAll commands on success
1ErrorI/O failure, bad arguments, wrong password, database corruption, busy timeout exceeded
2Not foundget, 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