Skip to content

kv: add Redis/Valkey driver - #60

Merged
mkorbi merged 1 commit into
feat/tenant-quotafrom
feat/redis-driver
Jul 28, 2026
Merged

kv: add Redis/Valkey driver#60
mkorbi merged 1 commit into
feat/tenant-quotafrom
feat/redis-driver

Conversation

@mkorbi

@mkorbi mkorbi commented Jul 25, 2026

Copy link
Copy Markdown
Member

Stacked on #59 (kv quota).

A third kv.Driver backed by any RESP server (Redis, Valkey, wire-compatible — the redis/valkey driver names are aliases). Opt-in per backend; serves the kv block only.

  • Records as hashes; per-namespace sorted-set index → ordered ZRANGEBYLEX Scan (byte-ordered, prefix + start_after cursor) and ZCARD quota
  • Expiry authoritative on a stored field (read paths filter > now, like postgres) + native PEXPIRE backstop; stale index members pruned lazily on Scan/Delete and when a quota'd Put hits the cap (self-healing)
  • Put (version bump + CAS + quota) and Delete (CAS) as atomic Lua scripts; MaxItems + tenant qualification flow through unchanged → per-tenant quotas work here too
  • config whitelist + newKVRedisDriver (dsn/dsn_env, key_prefix); go-redis/v9 dep; CI lex-range unit tests; integration conformance harness; docs

Verified: full kvtest conformance (all 12 cases incl. Quota, TTL, CAS, Scan cursor, ConcurrentPut) green against a live Redis.

🤖 Generated with Claude Code

A third kv.Driver backed by any RESP server — Redis, Valkey, or
wire-compatible (the `redis` and `valkey` config driver names are
aliases for the same implementation). Opt-in per backend; serves the kv
block only (other blocks' factories reject it, matching how fs/s3 serve
artifact only).

Layout: each record is a hash `<prefix>h:<ns>\x1e<key>`; a per-namespace
sorted set `<prefix>i:<ns>` indexes live keys so Scan is an ordered
ZRANGEBYLEX (byte-ordered, prefix + start_after cursor, consistent with
the memory driver's sort and postgres's COLLATE "C") and the item quota
is a ZCARD. Expiry is authoritative on a stored expires_at field (read
paths filter > now, like postgres) with a native PEXPIRE backstop for
memory reclamation; index members orphaned by a reclaimed record are
pruned lazily on Scan/Delete and when a quota'd Put reaches the cap
(self-healing so the soft quota stays honest). Put (version bump + CAS +
quota) and Delete (CAS) run as atomic Lua scripts; MaxItems and tenant
qualification flow through unchanged, so per-tenant quotas work here too.

- internal/kv/drivers/redis: driver + lex-range unit tests (CI-run) +
  integration conformance harness (//go:build integration, testcontainers)
- config: whitelist redis|valkey; fix the unknown-driver negative test
- main: buildKVRegistry redis/valkey case + newKVRedisDriver (dsn/dsn_env,
  key_prefix)
- go.mod: github.com/redis/go-redis/v9, testcontainers redis module
- docs: kv Drivers table + config reference backends + example.yaml

Verified: full kvtest conformance (all 12 cases incl. Quota, TTL, CAS,
Scan cursor, ConcurrentPut) green against a live Redis; go build/vet,
go test -race ./..., golangci-lint ./... all clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mkorbi
mkorbi merged commit 0a4ad46 into feat/tenant-quota Jul 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant