Skip to content

Commit 66c8d72

Browse files
ddukbgdependabot[bot]yongwoo.kimdaipom
authored
Add Zstd compression support to S3 plugin (#439)
adds support for Zstd compression in the Fluentd S3 plugin. Changes - Implemented Zstd compression using the `zstd-ruby` library. - Introduced the `ZstdCompressor` class to handle log compression before uploading to S3. - Updated the example configuration to demonstrate the use of `store_as zstd`. - Ensured that the `Zstd` module is properly loaded to avoid uninitialized constant errors. Why this feature? Zstd compression provides a better compression ratio and performance compared to gzip, making it a valuable option for users who want efficient log storage on S3. --------- Signed-off-by: dependabot[bot] <[email protected]> Signed-off-by: yongwoo.kim <[email protected]> Signed-off-by: ddukbg <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: yongwoo.kim <[email protected]> Co-authored-by: Daijiro Fukuda <[email protected]>
1 parent 803cac2 commit 66c8d72

File tree

4 files changed

+71
-1
lines changed

4 files changed

+71
-1
lines changed

.github/workflows/linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ jobs:
2323
env:
2424
CI: true
2525
run: |
26-
gem install bundler rake
26+
gem install rake
2727
bundle install --jobs 4 --retry 3
2828
bundle exec rake test

fluent-plugin-s3.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Gem::Specification.new do |gem|
1919
gem.add_dependency "fluentd", [">= 0.14.22", "< 2"]
2020
gem.add_dependency "aws-sdk-s3", "~> 1.60"
2121
gem.add_dependency "aws-sdk-sqs", "~> 1.23"
22+
gem.add_dependency 'zstd-ruby'
2223
gem.add_development_dependency "rake", ">= 0.9.2"
2324
gem.add_development_dependency "test-unit", ">= 3.0.8"
2425
gem.add_development_dependency "test-unit-rr", ">= 1.0.3"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
require 'zstd-ruby'
2+
3+
module Fluent::Plugin
4+
class S3Output
5+
class ZstdCompressor < Compressor
6+
S3Output.register_compressor('zstd', self)
7+
8+
config_section :compress, param_name: :compress_config, init: true, multi: false do
9+
desc "Compression level for zstd (1-22)"
10+
config_param :level, :integer, default: 3
11+
end
12+
13+
def ext
14+
'zst'.freeze
15+
end
16+
17+
def content_type
18+
'application/x-zst'.freeze
19+
end
20+
21+
def compress(chunk, tmp)
22+
compressed = Zstd.compress(chunk.read, level: @compress_config.level)
23+
tmp.write(compressed)
24+
rescue => e
25+
log.warn "zstd compression failed: #{e.message}"
26+
raise
27+
end
28+
end
29+
end
30+
end

test/test_out_s3.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,18 @@ def test_configure_with_mime_type_lzo
109109
assert(e.is_a?(Fluent::ConfigError))
110110
end
111111

112+
data('level default' => nil,
113+
'level 1' => 1)
114+
def test_configure_with_mime_type_zstd(level)
115+
conf = CONFIG.clone
116+
conf << "\nstore_as zstd\n"
117+
conf << "\n<compress>\nlevel #{level}\n</compress>\n" if level
118+
d = create_driver(conf)
119+
assert_equal 'zst', d.instance.instance_variable_get(:@compressor).ext
120+
assert_equal 'application/x-zst', d.instance.instance_variable_get(:@compressor).content_type
121+
assert_equal (level || 3), d.instance.instance_variable_get(:@compressor).instance_variable_get(:@compress_config).level
122+
end
123+
112124
def test_configure_with_path_style
113125
conf = CONFIG.clone
114126
conf << "\nforce_path_style true\n"
@@ -456,6 +468,33 @@ def test_write_with_custom_s3_object_key_format_containing_hex_random_placeholde
456468
FileUtils.rm_f(s3_local_file_path)
457469
end
458470

471+
def test_write_with_zstd
472+
setup_mocks(true)
473+
s3_local_file_path = "/tmp/s3-test.zst"
474+
475+
expected_s3path = "log/events/ts=20110102-13/events_0-#{Socket.gethostname}.zst"
476+
477+
setup_s3_object_mocks(s3_local_file_path: s3_local_file_path, s3path: expected_s3path)
478+
479+
config = CONFIG_TIME_SLICE + "\nstore_as zstd\n"
480+
d = create_time_sliced_driver(config)
481+
482+
time = event_time("2011-01-02 13:14:15 UTC")
483+
d.run(default_tag: "test") do
484+
d.feed(time, { "a" => 1 })
485+
d.feed(time, { "a" => 2 })
486+
end
487+
488+
File.open(s3_local_file_path, 'rb') do |file|
489+
compressed_data = file.read
490+
uncompressed_data = Zstd.decompress(compressed_data)
491+
expected_data = %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] +
492+
%[2011-01-02T13:14:15Z\ttest\t{"a":2}\n]
493+
assert_equal expected_data, uncompressed_data
494+
end
495+
FileUtils.rm_f(s3_local_file_path)
496+
end
497+
459498
class MockResponse
460499
attr_reader :data
461500

0 commit comments

Comments
 (0)