Skip to content

feat(spans): Flush oversized segments in chunks#111820

Merged
lvthanh03 merged 5 commits intomasterfrom
tony/not-drop-spans
Apr 7, 2026
Merged

feat(spans): Flush oversized segments in chunks#111820
lvthanh03 merged 5 commits intomasterfrom
tony/not-drop-spans

Conversation

@lvthanh03
Copy link
Copy Markdown
Member

Refs STREAM-826

  • Adds an option spans.buffer.flush-oversized-segments to allow for flushing entire segments that exceed spans.buffer.max-segments-bytes bytes.
  • Adds the _chunk_segment() function in the flusher that splits span payloads into chunks, each chunk under max-segment-bytes

When the option is enabled, the flusher produces one Kafka message per chunk instead of one per segment.

@linear-code
Copy link
Copy Markdown

linear-code bot commented Mar 30, 2026

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Mar 30, 2026
Comment thread src/sentry/spans/consumers/process/flusher.py Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread src/sentry/spans/consumers/process/flusher.py Outdated
Comment thread src/sentry/spans/consumers/process/flusher.py Outdated
Copy link
Copy Markdown
Member

@untitaker untitaker left a comment

Choose a reason for hiding this comment

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

We also drop spans during ingestion into the redis lua script, those spans are still dropped. Instead we should stop merging sets and flush them out individually. This will make it unnecessary to do the same in the flusher, and preserves the invariant that there is only one root span per flushed chunk. Right now it's possible that multiple unrelated spans (i.e. distinct trees) are flushed in a single chunk and it's not clear to me how the segments consumer handles that (or that it's supposed to)

@fpacifici
Copy link
Copy Markdown
Contributor

A few questions on the requirements we are working towards:

  • Did the product team review those use cases ?
  • When we reach the max segment size, are we expected to flush the following part of the segment through the same policy (accumulate till we reach max size) or should we flush each span individually ? If we send individual spans, does @mjq know and does the segment consumer require changes ?
  • Let's say we are building a segment that contains the root span, we reach the max size and flush it. What is the expected behavior of the following spans ? They would not be able to merge into a single segment (the root span is gone), so at best they will be broken down into smaller segments. IS product on board with this ?

Copy link
Copy Markdown
Contributor

@fpacifici fpacifici left a comment

Choose a reason for hiding this comment

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

Please update the README as this is changing the flush policy.

Comment on lines +375 to +388

if flush_oversized_segments:
chunks = _chunk_segment(spans)
else:
chunks = [spans]

for chunk in chunks:
kafka_payload = KafkaPayload(None, orjson.dumps({"spans": chunk}), [])
metrics.timing(
"spans.buffer.segment_size_bytes",
len(kafka_payload.value),
tags={"shard": shard_tag},
)
produce(flushed_segment.project_id, kafka_payload, len(chunk))
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.

I don't think this logic belongs here.
The design of this system is such that buffer.py contains all the business logic to manage segments and flushing.
This is the consumer code, which takes care of run the business logic into a kafka consumer. If (unlikely) tomorrow, we started flushing into ObjectStore we would touch this but the business logic would not change.

Deciding to chunk the segment is a business logic concern not a kafka consumer concern. This logic should go into the buffer.py file.
Is there any specific reason you added it here ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah it makes sense to have business logic exclusively in buffer.py, I added it in the flusher since it seemed straightforward to me at the time that, we want to chunk the spans before producing, so let's chunk the spans where we produce them to Kafka.

I've pushed a fix for this by adding a to_messages method to the FlushedSegment dataclass.

@mjq
Copy link
Copy Markdown
Member

mjq commented Apr 1, 2026

@fpacifici

A few questions on the requirements we are working towards:

  • Did the product team review those use cases ?

Sorry, what's "those" referring to?

In general the product understanding is in the proposal on Notion. The limits listed there are fine with product. Any other limitations would have to be discussed.

  • When we reach the max segment size, are we expected to flush the following part of the segment through the same policy (accumulate till we reach max size) or should we flush each span individually ? If we send individual spans, does @mjq know and does the segment consumer require changes ?

Once a segment reaches its max size, we'd like spans in that segment to start skipping enrichment entirely, going straight to snuba-items/EAP. (Instead of the current behaviour, where they're dropped.) Depending on how we structure this that might need changes to segment consumer, just so it knows to emit certain spans as trace items immediately.

  • Let's say we are building a segment that contains the root span, we reach the max size and flush it. What is the expected behavior of the following spans ? They would not be able to merge into a single segment (the root span is gone), so at best they will be broken down into smaller segments. IS product on board with this ?

Yes. Their segment ID will already be present so it won't be broken down into different segments, but the enrichment will be incorrect. Product accepts that. (It's in the doc too).

If we really wanted to protect ourselves agains that we could query EAP to see if spans in that segment have already been ingested, but that has its own scalability concerns.

@lvthanh03 lvthanh03 merged commit e38736c into master Apr 7, 2026
80 checks passed
@lvthanh03 lvthanh03 deleted the tony/not-drop-spans branch April 7, 2026 17:59
lvthanh03 added a commit that referenced this pull request Apr 8, 2026
…ue flag (#112024)

Going off of product requirements from
#111820 (comment),
we want to have a way to signal the process-segments consumer to skip
enrichment once a segment hits the size limit.

When this happens, the process-spans consumer would produce the segment
in chunks to the buffered-segments topic, where each message contains
one chunk along with the flag `skip_enrichment=True`.
george-sentry pushed a commit that referenced this pull request Apr 9, 2026
Refs STREAM-826

- Adds an option `spans.buffer.flush-oversized-segments` to allow for
flushing entire segments that exceed `spans.buffer.max-segments-bytes`
bytes.
- Adds the `_chunk_segment()` function in the flusher that splits span
payloads into chunks, each chunk under `max-segment-bytes`

When the option is enabled, the flusher produces one Kafka message per
chunk instead of one per segment.
george-sentry pushed a commit that referenced this pull request Apr 9, 2026
…ue flag (#112024)

Going off of product requirements from
#111820 (comment),
we want to have a way to signal the process-segments consumer to skip
enrichment once a segment hits the size limit.

When this happens, the process-spans consumer would produce the segment
in chunks to the buffered-segments topic, where each message contains
one chunk along with the flag `skip_enrichment=True`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants