|
| 1 | +--- |
| 2 | +title: Batch Processor |
| 3 | +--- |
| 4 | + |
| 5 | +<Alert level="warning"> |
| 6 | + 🚧 This document is work in progress. |
| 7 | +</Alert> |
| 8 | + |
| 9 | +<Alert> |
| 10 | + This document uses key words such as "MUST", "SHOULD", and "MAY" as defined in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) to indicate requirement levels. |
| 11 | +</Alert> |
| 12 | + |
| 13 | +When an SDK implements span streaming or logs, it MUST batch multiple spans and logs into envelopes to reduce the number of HTTP. SDKs MUST implement a BatchProcessor to achieve this. The BatchProcessor keeps finished spans and logs in memory and batches them together in envelopes. It uses a combination of timeout and [weight](#weight) to decide when to batch its spans and logs into an envelope and send it to Sentry. |
| 14 | +The SDK SHOULD use the BatchProcessor in the client because the transport SHOULD NOT be aware of spans or logs. The SDK MAY deviate from this approach. The SDK MUST call filtering and sampling before adding spans or logs to the BatchProcessor. This concept is similar to [OpenTelemetry's Batch Processors](https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/batchprocessor/README.md). |
| 15 | + |
| 16 | +The BatchProcessor starts a timeout of `x` seconds when the SDK adds the first span or log. When the timeout exceeds, the BatchProcessor sends all spans or logs no matter how many items it contains. The BatchProcessor also sends all items after the SDK captures spans or logs with weight more than `y`. When the BatchProcessor sends all spans or logs, it resets its timeout and removes all spans and logs in the BatchProcessor. When a span and its children have more weight than the max BatchProcessor weight `y`, the BatchProcessor MUST send the spans or logs together in one envelope directly to Sentry. |
| 17 | + |
| 18 | +The specification is written in the [Gherkin syntax](https://cucumber.io/docs/gherkin/reference/) and uses `x = 10` seconds for the timeout and `y = 1024 * 1024` for the maximum batch byte size in the BatchProcessor. SDKs MAY use different values for `x` and `y` depending on their needs. If the timeout is set to `0`, then the SDK sends every span and log immediately. Initially, we don't plan adding options for these variables, but we can make them configurable if required in the future, similar to the option `maxCacheItems`. The specification uses spans as an example, but the same applies to logs or any other future telemetry data. |
| 19 | + |
| 20 | + |
| 21 | +```Gherkin |
| 22 | +Scenario: No spans in BatchProcessor 1 span added |
| 23 | + Given no spans in the BatchProcessor |
| 24 | + When the SDK adds 1 span |
| 25 | + Then the SDK adds this span to the BatchProcessor |
| 26 | + And starts a timeout of 10 seconds |
| 27 | + And doesn't send the span to Sentry |
| 28 | +
|
| 29 | +Scenario: Span added before timeout exceeds |
| 30 | + Given 1 span in the BatchProcessor |
| 31 | + Given 9.9 seconds pass |
| 32 | + When the SDK adds 1 span |
| 33 | + Then the SDK adds this span to the BatchProcessor |
| 34 | + And doesn't reset the timeout |
| 35 | + And doesn't send the spans in the BatchProcessor to Sentry |
| 36 | +
|
| 37 | +Scenario: Spans with size of y - 1 added, timeout exceeds |
| 38 | + Given spans with size of y - 1 in the BatchProcessor |
| 39 | + When the timeout exceeds |
| 40 | + Then the SDK adds all the spans to one envelope |
| 41 | + And sends them to Sentry |
| 42 | + And resets the timeout |
| 43 | + And clears the BatchProcessor |
| 44 | +
|
| 45 | +Scenario: Spans with size of y added within 9.9 seconds |
| 46 | + Given no spans in the BatchProcessor |
| 47 | + When the SDK adds spans with a weight of y within 9.9 seconds |
| 48 | + Then the SDK puts all spans into one envelope |
| 49 | + And sends the envelope to Sentry |
| 50 | + And resets the timeout |
| 51 | + And clears the BatchProcessor |
| 52 | +
|
| 53 | +Scenario: 1 span added app crashes |
| 54 | + Given 1 span in the SpansAggregator |
| 55 | + When the SDK detects a crash |
| 56 | + Then the SDK does nothing with the BatchProcessor |
| 57 | + And loses the spans in the BatchProcessor |
| 58 | +
|
| 59 | +Scenario: Unfinished spans |
| 60 | + Given no span is in the SpansAggregator |
| 61 | + When the SDK starts a span but doesn't finish it |
| 62 | + Then the SpansAggregator is empty |
| 63 | +
|
| 64 | +Scenario: Spans in SpansAggregator, span with children |
| 65 | + Given spans with a size of y - 1 in the BatchProcessor |
| 66 | + When the SDK finishes a span with one child |
| 67 | + Then the SDK puts the spans with a size of y - 1 already in the BatchProcessor into an envelope |
| 68 | + And sends the envelope to Sentry. |
| 69 | + And stores the span with its child into the BatchProcessor |
| 70 | + And resets the timeout |
| 71 | +
|
| 72 | +Scenario: Span with more children than max BatchProcessor weight |
| 73 | + Given one span A is in the BatchProcessor |
| 74 | + When the SDK starts a span B |
| 75 | + And starts child spans with a size of y for span B |
| 76 | + When the SDK finishes the span B and all it's children |
| 77 | + Then the SDK directly puts all spans of span B into one envelope |
| 78 | + And sends the envelope to Sentry. |
| 79 | + And doesn't store the spans of span B in the BatchProcessor |
| 80 | + And keeps the existing span A in the BatchProcessor |
| 81 | + And doesn't reset the timeout |
| 82 | +
|
| 83 | +Scenario: Timeout set to 0 span without children |
| 84 | + Given the timeout is set to 0 |
| 85 | + When the SDK finishes one span without any children |
| 86 | + Then the SDK puts the span into one one envelope |
| 87 | + And sends the envelope to Sentry. |
| 88 | +
|
| 89 | +Scenario: Timeout set to 0 span with children |
| 90 | + Given the timeout is set to 0 |
| 91 | + When the SDK finishes one span with children of a weight of 100 |
| 92 | + Then the SDK puts the span with the children into one envelope |
| 93 | + And sends the envelope to Sentry. |
| 94 | +
|
| 95 | +Scenario: Timeout set to 0 spans without children |
| 96 | + Given the timeout is set to 0 |
| 97 | + When the SDK finishes two spans without any children |
| 98 | + Then the SDK puts every span into one envelope |
| 99 | + And sends both envelopes to Sentry. |
| 100 | +
|
| 101 | +``` |
| 102 | + |
| 103 | +## Weight |
| 104 | + |
| 105 | +The SDK MUST implement a way to calculate the weight of a span or a log to manage the BatchProcessor's memory footprint. Depending on the serialization strategy, the SDK MAY either serialize the span or log into bytes and count these or serialize the span or log and recursively count the number of elements in the dictionary. Every key in a dictionary and every element in an array add a weight of one. For a detailed explanation of how to count the weight, see the example below. As serialization is expensive, the BatchProcessor SHOULD keep track of the serialized spans and logs and directly pass them to the envelope item to avoid serializing multiple times. |
| 106 | + |
| 107 | +```JSON |
| 108 | +{ |
| 109 | + // All simple properties count as 1 so in total 12 |
| 110 | + "timestamp": 1705031078.623853, |
| 111 | + "start_timestamp": 1705031078.337715, |
| 112 | + "description": "ExtraViewController full display", |
| 113 | + "op": "ui.load.full_display", |
| 114 | + "span_id": "794d0cba0ac64235", |
| 115 | + "parent_span_id": "45054abc6ded413a", |
| 116 | + "trace_id": "65880cfc084f4bd5ab3abc7d598b3c14", |
| 117 | + "status": "ok", |
| 118 | + "origin": "manual.ui.time_to_display", |
| 119 | + "hash": "a925395473cfe97d", |
| 120 | + "sampled": true, |
| 121 | + "type": "trace", |
| 122 | + |
| 123 | + // The data object has 5 simple properties, which count as 5 |
| 124 | + // and one list with 3 elements counting as 3 |
| 125 | + "data": { |
| 126 | + "frames.frozen": 0, |
| 127 | + "frames.slow": 1, |
| 128 | + "frames.total": 1, |
| 129 | + "thread.id": 259, |
| 130 | + "thread.name": "main", |
| 131 | + "list" : [1, 2, 3] |
| 132 | + }, |
| 133 | + |
| 134 | + // Tags count as 2 |
| 135 | + "sentry_tags": { |
| 136 | + "environment": "ui-tests", |
| 137 | + "main_thread": "true", |
| 138 | + }, |
| 139 | + |
| 140 | + // The weight is |
| 141 | + // 12 (simple properties) |
| 142 | + // 8 (data) |
| 143 | + // 2 (tags) |
| 144 | + // = 22 |
| 145 | +} |
| 146 | +``` |
0 commit comments