Skip to content

adapter: split redis.go / redis_compat_commands.go into cohesive files (no behavior change)#956

Merged
bootjp merged 5 commits into
mainfrom
refactor/split-adapter-redis
Jun 24, 2026
Merged

adapter: split redis.go / redis_compat_commands.go into cohesive files (no behavior change)#956
bootjp merged 5 commits into
mainfrom
refactor/split-adapter-redis

Conversation

@bootjp

@bootjp bootjp commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

Behavior-preserving refactor that splits the two largest Redis adapter files into cohesive, same-package (adapter) files. This is pure code movement: types/functions/methods/consts were relocated verbatim (comments moved with their code). No signatures, logic, names, or semantics changed.

  • adapter/redis.go: 4,483 -> 935 lines (core entry kept: server struct, options, NewRedisServer, lifecycle, dispatch infra, conn state, metrics/error helpers, ping, validateCmd, pub/sub fan-out).
  • adapter/redis_compat_commands.go: 5,434 -> 81 lines (now holds only the shared top-level const block).

File map (region -> new file, with line counts)

Moved out of redis.go:

Region New file Lines
SET/GET option types + parsing, set/get/del/exists/keys-string core, leader-expiry helpers redis_strings.go 644
KEYS/scan helpers, pattern matching, visible-user-key encoding redis_keys.go 320
txnContext + all txn methods, MULTI/DISCARD/EXEC, runTransaction, runTransactionWithDedup, dispatchExecReuse, firstExecAttempt, txnStartTS, txnApplyHandlers redis_txn.go 1,526
Leader proxying (txn + per-key) and go-redis result writers redis_proxy_leader.go 375
List ops + dedup path (listPushCore, listPushCoreWithDedup, dispatchListPushReuse, pop/range/trim) redis_lists.go 836

Moved out of redis_compat_commands.go:

Region New file Lines
INFO/CLIENT/COMMAND/HELLO/SELECT/TYPE/SCAN/PUBLISH/SUBSCRIBE/DBSIZE/FLUSH*/PUBSUB redis_server_cmds.go 795
SETEX/GETDEL/SETNX, TTL/PTTL/EXPIRE/PEXPIRE family redis_expire_cmds.go 346
SADD/SREM/SISMEMBER/SMEMBERS/PFADD/PFCOUNT + set helpers redis_set_cmds.go 632
HSET/HGET/HDEL/HEXISTS/HLEN/HINCRBY/HGETALL/INCR + hash helpers redis_hash_cmds.go 927
ZADD/ZINCRBY/ZRANGE/ZREM/ZREMRANGEBYRANK/BZPOPMIN + zset fast-path helpers redis_zset_cmds.go 1,155
XADD/XTRIM/XRANGE/XREVRANGE/XREAD/XLEN + stream parsing/scan helpers redis_stream_cmds.go 1,492
LPUSH/LTRIM/LINDEX (list cmds living in compat file) redis_lists.go (shared dest)

buildRouteMap (the dispatch table) was already in redis_command_specs.go and was left untouched.

No behavior change

Pure move. Verified two independent ways:

  1. go doc -all ./adapter identical — captured the exported API surface against the worktree base (via git stash of the source files) and after the split; diff reports no differences.
  2. Code-line multiset identical — stripping package/import blocks from all 13 files and comparing the multiset of non-blank code lines against the two originals: 9,144 lines on both sides, 0 missing, 0 extra. The +147 net line delta in the raw diff is entirely the added package adapter + import (...) boilerplate in the 11 new files.

Imports were recomputed per file and the source-file alias conventions preserved exactly: redis.go-derived files keep "github.com/cockroachdb/errors" unaliased; redis_compat_commands.go-derived files keep stdlib "errors" plus cockerrors "github.com/cockroachdb/errors", and redis_stream_cmds.go keeps google.golang.org/grpc/{codes,status}.

Jepsen-guarded blocks moved intact

The recently-merged one-phase-dedup default-flip config options (#943) — WithOnePhaseTxnDedup / WithStandaloneSetDedup — were left in place in redis.go core. The txn/dedup machinery (txnContext + methods, runTransaction, runTransactionWithDedup, dispatchExecReuse, firstExecAttempt, txnStartTS, txnApplyHandlers) was moved as one intact contiguous block into redis_txn.go; the list-dedup path (listPushCoreWithDedup / dispatchListPushReuse / listPushCore) was moved intact into redis_lists.go. No internals of these blocks were reorganized.

Verification evidence

go build ./...                                         -> ok
gofmt -l adapter/                                      -> (empty)
go vet ./adapter/                                      -> ok
golangci-lint --config=.golangci.yaml run ./adapter/  -> 0 issues
go test -race -count=1 -run 'TestRedis|Redis' \
        -timeout 900s ./adapter/                       -> ok  252.675s
go doc -all ./adapter  (base vs after)                 -> identical
code-line multiset (base vs after)                     -> identical (9144, 0 missing, 0 extra)

Five-lens self-review (pure move)

  1. Data loss — No semantic change. Raft propose/apply, FSM, snapshot, TTL, and dedup code is byte-identical and merely relocated; the code-line multiset and go doc checks prove no statement was dropped or altered. No new error-handling paths.
  2. Concurrency / distributed failures — No change to locking, leader-change handling, OCC resolution, or the lease-read window; the txn/list dedup blocks (the parts Jepsen guards) moved intact. go test -race on the full Redis suite passes.
  3. Performance — No hot-path edits, no new allocations, no extra Raft round-trips; identical machine code modulo file boundaries. No HLC Next() path touched.
  4. Data consistency — MVCC visibility, OCC commit-ts ordering, HLC ceiling, and adapter wire semantics are unchanged (same statements, same order). go doc parity confirms the public contract is unchanged.
  5. Test coverage — No new branches were introduced, so no new tests are required; existing co-located *_test.go files were not split and continue to exercise the moved code (full TestRedis|Redis race suite green).

Summary by CodeRabbit

  • New Features
    • Added full support for core Redis commands across strings, lists, sets, hashes, sorted sets, and streams (including range queries and ZSET/ZPOP-style pops).
    • Implemented Redis-compatible TTL/expiration handling (EX/PX, NX/XX, GET semantics) and leader-aware routing for accurate reads/writes.
    • Added pattern-based key discovery (KEYS/SCAN) and Redis MULTI/EXEC/DISCARD transactions with correct staging and execution behavior.
    • Extended server introspection, client metadata, and Pub/Sub monitoring.
  • Bug Fixes
    • Improved deletion correctness for wide-column sorted sets during removals.
  • Tests
    • Added coverage for KEYS argument validation and wide-column ZREM cleanup behavior.

@bootjp

bootjp commented Jun 12, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Jun 12, 2026

Copy link
Copy Markdown

Claude finished @bootjp's task in 2m 12s —— View job


Code Review: adapter: split redis.go / redis_compat_commands.go into cohesive files

  • Load tools and set up
  • Read modified files (redis.go, redis_compat_commands.go)
  • Read new files: redis_txn.go, redis_lists.go, redis_strings.go, redis_keys.go, redis_proxy_leader.go
  • Read new files: redis_server_cmds.go, redis_expire_cmds.go, redis_set_cmds.go, redis_hash_cmds.go, redis_zset_cmds.go, redis_stream_cmds.go
  • Verify import conventions, no duplicate symbols, coherence of splits
  • Post full review feedback

Summary

This is a well-executed, well-documented pure refactor. The split is clean, code movement is verbatim, and the verification methodology is unusually thorough. A few minor observations below — none are blocking.


Observations

1. redis_compat_commands.go is now misnamed

After the split, redis_compat_commands.go contains only constants — no commands remain. The filename implies "compat commands" but the file is now just a constants block. This won't confuse the compiler, but a reader landing on it for the first time will be puzzled. Consider renaming it to redis_constants.go in a follow-up.

2. redis_proxy_leader.go is broader than its name implies

The file contains not only leader-proxy functions but also:

  • parseInt / boolToInt — general scalar utilities
  • writeGoRedisResult / writeGoRedisValue / writeGoRedisArray — go-redis result writers (not proxying)
  • tryLeaderGetAt / readValueAt — closer to "remote read helpers" than "proxy"

The PR body documents this as "Leader proxying (txn + per-key) and go-redis result writers", so it's not a surprise. But redis_proxy_leader.go is still a mildly misleading name for future readers. A follow-up rename to redis_proxy.go or splitting writers into a redis_writers.go would be cleaner.

3. Orphaned comment in redis_txn.go (line 34–36)

// argsLen is derived from redisCommandSpecs in adapter/redis_command_specs.go.
// See that file for the canonical row list and the rationale for the
// single source of truth.

This comment describes argsLen, but argsLen is not defined in this file — it lives in redis_command_specs.go. The comment was moved with adjacent code (the txnApplyHandlers map) but now floats free without its referent. It's harmless but slightly confusing: a reader scanning redis_txn.go will wonder what argsLen this refers to. The comment belongs co-located with the argsLen declaration in redis_command_specs.go.

Fix this →

4. Pre-existing time.Now() in OCC/TTL path (not introduced here, but flagged for awareness)

redis_txn.go:303 (loadZSetState) and several other moved functions compare time.Now() to decide TTL expiry during MVCC reads:

if ttlSt.value != nil && !ttlSt.value.After(time.Now()) {

CLAUDE.md says: "Never use the local wall clock … for … expiry decisions". This pattern was present before this PR and is not introduced here, but the refactor makes it easier to find — the TTL-check code is now concentrated in redis_txn.go. Worth tracking as a follow-up (the HLC leader-issued read-timestamp pipeline should own expiry decisions to avoid skew across replicas).

5. Import conventions — verified correct

The claimed split of import styles is accurate throughout all 11 new files:

  • redis.go-derived files (redis_txn.go, redis_lists.go, redis_strings.go, redis_keys.go, redis_proxy_leader.go): use "github.com/cockroachdb/errors" unaliased ✓
  • redis_compat_commands.go-derived files (redis_server_cmds.go, redis_expire_cmds.go, redis_set_cmds.go, redis_hash_cmds.go, redis_zset_cmds.go, redis_stream_cmds.go): use stdlib "errors" + cockerrors "github.com/cockroachdb/errors"
  • redis_stream_cmds.go retains the google.golang.org/grpc/{codes,status} imports ✓

6. Domain grouping is logical

The new file map reads clearly:

File Domain
redis.go Server struct, lifecycle, dispatch, tracing, pub/sub infra
redis_txn.go MULTI/EXEC, txnContext, dedup machinery
redis_lists.go List push/pop/range/trim (both directions, dedup path)
redis_strings.go SET/GET/DEL/EXISTS options and execution
redis_keys.go KEYS/SCAN pattern matching, visible-key encoding
redis_proxy_leader.go Leader forwarding, go-redis writers
redis_expire_cmds.go SETEX/GETDEL/SETNX, TTL/EXPIRE family
redis_server_cmds.go INFO/CLIENT/COMMAND/HELLO/SELECT/PUBLISH/FLUSH/PUBSUB
redis_set_cmds.go SADD/SREM/SISMEMBER/SMEMBERS/PFADD/PFCOUNT
redis_hash_cmds.go HSET/HGET/HDEL/HEXISTS/HLEN/HINCRBY/HGETALL/INCR
redis_zset_cmds.go ZADD/ZINCRBY/ZRANGE/ZREM/BZPOPMIN
redis_stream_cmds.go XADD/XTRIM/XRANGE/XREVRANGE/XREAD/XLEN
redis_compat_commands.go Shared constants (now misnamed — see §1)

Verdict

Approve. The refactor is correct and the verification methodology (go doc parity + code-line multiset) is a solid proof of no behavioral change. Items 1–3 above are minor cleanup candidates for a follow-up PR; item 4 is pre-existing and tracked separately. Nothing here warrants blocking the merge.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3376f5d0-b9d0-4b0b-b0ac-4333b53ff80d

📥 Commits

Reviewing files that changed from the base of the PR and between 817356b and 4e8854b.

📒 Files selected for processing (8)
  • adapter/redis_hash_cmds.go
  • adapter/redis_keys.go
  • adapter/redis_keys_pattern_test.go
  • adapter/redis_lists.go
  • adapter/redis_retry_test.go
  • adapter/redis_set_cmds.go
  • adapter/redis_stream_cmds.go
  • adapter/redis_zset_cmds.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • adapter/redis_lists.go
  • adapter/redis_keys.go
  • adapter/redis_stream_cmds.go
  • adapter/redis_zset_cmds.go
  • adapter/redis_set_cmds.go

📝 Walkthrough

Walkthrough

Thirteen new files implement a complete Redis protocol adapter on top of ElasticKV's Raft-backed KV store. The adapter covers strings, lists, hashes, sets, sorted sets, streams, expiry commands, MULTI/EXEC transactions, KEYS/SCAN key discovery, and Raft-leader-aware proxying, all using wide-column storage with legacy-blob migration paths.

Changes

Redis Adapter — Full Command Implementation

Layer / File(s) Summary
Raft-leader proxying and read routing
adapter/redis_proxy_leader.go
Implements leader address resolution and cached go-redis client creation. Adds proxyTransactionToLeader which batches MULTI/EXEC commands via TxPipelined and handles transaction-abort semantics (WATCH conflicts via * -1 array reply). Supports forwarding LRANGE/RPUSH/LPUSH by computing per-key leader routing. Introduces tryLeaderGetAt to relay versioned raw-GET requests to the leader via protobuf relay connection. Updates readValueAt to choose between direct local leader reads (after VerifyLeaderForKey) and leader-proxied reads via tryLeaderGetAt when not leader.
Core server commands
adapter/redis_server_cmds.go
Adds INFO reporting Raft role and leader address mapping. Implements CLIENT subcommands (SETNAME, GETNAME, ID, INFO, SETINFO) with centralized arity enforcement. Covers full COMMAND family: COMMAND (all), COMMAND COUNT, COMMAND LIST, COMMAND INFO with nil handling, COMMAND DOCS in RESP2 flat-map layout, and COMMAND GETKEYS with nested arity validation. Implements HELLO with RESP2-only validation, rejecting AUTH and supporting SETNAME with all-or-nothing parsing. Adds SELECT, QUIT, TYPE with Raft routing. Implements SCAN with cursor/MATCH/COUNT parsing and visibility-based enumeration. Adds PUBLISH, SUBSCRIBE, DBSIZE with leader verification. Implements FLUSHDB/FLUSHALL by dispatching DEL_PREFIX ops, and FLUSHLEGACY to migrate unprefixed keys. Includes PUBSUB CHANNELS/NUMSUB/NUMPAT with pattern matching.
MULTI/EXEC/DISCARD transaction engine
adapter/redis_txn.go
Implements per-transaction txnContext with staging maps for strings, lists, zsets, TTLs, and stream tombstones. Tracks read keys for OCC validation and detects expired keys during reads. Provides apply handlers for SET (with NX/XX conditions and optional GET), DEL, GET, EXISTS, RPUSH, LRANGE (with persisted-vs-staged length), ZINCRBY, and EXPIRE/PEXPIRE with NX-only behavior. Builds write sets encoding strings with optional TTL elements, lists using delta-metadata scheme, zsets with both legacy and wide-column diff paths, and stream-wide-column deletion expansion. Validates OCC read set by comparing tracked keys' LatestCommitTS against startTS. Allocates fenced commitTS and dispatches via coordinator. Implements option-2 reuse/dedup via reusableExecTxn and dispatchExecReuse which re-dispatches the same write set under a new commitTS with PrevCommitTS.
String and expiry commands
adapter/redis_strings.go, adapter/redis_expire_cmds.go
Implements SET option parsing for EX/PX TTL validation, NX/XX mutual exclusivity, and GET intent tracking. loadRedisSetState probes raw key type once, then applies TTL filter for Redis semantics. executeSet applies NX/XX conditional logic and enforces WRONGTYPE when GET targets non-string. replaceWithStringTxn deletes non-string collection elements on overwrite, writes string value with embedded TTL, and maintains TTL scan index. Adds leader-only trySetFastPath avoiding cleanup unless non-string detected. Updates standalone SET handler to route through dedup when enabled. Implements GET with lease-read fast path, falling back to TTL-aware type probing on miss. Adds leaderEmbeddedTTLExpired and isLeaderKeyExpired for TTL detection. Implements DEL with per-key leadership routing and EXISTS with existsAtFast local probes. Implements SETEX parsing positive seconds TTL. GETDEL reads at stable snapshot, validates string type, atomically deletes logical elements. Implements SETNX via internal SET with NX-only config. TTL/PTTL check key existence and compute formatted remaining TTL. EXPIRE/PEXPIRE parse NX-only options via parseExpireNXOnly, pin expireAt once before retries via setExpire, execute via retryRedisWrite. doSetExpire inspects raw key type, routes plain-string anchors through dispatchStringExpire (read-modify-write reencoding), otherwise writes TTL scan index element.
List commands
adapter/redis_lists.go
Implements list key helpers and clampRange for index normalization. loadListMetaAt retrieves list metadata at given timestamp. buildRPushOps/buildLPushOps construct element writes using Delta metadata key and update in-memory Len/Tail. listPushCore shared retry loop resolves current list meta, allocates fenced commitTS, builds operations via provided builder, and dispatches transaction. Introduces reusableListPush and dispatchListPushReuse supporting write-set reuse by dispatching under fresh fenced commitTS with PrevCommitTS, including self-conflict probing via CommittedVersionAt. resolveReuseLength computes post-push length, probing whether prior commit landed or re-reading meta. Implements listPushCoreWithDedup option-2 retry loop with boundary read keys (Head and optionally Tail-1) for OCC safety. buildListPopElems emits claim keys for every sequence position to force conflicts under concurrent pops, deletes claimed items, returns popped values. listPopClaimOnce checks key type, resolves meta, clamps pop size, commits pop transaction. RPUSH/LPUSH command handlers delegate to listPushCmd via appropriate push function and proxy. LRANGE includes leader verification, type checks, bound parsing, and delegates to fetchListRange. LTRIM reads list values under retry, computes normalized trim bounds, rewrites list transactionally. LINDEX parses index, type checks, fetches value, normalizes index, returns bulk string or null.
Hash commands
adapter/redis_hash_cmds.go
Implements buildHashLegacyMigrationElems reading legacy hash blob into per-field Put operations, deleting legacy blob, writing base hash meta with migrated field count. addLegacyHashFieldsToMap tracks in-transaction migrated field names. buildHashFieldElems builds per-field Put ops for HSET/HMSET while counting newly-added unique fields via Redis-style in-call deduplication. applyHashFieldPairs validates argument pairing, enforces hash type-or-empty, allocates commitTS, conditionally migrates legacy blobs, bulk-scans existing field existence for new-field counting, appends single aggregated meta-delta, and dispatches retryable transaction. HGET uses wide-column fast path (hashFieldFastLookup) probing per-field entry with TTL liveness checks and narrow string-encoding priority guard; slow path type-probes and loads correct representation for NONE/WRONGTYPE disambiguation. HMGET returns type-probed array with per-field null handling; legacy blob loads on slow path. HDEL routes via wide-column deletion or legacy-blob deletion with per-field existence bulk-scan when large, otherwise per-field ExistsAt; appends negative meta-delta for wide-column deletions. HEXISTS uses wide-column fast path with TTL liveness and priority guard; falls back to type-probe and load. HLEN uses wide-column metadata resolution for O(1) count or falls back to legacy blob loading. HINCRBY parses increment, enforces hash-or-empty, reads current field integer from wide-column or legacy blob, atomically migrates legacy blob on demand, emits +1 meta-delta only when field is new. INCR validates key type (none or string), preserves existing TTL on update, clears stale TTL metadata, writes updated integer via transactional dispatch. HGETALL type-probes and returns empty array for absent/wrong-type, otherwise loads hash map, sorts fields, emits RESP (field, value) pairs.
Set commands
adapter/redis_set_cmds.go
Implements setKind/hllKind validation by checking stored key type and HLL-backed logical key existence. Adds legacy-to-wide-column migration element construction and extraction of legacy member names from migration ops for in-transaction visibility. Provides exact-set type validators for set and HLL encodings. persistExactSet branches by kind: empty-set cleanup via logical-key element deletion, legacy blob write for non-setKind, wide-column set rewrite for setKind (member key writes, set metadata length write, legacy blob deletion). Implements legacy exact-set mutation path: validates kind/type, loads current members, applies add/remove diff, short-circuits on no-op, persists via legacy transaction. Implements wide-column exact-set mutation path: validates setKind, allocates fenced commitTS, optionally includes legacy migration ops, builds base set of pre-existing legacy members for in-txn semantics, applies per-member mutations with length-delta tracking via set-meta-delta writes, dispatches transaction. Adds prefix-scan utilities for building existence maps by scanning member key prefixes and general reusable scan helpers. Implements batch existence-caching: initializes existence map via bulk scan when thresholds/migration make it worthwhile, falls back to per-member ExistsAt only for small clean batches, resolves membership while updating cache to reflect mutations. SISMEMBER uses fast existence probe guarded by higher-priority encoding checks and expiry detection; slow path validates key type and checks membership. SMEMBERS validates key type, returns empty array for absent keys, otherwise returns stored member list as Redis array. PFADD/PFCOUNT validate hllKind, load exact-set members, apply add/remove diffs, persist via legacy exact-set transaction (PFADD), or validate per-key HLL presence and load member lists per key to compute union cardinality (PFCOUNT).
Sorted set commands
adapter/redis_zset_cmds.go
Implements legacy-to-wide-column migration support and zsetMemberFastScore per-member wide-column score probe returning presence and TTL-alive status with wrong-encoding/priority handling. zsetRangeByScoreFast scan pipeline includes eligibility gating, bounded offset/limit scan budgeting, forward/reverse score-index scanning, post-scan priority guarding, decoding + score filtering + offset/limit pagination, TTL filtering, and truncation detection for slow-path fallback. zsetRangeEmptyFastResult disambiguates missing vs wrong type vs empty live set via meta resolution. Implements ZADD end-to-end for wide-column storage: flag parsing/validation and score-acceptance logic (parseZAddFlags, zaddFlags), pair parsing (parseZAddPairs), migration view construction (buildZSetMigrationView), in-txn member-score resolution (resolveZSetMemberScore), and per-pair application (applyZAddPair) emitting deletes/puts for member and score-index keys with in-txn view updates. zaddTxn assembles transaction (type enforcement, commitTS allocation, bulk view merging, per-pair op accumulation, optional length-delta emission), dispatchAndSignalZSet factors coordinator dispatch + local waiter signaling. zincrbyTxn handles wide-column format: type enforcement, commitTS allocation, NaN check, deletion of old score key, insertion/update of member and score-index keys, length-delta on first insert, dispatch + waiter signaling. ZRANGE option parsing and reply formatting via parseZRangeOptions, reverseZSetEntries, writeZRangeReply. removeZSetMembers utility for membership removal. persistZSetEntriesTxn transactional persistence including delete-when-empty. persistZSetRemovalsTxn deletes member/score-index keys for removed entries, updates length via meta-delta, or falls back to full remaining write on wide-column probe failure. ZRANGE read flow using typed loading, optional reverse ordering, normalized rank slicing. ZREM and ZREMRANGEBYRANK via typed load, removal computation, and transactional persistence. BZPOPMIN: tryBZPopMinWithMode performs typed checking (fast vs full), loads ZSET entries, returns nil on empty, detects wide-column vs legacy encoding, delegates post-pop persistence to persistBZPopMinResult (wide-column key deletes + meta-delta vs legacy blob rewrite). bzpopmin timeout parsing + deadline scheduling. bzpopminWaitLoop loops with fast/full probing controlled by waiter signals and bounded fallback timer. bzpopminTryAllKeys iterates keys, writes triplet replies on success, stops on terminal errors. waitForBlockedCommandUpdate shared select loop waits for waiter signal, fallback timer tick, handler-context cancellation, or BLOCK window deadline with timer stop/drain logic.
Stream commands
adapter/redis_stream_cmds.go
Implements XADD argument parsing for optional MAXLEN (~ or exact) and field pairs. Computes assigned entry ID for explicit IDs and * auto-ID generation via wall-clock clamping and strict successor computation with overflow handling. XADD handler parses input, retries writes via dispatch/transaction helper under timeout, replies with assigned stream entry ID. xaddTxn validates key type, loads stream meta or derives legacy cleanup, resolves final entry ID enforcing monotonicity, enforces MAXLEN wide-column constraints, queues entry Put plus optional trim deletions, updates stream meta, dispatches/signal-wakes stream waiters. dispatchAndSignalZSet applies written elements and wakes XREAD BLOCK waiters after local application. MAXLEN=0 special-case deletion of just-added entry. Trim sizing estimation and core trimming decision logic with capped deletion work and meta.Length adjustment to reflect actual committed deletions. XTRIM argument parsing (MAXLEN with optional modifiers) and handler that retries transaction and replies with removed count. xtrimTxn validates key type, flushes legacy cleanup when no-op, computes capped deletion list, updates meta.Length, dispatches changes, returns removed count. XREAD option parsing (COUNT/BLOCK/STREAMS), COUNT/block validation and clamping, STREAMS key/after-ID splitting. After-ID resolution for $ consulting stream meta. Selection logic for choosing subset of already-read entries after given after-ID. Non-blocking XREAD single-pass implementation across requested streams. Scanning of entry-per-key stream keys strictly after after-ID with normalization, strict ID validation, bounded/unbounded scan caps, unmarshalling to redisStreamEntry values. Helpers for computing correct inclusive scan start for strictly-after semantics and normalizing XREAD after-IDs. RESP reply writers for stream entries and results. Error classification to detect XREAD iteration context deadline/cancel errors. XREAD command handler with after-ID resolution short timeout. BLOCK-window busy poll loop registering waiters, checking server shutdown, iterating with per-round timeouts, emitting null on deadline, returning results when available. XLEN returns length for new-layout streams via meta or counts entries from legacy layout when meta absent, with proper type handling. XRANGE/XREVRANGE COUNT parsing and selection helpers filtering entries by (start,end) bounds in forward or reverse order. Shared XRANGE/XREVRANGE command path detecting whether new-layout meta exists, serving via entry-per-key range scanning when present, otherwise serving from legacy loaded entries. New-layout XRANGE/XREVRANGE via bounded scan bounds translation, forward/reverse scan execution with COUNT caps, unmarshalling, and pathological-stream result-size protection. Scan-bound mapping from raw Redis range inputs (inclusive exclusive variants and ms shorthand) to half-open binary scan ranges with empty-range detection. Raw bound ID parsing rules, binary scan-key construction, and bound inclusion predicates for selecting entries within lower/upper constraints.
KEYS and key discovery
adapter/redis_keys.go
Implements keys command with leader verification using per-call timeout or proxyKeys for non-leaders. localKeysExact determines whether specific key is visible by checking type. localKeysPattern supports wildcard patterns by computing scan bounds, scanning store keys at fixed read timestamp, filtering through collectUserKeys, and when pattern is bounded, also scanning internal namespaces via mergeInternalNamespaces. mergeInternalNamespaces merges keys from internal storage namespaces for list structures, wide-column hash/set/zset using user-key-based bounds, and post-migration stream meta keys via streamMetaScanStart. patternScanBounds derives bounded scan start/end values from *-containing patterns. listPatternScanBounds extends user pattern bounds with namespace prefix. matchesAsteriskPattern matches byte-slice keys against * patterns. collectUserKeys iterates store KV pairs, translates to visible user key via redisVisibleUserKey, includes only keys matching pattern. zsetWideColumnVisibleUserKey maps zset internal keys (meta/delta/member/score) to visible user keys or internal-only classification. wideColumnVisibleUserKey maps hash/set/zset/stream wide-column keys to visible user keys excluding internal meta/delta/stream entry keys. redisVisibleUserKey applies Redis visibility rules: filters transaction/TTL keys, maps list item keys, excludes list meta/delta/claim keys, maps wide-column keys, extracts other internal user keys, defaults to original key bytes.
Unit tests
adapter/redis_keys_pattern_test.go, adapter/redis_retry_test.go
Adds TestKeysRejectsMissingPattern validating that KEYS command invoked with only command name (no pattern argument) reports wrong-number-of-arguments error. Adds TestZRemDeletesWideColumnRows seeding wide-column ZSet with two members, invoking ZREM to remove member a, asserting reply success, verifying deletion of a's wide-column member row and score-index row, confirming loaded ZSet contains only member b.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RedisServer
  participant txnContext
  participant Coordinator
  participant Store

  Client->>RedisServer: MULTI
  Client->>RedisServer: SET k v
  Client->>RedisServer: RPUSH list a
  Client->>RedisServer: EXEC
  RedisServer->>RedisServer: verify leader or proxyTransactionToLeader
  RedisServer->>txnContext: create with txnStartTS (LastCommitTS+Observe)
  loop per queued command
    RedisServer->>txnContext: applyXxx (stage dirty values, track read keys)
  end
  txnContext->>txnContext: validateReadSet (OCC vs LatestCommitTS)
  txnContext->>txnContext: prepareDispatch (OperationGroup + fenced commitTS)
  txnContext->>Coordinator: commit dispatch
  Coordinator-->>RedisServer: ok or retryable error
  alt retryable and dedup enabled
    RedisServer->>Coordinator: dispatchExecReuse (same write set, new commitTS)
  end
  RedisServer-->>Client: RESP2 array results

  note over Client, Store: Follower read proxying
  Client->>RedisServer: GET key (follower node)
  RedisServer->>Store: tryLeaderGetAt (versioned RawGet via leader)
  Store-->>RedisServer: value or ErrKeyNotFound
  RedisServer-->>Client: bulk string reply
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • bootjp/elastickv#518: Both PRs touch Redis expiration/TTL write handling around setExpire/EXPIRE-family semantics—main PR adds new expiration command handlers with TTL update logic, while the retrieved PR changes the underlying TTL write path to use the ttlBuffer instead of Raft/OCC TTL ops.
  • bootjp/elastickv#549: The main PR's Redis adapter implementation (e.g., redis_strings.go GET's lease-aware fast path) builds on the lease-read coordinator/engine APIs introduced in the retrieved PR.
  • bootjp/elastickv#565: Both PRs implement/tune wide-column fast-path lookup semantics for Redis commands (notably HGET, HEXISTS, and SISMEMBER) including TTL/WRONGTYPE handling in the Redis adapter.

Poem

🐰 Hoppity-hop through each data type,
Strings and lists, hashes in a crate,
ZSets sorted, streams flowing free,
MULTI/EXEC for atomicity!
The Raft leader guides every write,
ElasticKV shines so bright. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: a refactoring that splits two large Redis adapter files into smaller, cohesive files organized by command family, with explicit emphasis that no behavior change occurs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/split-adapter-redis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements various Redis command adapters and transaction handlers for elastickv, introducing wide-column storage layouts, fast-path lookups, and transactional optimizations. The review feedback focuses heavily on ensuring proper context propagation by replacing instances of context.Background() with the active request or transaction context (ctx or t.ctx) across multiple command implementations to respect timeouts and cancellations. Additionally, it is recommended to log connection close errors in redis_server_cmds.go instead of silently ignoring them.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread adapter/redis_expire_cmds.go Outdated
var v []byte
err := r.retryRedisWrite(ctx, func() error {
readTS := r.readTS()
typ, err := r.keyTypeAt(context.Background(), key, readTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The request context ctx is available in this scope but context.Background() is passed to r.keyTypeAt. Using ctx ensures that request timeouts and cancellations are properly respected during the key type lookup.

Suggested change
typ, err := r.keyTypeAt(context.Background(), key, readTS)
typ, err := r.keyTypeAt(ctx, key, readTS)

Comment thread adapter/redis_hash_cmds.go Outdated
// Wide-column path: check if any !hs|fld| keys exist for this key.
hashFieldPrefix := store.HashFieldScanPrefix(key)
hashFieldEnd := store.PrefixScanEnd(hashFieldPrefix)
wideKVs, err := r.store.ScanAt(context.Background(), hashFieldPrefix, hashFieldEnd, 1, readTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The request context ctx is available in hdelTxn but context.Background() is passed to r.store.ScanAt. Using ctx ensures that request timeouts and cancellations are properly respected during the scan.

Suggested change
wideKVs, err := r.store.ScanAt(context.Background(), hashFieldPrefix, hashFieldEnd, 1, readTS)
wideKVs, err := r.store.ScanAt(ctx, hashFieldPrefix, hashFieldEnd, 1, readTS)

Comment thread adapter/redis_hash_cmds.go Outdated
}

// Legacy blob path.
value, err := r.loadHashAt(context.Background(), key, readTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The request context ctx is available in hdelTxn but context.Background() is passed to r.loadHashAt. Using ctx ensures that request timeouts and cancellations are properly respected during the hash load.

Suggested change
value, err := r.loadHashAt(context.Background(), key, readTS)
value, err := r.loadHashAt(ctx, key, readTS)

Comment thread adapter/redis_lists.go Outdated
}

readTS := r.readTS()
typ, err := r.keyTypeAt(context.Background(), key, readTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The request context ctx is available in rangeList but context.Background() is passed to r.keyTypeAt. Using ctx ensures that request timeouts and cancellations are properly respected during the key type lookup.

Suggested change
typ, err := r.keyTypeAt(context.Background(), key, readTS)
typ, err := r.keyTypeAt(ctx, key, readTS)

Comment thread adapter/redis_lists.go Outdated
return nil, errors.WithStack(err)
}

meta, exists, err := r.resolveListMeta(context.Background(), key, readTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The request context ctx is available in rangeList but context.Background() is passed to r.resolveListMeta. Using ctx ensures that request timeouts and cancellations are properly respected during the list metadata resolution.

Suggested change
meta, exists, err := r.resolveListMeta(context.Background(), key, readTS)
meta, exists, err := r.resolveListMeta(ctx, key, readTS)

Comment thread adapter/redis_txn.go Outdated
return typ, nil
}
t.trackTypeReadKeys(key)
return t.server.keyTypeAt(context.Background(), key, t.startTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The transaction context t.ctx is available on txnContext but context.Background() is used directly in stagedKeyType. Using t.ctx (with a nil fallback) ensures that the transaction's timeout and cancellation are properly respected during the key type lookup.

Suggested change
return t.server.keyTypeAt(context.Background(), key, t.startTS)
ctx := t.ctx
if ctx == nil {
ctx = context.Background()
}
return t.server.keyTypeAt(ctx, key, t.startTS)

Comment thread adapter/redis_txn.go Outdated
}

func (t *txnContext) applySet(cmd redcon.Command) (redisResult, error) {
if isList, err := t.server.isListKeyAt(context.Background(), cmd.Args[1], t.startTS); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The transaction context t.ctx is available on txnContext but context.Background() is used directly in applySet. Using t.ctx (with a nil fallback) ensures that the transaction's timeout and cancellation are properly respected during the list check.

	ctx := t.ctx
	if ctx == nil {
		ctx = context.Background()
	}
	if isList, err := t.server.isListKeyAt(ctx, cmd.Args[1], t.startTS); err != nil {

Comment thread adapter/redis_txn.go Outdated
state.value = &expireAt
state.dirty = true
if typ == redisTypeString {
plain, err := t.server.isPlainRedisString(context.Background(), key, t.startTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The transaction context t.ctx is available on txnContext but context.Background() is used directly in applyPositiveExpire. Using t.ctx (with a nil fallback) ensures that the transaction's timeout and cancellation are properly respected during the plain string check.

		ctx := t.ctx
		if ctx == nil {
			ctx = context.Background()
		}
		plain, err := t.server.isPlainRedisString(ctx, key, t.startTS)

Comment thread adapter/redis_txn.go Outdated
Comment on lines +778 to +784
switch {
case e < persistedLen:
return t.server.fetchListRange(context.Background(), key, st.meta, int64(s), int64(e), t.startTS)
case s >= persistedLen:
return appendValues(st.appends, s-persistedLen, e-persistedLen), nil
default:
head, err := t.server.fetchListRange(context.Background(), key, st.meta, int64(s), int64(persistedLen-1), t.startTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The transaction context t.ctx is available on txnContext but context.Background() is used directly in listRangeValues. Using t.ctx (with a nil fallback) ensures that the transaction's timeout and cancellation are properly respected during the list range fetch.

Suggested change
switch {
case e < persistedLen:
return t.server.fetchListRange(context.Background(), key, st.meta, int64(s), int64(e), t.startTS)
case s >= persistedLen:
return appendValues(st.appends, s-persistedLen, e-persistedLen), nil
default:
head, err := t.server.fetchListRange(context.Background(), key, st.meta, int64(s), int64(persistedLen-1), t.startTS)
ctx := t.ctx
if ctx == nil {
ctx = context.Background()
}
switch {
case e < persistedLen:
return t.server.fetchListRange(ctx, key, st.meta, int64(s), int64(e), t.startTS)
case s >= persistedLen:
return appendValues(st.appends, s-persistedLen, e-persistedLen), nil
default:
head, err := t.server.fetchListRange(ctx, key, st.meta, int64(s), int64(persistedLen-1), t.startTS)

Comment thread adapter/redis_server_cmds.go Outdated

func (r *RedisServer) quit(conn redcon.Conn, _ redcon.Command) {
conn.WriteString("OK")
_ = conn.Close()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error returned by conn.Close() is silently ignored. Network connection close errors should be logged to ensure resource leaks or other cleanup problems are visible.

	if err := conn.Close(); err != nil {
		log.Printf("failed to close connection: %v", err)
	}
References
  1. Do not silently ignore errors from Close() methods on resources like network connections. Log them to ensure resource leaks or other cleanup problems are visible.

@bootjp

bootjp commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@bootjp

bootjp commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: a2c9e69049

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@bootjp

bootjp commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Jun 14, 2026

Copy link
Copy Markdown

Claude finished @bootjp's task in 0s —— View job


I'll analyze this and get back to you.

@bootjp

bootjp commented Jun 18, 2026

Copy link
Copy Markdown
Owner Author

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved the merge conflicts by merging origin/main into this branch and applying the Redis conflict resolution in merge commit 817356b5.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (14)
adapter/redis_keys.go-205-237 (1)

205-237: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pattern matching allows prefix/suffix overlap, causing false positives.

The pattern a*a incorrectly matches key a. After consuming the prefix "a" (pos=1), the suffix check HasSuffix(key, "a") passes because the entire key satisfies it, but the prefix and suffix share the same character. Redis glob semantics require * to match zero-or-more characters between distinct prefix and suffix portions.

Proposed fix
 	last := parts[len(parts)-1]
-	if len(last) > 0 && !bytes.HasSuffix(key, last) {
+	if len(last) > 0 {
+		if len(key) < pos+len(last) || !bytes.HasSuffix(key, last) {
+			return false
+		}
+	}
-		return false
-	}
 
 	return true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_keys.go` around lines 205 - 237, The matchesAsteriskPattern
function has a bug where the suffix check using HasSuffix does not account for
prefix/suffix overlap. When checking if the key ends with the suffix portion
(the last part after splitting by asterisk), add a validation that the suffix
does not overlap with the already-consumed prefix position. Specifically, ensure
that before calling HasSuffix for the last part, verify that the position where
the suffix would need to start (len(key) - len(last)) is greater than or equal
to the current position (pos) to prevent patterns like "a*a" from incorrectly
matching keys like "a" where the prefix and suffix overlap.
adapter/redis_set_cmds.go-577-577 (1)

577-577: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Propagate ctx to loadSetAt instead of context.Background().

The outer context has a timeout but this call ignores it.

Suggested fix
-		value, err := r.loadSetAt(context.Background(), hllKind, cmd.Args[1], readTS)
+		value, err := r.loadSetAt(ctx, hllKind, cmd.Args[1], readTS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_set_cmds.go` at line 577, The loadSetAt function call is using
context.Background() which ignores any timeout settings from the outer context.
Replace context.Background() with ctx in the loadSetAt call to properly
propagate the outer context's timeout and cancellation behavior through the
function call.
adapter/redis_set_cmds.go-461-484 (1)

461-484: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add timeout to context in sismember.

Other command handlers use context.WithTimeout (e.g., line 451, 568). This handler uses context.Background() without a timeout, which could cause unbounded blocking on store operations.

Suggested fix
 func (r *RedisServer) sismember(conn redcon.Conn, cmd redcon.Command) {
 	if r.proxyToLeader(conn, cmd, cmd.Args[1]) {
 		return
 	}
 	key := cmd.Args[1]
 	member := cmd.Args[2]
 	readTS := r.readTS()
-	ctx := context.Background()
+	ctx, cancel := context.WithTimeout(context.Background(), redisDispatchTimeout)
+	defer cancel()
 
 	hit, alive, err := r.setMemberFastExists(ctx, key, member, readTS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_set_cmds.go` around lines 461 - 484, The sismember function
uses context.Background() without a timeout, which could cause unbounded
blocking on store operations, while other command handlers in the codebase use
context.WithTimeout. Replace the ctx initialization in the sismember function to
use context.WithTimeout instead of context.Background(), applying the same
timeout pattern used by other command handlers (referenced at lines 451 and
568). This ensures the context passed to setMemberFastExists and sismemberSlow
methods has appropriate timeout protection.
adapter/redis_set_cmds.go-534-562 (1)

534-562: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add timeout context to smembers.

This handler makes multiple store calls using context.Background() without a timeout. For consistency with write handlers and to prevent unbounded blocking, use a timeout-scoped context.

Suggested fix
 func (r *RedisServer) smembers(conn redcon.Conn, cmd redcon.Command) {
 	if r.proxyToLeader(conn, cmd, cmd.Args[1]) {
 		return
 	}
+	ctx, cancel := context.WithTimeout(context.Background(), redisDispatchTimeout)
+	defer cancel()
 	readTS := r.readTS()
-	typ, err := r.keyTypeAt(context.Background(), cmd.Args[1], readTS)
+	typ, err := r.keyTypeAt(ctx, cmd.Args[1], readTS)
 	if err != nil {
 		writeRedisError(conn, err)
 		return
 	}
 	...
-	value, err := r.loadSetAt(context.Background(), setKind, cmd.Args[1], readTS)
+	value, err := r.loadSetAt(ctx, setKind, cmd.Args[1], readTS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_set_cmds.go` around lines 534 - 562, The smembers function uses
context.Background() without a timeout in both the r.keyTypeAt and r.loadSetAt
calls, which can cause unbounded blocking. Create a timeout-scoped context at
the beginning of the smembers function (following the pattern used in write
handlers) and replace both context.Background() calls with this timeout context
to ensure consistency and prevent unbounded blocking operations.
adapter/redis_set_cmds.go-30-34 (1)

30-34: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Propagate context instead of using context.Background().

This function is called from within retry loops that have timeout-scoped contexts. Using context.Background() ignores cancellation signals and timeouts.

Suggested fix
-func (r *RedisServer) validateExactSetKind(kind string, key []byte, readTS uint64) error {
-	typ, err := r.keyTypeAt(context.Background(), key, readTS)
+func (r *RedisServer) validateExactSetKind(ctx context.Context, kind string, key []byte, readTS uint64) error {
+	typ, err := r.keyTypeAt(ctx, key, readTS)

Then update callers (lines 186, 211, 573) to pass ctx.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_set_cmds.go` around lines 30 - 34, The validateExactSetKind
function is using context.Background() which ignores timeout and cancellation
signals from parent contexts in retry loops. Add a context parameter to the
validateExactSetKind function signature, replace the context.Background() call
with this new parameter when calling r.keyTypeAt(), and then update all three
callers of validateExactSetKind (referenced at lines 186, 211, and 573) to pass
their own context instead of letting the function create a new one.
adapter/redis_expire_cmds.go-222-223 (1)

222-223: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use r.handlerContext() for proper shutdown propagation.

Same issue as other handlers in this file.

Suggested fix
-	ctx, cancel := context.WithTimeout(context.Background(), redisDispatchTimeout)
+	ctx, cancel := context.WithTimeout(r.handlerContext(), redisDispatchTimeout)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_expire_cmds.go` around lines 222 - 223, The context creation at
lines 222-223 uses context.Background() which does not propagate shutdown
signals properly. Replace the context.WithTimeout call that uses
context.Background() with a call that uses r.handlerContext() instead, while
maintaining the same timeout duration (redisDispatchTimeout). This ensures the
context respects the handler's lifecycle and proper shutdown propagation similar
to other handlers in this file.
adapter/redis_expire_cmds.go-94-95 (1)

94-95: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use r.handlerContext() for proper shutdown propagation.

Same issue as other handlers in this file.

Suggested fix
-	ctx, cancel := context.WithTimeout(context.Background(), redisDispatchTimeout)
+	ctx, cancel := context.WithTimeout(r.handlerContext(), redisDispatchTimeout)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_expire_cmds.go` around lines 94 - 95, The context is being
created using context.WithTimeout(context.Background(), redisDispatchTimeout)
which does not properly propagate shutdowns. Replace this context creation with
r.handlerContext() which is the correct method used by other handlers in this
file to ensure proper shutdown propagation and context lifecycle management.
adapter/redis_expire_cmds.go-30-31 (1)

30-31: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use r.handlerContext() for proper shutdown propagation.

context.Background() is used here, but other handlers (e.g., setLegacy in redis_strings.go:265) derive context from r.handlerContext(). This ensures in-flight requests cancel when the server shuts down.

Suggested fix
-	ctx, cancel := context.WithTimeout(context.Background(), redisDispatchTimeout)
+	ctx, cancel := context.WithTimeout(r.handlerContext(), redisDispatchTimeout)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_expire_cmds.go` around lines 30 - 31, Replace
context.Background() with r.handlerContext() in the context creation at the
beginning of the handler to ensure proper shutdown propagation. This aligns with
the pattern used in other handlers like setLegacy and ensures in-flight requests
are properly cancelled when the server shuts down. The ctx variable will then
derive from the handler's context instead of being a detached background
context.
adapter/redis_expire_cmds.go-180-186 (1)

180-186: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use cockerrors.Wrap instead of fmt.Errorf for error wrapping.

Per coding guidelines, errors at boundaries should use github.com/cockroachdb/errors for proper stack traces.

Suggested fix
 func parseExpireTTL(raw []byte) (int64, error) {
 	ttl, err := strconv.ParseInt(string(raw), 10, 64)
 	if err != nil {
-		return 0, fmt.Errorf("parse expire ttl: %w", err)
+		return 0, cockerrors.Wrap(err, "parse expire ttl")
 	}
 	return ttl, nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_expire_cmds.go` around lines 180 - 186, In the parseExpireTTL
function, replace the fmt.Errorf call with cockerrors.Wrap to ensure proper
stack trace handling according to coding guidelines. Import the cockerrors
package from github.com/cockroachdb/errors if not already imported, and use
cockerrors.Wrap to wrap the error instead of fmt.Errorf with the %w verb.

Source: Coding guidelines

adapter/redis_expire_cmds.go-46-47 (1)

46-47: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use r.handlerContext() for proper shutdown propagation.

Same issue as setex - should derive context from r.handlerContext() for consistency and proper shutdown behavior.

Suggested fix
-	ctx, cancel := context.WithTimeout(context.Background(), redisDispatchTimeout)
+	ctx, cancel := context.WithTimeout(r.handlerContext(), redisDispatchTimeout)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_expire_cmds.go` around lines 46 - 47, The context
initialization at lines 46-47 is creating a new context from
context.Background() instead of using the handler's context for proper shutdown
propagation. Replace the context.WithTimeout call to derive from
r.handlerContext() instead of context.Background(), ensuring consistency with
the setex implementation and allowing proper shutdown behavior throughout the
handler.
adapter/redis_expire_cmds.go-3-16 (1)

3-16: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Mixed error package usage violates coding guidelines.

The file imports both standard errors (line 5) and cockerrors "github.com/cockroachdb/errors" (line 14). Per coding guidelines, errors at boundaries should consistently use github.com/cockroachdb/errors for proper stack traces and error wrapping.

Line 169 uses errors.New("ERR syntax error") from the standard library.

Suggested fix
 import (
 	"context"
-	"errors"
 	"fmt"
 	"math"
 	"strconv"

And update line 169:

-		return false, errors.New("ERR syntax error")
+		return false, cockerrors.New("ERR syntax error")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_expire_cmds.go` around lines 3 - 16, The file has mixed error
package imports (standard errors and cockerrors) which violates coding
guidelines. Remove the standard `errors` import from the import block and
replace the `errors.New("ERR syntax error")` call (referenced in the content at
line 169) with `cockerrors.New("ERR syntax error")` to ensure consistent use of
the cockroachdb errors package for proper stack traces and error wrapping
throughout the file.

Source: Coding guidelines

adapter/redis_zset_cmds.go-825-860 (1)

825-860: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

loadZSetAt uses context.Background() instead of the available ctx.

Line 845 calls loadZSetAt(context.Background(), ...) inside the retry callback where ctx (with timeout) is in scope. This prevents proper cancellation if the parent context times out.

-		value, _, err := r.loadZSetAt(context.Background(), cmd.Args[1], readTS)
+		value, _, err := r.loadZSetAt(ctx, cmd.Args[1], readTS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_zset_cmds.go` around lines 825 - 860, The zrem method creates a
context with timeout using ctx, cancel := context.WithTimeout(...) but then
passes context.Background() instead of ctx to the loadZSetAt call on the line
that loads the ZSet value. Replace the context.Background() argument in the
loadZSetAt call with the ctx variable to ensure proper timeout propagation and
cancellation behavior throughout the operation.
adapter/redis_zset_cmds.go-862-911 (1)

862-911: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same context.Background() issue as zrem.

Line 893 should use ctx instead of context.Background() for consistent timeout propagation.

-		value, _, err := r.loadZSetAt(context.Background(), cmd.Args[1], readTS)
+		value, _, err := r.loadZSetAt(ctx, cmd.Args[1], readTS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_zset_cmds.go` around lines 862 - 911, In the zremrangebyrank
function, replace the context.Background() argument in the r.loadZSetAt call
with ctx to ensure the timeout created at the start of the function is properly
propagated to all operations. The ctx variable with redisDispatchTimeout is
already defined earlier in the function and should be used consistently
throughout instead of creating a new background context.
adapter/redis_zset_cmds.go-920-970 (1)

920-970: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same context.Background() issue in loadZSetAt call.

Line 943 should use ctx for timeout propagation.

-		value, _, err := r.loadZSetAt(context.Background(), key, readTS)
+		value, _, err := r.loadZSetAt(ctx, key, readTS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_zset_cmds.go` around lines 920 - 970, In the
tryBZPopMinWithMode function, the call to r.loadZSetAt is using
context.Background() instead of the ctx variable that was created with a timeout
at the beginning of the function. Replace context.Background() with ctx in the
r.loadZSetAt call to ensure proper timeout propagation throughout the operation.
🧹 Nitpick comments (6)
adapter/redis_server_cmds.go (2)

490-495: ⚡ Quick win

Use slog for structured logging per coding guidelines.

The log.Printf call should be replaced with slog for consistency with the project's structured logging requirements.

Suggested fix
 func (r *RedisServer) quit(conn redcon.Conn, _ redcon.Command) {
 	conn.WriteString("OK")
 	if err := conn.Close(); err != nil {
-		log.Printf("redis quit close failed: %v", err)
+		slog.Error("redis quit close failed", "error", err)
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_server_cmds.go` around lines 490 - 495, In the quit method of
the RedisServer type, replace the log.Printf call with slog for structured
logging to maintain consistency with the project's logging standards. Use the
appropriate slog function (such as slog.Error) to log the error when
conn.Close() fails, ensuring the error information is preserved in the
structured log output.

Source: Coding guidelines


571-577: ⚡ Quick win

Use slog for trace logging per coding guidelines.

Similar to the quit function, trace logging should use slog with appropriate structured keys.

Suggested fix
 func (r *RedisServer) publish(conn redcon.Conn, cmd redcon.Command) {
 	count := r.publishCluster(context.Background(), cmd.Args[1], cmd.Args[2])
 	if r.traceCommands {
-		log.Printf("redis trace publish remote=%s channel=%q subscribers=%d", conn.RemoteAddr(), string(cmd.Args[1]), count)
+		slog.Debug("redis trace publish", "remote", conn.RemoteAddr(), "channel", string(cmd.Args[1]), "subscribers", count)
 	}
 	conn.WriteInt64(count)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_server_cmds.go` around lines 571 - 577, In the publish method
of the RedisServer type, replace the log.Printf call used for trace logging with
structured logging using slog. Extract the trace message components (remote
address from conn.RemoteAddr(), channel from cmd.Args[1], and subscriber count)
as structured key-value pairs to pass to slog, following the same pattern used
in the quit function as referenced in the guidelines.

Source: Coding guidelines

adapter/redis_proxy_leader.go (1)

226-245: 💤 Low value

Cached Redis clients are never closed, risking connection leaks.

When leadership changes, stale clients for old leader addresses remain in the cache indefinitely. Over time, this can exhaust connection pools or file descriptors. Consider adding a cleanup mechanism (e.g., periodic eviction of unused clients, or replacing the cache entry when a new client is created for the same address after a leadership change).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_proxy_leader.go` around lines 226 - 245, The
getOrCreateLeaderClient method caches Redis clients indefinitely without ever
closing them, leading to connection leaks when leadership changes. Add a cleanup
mechanism to close and remove stale clients from the r.leaderClients cache when
they are no longer needed, such as closing the old client before storing a new
one for the same address, or implementing a separate method to explicitly close
and evict clients from the cache when leadership transitions occur. Ensure the
cleanup respects the existing mutex synchronization (r.leaderClientsMu) to
prevent race conditions.
adapter/redis_stream_cmds.go (1)

1143-1165: ⚡ Quick win

Read operations use context.Background() with no timeout.

Multiple store calls (keyTypeAtExpect, loadStreamMetaAt, loadStreamAt) use context.Background(), which means these reads have no timeout and won't abort during graceful shutdown. While less critical than write commands, this is inconsistent with xread which properly derives context from r.handlerContext().

Consider wrapping these in a context with timeout for consistency and to prevent slow reads from hanging indefinitely.

Suggested approach
 func (r *RedisServer) xlen(conn redcon.Conn, cmd redcon.Command) {
 	if r.proxyToLeader(conn, cmd, cmd.Args[1]) {
 		return
 	}
+	ctx, cancel := context.WithTimeout(r.handlerContext(), redisDispatchTimeout)
+	defer cancel()
 	readTS := r.readTS()
-	typ, err := r.keyTypeAtExpect(context.Background(), cmd.Args[1], readTS, redisTypeStream)
+	typ, err := r.keyTypeAtExpect(ctx, cmd.Args[1], readTS, redisTypeStream)
 	// ... apply same pattern to other context.Background() calls
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_stream_cmds.go` around lines 1143 - 1165, The read operations
in this stream command handler are using context.Background() which provides no
timeout for the keyTypeAtExpect, loadStreamMetaAt, and loadStreamAt calls,
potentially allowing reads to hang indefinitely and making it inconsistent with
how other commands like xread derive their context. Replace all instances of
context.Background() in this code block with a context derived from
r.handlerContext() to ensure these read operations have proper timeout handling
and can be cancelled during graceful shutdown, maintaining consistency with the
rest of the codebase.
adapter/redis_set_cmds.go (1)

46-52: ⚡ Quick win

Use cockerrors.Wrap for error wrapping consistency.

The coding guidelines require using github.com/cockroachdb/errors for error wrapping. This function uses fmt.Errorf while the rest of the file uses cockerrors.

Suggested fix
 func (r *RedisServer) hllExistsAt(key []byte, readTS uint64) (bool, error) {
 	exists, err := r.store.ExistsAt(context.Background(), redisHLLKey(key), readTS)
 	if err != nil {
-		return false, fmt.Errorf("exists hll: %w", err)
+		return false, cockerrors.Wrap(err, "exists hll")
 	}
 	return exists, nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_set_cmds.go` around lines 46 - 52, In the hllExistsAt function,
replace the fmt.Errorf call with cockerrors.Wrap to maintain consistency with
error wrapping practices in the rest of the file. The cockerrors.Wrap function
should be called with the error returned from r.store.ExistsAt and a descriptive
error message like "exists hll" to provide proper error context.

Source: Coding guidelines

adapter/redis_zset_cmds.go (1)

792-823: 💤 Low value

Consider propagating a timeout context for read operations.

zrangeRead uses context.Background() for both keyTypeAtExpect (line 794) and loadZSetAt (line 808), unlike write handlers that create timeout contexts. This could cause the operation to hang indefinitely on a slow or unresponsive store.

Given that the PR objectives mention context propagation changes, this may warrant verification.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/redis_zset_cmds.go` around lines 792 - 823, The zrangeRead function
uses context.Background() for both the keyTypeAtExpect and loadZSetAt
operations, which lacks timeout protection unlike write handlers. Create a
timeout context at the beginning of the zrangeRead function (similar to how
write handlers handle context creation) and pass that timeout context to both
the keyTypeAtExpect call and the loadZSetAt call instead of context.Background()
to ensure operations cannot hang indefinitely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@adapter/redis_hash_cmds.go`:
- Around line 848-856: The keyTypeAt function call on line 850 uses
context.Background() instead of the ctx parameter that has the
redisDispatchTimeout configured, which bypasses timeout propagation. Replace
context.Background() with ctx in the keyTypeAt function call to ensure the
timeout context is properly passed through to the store operation.
- Around line 75-150: The functions buildSetLegacyMigrationElems and
buildZSetLegacyMigrationElems are defined in redis_hash_cmds.go but are only
used in redis_set_cmds.go and redis_zset_cmds.go respectively, violating proper
code organization. Move buildSetLegacyMigrationElems to redis_set_cmds.go and
buildZSetLegacyMigrationElems to redis_zset_cmds.go. Additionally, on line 126
where the nolint:mnd directive appears in the capacity calculation for the elems
slice in buildZSetLegacyMigrationElems, replace the //nolint:mnd comment with a
named constant to clarify why 2 operations are needed per entry (one for member
key and one for score index key), and update all references to use this constant
instead of magic numbers.

In `@adapter/redis_keys.go`:
- Around line 14-16: The keys method in the RedisServer type accesses
cmd.Args[1] without validating that the cmd.Args slice contains at least 2
elements, which will cause a panic when a KEYS command is sent without the
required pattern argument. Add a length check before accessing cmd.Args[1] to
verify that len(cmd.Args) >= 2, and if the validation fails, use conn to send an
appropriate error response back to the client indicating that the pattern
argument is missing.

In `@adapter/redis_lists.go`:
- Around line 714-726: The listPushCmd function uses context.Background()
without timeouts in two places, which is inconsistent with other handlers like
lrange and risks indefinite hangs without parent context information. Replace
the context.Background() call in the r.keyTypeAt() method with
context.WithTimeout(r.handlerContext(), redisDispatchTimeout), and similarly
replace the ctx := context.Background() line with the same timeout context
pattern before passing it to pushFn(). This ensures consistency with other
handlers and provides proper timeout and context propagation.
- Around line 811-825: The lindex command handler uses context.Background()
without a timeout in the calls to r.keyTypeAt() and r.listValuesAt() methods,
which can lead to hung operations. Replace both context.Background() calls (at
lines 812 and 825) with a properly timeout-protected context by using
context.WithTimeout(), following the same pattern that was applied to fix
listPushCmd. This ensures that both the type check and value retrieval
operations have proper timeout protection.

In `@adapter/redis_set_cmds.go`:
- Around line 599-632: The pfcount function lacks a timeout-scoped context for
its store operations. After the proxyToLeader check, add a context with timeout
using `ctx, cancel := context.WithTimeout(context.Background(),
redisDispatchTimeout)` and defer the cancel call (following the pattern used in
pfadd), then replace all `context.Background()` calls within the loop for the
keyTypeAt, ExistsAt, and loadSetAt calls with this new ctx variable.
Additionally, verify whether the multi-key sharding assumption is correct since
proxyToLeader only examines cmd.Args[1] while pfcount iterates over all keys in
cmd.Args[1:]; confirm whether all keys in a single command are guaranteed to be
co-located on the same shard or if per-key routing is required.

In `@adapter/redis_stream_cmds.go`:
- Around line 215-216: Replace context.Background() with r.handlerContext() in
the context.WithTimeout calls within both the xadd handler (at lines 215-216)
and the xtrim handler (at line 546) to ensure these Redis write commands respect
server shutdown signals. This aligns with how the xread handler properly uses
r.handlerContext() at line 1052 and completes the "propagate Redis command
contexts" work. The change ensures operations will be properly cancelled during
graceful shutdown instead of potentially outliving the server's drain window.

In `@adapter/redis_zset_cmds.go`:
- Around line 849-854: The ZREM operation removes members from the zset but only
persists the remaining entries to the legacy blob key via persistZSetEntriesTxn,
leaving the wide-column ZSetMemberKey and ZSetScoreKey entries for removed
members untouched. This causes the removed members to be reloaded on the next
read. To fix this, after calling removeZSetMembers to identify which members
were removed, explicitly delete the ZSetMemberKey and ZSetScoreKey wide-column
entries for each removed member (following the same pattern used in
persistBZPopMinResult for lines 980-998) before or in conjunction with the
persistZSetEntriesTxn call.

---

Minor comments:
In `@adapter/redis_expire_cmds.go`:
- Around line 222-223: The context creation at lines 222-223 uses
context.Background() which does not propagate shutdown signals properly. Replace
the context.WithTimeout call that uses context.Background() with a call that
uses r.handlerContext() instead, while maintaining the same timeout duration
(redisDispatchTimeout). This ensures the context respects the handler's
lifecycle and proper shutdown propagation similar to other handlers in this
file.
- Around line 94-95: The context is being created using
context.WithTimeout(context.Background(), redisDispatchTimeout) which does not
properly propagate shutdowns. Replace this context creation with
r.handlerContext() which is the correct method used by other handlers in this
file to ensure proper shutdown propagation and context lifecycle management.
- Around line 30-31: Replace context.Background() with r.handlerContext() in the
context creation at the beginning of the handler to ensure proper shutdown
propagation. This aligns with the pattern used in other handlers like setLegacy
and ensures in-flight requests are properly cancelled when the server shuts
down. The ctx variable will then derive from the handler's context instead of
being a detached background context.
- Around line 180-186: In the parseExpireTTL function, replace the fmt.Errorf
call with cockerrors.Wrap to ensure proper stack trace handling according to
coding guidelines. Import the cockerrors package from
github.com/cockroachdb/errors if not already imported, and use cockerrors.Wrap
to wrap the error instead of fmt.Errorf with the %w verb.
- Around line 46-47: The context initialization at lines 46-47 is creating a new
context from context.Background() instead of using the handler's context for
proper shutdown propagation. Replace the context.WithTimeout call to derive from
r.handlerContext() instead of context.Background(), ensuring consistency with
the setex implementation and allowing proper shutdown behavior throughout the
handler.
- Around line 3-16: The file has mixed error package imports (standard errors
and cockerrors) which violates coding guidelines. Remove the standard `errors`
import from the import block and replace the `errors.New("ERR syntax error")`
call (referenced in the content at line 169) with `cockerrors.New("ERR syntax
error")` to ensure consistent use of the cockroachdb errors package for proper
stack traces and error wrapping throughout the file.

In `@adapter/redis_keys.go`:
- Around line 205-237: The matchesAsteriskPattern function has a bug where the
suffix check using HasSuffix does not account for prefix/suffix overlap. When
checking if the key ends with the suffix portion (the last part after splitting
by asterisk), add a validation that the suffix does not overlap with the
already-consumed prefix position. Specifically, ensure that before calling
HasSuffix for the last part, verify that the position where the suffix would
need to start (len(key) - len(last)) is greater than or equal to the current
position (pos) to prevent patterns like "a*a" from incorrectly matching keys
like "a" where the prefix and suffix overlap.

In `@adapter/redis_set_cmds.go`:
- Line 577: The loadSetAt function call is using context.Background() which
ignores any timeout settings from the outer context. Replace
context.Background() with ctx in the loadSetAt call to properly propagate the
outer context's timeout and cancellation behavior through the function call.
- Around line 461-484: The sismember function uses context.Background() without
a timeout, which could cause unbounded blocking on store operations, while other
command handlers in the codebase use context.WithTimeout. Replace the ctx
initialization in the sismember function to use context.WithTimeout instead of
context.Background(), applying the same timeout pattern used by other command
handlers (referenced at lines 451 and 568). This ensures the context passed to
setMemberFastExists and sismemberSlow methods has appropriate timeout
protection.
- Around line 534-562: The smembers function uses context.Background() without a
timeout in both the r.keyTypeAt and r.loadSetAt calls, which can cause unbounded
blocking. Create a timeout-scoped context at the beginning of the smembers
function (following the pattern used in write handlers) and replace both
context.Background() calls with this timeout context to ensure consistency and
prevent unbounded blocking operations.
- Around line 30-34: The validateExactSetKind function is using
context.Background() which ignores timeout and cancellation signals from parent
contexts in retry loops. Add a context parameter to the validateExactSetKind
function signature, replace the context.Background() call with this new
parameter when calling r.keyTypeAt(), and then update all three callers of
validateExactSetKind (referenced at lines 186, 211, and 573) to pass their own
context instead of letting the function create a new one.

In `@adapter/redis_zset_cmds.go`:
- Around line 825-860: The zrem method creates a context with timeout using ctx,
cancel := context.WithTimeout(...) but then passes context.Background() instead
of ctx to the loadZSetAt call on the line that loads the ZSet value. Replace the
context.Background() argument in the loadZSetAt call with the ctx variable to
ensure proper timeout propagation and cancellation behavior throughout the
operation.
- Around line 862-911: In the zremrangebyrank function, replace the
context.Background() argument in the r.loadZSetAt call with ctx to ensure the
timeout created at the start of the function is properly propagated to all
operations. The ctx variable with redisDispatchTimeout is already defined
earlier in the function and should be used consistently throughout instead of
creating a new background context.
- Around line 920-970: In the tryBZPopMinWithMode function, the call to
r.loadZSetAt is using context.Background() instead of the ctx variable that was
created with a timeout at the beginning of the function. Replace
context.Background() with ctx in the r.loadZSetAt call to ensure proper timeout
propagation throughout the operation.

---

Nitpick comments:
In `@adapter/redis_proxy_leader.go`:
- Around line 226-245: The getOrCreateLeaderClient method caches Redis clients
indefinitely without ever closing them, leading to connection leaks when
leadership changes. Add a cleanup mechanism to close and remove stale clients
from the r.leaderClients cache when they are no longer needed, such as closing
the old client before storing a new one for the same address, or implementing a
separate method to explicitly close and evict clients from the cache when
leadership transitions occur. Ensure the cleanup respects the existing mutex
synchronization (r.leaderClientsMu) to prevent race conditions.

In `@adapter/redis_server_cmds.go`:
- Around line 490-495: In the quit method of the RedisServer type, replace the
log.Printf call with slog for structured logging to maintain consistency with
the project's logging standards. Use the appropriate slog function (such as
slog.Error) to log the error when conn.Close() fails, ensuring the error
information is preserved in the structured log output.
- Around line 571-577: In the publish method of the RedisServer type, replace
the log.Printf call used for trace logging with structured logging using slog.
Extract the trace message components (remote address from conn.RemoteAddr(),
channel from cmd.Args[1], and subscriber count) as structured key-value pairs to
pass to slog, following the same pattern used in the quit function as referenced
in the guidelines.

In `@adapter/redis_set_cmds.go`:
- Around line 46-52: In the hllExistsAt function, replace the fmt.Errorf call
with cockerrors.Wrap to maintain consistency with error wrapping practices in
the rest of the file. The cockerrors.Wrap function should be called with the
error returned from r.store.ExistsAt and a descriptive error message like
"exists hll" to provide proper error context.

In `@adapter/redis_stream_cmds.go`:
- Around line 1143-1165: The read operations in this stream command handler are
using context.Background() which provides no timeout for the keyTypeAtExpect,
loadStreamMetaAt, and loadStreamAt calls, potentially allowing reads to hang
indefinitely and making it inconsistent with how other commands like xread
derive their context. Replace all instances of context.Background() in this code
block with a context derived from r.handlerContext() to ensure these read
operations have proper timeout handling and can be cancelled during graceful
shutdown, maintaining consistency with the rest of the codebase.

In `@adapter/redis_zset_cmds.go`:
- Around line 792-823: The zrangeRead function uses context.Background() for
both the keyTypeAtExpect and loadZSetAt operations, which lacks timeout
protection unlike write handlers. Create a timeout context at the beginning of
the zrangeRead function (similar to how write handlers handle context creation)
and pass that timeout context to both the keyTypeAtExpect call and the
loadZSetAt call instead of context.Background() to ensure operations cannot hang
indefinitely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4205ad0d-e44c-488c-ac90-aaa093ddf6d2

📥 Commits

Reviewing files that changed from the base of the PR and between ebdf8eb and 817356b.

📒 Files selected for processing (13)
  • adapter/redis.go
  • adapter/redis_compat_commands.go
  • adapter/redis_expire_cmds.go
  • adapter/redis_hash_cmds.go
  • adapter/redis_keys.go
  • adapter/redis_lists.go
  • adapter/redis_proxy_leader.go
  • adapter/redis_server_cmds.go
  • adapter/redis_set_cmds.go
  • adapter/redis_stream_cmds.go
  • adapter/redis_strings.go
  • adapter/redis_txn.go
  • adapter/redis_zset_cmds.go

Comment thread adapter/redis_hash_cmds.go Outdated
Comment thread adapter/redis_hash_cmds.go
Comment thread adapter/redis_keys.go
Comment thread adapter/redis_lists.go
Comment thread adapter/redis_lists.go Outdated
Comment thread adapter/redis_set_cmds.go
Comment thread adapter/redis_stream_cmds.go Outdated
Comment thread adapter/redis_zset_cmds.go Outdated
@bootjp

bootjp commented Jun 23, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Current-head review sweep complete. The Major inline findings are already addressed in commit 4e8854b:

  • KEYS now validates the pattern argument before indexing cmd.Args[1]
  • listPushCmd uses a timeout-scoped handler context for type checks and pushFn
  • INCR passes the timeout ctx to keyTypeAt
  • txn TTL loading uses t.ctx with a nil fallback
  • PFCOUNT uses a timeout-scoped context for keyTypeAt, ExistsAt, and loadSetAt; the CodeRabbit thread also marks this addressed

No additional code change was needed in this tick.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 4e8854be28

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@bootjp bootjp merged commit 5df87f7 into main Jun 24, 2026
9 checks passed
@bootjp bootjp deleted the refactor/split-adapter-redis branch June 24, 2026 08:52
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.

2 participants