Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.8.1] — 2026-07-19

### Added
- **`conf/moon-standalone.conf` + a broadened `--profile standalone`: the
best single-shard tuning as one flag or one file, now safe on any host.**
`--profile standalone` still fills `--shards 1`, `--io-busy-poll-us 40`, and
`--io-driver epoll`, and now additionally drops the jemalloc arena cap to `2`
on `--features jemalloc` builds — a single-shard instance has one hot
allocator thread, so the baked-in 8 arenas are oversized and 2 lowers the RSS
baseline with no contention cost. The arena cap is resolved in the pre-clap
allocator re-spawn scan (the only path that reaches jemalloc before init), so
it is CLI-only — `--profile standalone` on the command line folds it in; a
conf-file `profile standalone` still sets the other three. The new annotated
[`conf/moon-standalone.conf`](conf/moon-standalone.conf) ships the same tuning
(durability on) as an editable config file. Paired with the O3 contention
governor below, the preset no longer requires pinned cores — the tuning and
configuration guides drop the pinned-cores-only caveat throughout and the
README/architecture docs are updated to match.

### Performance
- **`--io-busy-poll-us` is now deploy-safe: a per-shard contention governor
gates the spin on shared cores (O3).** The p=1 busy-poll win (GCE pinned:
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.8.0"
version = "0.8.1"
edition = "2024"
rust-version = "1.94"
description = "A high-performance Redis-compatible server written in Rust"
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,12 @@ classic gaps too:

- **Unpipelined (p=1) single-connection** — historically Redis's best case —
now a Moon **win on both GCE architectures** with the shipped
`--io-busy-poll-us 40` poll-mode park (`--profile standalone` sets it for
you): **1.19–1.21×** Redis on ARM (c4a Axion), **1.65–1.66×** on x86 (c3),
same-instance A/Bs, n=3.
`--io-busy-poll-us 40` poll-mode park (`--profile standalone`, or the
annotated [`conf/moon-standalone.conf`](conf/moon-standalone.conf), sets it
for you): **1.19–1.21×** Redis on ARM (c4a Axion), **1.65–1.66×** on x86
(c3), same-instance A/Bs, n=3. As of v0.8.1 the busy-poll **auto-gates on
shared cores** (per-shard contention governor), so the preset is safe on any
host — not just pinned ones.
- **Fully durable writes** (`appendfsync always`, p=16) went from 0.12× to
**0.91× Redis** via per-batch group commit + coalesced writes, while
`everysec` p=16 is a **1.32× win** — with kill-9-lossless recovery.
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.8.1 — 2026-07-19
milestones: none (patch release folding the post-v0.8.0 perf/correctness train + the single-shard tuning preset)
waivers: none new — `XSHARD-READ-01` remains the open GA gap (ROADMAP R4), unchanged from v0.8.0.
evidence: Patch release rolling up the PRs merged after the v0.8.0 tag (#361–#392) plus a single-shard tuning preset shipped in this cycle. **Performance / CPU (the CPU-cache digest, adjudicated by measurement):** `--io-busy-poll-us` is now deploy-safe on any host — the O3 per-shard contention governor samples each shard thread's own `nonvoluntary_ctxt_switches` and auto-gates the busy-poll on shared/oversubscribed cores (PR #392), retiring the pinned-cores-only caveat; auxiliary threads re-pinned off the shard cores (O5, #388); `--memory-thp` added as a **permanently opt-in** jemalloc value-heap THP knob (O4, #387) after a 45-min RSS-drift soak **disqualified** a default flip (#390, idle khugepaged re-collapse drifts RSS ~+31%); value-line prefetch (O1) and a lock-free `Notify` swap (O2) both **rejected by measurement** and recorded as dead ends with reproducible harnesses (#389, #391). Idle-tick CPU trimmed to ~0.5–0.9% via the adaptive idle park + O(1) pagecache counter (#375/#378). **Correctness:** multi-shard SWAPDB is durable + abort-safe before `+OK` (#133/#385); cold recovery re-attaches spilled keys to their own database (#139/#383); SCAN honors the stable-key guarantee and range-resumes both planes with true O(COUNT) hot pages under disk-offload (#368 → #379/#380/#382); a data-dir instance lock refuses a double-start on the same `--dir` (#363); DBSIZE/INFO and SCAN/KEYS/RANDOMKEY count/enumerate the cold plane under offload (#355/#364); DashTable double-`NeedsSplit` recovery panic fixed. **Single-shard tuning preset:** `--profile standalone` broadened (jemalloc arena cap → 2 on jemalloc builds) and shipped as the annotated `conf/moon-standalone.conf`; tuning/configuration/README docs updated to reflect the now-safe-everywhere busy-poll. Release gate — crash-matrix nightly full 46-cell matrix + `ITERS=20` probabilistic soak dispatched on the RC via `workflow_dispatch`, both green **before** merge (soak-first-then-tag); this patch touches no crash/persistence path, but the gate is run to hold the ritual. Disclosed non-blocking follow-ups carried from v0.8.0 remain open: task #59 read-vs-spill fairness (now shipped as #361 off-loop manifest fsync + pacing), monoio-only unit tests are CI-invisible (all CI test jobs run `runtime-tokio`).

## v0.8.0 — 2026-07-16
milestones: v0.8.0 "One Storage Kernel GA" (close-out + verification of the kernel built during the v0.7.0 cycle)
waivers: none new — `XSHARD-READ-01` remains the open GA gap (ROADMAP R4), unchanged.
Expand Down
84 changes: 84 additions & 0 deletions conf/moon-standalone.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# ============================================================================
# moon-standalone.conf — best single-shard performance preset
# ============================================================================
#
# A ready-to-use moon.conf for the most common deployment: ONE dedicated Moon
# instance answering low-pipeline request/response traffic (sessions, rate
# limits, cache, small-fanout pub/sub) with durability ON. This is the config
# behind Moon's "beat Redis at p=1" numbers — 1.19–1.21× (ARM) / 1.65–1.66×
# (x86) single-connection GET/SET vs Redis on dedicated-vCPU cloud instances.
#
# Usage:
# moon --config /etc/moon/moon-standalone.conf
#
# One-flag CLI equivalent (identical tuning, plus the jemalloc arena cap — see
# the ALLOCATOR note below):
# moon --profile standalone
#
# Format: redis.conf-style `key value`. `#` starts a comment. `_` and `-` in
# keys are interchangeable. Anything you set here can be overridden on the CLI
# (the CLI always wins), and every line below is a documented default or a
# recipe from docs/guides/tuning.md — read that guide before changing shard
# count for a higher-concurrency or pipelined workload.
# ----------------------------------------------------------------------------

# ── Network ─────────────────────────────────────────────────────────────────
port 6379
bind 127.0.0.1
# maxclients 10000
# timeout 0 # client idle timeout, seconds; 0 = never

# ── Shard count: single shard is fastest for low-concurrency traffic ────────
# One shard = no cross-thread key hops, best per-operation latency, and the
# fair configuration for per-key memory comparisons. Move to `shards 4` only
# when you serve 8+ concurrent connections OR any pipelined traffic (depth ≥16)
# — see docs/guides/tuning.md#shard-count-the-most-important-knob.
shards 1

# ── Busy-poll: delete scheduler wakeup latency from the request path ─────────
# Each shard thread polls for new I/O for up to 40 µs before parking, removing
# the sleep/wake that otherwise dominates shallow request/response traffic.
# This is the single biggest lever behind the p=1 win over Redis.
#
# Safe on ANY host as of the O3 contention governor: each shard thread watches
# its own involuntary-preemption rate and automatically stops spinning when the
# core is shared/oversubscribed (laptops, burstable VMs, busy Kubernetes
# nodes), re-enabling once the core is quiet again. On a genuinely dedicated,
# pinned core you get the full latency win; on a contended one you pay at most
# ~one sampling window of spin. Costs up to the budget in CPU per idle park on
# a dedicated core (~4%/core at 40 µs) — the price of the latency win.
# MOON_SPIN_ADAPTIVE=0 disables the governor (unconditional spin)
# MOON_SPIN_MAX_PREEMPTS_PER_SEC tunes the gate threshold (default 25/s)
io-busy-poll-us 40

# The busy-poll is observable only on the legacy (epoll/kqueue) driver; io_uring
# completions cannot be polled this way. `epoll` is also the measured winner for
# single-connection KV on GCE ARM (c4a Axion) regardless of busy-poll.
io-driver epoll

# ── Persistence: durable by default ─────────────────────────────────────────
# Per-shard WAL/AOF with everysec fsync costs ≈ zero throughput at one shard
# and bounds data loss to ~1s. Set `appendfsync always` for RPO 0 (disk-fsync
# bound; pipelines still fine). Set `appendonly no` for a pure cache.
appendonly yes
appendfsync everysec
# dir /var/lib/moon # data directory (WAL/AOF/snapshots live here)
# save "900 1 300 10 60 10000" # RDB snapshot cadence (seconds changes)

# ── Memory ──────────────────────────────────────────────────────────────────
# With no explicit --maxmemory, Moon auto-caps at ~80% of the detected memory
# limit (cgroup/host) and, if the policy is `noeviction`, switches to an
# evicting one so the cap sheds memory instead of OOM-ing. For a pure cache,
# pin both explicitly:
# maxmemory 2gb
# maxmemory-policy allkeys-lru

# ── ALLOCATOR (jemalloc builds only; CLI-only, cannot be set here) ──────────
# A single-shard instance has one hot allocator thread, so jemalloc's default
# of 8 arenas is oversized. `--profile standalone` drops it to 2 (lower RSS
# baseline, no contention at this concurrency). The allocator config is read
# once at process start, BEFORE this file is parsed, so `memory-arenas-cap`
# CANNOT take effect from a conf file — pass it on the CLI:
# moon --config .../moon-standalone.conf --memory-arenas-cap 2
# or just use `moon --profile standalone`, which folds it in. Applies only to
# `--features jemalloc` builds; the default (mimalloc) build ignores it.
18 changes: 15 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,25 @@ promotion (`REPLICAOF NO ONE`), and the replica TTL caveat: see the
| `--tcp-keepalive` | `300` | TCP keepalive interval in seconds (0 = disabled) |
| `--slowlog-log-slower-than` | `10000` | Slowlog threshold in microseconds |
| `--slowlog-max-len` | `128` | Maximum slowlog entries |
| `--profile` | *(none)* | Apply a named tuning preset (currently `standalone`). Only fills flags left at their default — an explicit flag always wins. Logs exactly what it set. **`standalone` requires pinned/dedicated cores** (regresses on shared hosts). See the [tuning guide](guides/tuning.md#profiles) |
| `--profile` | *(none)* | Apply a named tuning preset (currently `standalone`). Only fills flags left at their default — an explicit flag always wins. Logs exactly what it set. Safe on any host (busy-poll auto-gates on shared cores). See the [tuning guide](guides/tuning.md#profiles) |
| `--io-driver` | `auto` | I/O driver: `auto` (io_uring on Linux, kqueue on macOS) or `epoll` |
| `--io-busy-poll-us` | `0` (off) | Busy-poll the I/O driver for N µs before parking. Large single-op latency win on **dedicated, pinned cores**; a regression on shared/oversubscribed hosts. See the [tuning guide](guides/tuning.md) |
| `--io-busy-poll-us` | `0` (off) | Busy-poll the I/O driver for N µs before parking. Large single-op latency win on dedicated cores; **auto-disables on shared/oversubscribed cores** via the per-shard contention governor, so it no longer regresses there. See the [tuning guide](guides/tuning.md#busy-polling-single-op-latency-on-dedicated-cores) |
| `--initial-keyspace-hint` | `0` | Pre-size the keyspace (e.g. `1000000`) to avoid rehash pauses during bulk loads |
| `--memory-arenas-cap` | `8` | Cap jemalloc arenas — lower (e.g. `2`) for small containers |
| `--memory-arenas-cap` | `8` | Cap jemalloc arenas — lower (e.g. `2`) for a single-shard / small-container footprint. jemalloc builds only; **CLI-only** (read before the config file). `--profile standalone` sets `2` |
| `--memory-thp` | *(off)* | Opt the jemalloc value heap into transparent huge pages (`thp:always`). Measured GET +12–24% on GCE, but **permanently opt-in**: idle khugepaged re-collapse drifts RSS ~+31% after mixed-size churn, so enable only on uniform-value-size fleets with RSS headroom. jemalloc + Linux only; **CLI-only** |
| `--uring-sqpoll` | *(disabled)* | io_uring SQPOLL idle timeout in ms. Requires CAP_SYS_NICE. Linux only |

### Environment tuning knobs (diagnostics)

These are same-binary A/B / diagnostic overrides, not production tuning:

| Env var | Effect |
|---------|--------|
| `MOON_SPIN_ADAPTIVE=0` | Disable the busy-poll contention governor — the shard spins unconditionally whenever `--io-busy-poll-us > 0` (pre-governor behaviour; dedicated cores only) |
| `MOON_SPIN_MAX_PREEMPTS_PER_SEC` | Governor gate threshold in involuntary preemptions/sec (default `25`). One window above this disables the spin on that shard; five quiet windows re-enable it |
| `MOON_NO_URING=1` | Force-disable io_uring everywhere (epoll/kqueue fallback); for CI/containers/WSL. CLI equivalent: `--io-driver epoll` |
| `MOON_IDLE_PARK=0` | Disable the adaptive idle-park (pins the shard loop to its fixed 1 ms tick instead of stretching to 10 ms after proven quiet) |

## Disk offload (tiered storage)

| Flag | Default | Description |
Expand Down
5 changes: 3 additions & 2 deletions docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ costs ~10µs), keeps every key co-located (no `{hash-tag}` planning), and — wi
`--appendonly yes` + `--wal-kv-log auto` — writes each KV record to disk exactly
**once** (the AOF). Reach for more shards only with 8+ concurrent connections or
deep pipelines (measured 1.3–2.5× Redis at 8–64 conns on 4 shards), and see
[Tuning](tuning.md) for `--io-busy-poll-us` (a p=1 latency win **only** on
pinned/dedicated cores).
[Tuning](tuning.md) for `--io-busy-poll-us` (a p=1 latency win on dedicated
cores that now auto-gates on shared ones, so it's safe on any host — or use
`--profile standalone` to fold it in).

## Persistence

Expand Down
Loading
Loading