Skip to content

feat(monitor): distributed-NitroSketch coordinated update-sampling (prototype) - #480

Merged
zzylol merged 3 commits into
mainfrom
feat/distributed-nitrosketch
Jun 11, 2026
Merged

zzylol merged 3 commits into
mainfrom
feat/distributed-nitrosketch

Conversation

@zzylol

@zzylol zzylol commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

What

Prototype of distributed NitroSketch: extends NitroSketch's per-sketch update-sampling to a multi-edge setting, where the CDM coordinator allocates each edge a different sampling rate p_i to minimize total edge update CPU at a target merged accuracy — instead of one p everywhere.

Stacked on #458 (sub-window delta producer) — it shares countsketch.go. Base is feat/sub-window-delta-producer; retarget to main once #458 merges.

Contents

  • docs/distributed-nitrosketch-coordinated-sampling.md — the design: variance composition Var_merged = Σ f_i(1−p_i)/p_i, the KKT solution p_i ∝ √(f_i/rate_i), per-family applicability, coordinator hooks, the joint CPU/bandwidth budget with the sub-window emitter, and a skeptical assessment.
  • monitor/sampling_alloc.goAllocateSampleRates (the coordinator's allocation step, p_i ∝ √(f_i/rate_i) via bisection on the variance constraint) + UniformSampleRate (the per-edge-independent baseline). Reference impl to port to the Rust coordinator.
  • sketches/countsketch.goCountSketchWrapper.WithSampleP: geometric update-sampling at the wrapper (admit w.p. p, upweight by 1/p ⇒ unbiased estimate, no backend rescale needed).
  • monitor.Grant.SampleP — the wire field the coordinator populates and the edge applies via WithSampleP at EpochReset (orthogonal to LocalSlack, which governs emission/bandwidth).

Result (the coordination win, quantified)

On a skewed fleet (1 hot edge at 100k/win + 4 quiet at 1k/win, key spread evenly) the coordinated allocation saves ~62% edge update-work at EQUAL merged variance vs uniform p (hot edge p=0.056, quiet p=0.56). A flat-fleet test confirms ~0 win (honest — the win scales with the per-edge rate skew). Count-Sketch sampled at p=0.25 estimates within ~1% of true while touching counters ~¼ as often.

Scope (prototype)

The allocation algorithm + edge sampling capability + wire field are done and tested. The remaining integration (not in this PR): the coordinator computing p_i from per-edge rate reports and granting over gRPC (mirrors the existing slack path), and — if you prefer uniform-rescale over the wrapper's upweight-on-admit — a Count-Sketch backend 1/p rescale. Pairs with the backend CMS/HLL 1/p rescale fix (separate PR).

🤖 Generated with Claude Code

zzylol and others added 3 commits June 10, 2026 05:44
…rototype)

Extends NitroSketch's per-sketch update-sampling to a multi-edge setting: the
CDM coordinator allocates each edge a DIFFERENT sampling rate p_i to minimize
total edge update CPU at a target merged accuracy, instead of one p everywhere.

- docs/distributed-nitrosketch-coordinated-sampling.md — the design (variance
  composition Var_merged = Σ f_i(1−p_i)/p_i, KKT solution p_i ∝ √(f_i/rate_i),
  per-family applicability, coordinator hooks, joint CPU/bandwidth budget,
  honest assessment).
- monitor/sampling_alloc.go — AllocateSampleRates (the coordinator's allocation
  step, p_i ∝ √(f_i/rate_i) via bisection on the variance constraint) +
  UniformSampleRate (the per-edge-independent baseline). Reference impl to port
  to the Rust coordinator.
- monitor/sampling_alloc_test.go — on a skewed fleet (1 hot edge + quiet tail,
  key spread evenly) the coordinated allocation saves ~62% edge update work at
  EQUAL merged variance vs uniform p; flat-fleet test confirms ~0 win (honest).
- sketches/countsketch.go — CountSketchWrapper.WithSampleP: geometric
  update-sampling at the wrapper level (admit w.p. p, upweight by 1/p ⇒ unbiased
  estimate, no backend rescale needed). Test: sampled p=0.25 estimate within
  ~1% of true while touching counters ~1/4 as often.
- monitor.Grant.SampleP — the wire field the coordinator populates and the edge
  applies via WithSampleP at EpochReset (orthogonal to LocalSlack, which governs
  emission/bandwidth).

Prototype: the allocation algorithm + edge capability + wire field are done and
tested; the coordinator computing p_i from per-edge reports and granting over
gRPC (mirrors the existing slack path) is the remaining integration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(pN)) coupling, and the empirical in-collector CPU caveat

- Combined bound: when update-sampling and the CDM threshold run together at the
  edge, the value the CDM tests is an unbiased-but-noisy 1/p-rescaled estimate
  (sampling error ε_s ≈ √((1−p)/(pN))). For ε-approximation/freshness the bound
  holds additively (ε_sk + ε_s + ε_cdm, shrinking with N); for the slack-countdown
  alert the no-missed-crossing safety becomes (1−δ)-probabilistic and needs a
  confidence-margin inflation. Communication only holds if the threshold band
  absorbs the jitter — the coupling rule ε_cdm ≳ √((1−p)/(pN)) (don't sample so
  hard the noise exceeds the CDM tolerance; higher per-series N relaxes it).
- Empirical caveat: a multi-shape e2e shows the CPU win does NOT materialize in
  the OTel collector (edge CPU flat ±noise) while egress drops ~9% — the sketch-
  update loop is a small fraction vs OTLP decode + key extraction + allocation
  (allocation-bound, per the repo's jemalloc 2.2× finding). Reframe the objective
  as egress minimization unless a profile shows the counter loop dominates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…alloc)

A producer CPU profile found the dominant edge cost is CMSWrapper.ComputeDeltaAgainst
re-materializing the full d×w matrix every sub-window/flush emit: it
DeserializeCountMinSketchFromProtoBytes(prev) (allocating+zeroing a matrix) +
Snapshot() (re-serialize) just to diff. Under the PWR contract prev is always the
empty base, so this reconstructs a zero matrix every emit to subtract zero.

Fast path: detect the empty base cheaply (bytes.Equal vs a memoized empty-base
envelope — exactly the bytes the SnapshotCache hands back each window) and call
sketchlib's new ComputeDeltaAgainstEmpty (ProjectASAP/sketchlib-go#67) instead of
the deserialize+diff. The size-clamp (Snapshot) and the non-empty (real prev) path
are unchanged, so the emitted bytes are byte-identical (parity test asserts the
fast-path payload decodes to exactly-equal Count-Min state). WithSampleP
invalidates the memo.

This removes the per-emit CMS cost the profiler flagged (~32% of edge CPU under
sub-window) — i.e. it's what makes the threshold-driven sub-window producer and
the coordinated-sampling emit-gating actually cheap. Requires sketchlib-go#67
(go.mod replace resolves it locally).

Bench (5x2048, delta-against-empty): 837us->306us, 978KB->157KB, 65->21 allocs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zzylol
zzylol changed the base branch from feat/sub-window-delta-producer to main June 11, 2026 17:24
@zzylol
zzylol merged commit e2c4241 into main Jun 11, 2026
@zzylol
zzylol deleted the feat/distributed-nitrosketch branch June 11, 2026 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant