Repository navigation
fix(telemetry): binary-search Truncate drop count to avoid O(n) re-renders (#153) - #154
Merged
yaythomas merged 1 commit intoNov 3, 2026
Conversation
…nders (aws#153) Truncate dropped operation Results (step 1) and whole operations (step 2) one at a time, re-rendering the entire record to JSON after every single drop. For an invocation with tens of thousands of operations that is O(n) full renders of a multi-megabyte record inside OnInvocationEnd — a 29,999-operation run stalled to the 900s Lambda timeout and failed with ExecutionLimitExceeded. Dropping more content only shrinks the rendered record, so the 'fits after dropping k' predicate is monotonic. Replace both one-at-a-time loops with sort.Search, reducing thousands of renders to O(log n) (~15). Each candidate is sized with the final Truncated/DroppedOperations metadata already set, so the returned record accounts for those bytes and cannot creep back over the cap (this also fixes the record that landed just over DefaultMaxRecordSizeBytesLog and was cut by CloudWatch Logs). Drop order, retained operations, and all truncation flags are unchanged. Add a regression test asserting the render count stays O(log n) for a 20,000-operation record (the old code renders many thousands of times).
yaythomas
approved these changes
Nov 3, 2026
yaythomas
left a comment
Contributor
There was a problem hiding this comment.
Thanks a bunch @PratikDhanave, welcome to dex! 🙇
This keeps the record within the 262,144-byte cap. The event in the issue is 262,118 bytes, though, which is already under the cap. 262,118 is 262,144 minus the 26 bytes CloudWatch Logs counts per log event. So that is most likely the size the event was cut to. So a record between 262,118 and 262,144 bytes is still cut after this change. Lowering the cap is a separate change, so I'll remove the part about the cut event from the squash commit message.
This was referenced Nov 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #153.
Problem
telemetry.Truncatedrops operationResultfields (step 1) and whole operations (step 2) one at a time, re-rendering the entire record to JSON after every single drop (fits→render). For an invocation with tens of thousands of operations that is O(n) full renders of a multi-megabyte record, insideOnInvocationEnd:ExecutionLimitExceeded(the same handler without the plugin succeeds in ~25 s). A CPU profile of a 5,000-operation run showed ~79 % of time inTruncate → fits → json.Marshal.Truncated/DroppedOperationswere set after thefitscheck, so the final record could land a few bytes over the cap — the reported 262,118-byte event exceededDefaultMaxRecordSizeBytesLogand was cut by CloudWatch Logs so it no longer parsed.Fix
Dropping more content only shrinks the rendered record, so "fits after dropping the oldest k" is monotonic. Replace both one-at-a-time loops with
sort.Search, cutting thousands of renders to O(log n) (~15). Each candidate is sized with the finalTruncated/DroppedOperationsmetadata already applied, so the returned record accounts for those bytes and cannot creep back over the cap.Drop order (results oldest-first, then whole operations oldest-first, then Input, then Output), the set of retained operations, and all truncation flags are unchanged — this is purely an algorithmic change, no exported API change.
Validation
telemetry/truncate_dropops_test.go: asserts a 20,000-operation oversized record truncates to fit and the render count stays O(log n) (≤100). On the old code this test does not complete — the one-at-a-time loop renders many thousands of times (locally it exceeds a 2-minute timeout, mirroring the production stall); with the fix it finishes in well under a second.go test ./telemetry/...passes;go vetandgofmtclean.Developed with AI assistance (Claude Code): the root cause from the issue was confirmed, the binary-search fix implemented, and the regression test written and run locally. I reviewed and verified the change and take responsibility for it.