Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
5 changes: 5 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/PRODUCTION-CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
23 changes: 13 additions & 10 deletions docs/guides/clustering.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down