diff --git a/CHANGELOG.md b/CHANGELOG.md index f1445d16f..e604a09f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.1] — 2026-07-15 + +Patch release closing the two follow-ups disclosed in the v0.7.0 tag notes: the +SQ8 vector CPU error-storm and replica TTL determinism. + ### Fixed - **Vector: SQ8/TQ code-size mis-dispatch CPU error-storm.** SQ8's `bits()` returns 8, which falls outside TurboQuant's supported `1..=4` range; the free diff --git a/Cargo.lock b/Cargo.lock index 09aa29928..cd4c6a4c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1820,7 +1820,7 @@ dependencies = [ [[package]] name = "moon" -version = "0.7.0" +version = "0.7.1" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 5ff4115d4..f6bb12fdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "moon" -version = "0.7.0" +version = "0.7.1" edition = "2024" rust-version = "1.94" description = "A high-performance Redis-compatible server written in Rust" diff --git a/RELEASES.md b/RELEASES.md index c283d328b..05912e5be 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,10 @@ # Releases +## v0.7.1 — 2026-07-15 +milestones: none (patch release folding in the two v0.7.0-tag-disclosed follow-ups) +waivers: none new — `XSHARD-READ-01` remains the open GA gap (ROADMAP R4), unchanged from v0.7.0. +evidence: Two correctness fixes shipped under one patch. (1) **Vector SQ8/TQ CPU error-storm (task #73, #334):** SQ8's `bits()=8` fell outside TurboQuant's `1..=4` range, so `code_bytes_per_vector` hit its `_ =>` arm returning `0` while emitting a `tracing::error!` on **every** call on the hot memory-accounting path — a CPU-pegging storm plus wrong resident-byte accounting for SQ8 indexes; dispatch now computes the true SQ8 layout directly and the free-fn warns at most once via an `AtomicBool` latch. (2) **Deterministic replica TTL (task #71, #335):** relative-expiry commands (`EXPIRE`/`PEXPIRE`/`SETEX`/`PSETEX`/`SET … EX/PX`/`GETEX … EX/PX`) are rewritten to absolute deadlines (`PEXPIREAT`/`SET … PXAT`) on the master before entering the durable log and replication stream, computed from the per-tick cached clock the handler used — so replicas and AOF replay reproduce the master's expiry *instant* (#71a); and a replica no longer runs its own active-expiry sweep, converging on the master's authoritative removal instead of racing an independent TTL cycle (#71b). This closes the replica relative-TTL caveat disclosed in the v0.7.0 tag. Both PRs green on the self-hosted CI matrix (Lint + Check + MSRV + Memory gate) and admin-squash-merged to `main` (f5641707, 389acb61). New unit coverage: 16 `expire_rewrite` transform tests + 2 black-box two-node replication TTL integration tests (`tests/replication_ttl_semantics.rs`). + ## v0.7.0 — 2026-07-15 milestones: v0.7.0 "Replication GA for multi-shard masters" (soak-gated tag; v0.6.1 hardening folded in) waivers: none — the v0.6.0 `shardslice-migration` waiver (was: expires 2026-08-01) is **retired**; lock-free cross-shard read work is now tracked as an open GA gap (`XSHARD-READ-01`, ROADMAP R4), not a time-boxed waiver. diff --git a/docs/PRODUCTION-CONTRACT.md b/docs/PRODUCTION-CONTRACT.md index c86096d92..a0b543bf9 100644 --- a/docs/PRODUCTION-CONTRACT.md +++ b/docs/PRODUCTION-CONTRACT.md @@ -127,7 +127,7 @@ per the CI gate · Blocking `—` = tracked but never blocks a tag (see Out of S | ✅ | WAIT-01 | `WAIT` reflects real replica ACK state | R1 (task #19, PR #282): replica 1s `REPLCONF ACK` ticker on the split PSYNC socket; master `ack_read_loop` + `drain_ack_offsets` record into `ReplicaInfo.ack_offsets`; connection-layer `try_handle_wait` blocks until ACK ≥ target or timeout. `wait_returns_acked_replica_count` e2e; exact on multi-shard masters too (summed snapshot offset). | GA | | ✅ | REPL-PLANES-01 | Every write plane replicates, not just KV: eviction/expiry DELs, Lua effects, graph, vector/text index defs+contents, WS.*, MQ.*, TEMPORAL.* | Wave A (PR #285): eviction/expiry DELs + Lua effects to both planes (EVAL was previously durable in neither). Wave B (PR #294 + task #34): WS/MQ deterministic records + replica apply + PSYNC registry blob. Graph plane (task #25): live GRAPH.* streaming + snapshot backfill. Suites: `tests/replication_planes.rs`, `replication_graph.rs`, `replication_mq.rs`, `replication_readonly_ws_mq.rs`. Unified poison-record policy for replica apply (task #48). | GA | | ✅ | REPL-SOAK-01 | 24h replication soak: kill -9 either side under WAIT-confirmed load, zero acked-write loss | **PASSED 2026-07-15** — `SOAK-PASS duration=86400s cycles=114 acked=82044 inflight=7 master_kills=57 replica_kills=57`; 82,044 WAIT-acked writes preserved across 114 alternating kill-9 cycles, zero acked-loss. Run dir `moon-soak/runs/20260714-141946`, RC `e2d87893`. Gates the v0.7.0 tag (task #65). | GA | -| ⬜ | KEYSPACE-NOTIF-01 | `notify-keyspace-events` keyspace notifications | No implementation found in `src/`. ROADMAP v0.7.0 workstream R5 — deferred to v0.7.1 (one-headline rule). | GA | +| ⬜ | KEYSPACE-NOTIF-01 | `notify-keyspace-events` keyspace notifications | No implementation found in `src/`. ROADMAP v0.7.0 workstream R5 — deferred to a later release (v0.7.1 was a patch: SQ8 CPU-storm + replica TTL determinism). | GA | | ⬜ | MONITOR-01 | `MONITOR` command | No implementation found in `src/command/`. ROADMAP v0.7.0 workstream R5. | GA | | ⬜ | XSHARD-READ-01 | Lock-free cross-shard read path (retire the shardslice waiver) | Waiver **expires 2026-08-01** per `RELEASES.md` v0.6.0 entry and ROADMAP §5; L4 redesign (`tmp/MULTISHARD-REDESIGN.md`) unstarted. ROADMAP v0.7.0 workstream R4. | GA | diff --git a/docs/guides/clustering.md b/docs/guides/clustering.md index 21a6d3305..8718cf642 100644 --- a/docs/guides/clustering.md +++ b/docs/guides/clustering.md @@ -178,16 +178,19 @@ Moon does not implement the Redis Sentinel protocol. - **Lazy backlog** — allocated only when the first replica handshake begins (REPLCONF), saving ~12 MB baseline memory - **Validated** — 24 h continuous-load kill-9 soak (alternating master/replica restarts), zero loss of any WAIT-acknowledged write -!!! warning "Replica TTL semantics (v0.7)" - Relative-expire commands (`EXPIRE`, `SETEX`, `PEXPIRE`, `GETEX` with a relative - TTL) replicate verbatim rather than being rewritten to absolute `PEXPIREAT`, and - replicas run their own active-expiry cycle. Because the replica's countdown starts - when it **applies** the command — not when the master ran it — replication/apply - delay shifts a relative-TTL key's expiry moment by that delay **even with perfectly - synchronized clocks** (the replica holds the key slightly longer). Master/replica - clock skew adds a further offset on top. For exact cross-node expiry parity, set - absolute deadlines with `PEXPIREAT`. Absolute-rewrite + role-gated expiry land in - v0.7.1. +!!! note "Replica TTL semantics (deterministic since v0.7.1)" + Relative-expire commands (`EXPIRE`, `SETEX`, `PEXPIRE`, `SET … EX/PX`, `GETEX` + with a relative TTL) are rewritten to absolute deadlines on the master + (`PEXPIREAT`/`SET … PXAT`) **before** they enter the durable log and replication + stream, using the master's per-tick cached clock — the exact value its command + handler stored. The replica (and an AOF replay after a restart) therefore + reproduces the master's expiry **instant**, not a countdown restarted at apply + time, so apply delay no longer shifts a key's expiry moment. A replica also no + longer runs its own active-expiry deletion sweep: it holds a logically-expired + key resident (reads still see it as gone) until the master streams the + authoritative removal, so both nodes delete at the same point in the stream. + Master/replica clock skew is thus the only remaining source of divergence, and it + affects only the wall-clock instant of deletion, not which node deletes first. ## Cluster mode