Add aws_s3_stream output - #664
Conversation
Adds new streaming S3 output that uses S3 multipart uploads with per-partition writer pooling for constant memory usage and correct partition routing. ## Added Files - output_aws_s3_stream.go: Main output plugin with partition routing - output_aws_s3_stream_test.go: Unit tests (18 tests) - output_aws_s3_stream_integration_test.go: Integration tests (4 tests) - s3_streaming_writer.go: S3 multipart streaming writer with S3API interface - s3_streaming_writer_test.go: Writer unit tests (12 tests) ## Key Features - partition_by parameter for per-message partition evaluation - Configurable buffer thresholds (bytes, count, time) - S3 multipart upload with automatic part management - Content-Type and Content-Encoding metadata support - Graceful shutdown with upload completion
CI FixesFixed all linting errors and CI check failures:
All tests and lint checks now pass. I will review the generated documentation output more closely for any additional improvements needed. |
Rename S3API -> s3StreamingAPI, WriterStats -> S3StreamWriterStats, and mockS3Client -> mockS3StreamClient to prevent type conflicts when both streaming output implementations are merged to upstream.
Regarding the partition routing problemIn the PR description you have written:
But does it? In the existing key, err := msg.TryInterpolatedString(i, a.conf.Path)
if err != nil {
return fmt.Errorf("key interpolation: %w", err)
}For each message in the batch we do evaluate the key? In your config you shared: output:
aws_s3:
path: 'partition=${! meta("key") }/${! uuid_v4() }.json'
batching:
count: 10000
processors:
- archive:
format: linesYou are using the
assuming the individual messages are json. Sounds like you could use the group_by_value processor - like: input:
batched:
child:
file:
paths:
- ./test_data/*.json
policy:
count: 10
pipeline:
processors:
- group_by_value:
value: ${! json("key") }
- mapping: |
if batch_index() == 0 { meta key = this.key }
- archive:
format: lines
output:
aws_s3:
bucket: bento-aws-s3-stream-test
path: ${! meta("key") }/foobar.json./test_data/ is populated with some json files that have a json key "key" with either "A" / "B" as the value - and then in the s3 bucket you get two different s3 keys: /A/foobar.json & /B/foobar.json - split such that the s3 keys "A" & "B" only have the json files with the matching "key" value. Could you elaborate on what the issue is exactly with the |
|
Yeah it is mentioned on the
|
|
Guess the aws_s3 batching docs fail to mention this... |
jem-davies
left a comment
There was a problem hiding this comment.
Will take another look very soon - to give a test - and look more at the s3_streaming_writer.go file.
Co-authored-by: Jem Davies <[email protected]>
|
You're right - the partition routing issue is specific to using I'll update the PR description to focus on the memory efficiency benefits rather than framing this as a general partition routing problem with |
- Simplify description to focus on multipart upload benefits and large files - Use service.NewDurationField for max_buffer_period field - Use pConf.FieldDuration for cleaner duration parsing - Move s3Client creation from constructor to Connect() method - Replace sync.Mutex with sync.RWMutex for better read/write concurrency - Add clarifying comment about contentType/encoding evaluation from first message - Downgrade "Closing writers" log from Info to Debug level
d14a3ce to
e27358e
Compare
Co-authored-by: Jem Davies <[email protected]>
Co-authored-by: Jem Davies <[email protected]>
Signed-off-by: Jem Davies <[email protected]>
Signed-off-by: Jem Davies <[email protected]>
jem-davies
left a comment
There was a problem hiding this comment.
I have added a PR to alter the backoff logic & make it configurable triddell#2
I have given it a test with a 200MB file and is looking good 🥳
|
Fixed! Changed error path Kept |
* Add aws_s3_stream output for memory-efficient partitioned S3 writes Adds new streaming S3 output that uses S3 multipart uploads with per-partition writer pooling for constant memory usage and correct partition routing. ## Added Files - output_aws_s3_stream.go: Main output plugin with partition routing - output_aws_s3_stream_test.go: Unit tests (18 tests) - output_aws_s3_stream_integration_test.go: Integration tests (4 tests) - s3_streaming_writer.go: S3 multipart streaming writer with S3API interface - s3_streaming_writer_test.go: Writer unit tests (12 tests) ## Key Features - partition_by parameter for per-message partition evaluation - Configurable buffer thresholds (bytes, count, time) - S3 multipart upload with automatic part management - Content-Type and Content-Encoding metadata support - Graceful shutdown with upload completion * fix lint errors and update deprecated AWS endpoint resolver * docs: remove partition_by YAML example causing validation error * refactor: rename types to avoid conflicts with parquet streaming writer Rename S3API -> s3StreamingAPI, WriterStats -> S3StreamWriterStats, and mockS3Client -> mockS3StreamClient to prevent type conflicts when both streaming output implementations are merged to upstream. * Apply suggestions from code review Co-authored-by: Jem Davies <[email protected]> * Address code review feedback - Simplify description to focus on multipart upload benefits and large files - Use service.NewDurationField for max_buffer_period field - Use pConf.FieldDuration for cleaner duration parsing - Move s3Client creation from constructor to Connect() method - Replace sync.Mutex with sync.RWMutex for better read/write concurrency - Add clarifying comment about contentType/encoding evaluation from first message - Downgrade "Closing writers" log from Info to Debug level * update stale docs * Update internal/impl/aws/output_aws_s3_stream.go Co-authored-by: Jem Davies <[email protected]> * Update internal/impl/aws/s3_streaming_writer.go Co-authored-by: Jem Davies <[email protected]> * chore: fix golangci-lint formatting and modernization issues * rename files so to group when sorted alphabetically Signed-off-by: Jem Davies <[email protected]> * use cenkalti/backoff for retry logic Signed-off-by: Jem Davies <[email protected]> * fix: nil pointer when using default BackoffCtor in NewS3StreamingWriter Signed-off-by: Jem Davies <[email protected]> * fix: use passed context in error paths and document timer callback context usage --------- Signed-off-by: Jem Davies <[email protected]> Co-authored-by: Jem Davies <[email protected]> Co-authored-by: Jem Davies <[email protected]>
Overview
This pull request introduces
aws_s3_stream, a new output plugin designed to stream generic text-based files (JSON, NDJSON, CSV, logs) directly to Amazon S3 using multipart uploads. The implementation addresses two critical issues: excessive memory consumption and incorrect partition routing in the existing batch approach.Primary benefit: Massive memory reduction (95%+) enables processing large datasets that fail with batch approach, while maintaining constant memory usage.
Key Problems Addressed
Memory Efficiency: The current
aws_s3output with batching buffers entire batches in memory before writing, consuming approximately 3.31 GB for 100K events. This solution reduces memory usage to ~180 MB—a 95% improvement. For 500K events, the batch approach fails completely after 15+ minutes, while streaming succeeds in 38.56 seconds with only 170 MB of memory.Partition Routing: When using archive processors in batching, metadata from only the first message is preserved, which can cause partition misrouting. This output's partition_by design prevents this pattern by maintaining per-message partition evaluation.
Small File Problem: Batch processing creates many small files instead of optimal large files per partition. For example, 500K events produces 13+ incomplete files with batch vs 1 optimal 788 MB file with streaming, negatively impacting query performance and S3 costs.
Solution Architecture
The implementation uses:
partition_byexpressionsMemory footprint: 170–260 MB (constant regardless of dataset size).
Files Added
Core implementation (5 files):
output_aws_s3_stream.go(438 lines): Plugin configuration and partition routing logics3_streaming_writer.go(397 lines): Generic S3 streaming writer with multipart upload managementoutput_aws_s3_stream_test.go(373 lines): Unit tests for output configuration and partition logicoutput_aws_s3_stream_integration_test.go(371 lines): LocalStack integration testss3_streaming_writer_test.go(405 lines): Unit tests for streaming writer with mock S3 clientFiles Modified
None. This PR only adds new files without modifying any existing code.
Configuration Example
With Compression
Testing
The PR includes 30 unit tests and 4 LocalStack-based integration tests validating:
Unit tests (18 tests for output plugin):
Unit tests (12 tests for streaming writer):
Integration tests (4 tests with LocalStack):
Manual testing with OCSF security event data (complex nested JSON):
Performance Results
At small scales, batch is faster. At production scales, batch fails completely while streaming succeeds with constant memory.
Why No Built-in Compression?
Unlike
aws_s3, this output does not provide acompressparameter. S3 multipart uploads require non-final parts to be ≥5MB. After compressing a 5MB buffer, the compressed size is unpredictable (often <5MB), causing S3 to reject the upload.Solution: Use pipeline-level compression via the
compressprocessor (see example above). This works reliably because concatenated gzip streams are valid, and each compressed message can be decompressed independently.Migration Path
Before (Broken)
After (Fixed)
Key changes:
archive: lineslogic to pipeline (add\nto each message)partition_byparameter for correct routingbatchingsection from outputaws_s3_streaminstead ofaws_s3Backwards Compatibility
No breaking changes. This PR introduces a new plugin (
aws_s3_stream) alongside the existingaws_s3output. Users can migrate at their own pace. All code is new with zero modifications to existing files.Related Work
This PR follows the same pattern as PR #661 (
aws_s3_parquet_stream), which addresses identical partition routing and memory issues for Parquet format. Both implementations use:Documentation
The plugin includes comprehensive inline documentation:
aws_s3