Repository navigation
fix(source-amazon-seller-partner): use streaming JSON decoder for large Brand Analytics reports - #77709
Closed
devin-ai-integration[bot] wants to merge 1 commit into
Closed
fix(source-amazon-seller-partner): use streaming JSON decoder for large Brand Analytics reports#77709devin-ai-integration[bot] wants to merge 1 commit into
devin-ai-integration[bot] wants to merge 1 commit into
Conversation
…ge Brand Analytics reports The five Brand Analytics streams (search terms, market basket, repeat purchase, alternate purchase, item comparison) download single-document gzipped JSON reports that can be multiple gigabytes when decompressed. The legacy GzipJsonDecoder loads the entire response into memory and then parses the full document at once, which causes the connector container to be OOM-killed (exit code 137) on multi-GB reports. This change adds a GzipJsonStreamingItemsDecoder that: - Streams the HTTP body off the socket (is_stream_response=True) - Decompresses gzip incrementally - Yields each element of the configured array path one at a time via ijson, without ever materializing the full document or its parsed object tree The five Brand Analytics streams are switched over to the new decoder; all other streams keep using the legacy GzipJsonDecoder. Resolves airbytehq/oncall#12143
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Contributor
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksPR Slash CommandsAirbyte Maintainers (that's you!) can execute the following slash commands on your PR:
📚 Show Repo GuidanceHelpful Resources
|
Contributor
|
Deploy preview for airbyte-docs ready!
Deployed with vercel-action |
Contributor
|
Contributor
Author
|
CI status note (PR is intentionally still draft):
Unblocking sequence:
|
Open
4 tasks
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.
Resolves https://github.com/airbytehq/oncall/issues/12143:
What
The five Brand Analytics streams download a single gzipped JSON document whose
dataByDepartmentAndSearchTerm/dataByAsinarray can be multiple gigabytes when decompressed. The legacyGzipJsonDecoderreads the full HTTP body into memory, decompresses it, decodes it as a string and finally callsjson.loads()on it — peak memory usage is roughly 4–5x the decompressed size because the gzipped body, decompressed bytes, decoded string and parsed Python object tree all coexist briefly. On the customer-reported sync the report was approximately 3.2 GB decompressed and the connector container was OOM-killed (exit code 137) on an 8 GB cap.This change introduces a streaming decoder that yields one record at a time without ever materializing the full document and switches the five Brand Analytics streams over to it. All other streams are unaffected.
How
New
GzipJsonStreamingItemsDecoder(insource-amazon-seller-partner/components.py):is_stream_response() = Trueso the CDK reads the body off the socket instead of buffering it intoresponse.content.gzip.GzipFile(fileobj=...)instead ofgzip.decompress(response.content).ijson.items(stream, "<items_field_path>.item"), which yields each element of the configured array path one at a time.1f 8b) at the head of the body and falls back to plain-JSON parsing when the body is not gzipped (used by the test setup; the real S3-pre-signed URL response is always gzipped).Manifest changes (one block per stream, applied to all five Brand Analytics streams):
The extractor's
field_pathis now empty because the decoder already yields individual records.Declarative-First Evaluation
Per the
/ai-fixDeclarative-First Evaluation Gate I evaluated whether this could be solved with built-in CDK / declarative features:RecordFilter,AddFields,RemoveFields,DatetimeBasedCursor,DefaultPaginator,SubstreamPartitionRouter,HttpRequestererror handlers, transformations,$refoverrides — none of these affect how the response body is read or parsed.JsonDecoder,GzipJsonDecoder,IterableDecoder,JsonlDecoder,XmlDecoder— all of these either buffer the full body into memory or assume a line-delimited format that the Brand Analytics report does not have (it is a single JSON document with a long array nested inside a wrapping object).A streaming gzip + streaming JSON parser is the only thing that can keep peak memory bounded for a multi-GB single-document report, and no declarative component currently provides that. A custom Python component is therefore required.
Test Coverage
New
unit_tests/test_streaming_decoder.pycovers:dataByDepartmentAndSearchTermanddataByAsin(the two paths used by the five streams).is_stream_response()returningTrue.next(generator)returns the first record, less than the full response body has been read from the underlying stream — confirms records are produced as they are parsed, not after the whole document is buffered.tracemallocallocations for the streaming decoder are at least 5x lower than the legacyGzipJsonDecoderon the same 20 000-record gzipped payload, which is the property that fixes the OOM. (Without the streaming decoder this test fails because the legacy decoder materializes the full document.)All 508 existing connector unit tests still pass locally.
Review guide
airbyte-integrations/connectors/source-amazon-seller-partner/components.py— newGzipJsonStreamingItemsDecoderand_PrefixedStreamhelper.airbyte-integrations/connectors/source-amazon-seller-partner/manifest.yaml— five Brand Analytics streams switched to the new decoder.airbyte-integrations/connectors/source-amazon-seller-partner/unit_tests/test_streaming_decoder.py— new tests.airbyte-integrations/connectors/source-amazon-seller-partner/metadata.yaml— version bump 5.7.4 → 5.7.5.docs/integrations/sources/amazon-seller-partner.md— changelog entry.User Impact
GET_BRAND_ANALYTICS_SEARCH_TERMS_REPORT,GET_BRAND_ANALYTICS_MARKET_BASKET_REPORT,GET_BRAND_ANALYTICS_REPEAT_PURCHASE_REPORT,GET_BRAND_ANALYTICS_ALTERNATE_PURCHASE_REPORT,GET_BRAND_ANALYTICS_ITEM_COMPARISON_REPORT) no longer OOM on multi-GB reports; peak memory is bounded by the size of a single record plus ijson's small parse buffers rather than the full document.GzipJsonDecoderand are unchanged.Breaking change evaluation
Walked the Breaking Change Evaluation Gate checklist:
This is a non-breaking PATCH bump.
Dependencies
This PR depends on the
ijsonruntime dependency being available in thesource-declarative-manifestbase image. That is being added in airbytehq/airbyte-python-cdk#1011. Once that PR is merged and a new SDM image is published, this PR'smetadata.yamlbaseImageshould be bumped to that version before merging. The PR is kept in draft until then.Can this PR be safely reverted and rolled back?
Reverting drops back to the legacy
GzipJsonDecoderfor the five streams; users would again hit the OOM on very large reports but otherwise behavior is identical.Link to Devin session: https://app.devin.ai/sessions/e31a7df6ebe54ce4a68e0eecc7117555