Skip to content

[SVLS-8169] feat: Support span dedup#939

Merged
lym953 merged 14 commits into
mainfrom
yiming.luo/span-dedup
Nov 25, 2025
Merged

[SVLS-8169] feat: Support span dedup#939
lym953 merged 14 commits into
mainfrom
yiming.luo/span-dedup

Conversation

@lym953

@lym953 lym953 commented Nov 21, 2025

Copy link
Copy Markdown
Contributor

TL;DR

  • Support deduping spans by span_id
  • which fixes trace stats overcount issue for Node.js

Problem

Node.js tracer sometimes sends duplicate traces to the extension, causing over-count of trace stats. In our tests, the over-count is usually 1–4 for 50, 500 or 5000 invocations. This only happens when the "default" flushing strategy (which uses the "continuous" strategy) is used, and doesn't happen when the "end" strategy is used.

Cause of problem

I think it's similar to a known problem of continuous flushing.

  • The known problem is, when Lambda runtime is frozen, the connection between extension and DD endpoint can time out, causing data flush failure.
  • In the trace stats case, the problem is, when Lambda runtime is frozen, the connection between tracer and extension can time out. Extension receives the request from tracer, then freezes before sending a response, causing the tracer's request to time out, which makes the tracer resend the trace. This doesn't happen with the END flush strategy because in that case, after the Lambda handler finishes, extension still needs to flush the data and doesn't freeze so fast, and it has enough time to respond to tracer.

Testing

Steps

  • Build a test extension layer
  • Run e2e tests, including:
    • Install it on various Lambda functions
    • Invoke these functions with various traffic pattern
    • Check the trace stats result

Result

Before:

  • Over-count happens for (1) Node.js runtime + (2) "Sampling" test, which uses the default flush strategy
  • (vs expected 50)
Screenshot 2025-11-20 at 9 30 00 PM

After:

  • No over-count (vs expected 5000)
Screenshot 2025-11-20 at 9 25 32 PM

Options considered

  1. At most once: Disable retry in tracer, at least for Lambda.
  2. At least once
    • 2.1 Do nothing. Call out this as a known limitation.
    • 2.2 Treat tracer as VIP. Before calling /next, make sure tracer's requests have been responded. This may cause regression on invocation duration, especially when volume is high.
  3. Exactly once: Implement dedup in extension, by trace_id.

This PR chooses 3 because it's the easiest.

Notes

Thanks @astuyve @rochdev @purple4reina for discussion.
Thanks Cursor for writing most of the code.
The under-count issue will be addressed separately.

Related issue: #688

@lym953
lym953 force-pushed the yiming.luo/span-dedup branch from 2071e2f to d1dff83 Compare November 21, 2025 02:03
@lym953
lym953 marked this pull request as ready for review November 21, 2025 02:34
@lym953
lym953 requested a review from a team as a code owner November 21, 2025 02:34
Comment thread bottlecap/src/traces/dedup.rs Outdated

/// A deduplicator that maintains a fixed-size cache of recently seen IDs.
/// When the capacity is reached, the oldest ID is evicted (FIFO).
pub struct Deduper {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this deduper is based on span id. Is it guaranteed to be globally unique? ie. what if between 2 traces they happen to have the same span id?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I didn't know two traces can have the same span id. Added trace id to dedup key.

Comment thread bottlecap/src/bin/bottlecap/main.rs Outdated

let stats_processor = Arc::new(stats_processor::ServerlessStatsProcessor {});

// Deduper

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 1-line comments that just describe a single line of code feel a bit noisy. Can we remove them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

/// Creates a new dedup service with the default capacity (50).
#[must_use]
pub fn new() -> (Self, DedupHandle) {
Self::with_capacity(50)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need to let this be configurable, just always use the same size. Also, maybe worth bumping to 100? (non-blocking comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to keep it for tests so I can set capacity to 3 to test eviction. But I'll remove pub. Let me know if you have a better alternative.
I don't have a strong idea on capacity. Will bump to 100.

Base automatically changed from yiming.luo/upgrade-libdd to main November 25, 2025 18:36
@lym953

lym953 commented Nov 25, 2025

Copy link
Copy Markdown
Contributor Author

Tianning's comment has been addressed. Bypass requirements to merge this PR.

@lym953
lym953 merged commit 5f2e83a into main Nov 25, 2025
39 checks passed
@lym953
lym953 deleted the yiming.luo/span-dedup branch November 25, 2025 20:50
@lym953 lym953 changed the title [SVLS-6907] feat: Support span dedup [SVLS-8169] feat: Support span dedup Dec 12, 2025
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.

3 participants