feat(docs): document OpenTelemetry limits on large AI events page - #19799
Conversation
Add an OpenTelemetry section to the large AI events page: the 4MB per-export and 8MB per-event caps, the silent drop that surfaces only as a MessageSizeTooLarge ingestion warning, the collector batch processor workaround, and cross-links between the large events page and the OpenTelemetry install page. Generated-By: PostHog Desktop Task-Id: 26585af1-7b08-47a1-b621-5f5bf08cccc0
🦔 PostHog Review reviewed this pull requestFound 1 must fix, 2 should fix, 0 consider. Published 3 findings (view the review). |
Deploy preview
|
|
Vale prose linter → found 0 errors, 9 warnings, 0 suggestions in your markdown Full report → Copy the linter results into an LLM to batch-fix issues. Linter being weird? Update the rules!
|
| Line | Severity | Message | Rule |
|---|---|---|---|
| 4:15 | warning | 'opentelemetry' is a possible misspelling. | PostHogBase.Spelling |
contents/docs/ai-observability/large-events.mdx — 0 errors, 8 warnings, 0 suggestions
| Line | Severity | Message | Rule |
|---|---|---|---|
| 9:54 | warning | Use 'PostHog' instead of 'posthog'. | Vale.Terms |
| 9:122 | warning | Use 'PostHog' instead of 'posthog'. | Vale.Terms |
| 45:137 | warning | Use the Oxford comma before 'and' or 'or' in a list of three or more items. | PostHogBase.OxfordComma |
| 45:180 | warning | Use the Oxford comma before 'and' or 'or' in a list of three or more items. | PostHogBase.OxfordComma |
| 91:59 | warning | 'awaitable' is a possible misspelling. | PostHogBase.Spelling |
| 107:4 | warning | 'Sending over OpenTelemetry' heading should be in sentence case, and product names should be capitalized. | PostHogBase.SentenceCase |
| 117:102 | warning | 'gzipped' is a possible misspelling. | PostHogBase.Spelling |
| 117:179 | warning | Capitalize 'Logs' for PostHog's product. Use 'logs' for the general industry concept. | PostHogBase.ProductNames |
|
PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
| - **4MB per export.** PostHog accepts an OTLP export body up to 4MB. This cap is lower than the per-event ceiling, so a batch of spans can exceed it before any single span does. | ||
| - **8MB per event.** Each span becomes one AI event, held to the same 8MB ceiling as the other paths. | ||
|
|
||
| An export or span over these limits is dropped, but the request still returns success. A failure response would only make your collector retry the same oversize data, so PostHog accepts the request and discards what does not fit. The only signal is a `MessageSizeTooLarge` [ingestion warning](/docs/data/ingestion-warnings) – your application gets no error, and the data is lost silently. |
There was a problem hiding this comment.
The 4 MiB export limit does not return success
Why we think it's a valid issue
- Checked: The OTLP handler
rust/capture/src/otel/mod.rsin PostHog/posthog, plus the body extractor, the gzip decompressor, and theCaptureErrorto HTTP status map. - Found:
otel/mod.rs:36setsOTEL_BODY_SIZE = 4 * 1024 * 1024.otel/mod.rs:77-88passes it toextract_body_with_timeoutand returnsErr(e.into_response())on failure.extractors.rs:176-193raisesCaptureError::EventTooBigwhen the body passes the limit, andapi.rs:170-172mapsEventTooBigtoStatusCode::PAYLOAD_TOO_LARGE. A body over 4MB therefore gets HTTP 413, not success. - Found: The gzip case ends the same way.
otel/ingestion.rs:61-62callsdecompress_gzip_to_bytes(body, body_limit)withOTEL_BODY_SIZE, andpayload/decompression.rs:54-64raisesEventTooBigwhen the decompressed size passes the limit, which is again 413. - Found: Only the per-event ceiling behaves as the page describes.
otel/mod.rs:298-310usesretainto shed spans overstate.ai_max_event_bytes, emitsemit_span_too_big_warning, and lets the handler continue toOk(Json(json!({})))at line 361. The comment atotel/mod.rs:286-297states this divergence directly: "Shed spans past the deployment's per-event ceiling rather than refusing the export." - Found: No
MessageSizeTooLargewarning comes from the 4MB rejection. The body-read failure path atotel/mod.rs:85-88calls onlyreport_internal_error_metrics, with noingestion_warning_emitter. The gzip path atotel/mod.rs:157-168emitsemit_otel_parse_warning, which is a parse warning and notMessageSizeTooLarge. - Impact: Three places state the wrong failure mode:
large-events.mdx:120("An export or span over these limits is dropped, but the request still returns success"),large-events.mdx:128("oversize data is dropped without an application error"), andinstallation/opentelemetry.mdx:59("Oversize data is dropped without an application error"). A sender that passes 4MB does get an error and must handle the 413. The page tells that sender the loss is silent and directs them to hunt for aMessageSizeTooLargewarning that the 4MB path never writes. The page also gives the same advice for both limits, so the reader cannot tell which limit produces which behavior. The silent-loss claim, which is the reason the section exists, holds for the 8MB per-span ceiling alone.
Issue description
The OTLP handler returns HTTP 413 when the request body exceeds 4 MiB. It also returns 413 when gzip expands past 4 MiB. Only a converted event over 8 MiB is dropped while the export returns 200. The initial body-size rejection also cannot create an ingestion warning.
Suggested fix
Describe the cases separately. State that a body over 4 MiB rejects the complete export with HTTP 413. State that an event over 8 MiB drops only that span, returns 200, and creates MessageSizeTooLarge. Update the installation callout and the Limits note with the same distinction.
Prompt to fix with AI (copy-paste)
## Context
@contents/docs/ai-observability/large-events.mdx#L120
<issue_description>
The OTLP handler returns HTTP 413 when the request body exceeds 4 MiB. It also returns 413 when gzip expands past 4 MiB. Only a converted event over 8 MiB is dropped while the export returns 200. The initial body-size rejection also cannot create an ingestion warning.
</issue_description>
<issue_validation>
- **Checked:** The OTLP handler `rust/capture/src/otel/mod.rs` in PostHog/posthog, plus the body extractor, the gzip decompressor, and the `CaptureError` to HTTP status map.
- **Found:** `otel/mod.rs:36` sets `OTEL_BODY_SIZE = 4 * 1024 * 1024`. `otel/mod.rs:77-88` passes it to `extract_body_with_timeout` and returns `Err(e.into_response())` on failure. `extractors.rs:176-193` raises `CaptureError::EventTooBig` when the body passes the limit, and `api.rs:170-172` maps `EventTooBig` to `StatusCode::PAYLOAD_TOO_LARGE`. A body over 4MB therefore gets HTTP 413, not success.
- **Found:** The gzip case ends the same way. `otel/ingestion.rs:61-62` calls `decompress_gzip_to_bytes(body, body_limit)` with `OTEL_BODY_SIZE`, and `payload/decompression.rs:54-64` raises `EventTooBig` when the decompressed size passes the limit, which is again 413.
- **Found:** Only the per-event ceiling behaves as the page describes. `otel/mod.rs:298-310` uses `retain` to shed spans over `state.ai_max_event_bytes`, emits `emit_span_too_big_warning`, and lets the handler continue to `Ok(Json(json!({})))` at line 361. The comment at `otel/mod.rs:286-297` states this divergence directly: "Shed spans past the deployment's per-event ceiling rather than refusing the export."
- **Found:** No `MessageSizeTooLarge` warning comes from the 4MB rejection. The body-read failure path at `otel/mod.rs:85-88` calls only `report_internal_error_metrics`, with no `ingestion_warning_emitter`. The gzip path at `otel/mod.rs:157-168` emits `emit_otel_parse_warning`, which is a parse warning and not `MessageSizeTooLarge`.
- **Impact:** Three places state the wrong failure mode: `large-events.mdx:120` ("An export or span over these limits is dropped, but the request still returns success"), `large-events.mdx:128` ("oversize data is dropped without an application error"), and `installation/opentelemetry.mdx:59` ("Oversize data is dropped without an application error"). A sender that passes 4MB does get an error and must handle the 413. The page tells that sender the loss is silent and directs them to hunt for a `MessageSizeTooLarge` warning that the 4MB path never writes. The page also gives the same advice for both limits, so the reader cannot tell which limit produces which behavior. The silent-loss claim, which is the reason the section exists, holds for the 8MB per-span ceiling alone.
</issue_validation>
## Task
Investigate the issue and solve it
<potential_solution>
Describe the cases separately. State that a body over 4 MiB rejects the complete export with HTTP 413. State that an event over 8 MiB drops only that span, returns 200, and creates `MessageSizeTooLarge`. Update the installation callout and the Limits note with the same distinction.
</potential_solution>
|
|
||
| An export or span over these limits is dropped, but the request still returns success. A failure response would only make your collector retry the same oversize data, so PostHog accepts the request and discards what does not fit. The only signal is a `MessageSizeTooLarge` [ingestion warning](/docs/data/ingestion-warnings) – your application gets no error, and the data is lost silently. | ||
|
|
||
| To stay under the 4MB export cap, add the [`batch` processor](https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/batchprocessor/README.md) to your collector and set a small `send_batch_max_size`, so it splits large batches into smaller exports. For a single span that is too large on its own, capture it with [`capture_ai`](#calling-capture_ai-directly) instead. |
There was a problem hiding this comment.
A small maximum batch size can make the Collector configuration invalid
Why we think it's a valid issue
- Checked: The upstream
batchprocessor that line 122 links to — itsConfig.Validate(), its default config, and its README — in open-telemetry/opentelemetry-collector. - Found:
processor/batchprocessor/factory.go:19setsdefaultSendBatchSize = uint32(8192), andcreateDefaultConfig()atfactory.go:39-45applies it. A user who does not namesend_batch_sizegets 8192. - Found:
processor/batchprocessor/config.go:53-56rejects the combination:if cfg.SendBatchMaxSize > 0 && cfg.SendBatchMaxSize < cfg.SendBatchSize { return errors.New("send_batch_max_size must be greater or equal to send_batch_size") }. The README states the same rule: "It must be greater than or equal tosend_batch_size." ComponentValidate()runs at service startup, so the collector refuses to start. - Found: The failure is the normal outcome of the advice, not an edge case. Line 122 tells the reader to "set a small
send_batch_max_size" and names no other setting. Every value below 8192 trips the check against the defaultsend_batch_size. "Small" is the whole point of the advice, so a reader who follows it literally breaks the config almost every time. - Found: A supporting weakness in the same sentence. The README defines both settings as a count of "spans, metric data points, or log records", not a count of bytes.
send_batch_max_sizetherefore bounds the span count per export and not the 4MB body the section is about. No fixed count keeps a sender under 4MB unless the reader knows the size of their spans. - Impact: The page adds an actionable collector instruction that stops the reader's telemetry pipeline from starting. The reader loses all trace export, not only the oversize part. The fix is small: show a snippet that sets both values and state the ordering rule.
- Impact: The failure is loud. The collector prints the exact error and names both settings, so a reader recovers fast. That is why this stays below the inverted-failure-mode problem on line 120 and does not need a higher priority.
Issue description
The Collector defaults send_batch_size to 8192. Its validation requires send_batch_max_size to be at least send_batch_size. A user who sets only a small maximum can make the Collector reject its configuration. The telemetry pipeline then fails to start.
Suggested fix
Show a configuration that sets both values. State that send_batch_size must be less than or equal to send_batch_max_size. Do not give a fixed count unless it matches the expected span sizes.
Prompt to fix with AI (copy-paste)
## Context
@contents/docs/ai-observability/large-events.mdx#L122
<issue_description>
The Collector defaults `send_batch_size` to 8192. Its validation requires `send_batch_max_size` to be at least `send_batch_size`. A user who sets only a small maximum can make the Collector reject its configuration. The telemetry pipeline then fails to start.
</issue_description>
<issue_validation>
- **Checked:** The upstream `batch` processor that line 122 links to — its `Config.Validate()`, its default config, and its README — in open-telemetry/opentelemetry-collector.
- **Found:** `processor/batchprocessor/factory.go:19` sets `defaultSendBatchSize = uint32(8192)`, and `createDefaultConfig()` at `factory.go:39-45` applies it. A user who does not name `send_batch_size` gets 8192.
- **Found:** `processor/batchprocessor/config.go:53-56` rejects the combination: `if cfg.SendBatchMaxSize > 0 && cfg.SendBatchMaxSize < cfg.SendBatchSize { return errors.New("send_batch_max_size must be greater or equal to send_batch_size") }`. The README states the same rule: "It must be greater than or equal to `send_batch_size`." Component `Validate()` runs at service startup, so the collector refuses to start.
- **Found:** The failure is the normal outcome of the advice, not an edge case. Line 122 tells the reader to "set a small `send_batch_max_size`" and names no other setting. Every value below 8192 trips the check against the default `send_batch_size`. "Small" is the whole point of the advice, so a reader who follows it literally breaks the config almost every time.
- **Found:** A supporting weakness in the same sentence. The README defines both settings as a count of "spans, metric data points, or log records", not a count of bytes. `send_batch_max_size` therefore bounds the span count per export and not the 4MB body the section is about. No fixed count keeps a sender under 4MB unless the reader knows the size of their spans.
- **Impact:** The page adds an actionable collector instruction that stops the reader's telemetry pipeline from starting. The reader loses all trace export, not only the oversize part. The fix is small: show a snippet that sets both values and state the ordering rule.
- **Impact:** The failure is loud. The collector prints the exact error and names both settings, so a reader recovers fast. That is why this stays below the inverted-failure-mode problem on line 120 and does not need a higher priority.
</issue_validation>
## Task
Investigate the issue and solve it
<potential_solution>
Show a configuration that sets both values. State that `send_batch_size` must be less than or equal to `send_batch_max_size`. Do not give a fixed count unless it matches the expected span sizes.
</potential_solution>
|
|
||
| An export or span over these limits is dropped, but the request still returns success. A failure response would only make your collector retry the same oversize data, so PostHog accepts the request and discards what does not fit. The only signal is a `MessageSizeTooLarge` [ingestion warning](/docs/data/ingestion-warnings) – your application gets no error, and the data is lost silently. | ||
|
|
||
| To stay under the 4MB export cap, add the [`batch` processor](https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/batchprocessor/README.md) to your collector and set a small `send_batch_max_size`, so it splits large batches into smaller exports. For a single span that is too large on its own, capture it with [`capture_ai`](#calling-capture_ai-directly) instead. |
There was a problem hiding this comment.
The batch processor cannot enforce the 4 MiB byte limit
Why we think it's a valid issue
- Checked: The
batchprocessor config that line 122 recommends, and whether the OpenTelemetry Collector offers any byte-based batching, in open-telemetry/opentelemetry-collector. - Found: The processor has no byte control at all.
processor/batchprocessor/config.go:16-48declares the completeConfig:Timeout,SendBatchSize,SendBatchMaxSize,MetadataKeys,MetadataCardinalityLimit.SendBatchMaxSizeis auint32, and the README defines both size fields as a "Number of spans, metric data points, or log records". The setting counts items, so it cannot bound an export body in bytes. - Found: This page's own audience makes the count-to-byte ratio unstable.
large-events.mdx:13states that "a single generation can carry megabytes of prompt context or base64-encoded media". A count that holds an export under 4MB for text spans can produce a 10MB export as soon as one span carries media, so no fixed number is portable between projects or stable over time. - Found: A byte-based mechanism does exist upstream, so the page recommends the weaker of two options. The exporter sending queue supports
batchwithsizer: bytesandmax_sizemeasured in bytes.exporter/exporterhelper/internal/queuebatch/config.go:129-131accepts onlyitemsorbytesfor the batch sizer, andexporter/exporterhelper/README.md:59definesbytesas "the size of serialized data in bytes". That setting enforces the 4MB cap directly;send_batch_max_sizeonly correlates with it. - Impact: A reader follows the documented remedy, picks a small span count, and still receives rejected exports. The page presents the setting as the way to "stay under the 4MB export cap" and gives no method to choose the number and no warning that the number depends on span content. The fix is real work, not a hedge: name the byte sizer, or state plainly that the count is a proxy the reader must tune against their own span sizes.
- Impact: Two things limit the damage and keep this below the inverted-failure claim on line 120. Once the page describes the 413 correctly, an oversize export fails loudly, so a reader can lower the count and converge. The page also routes a single oversize span to
capture_ai, so the advice is not a dead end. - Impact: This is a separate defect from the invalid-configuration problem on the same line. That one says the collector refuses to start; this one says the advice does not reach its stated goal when it does start. The two need different edits.
- Impact: Part of the proposed remedy is already on the page and needs no change.
large-events.mdx:126states the 8MB ceiling and says the SDKs drop larger events and log an error, which covers content above 8MB.
Issue description
send_batch_max_size limits the number of spans. It does not limit serialized bytes. A small count can still produce an export over 4 MiB when spans contain large prompts or media. The recommendation does not guarantee the stated outcome.
Suggested fix
State that this setting only reduces the risk of oversized exports. Tell users to measure encoded export sizes and choose a count from their largest normal span. Recommend capture_ai only when the resulting event stays under 8 MiB. Users must trim content above 8 MiB.
Prompt to fix with AI (copy-paste)
## Context
@contents/docs/ai-observability/large-events.mdx#L122
<issue_description>
`send_batch_max_size` limits the number of spans. It does not limit serialized bytes. A small count can still produce an export over 4 MiB when spans contain large prompts or media. The recommendation does not guarantee the stated outcome.
</issue_description>
<issue_validation>
- **Checked:** The `batch` processor config that line 122 recommends, and whether the OpenTelemetry Collector offers any byte-based batching, in open-telemetry/opentelemetry-collector.
- **Found:** The processor has no byte control at all. `processor/batchprocessor/config.go:16-48` declares the complete `Config`: `Timeout`, `SendBatchSize`, `SendBatchMaxSize`, `MetadataKeys`, `MetadataCardinalityLimit`. `SendBatchMaxSize` is a `uint32`, and the README defines both size fields as a "Number of spans, metric data points, or log records". The setting counts items, so it cannot bound an export body in bytes.
- **Found:** This page's own audience makes the count-to-byte ratio unstable. `large-events.mdx:13` states that "a single generation can carry megabytes of prompt context or base64-encoded media". A count that holds an export under 4MB for text spans can produce a 10MB export as soon as one span carries media, so no fixed number is portable between projects or stable over time.
- **Found:** A byte-based mechanism does exist upstream, so the page recommends the weaker of two options. The exporter sending queue supports `batch` with `sizer: bytes` and `max_size` measured in bytes. `exporter/exporterhelper/internal/queuebatch/config.go:129-131` accepts only `items` or `bytes` for the batch sizer, and `exporter/exporterhelper/README.md:59` defines `bytes` as "the size of serialized data in bytes". That setting enforces the 4MB cap directly; `send_batch_max_size` only correlates with it.
- **Impact:** A reader follows the documented remedy, picks a small span count, and still receives rejected exports. The page presents the setting as the way to "stay under the 4MB export cap" and gives no method to choose the number and no warning that the number depends on span content. The fix is real work, not a hedge: name the byte sizer, or state plainly that the count is a proxy the reader must tune against their own span sizes.
- **Impact:** Two things limit the damage and keep this below the inverted-failure claim on line 120. Once the page describes the 413 correctly, an oversize export fails loudly, so a reader can lower the count and converge. The page also routes a single oversize span to `capture_ai`, so the advice is not a dead end.
- **Impact:** This is a separate defect from the invalid-configuration problem on the same line. That one says the collector refuses to start; this one says the advice does not reach its stated goal when it does start. The two need different edits.
- **Impact:** Part of the proposed remedy is already on the page and needs no change. `large-events.mdx:126` states the 8MB ceiling and says the SDKs drop larger events and log an error, which covers content above 8MB.
</issue_validation>
## Task
Investigate the issue and solve it
<potential_solution>
State that this setting only reduces the risk of oversized exports. Tell users to measure encoded export sizes and choose a count from their largest normal span. Recommend `capture_ai` only when the resulting event stays under 8 MiB. Users must trim content above 8 MiB.
</potential_solution>
Bundle reportTotal JS (gzip)8.15 MiB (+1.3 KiB / +0.0%) Largest changed named chunks
Eager graph (modules shipped in each entrypoint's initial chunks)
Largest modules in the
|
| Module | Size |
|---|---|
./src/data/mcp-tools.json |
1055.2 KiB |
css ./node_modules/.pnpm/css-loader@5.2.7_webpack@5.101.3/node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[8].oneOf[1].use[1]!./node_modules/.pnpm/postcss-loader@4.3.0_postcss@8.5.6_webpack@5.101.3/node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[8].oneOf[1].use[2]!./src/styles/global.css |
754.5 KiB |
./src/components/Stickers/Stickers.tsx |
696.4 KiB |
./node_modules/.pnpm/@radix-ui+react-icons@1.3.2_react@18.3.1/node_modules/@radix-ui/react-icons/dist/react-icons.esm.js |
481.4 KiB |
./node_modules/.pnpm/rehype-raw@7.0.0/node_modules/rehype-raw/lib/index.js + 29 modules |
395.1 KiB |
./src/hooks/useCustomers.tsx + 55 modules |
370.0 KiB |
./node_modules/.pnpm/@posthog+icons@0.36.6_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
354.8 KiB |
./node_modules/.pnpm/react-markdown@8.0.7_@types+react@16.14.66_react@18.3.1/node_modules/react-markdown/lib/react-markdown.js + 88 modules |
351.4 KiB |
./src/components/ProductComparisonTable/index.tsx + 126 modules |
301.7 KiB |
./node_modules/.pnpm/cloudinary-core@2.14.0_lodash@4.17.21/node_modules/cloudinary-core/cloudinary-core.js |
281.9 KiB |
./src/components/SearchUI/index.tsx + 87 modules |
273.0 KiB |
./node_modules/.pnpm/@posthog+brand@0.8.0_react@18.3.1/node_modules/@posthog/brand/dist/generated/hoggies/svg/magnifying-glass.mjs |
254.7 KiB |
./node_modules/.pnpm/framer-motion@10.18.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/framer-motion/dist/es/render/dom/motion.mjs + 109 modules |
253.9 KiB |
./node_modules/.pnpm/d3@7.9.0/node_modules/d3/src/index.js + 208 modules |
247.4 KiB |
./src/components/Pricing/PricingSlider/Slider.tsx + 87 modules |
240.1 KiB |
Eager-graph budgets are report-only until a baseline is established. Sizes are gzip of public/**/*.js; eager size is webpack module source bytes for the modules actually shipped in the entrypoint's initial chunks (post-tree-shake).
Changes
Problem
capture_ai/captureAi, and a raw POST, but OpenTelemetry appears nowhere on it — so senders have no way to learn the limit or the failure mode.What changed
large-events.mdxwith the dedicated OTLP path and both size limits:MessageSizeTooLargeingestion warning, and that a failure response is withheld on purpose to stop the collector retrying the same oversize data.batchprocessor (smallsend_batch_max_size) to stay under 4MB, andcapture_aifor a single span that is too large on its own.Checklist
vercel.json(no pages moved)Created with PostHog Desktop from this inbox report.