Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/backup/redis_hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,11 @@ type hashFieldRecord struct {
Value json.RawMessage `json:"value"`
}

func marshalHashJSON(st *redisHashState) ([]byte, error) {
// nolint comment lives at the function head: dupl pairs this with
// marshalZSetJSON, which carries the rationale (parallel design-spec
// wrappers that can't collapse into a shared helper without breaking
// JSON field-order determinism). See redis_zset.go:marshalZSetJSON.
func marshalHashJSON(st *redisHashState) ([]byte, error) { //nolint:dupl // see comment above + redis_zset.go
// Sort by raw byte order for deterministic output across runs.
names := make([]string, 0, len(st.fields))
for name := range st.fields {
Expand Down
12 changes: 12 additions & 0 deletions internal/backup/redis_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,24 @@ func (r *RedisDB) HandleSetMetaDelta(_, _ []byte) error { return nil }
// setState lazily creates per-key state. Mirrors the hash/list
// kindByKey-registration pattern so HandleSetMeta, HandleSetMember,
// and the HandleTTL back-edge all agree on the kind.
//
// On first registration we drain any pendingTTL for the user key.
// `!redis|ttl|<k>` lex-sorts BEFORE `!st|...` (because `r` < `s`),
// so in real snapshot order the TTL arrives FIRST; HandleTTL parks
// it in pendingTTL, and this function inlines it into the set's
// `expire_at_ms`. Without this drain step, every TTL'd set would
// restore as permanent — a latent bug in PR #758 surfaced by codex
// on PR #790. Phase 0a tests added in the same PR pin the ordering.
func (r *RedisDB) setState(userKey []byte) *redisSetState {
uk := string(userKey)
if st, ok := r.sets[uk]; ok {
return st
}
st := &redisSetState{members: make(map[string]struct{})}
if expireAtMs, ok := r.claimPendingTTL(userKey); ok {
st.expireAtMs = expireAtMs
st.hasTTL = true
}
r.sets[uk] = st
r.kindByKey[uk] = redisKindSet
return st
Expand Down
Loading
Loading