Skip to content

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

Open
bootjp wants to merge 1 commit into
mainfrom
refactor/split-adapter-redis
Open

adapter: split redis.go / redis_compat_commands.go into cohesive files (no behavior change)#956
bootjp wants to merge 1 commit 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).

@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

Warning

Review limit reached

@bootjp, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 33 minutes and 20 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24847c09-3c8d-4669-aa84-ae01112a7342

📥 Commits

Reviewing files that changed from the base of the PR and between 688ba8a and 48ccba1.

📒 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
✨ Finishing Touches
🧪 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 and usage tips.

@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.

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)

// 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)

}

// 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
}

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
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
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
}

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
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
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)


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.

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