NATS JetStream KV as a Lightweight Microservice State Store
NATS JetStream KV can store microservice state without adding Redis, but it behaves differently than a database. Understand tombstones, quorum writes, and revision checks before you deploy.
30 Sept 2025, 14:03 UTC

Shared state without a new database
Your microservices need shared state—feature flags, session tokens, or configuration—but adding Redis or PostgreSQL just for a few keys feels like overkill. If you already run NATS, JetStream key-value (KV) buckets can fill that gap without a new operational dependency.
The useful takeaway: treat a KV bucket as a replicated, append-only log with key lookup, not as a database. It works well for configuration and caches, but it lacks secondary indexes, cross-key transactions, and immediate space reclamation on delete. This assumes a current NATS server release with JetStream enabled; older releases require direct stream manipulation.
How a bucket stores data
A NATS JetStream KV bucket is backed by a JetStream stream. When you PUT a key, the server writes a message under a subject derived from the bucket and key. GET returns the latest non-deleted value for that key. DELETE does not erase the previous value; it writes a tombstone marker so subsequent reads treat the key as absent.
Because the stream is append-only, old revisions remain in storage until the stream’s retention policy rolls them over or compacts the segment. Frequent updates and deletes can consume disk space long after the logical value has changed.
Replication and consistency
You configure replication per bucket. A write succeeds only after a quorum of replicas acknowledges it. With three replicas, two must confirm before the client receives success. Write latency therefore tracks the slowest member of the quorum, not the average.
Reads can be served by any replica. If a client writes to the leader and then immediately reads from a follower that has not yet caught up, it may see the previous value. This read-after-write gap is temporary but real, especially across regions.
Worked example: a feature-flag bucket
Run these commands in a terminal where the `nats` CLI is authenticated to a JetStream-enabled server. The user needs JetStream publish and subscribe permissions for the bucket’s subjects.
# Create a bucket named feature_flags with one replica and a one-hour max age.
# Replace nats://localhost:4222 with your server URL.
nats --server nats://localhost:4222 kv add feature_flags --max-age 3600 --replicas 1
# Write a flag.
nats --server nats://localhost:4222 kv put feature_flags checkout.enabled true
# Read it back.
nats --server nats://localhost:4222 kv get feature_flags checkout.enabled
# Delete the flag. A tombstone is written; the key reads as absent.
nats --server nats://localhost:4222 kv delete feature_flags checkout.enabled
# Inspect the underlying stream to verify replica count and stream name.
nats --server nats://localhost:4222 stream info KV_feature_flags
Expected checks: `kv get` returns `true` after the put, and returns a not-found indication after the delete. The stream info command should show a stream name derived from the bucket and the replica count you requested.
Risk: if you run this against a multi-replica cluster and immediately query a follower, you may see stale data until replication catches up. The deleted key still occupies stream storage until retention reclaims it.
Rollback: remove the test bucket with your CLI’s bucket-removal command, then confirm the underlying stream no longer appears in the stream list. This deletes all keys in the bucket and cannot be undone.
Limitations and when to look elsewhere
- No secondary indexes. You can look up by key only; scanning by value or metadata requires reading the entire bucket.
- No cross-key atomicity. Updating two flags together is not a single transaction. Use optimistic concurrency with expected revisions if a read-modify-write race matters.
- TTL is bucket-level and enforced by stream retention, not instant expiry. Expired entries linger until the stream rolls over.
- File-based storage. Disk throughput and available inodes directly affect bucket creation and write throughput.
Use JetStream KV for configuration, session caches, or feature flags. Choose a document store or relational database when you need complex queries, joins, or atomic multi-key updates.
Verify before you rely on it
- Start a local `nats-server` with JetStream enabled.
- Create a bucket, write a key, restart the server, and confirm the value persists.
- Write the same key again with a new value and confirm the old revision is no longer returned by default.
- Check the underlying stream metadata to confirm the replica count matches your cluster configuration.
If persistence, revision behavior, and replica counts match your expectations in a local test, the bucket is safe to promote to a staging namespace. If any check fails, revisit your server’s JetStream storage and cluster settings before writing production state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.