wu-sheng opened a new issue, #13962:
URL: https://github.com/apache/skywalking/issues/13962

   > Code paths in this issue are in `apache/skywalking-banyandb`. Umbrella: 
#13634.
   
   Found while reviewing apache/skywalking-banyandb#1234, which adds the first 
first-party samplers that implement a whole-trace keep policy ("keep if any 
span/segment has the error flag, or if the whole-trace duration is too long").
   
   ## Problem
   
   `pkg/pipeline/sdk/sdk.go:137-138` promises the plugin:
   
   > `Traces` holds one block per trace_id. The verdict's keep-mask is aligned 
to this slice.
   
   and documents `Decide` as a per-trace-id verdict. A tail sampler written 
against that contract is entitled to treat a `TraceBlock` as the complete trace.
   
   The merge path does not honor it. When a trace's spans exceed 
`maxUncompressedSpanSize` (2 MiB, `banyand/trace/trace.go:45`), `mergeBlocks` 
stages that trace as **several** `stagedTrace` entries 
(`banyand/trace/merger.go:1012` and `:1040` — the 
`pendingBlock.block.spanSize() >= maxUncompressedSpanSize` disjunct fires with 
the same trace id).
   
   `flushStaged` then builds the Decide batch from **only the first** staged 
block per trace id and applies that single verdict to all of them:
   
   ```go
   // banyand/trace/merger.go:836-847
   seenEval := make(map[string]struct{})
   for i := range staged {
       if _, seen := seenEval[staged[i].traceID]; seen {
           continue                       // <-- later blocks of the same trace 
never reach Decide
       }
       seenEval[staged[i].traceID] = struct{}{}
       ...
   }
   ```
   
   So the plugin receives a **prefix** of the trace while the SDK tells it the 
block is the trace.
   
   ## Impact
   
   Any existential whole-trace predicate silently mis-fires on large traces:
   
   - an error flag on a segment that landed in the second block is invisible → 
the trace is dropped;
   - a duration envelope (`max(start+duration) − min(start)`) is computed over 
the prefix → underestimated → a slow trace is dropped.
   
   Traces above 2 MiB of span bodies are disproportionately the slow ones and 
the ones carrying errors, so this hits precisely the traces a tail sampler 
exists to keep.
   
   ## Suggested fix
   
   Union the same-trace staged blocks into one `TraceBlock` before `Decide` 
(concatenating the projected tag columns row-wise is enough — every predicate 
is existential over rows), keeping the per-block staging for the write path. 
Alternatively, if the split is to remain visible to plugins, the SDK contract 
must say so explicitly instead of promising one block per trace_id.
   
   Related: apache/skywalking-banyandb#1234.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: 
[email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to