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
8066246
test(shard): shardslice-migration spec bundle + red suite (contract f…
tindangtts Jun 12, 2026
26cb172
feat(shard): C3+C5 — global workspace registry + published store-memo…
tindangtts Jun 12, 2026
b20cb98
feat(shard): C2 — SPSC hop variants + owner-side MQ execution module
tindangtts Jun 12, 2026
62f4e0f
feat(server): owner-route MQ/WS/TXN slice arms via SPSC hops (both ru…
tindangtts Jun 12, 2026
0f07a34
refactor(shard,server): collapse all is_initialized dual branches to …
tindangtts Jun 12, 2026
5dd035e
fix(persistence): fix test_ssm4a_fold_4shard_experimental — bounded A…
tindangtts Jun 12, 2026
98f8da0
feat(shard): C1+C6 structural cutover — ShardSlice live, lock wrapper…
tindangtts Jun 12, 2026
4cdc2f2
fix(persistence): exact C4 fold boundary + H1 fsync barrier for cross…
tindangtts Jun 12, 2026
7e3a6c4
fix(shard): slice re-entrancy in TXN intent capture + tokio test-harn…
tindangtts Jun 12, 2026
003ca37
docs(add): shardslice-migration §5 build record + §6 verify evidence
tindangtts Jun 12, 2026
f273c23
docs(add): record RISK-ACCEPTED gate for shardslice-migration
tindangtts Jun 12, 2026
dadc855
fix(test): adapt hybrid_filter harnesses + restore filter threading p…
tindangtts Jun 12, 2026
f6b4edb
fix(persistence): satisfy unwrap ratchet in fold-channel dispatch
tindangtts Jun 12, 2026
25c0cf3
fix(persistence): implement TopLevel cooperative fold for BGREWRITEAO…
tindangtts Jun 12, 2026
b3d56c7
fix(persistence): bound tokio TopLevel everysec flush at 1s deadline
tindangtts Jun 12, 2026
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: 17 additions & 2 deletions .add/state.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"project": "moon",
"stage": "production",
"active_task": "consistency-dispatch-gaps",
"active_task": "shardslice-migration",
"active_milestone": "v1-shared-nothing",
"tasks": {
"hotpath-lock-quickwins": {
Expand Down Expand Up @@ -33,6 +33,21 @@
"created": "2026-06-11T10:38:27+00:00",
"updated": "2026-06-12T02:21:52+00:00",
"flag_verified": true
},
"shardslice-migration": {
"title": "Wire ShardSlice: thread-local shared-nothing storage becomes the live path",
"phase": "done",
"gate": "RISK-ACCEPTED",
"milestone": "v1-shared-nothing",
"depends_on": [],
"created": "2026-06-12T03:23:36+00:00",
"updated": "2026-06-12T17:04:31+00:00",
"flag_verified": true,
"waiver": {
"owner": "Tin Dang",
"ticket": "follow-up task: cross-shard-read-acceleration (observe)",
"expires": "2026-08-01"
}
}
},
"milestones": {
Expand All @@ -46,7 +61,7 @@
}
},
"created": "2026-06-11T03:18:21+00:00",
"updated": "2026-06-12T02:21:52+00:00",
"updated": "2026-06-12T17:04:31+00:00",
"setup": {
"locked": true,
"locked_at": "2026-06-11T03:28:00+00:00",
Expand Down
737 changes: 737 additions & 0 deletions .add/tasks/shardslice-migration/TASK.md

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed — Shared-nothing thread-local shard storage (PR #175)

Shard storage moved from `RwLock`/`Mutex`-wrapped shared databases to
thread-local **ShardSlices** owned exclusively by each shard's event
loop, on both runtimes (monoio and tokio). The lock wrappers are
deleted; all cross-shard access routes through the owner shard's SPSC
ring. Routed multi-shard workloads are parity or better (up to +12% on
pipelined GET at 4 shards); the cross-shard *read fast-path* (direct
RwLock read of another shard's data) is definitionally gone and those
cells regress — accepted with a follow-up task for lock-free cross-shard
read acceleration. BGREWRITEAOF on every layout now uses the C4
cooperative-fold protocol: the AOF writer requests an atomic snapshot
from the owning shard and drains pre-snapshot appends with an exact
bound, so concurrent writes are neither lost nor double-applied. This
also fixes BGREWRITEAOF being silently inoperative on the top-level AOF
layout (`--shards 1`), where the rewrite previously errored internally
while the client was told it had started.

### Added — HYBRID FT.SEARCH `FILTER` push-down on both branches (PR #174)

`FT.SEARCH ... HYBRID` now accepts an optional `FILTER` clause that is
Expand Down
40 changes: 15 additions & 25 deletions src/admin/metrics_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1391,41 +1391,31 @@ pub fn spawn_moon_memory_publisher() {
/// Collect per-subsystem resident bytes and emit all 7
/// `moon_memory_bytes{kind=...}` series plus `moon_rss_bytes`.
///
/// Called every 15 s by `spawn_moon_memory_publisher`. Allocation-free in
/// the steady state — all label values are `&'static str`.
/// Called every 15 s by `spawn_moon_memory_publisher`. Lock-free: reads from
/// per-shard published atomics (C5 / M4). Figures lag at most one 100ms tick.
fn update_moon_memory_bytes() {
use std::sync::atomic::Ordering;

let rss = get_rss_bytes() as usize;

let mut dashtable: usize = 0;
let mut hnsw: usize = 0;
let mut sealed: usize = 0;
#[cfg_attr(not(feature = "graph"), allow(unused_mut))]
let sealed: usize = 0; // combined into hnsw from vector atomic (C5)
let mut csr: usize = 0;
let wal: usize = 0; // WalWriterV3 is stack-owned; not reachable here
let mut backlog: usize = 0;

if let Some(shard_dbs) = get_global_shard_databases() {
let num_shards = shard_dbs.num_shards();
for shard_id in 0..num_shards {
// Database + DashTable (DB 0 — the hot database)
let db_guard = shard_dbs.read_db(shard_id, 0);
dashtable += db_guard.resident_bytes();
dashtable += db_guard.data().resident_bytes();
drop(db_guard);

// VectorStore: (mutable/hnsw, immutable/sealed)
let vs = shard_dbs.vector_store(shard_id);
let (m, i) = vs.resident_bytes();
hnsw += m;
sealed += i;
drop(vs);

// GraphStore (CSR)
#[cfg(feature = "graph")]
{
let gs = shard_dbs.graph_store_read(shard_id);
csr += gs.resident_bytes();
}
// KV memory: sum of per-shard published atomics. Lock-free.
// C5 / M4: `read_memory_sum()` replaces per-shard `read_db(…)` locks.
dashtable = shard_dbs.read_memory_sum();

// Store memory: sum published per-shard vector/graph atomics.
// Values are refreshed by each shard's 100ms tick (publish_store_memory).
for mem in shard_dbs.store_memory_per_shard.iter() {
hnsw += mem.vector.load(Ordering::Relaxed);
// graph is cfg-gated at publish time; the atomic is always present.
csr += mem.graph.load(Ordering::Relaxed);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/command/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ mod tests {
// Wrap as a TopLevel pool to match the post-2e-β helper signature.
let (tx, _rx) = crate::runtime::channel::mpsc_bounded::<AofMessage>(1);
let pool = AofWriterPool::top_level(tx);
let shard_dbs = crate::shard::shared_databases::ShardDatabases::new(vec![vec![
let (shard_dbs, _inits) = crate::shard::shared_databases::ShardDatabases::new(vec![vec![
crate::storage::Database::new(),
]]);

Expand Down Expand Up @@ -505,7 +505,7 @@ mod tests {
let _guard = GATE_TEST_LOCK.lock();
let (tx, _rx) = crate::runtime::channel::mpsc_bounded::<AofMessage>(1);
let pool = AofWriterPool::top_level(tx);
let shard_dbs = crate::shard::shared_databases::ShardDatabases::new(vec![vec![
let (shard_dbs, _inits) = crate::shard::shared_databases::ShardDatabases::new(vec![vec![
crate::storage::Database::new(),
]]);

Expand Down
51 changes: 24 additions & 27 deletions src/command/server_admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,37 +383,34 @@ fn memory_doctor() -> Frame {
let rss = crate::admin::metrics_setup::get_rss_bytes() as usize;
let vsz = get_vsz_bytes();

// ── Gather per-subsystem resident bytes ──────────────────────────────
let mut dashtable_bytes: usize = 0;
let mut hnsw_bytes: usize = 0;
let mut sealed_bytes: usize = 0;
#[cfg_attr(not(feature = "graph"), allow(unused_mut))]
let mut csr_bytes: usize = 0;
// ── Gather per-subsystem resident bytes (C5 / M4 — lock-free atomics) ──
// KV and store memory are read from published per-shard atomics. Figures
// lag at most one 100ms tick — acceptable for an on-demand diagnostic.
use std::sync::atomic::Ordering;
let dashtable_bytes: usize;
let hnsw_bytes: usize;
let sealed_bytes: usize = 0; // combined into hnsw_bytes from vector atomic
#[cfg_attr(not(feature = "graph"), allow(unused_variables))]
let csr_bytes: usize;
let wal_bytes: usize = 0;

if let Some(shard_dbs) = crate::admin::metrics_setup::get_global_shard_databases() {
let num_shards = shard_dbs.num_shards();
for shard_id in 0..num_shards {
// Database + DashTable (DB 0 only — the hot database)
let db_guard = shard_dbs.read_db(shard_id, 0);
dashtable_bytes += db_guard.resident_bytes();
dashtable_bytes += db_guard.data().resident_bytes();
drop(db_guard);

// VectorStore: (mutable/hnsw, immutable/sealed)
let vs = shard_dbs.vector_store(shard_id);
let (mutable, immutable) = vs.resident_bytes();
hnsw_bytes += mutable;
sealed_bytes += immutable;
drop(vs);

// GraphStore (CSR)
#[cfg(feature = "graph")]
{
let gs = shard_dbs.graph_store_read(shard_id);
csr_bytes += gs.resident_bytes();
}
// KV memory: sum of per-shard published atomics. Lock-free.
dashtable_bytes = shard_dbs.read_memory_sum();

// Store memory: sum published per-shard vector/graph atomics.
let mut vec_total = 0usize;
let mut csr_total = 0usize;
for mem in shard_dbs.store_memory_per_shard.iter() {
vec_total += mem.vector.load(Ordering::Relaxed);
csr_total += mem.graph.load(Ordering::Relaxed);
}
hnsw_bytes = vec_total;
csr_bytes = csr_total;
} else {
dashtable_bytes = 0;
hnsw_bytes = 0;
csr_bytes = 0;
}

// Replication backlog via global state (same pattern as INFO replication).
Expand Down
Loading
Loading