Skip to content

fix(asapedge): value-weighted top-k on the agent-side modified-sketch path (Mode 1) - #499

Merged
zzylol merged 1 commit into
mainfrom
fix/mode1-value-weighted-topk
Jun 14, 2026
Merged

zzylol merged 1 commit into
mainfrom
fix/mode1-value-weighted-topk

Conversation

@zzylol

@zzylol zzylol commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the remaining gap from PR ProjectASAP/ASAPQuery-backend#372: the OTLP modified-sketch path (Mode 1) — where the edge agent pre-builds the heap-bearing CountSketch (emit_heap) in sketchlib-go and ships it via modified OTLP — built its top-k heap by frequency (+1 per datapoint), not by summed value.

PRs ProjectASAP/ASAPQuery-backend#375 and #372 fixed value-weighted top-k on (a) the read-side reducer and (b) the raw-input precompute ingest path, but left the agent-side build by frequency. So a topk(metric) answered by a Mode 1 heap ranked the most frequent item, not the item with the largest total value.

Root cause

The heap-bearing CountSketch observe path (obsKindKeyedItem in warm_sketch.go) fed Float: 1 per sample. sketchlib-go's CountSketch.UpdateString(key, w) feeds that weight into BOTH the count matrix and the Space-Saving candidate tracker, and the emitted wire heap derives each item's value from the CS matrix estimate (buildWireHeapEstimateStringCount). The +1 therefore made the matrix — and thus the heap — a frequency count.

Fix

Make the heap weight config-driven, mirroring the backend's TopkWeight::Value (default) / Count enum (PR #372):

  • weight_mode: "" | "value" | "sum" (DEFAULT) — add the datapoint VALUE per item, so the heap ranks by Σ value (top-k <item_label> by total <metric>).
  • weight_mode: "count" | "frequency" | "freq"+1 per event (heavy-hitter frequency top-k), explicit opt-in.

weight_mode is validated/normalised at boot: unknown value rejected; rejected on any non-emit_heap family.

Scope: no proto / backend change needed

UpdateString and the Space-Saving tracker already honour an arbitrary weight, and the backend decodes the heap value verbatim (CountMinSketchWithHeap::from_msgpack) and the read-side reducer already sorts the heap descending by that value (PR #375). So a value-built heap round-trips through the same MSGPACK frame and ranks by value-sum end to end — no sketchlib-go, wire, proto, or backend-decode change required.

Tests (all green)

go test ./... in processor/asapedgeprocessor:

  • TestWeightMode_DefaultValueWeightedTopK — adversarial stream where value-rank ≠ count-rank (/big: 10×50=500, /mid: 30×5=150, /small: 100×1=100). Default heap ranks /big > /mid > /small (recall@k = 1.0 for k=1..3); the highest-COUNT item (/small) is NOT first.
  • TestWeightMode_CountStillRanksByFrequencycount mode ranks /small > /mid > /big (by occurrence).
  • TestWeightMode_AliasesAndValidation — alias normalisation + unknown-value and misplaced-on-wrong-family rejection.
  • Existing TestEmitHeap_* stay green.

🤖 Generated with Claude Code

… path (Mode 1)

The OTLP modified-sketch path (Mode 1) — where the edge agent pre-builds the
heap-bearing CountSketch (emit_heap) and ships it via modified OTLP — built its
top-k heap by FREQUENCY (+1 per datapoint), not by summed value. PRs #375/#372
fixed value-weighted top-k on the read-side reducer and the raw-input precompute
path, but left this agent-side build by frequency: a `topk(metric)` answered by
a Mode 1 heap ranked the most FREQUENT item, not the item with the largest
total value.

The heap-bearing CountSketch observe path (obsKindKeyedItem in warm_sketch.go)
fed `Float: 1` per sample. sketchlib-go's CountSketch.UpdateString(key, w) feeds
that weight into BOTH the count matrix and the Space-Saving candidate tracker,
and the emitted wire heap derives each item's value from the CS matrix estimate
(buildWireHeap → EstimateStringCount). So the +1 made the matrix — and thus the
heap — a frequency count.

Make the heap weight config-driven, mirroring the backend's TopkWeight::Value
(default) / Count enum (PR #372):

  * weight_mode: "" / "value" / "sum"  (DEFAULT) — add the datapoint VALUE per
    item, so the heap ranks by Σ value (top-k <item_label> by total <metric>).
  * weight_mode: "count" / "frequency" / "freq"  — +1 per event (heavy-hitter
    frequency top-k), explicit opt-in.

No sketchlib-go / wire / proto / backend-decode change is needed: UpdateString
and the Space-Saving tracker already honour an arbitrary weight, and the backend
decodes the heap value verbatim (CountMinSketchWithHeap::from_msgpack) and the
read-side reducer already sorts the heap descending by that value (PR #375), so
a value-built heap round-trips and ranks by value-sum end to end.

weight_mode is validated/normalised at boot (unknown value rejected; rejected on
any non-emit_heap family). Tests: an adversarial stream where value-rank and
count-rank deliberately disagree asserts the default heap is top-k-by-value
(recall 1.0) and `count` mode still ranks by frequency; plus alias/validation
coverage. Existing emit_heap tests stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 98d4847 into main Jun 14, 2026
@zzylol
zzylol deleted the fix/mode1-value-weighted-topk branch July 17, 2026 20:08
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