Skip to content

Commit 4291b6e

Browse files
feat: sst file using lz4 and zstd compression (alibaba#79)
1 parent 63eab34 commit 4291b6e

34 files changed

Lines changed: 1150 additions & 101 deletions

.gitattributes

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
*.jar filter=lfs diff=lfs merge=lfs -text
44
*.tar filter=lfs diff=lfs merge=lfs -text
55
*.gz filter=lfs diff=lfs merge=lfs -text
6-
79d01717-8380-4504-86e1-387e6c058d0a filter=lfs diff=lfs merge=lfs -text
6+
test/test_data/sst/none/79d01717-8380-4504-86e1-387e6c058d0a filter=lfs diff=lfs merge=lfs -text
7+
test/test_data/sst/lz4/10540951-41d3-4216-aa2c-b15dfd25eb75 filter=lfs diff=lfs merge=lfs -text
8+
test/test_data/sst/zstd/83d05c53-2353-4160-b756-d50dd851b474 filter=lfs diff=lfs merge=lfs -text

src/paimon/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
# limitations under the License.
1414

1515
set(PAIMON_COMMON_SRCS
16+
common/compression/block_compression_factory.cpp
17+
common/compression/block_compressor.cpp
18+
common/compression/block_decompressor.cpp
1619
common/data/abstract_binary_writer.cpp
1720
common/data/binary_array.cpp
1821
common/data/binary_array_writer.cpp
@@ -116,6 +119,7 @@ set(PAIMON_COMMON_SRCS
116119
common/utils/bloom_filter.cpp
117120
common/utils/bloom_filter64.cpp
118121
common/utils/bucket_id_calculator.cpp
122+
common/utils/crc32c.cpp
119123
common/utils/decimal_utils.cpp
120124
common/utils/delta_varint_compressor.cpp
121125
common/utils/path_util.cpp
@@ -440,7 +444,9 @@ if(PAIMON_BUILD_TESTS)
440444

441445
add_paimon_test(common_sst_file_format_test
442446
SOURCES
447+
common/compression/block_compression_factory_test.cpp
443448
common/sst/sst_file_io_test.cpp
449+
common/utils/crc32c_test.cpp
444450
STATIC_LINK_LIBS
445451
paimon_shared
446452
test_utils_static
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/common/compression/block_compression_factory.h"
18+
19+
#include "paimon/common/compression/lz4/lz4_block_compression_factory.h"
20+
#include "paimon/common/compression/none_block_compression_factory.h"
21+
#include "paimon/common/compression/zstd/zstd_block_compression_factory.h"
22+
23+
namespace paimon {
24+
25+
Result<std::shared_ptr<BlockCompressionFactory>> BlockCompressionFactory::Create(
26+
BlockCompressionType compression) {
27+
switch (compression) {
28+
case BlockCompressionType::NONE:
29+
return std::make_shared<NoneBlockCompressionFactory>();
30+
case BlockCompressionType::LZ4:
31+
return std::make_shared<Lz4BlockCompressionFactory>();
32+
case BlockCompressionType::ZSTD:
33+
return std::make_shared<ZstdBlockCompressionFactory>(ZSTD_COMPRESSION_LEVEL);
34+
default:
35+
// TODO(liangzi): LZO support
36+
return Status::Invalid("Unsupported compression type: " +
37+
std::to_string(static_cast<int8_t>(compression)));
38+
}
39+
}
40+
41+
} // namespace paimon
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <memory>
20+
21+
#include "paimon/common/compression/block_compression_type.h"
22+
#include "paimon/common/compression/block_compressor.h"
23+
#include "paimon/common/compression/block_decompressor.h"
24+
#include "paimon/result.h"
25+
26+
namespace paimon {
27+
28+
/// Each compression codec has an implementation of {@link BlockCompressionFactory} to create
29+
/// compressors and decompressors.
30+
class BlockCompressionFactory {
31+
public:
32+
static Result<std::shared_ptr<BlockCompressionFactory>> Create(
33+
BlockCompressionType compression);
34+
35+
BlockCompressionFactory() = default;
36+
virtual ~BlockCompressionFactory() = default;
37+
38+
public:
39+
virtual BlockCompressionType GetCompressionType() const = 0;
40+
41+
virtual std::shared_ptr<BlockCompressor> GetCompressor() = 0;
42+
43+
virtual std::shared_ptr<BlockDecompressor> GetDecompressor() = 0;
44+
45+
private:
46+
// Align java implementation
47+
static constexpr int32_t ZSTD_COMPRESSION_LEVEL = 1;
48+
};
49+
} // namespace paimon
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/common/compression/block_compression_factory.h"
18+
19+
#include <cstdint>
20+
#include <string>
21+
#include <utility>
22+
#include <vector>
23+
24+
#include "gtest/gtest.h"
25+
#include "paimon/defs.h"
26+
#include "paimon/memory/memory_pool.h"
27+
#include "paimon/status.h"
28+
#include "paimon/testing/mock/mock_file_batch_reader.h"
29+
#include "paimon/testing/utils/read_result_collector.h"
30+
#include "paimon/testing/utils/testharness.h"
31+
32+
namespace paimon {
33+
class Predicate;
34+
} // namespace paimon
35+
36+
namespace paimon::test {
37+
class CompressionFactoryTest : public ::testing::TestWithParam<BlockCompressionType> {};
38+
39+
TEST_P(CompressionFactoryTest, TESTCompressThenDecompress) {
40+
int32_t original_len = 16;
41+
BlockCompressionType type = GetParam();
42+
43+
std::string data(original_len, '\0');
44+
for (int32_t i = 0; i < original_len; i++) {
45+
data[i] = static_cast<char>(i);
46+
}
47+
48+
ASSERT_OK_AND_ASSIGN(auto factory, BlockCompressionFactory::Create(type));
49+
ASSERT_EQ(type, factory->GetCompressionType());
50+
51+
// compress
52+
auto compressor = factory->GetCompressor();
53+
auto max_len = compressor->GetMaxCompressedSize(data.size());
54+
std::string compressed_data(max_len, '\0');
55+
auto compressed_size =
56+
compressor->Compress(data.data(), data.size(), compressed_data.data(), max_len);
57+
ASSERT_OK(compressed_size);
58+
ASSERT_GT(compressed_size.value(), 0);
59+
compressed_data.resize(compressed_size.value());
60+
61+
// decompress
62+
auto decompressor = factory->GetDecompressor();
63+
std::string decompressed_data(original_len, '\0');
64+
auto decompressed_size =
65+
decompressor->Decompress(compressed_data.data(), compressed_data.size(),
66+
decompressed_data.data(), decompressed_data.size());
67+
ASSERT_OK(decompressed_size);
68+
ASSERT_GT(decompressed_size.value(), 0);
69+
ASSERT_EQ(data, decompressed_data);
70+
71+
std::string read_write_le{4, '\0'};
72+
compressor->WriteIntLE(123, read_write_le.data());
73+
ASSERT_EQ(123, decompressor->ReadIntLE(read_write_le.data()));
74+
compressor->WriteIntLE(100000, read_write_le.data());
75+
ASSERT_EQ(100000, decompressor->ReadIntLE(read_write_le.data()));
76+
compressor->WriteIntLE(-6555, read_write_le.data());
77+
ASSERT_EQ(-6555, decompressor->ReadIntLE(read_write_le.data()));
78+
compressor->WriteIntLE(0, read_write_le.data());
79+
ASSERT_EQ(0, decompressor->ReadIntLE(read_write_le.data()));
80+
}
81+
82+
INSTANTIATE_TEST_SUITE_P(BlockCompressionTypeGroup, CompressionFactoryTest,
83+
::testing::Values(BlockCompressionType::LZ4, BlockCompressionType::ZSTD));
84+
85+
} // namespace paimon::test
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include "paimon/result.h"
20+
21+
namespace paimon {
22+
23+
/// Block Compression type.
24+
enum class BlockCompressionType { NONE = 0, ZSTD = 1, LZ4 = 2, LZO = 3 };
25+
26+
} // namespace paimon
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/common/compression/block_compressor.h"
18+
19+
namespace paimon {
20+
21+
void BlockCompressor::WriteIntLE(int32_t val, char* buf) {
22+
buf[0] = static_cast<char>(val);
23+
buf[1] = static_cast<char>(val >> 8);
24+
buf[2] = static_cast<char>(val >> 16);
25+
buf[3] = static_cast<char>(val >> 24);
26+
}
27+
28+
} // namespace paimon
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include "paimon/result.h"
20+
21+
namespace paimon {
22+
23+
/// A compressor which compresses a whole byte array each time.
24+
class BlockCompressor {
25+
public:
26+
static void WriteIntLE(int32_t val, char* buf);
27+
28+
public:
29+
virtual ~BlockCompressor() = default;
30+
31+
public:
32+
/**
33+
* Get the max compressed size for a given original size.
34+
* @param src_size The original size
35+
* @return The max compressed size
36+
*/
37+
virtual int32_t GetMaxCompressedSize(int32_t src_size) = 0;
38+
39+
/**
40+
* Compress data read from src, and write the compressed data to dst.
41+
*
42+
* @param src Uncompressed data to read from
43+
* @param src_length The length of data which want to be compressed
44+
* @param dst The target to write compressed data
45+
* @param dst_length The max length of data
46+
* @return Length of compressed data
47+
*/
48+
/// Compress data read from src, and write the compressed data to dst.
49+
virtual Result<int32_t> Compress(const char* src, int32_t src_length, char* dst,
50+
int32_t dst_length) = 0;
51+
52+
public:
53+
/**
54+
* We put two integers before each compressed block, the first integer represents the compressed
55+
* length of the block, and the second one represents the original length of the block.
56+
*/
57+
static constexpr int32_t HEADER_LENGTH = 8;
58+
};
59+
} // namespace paimon
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/common/compression/block_decompressor.h"
18+
19+
#include "fmt/format.h"
20+
namespace paimon {
21+
22+
int32_t BlockDecompressor::ReadIntLE(const char* buf) {
23+
return (buf[0] & 0xFF) | ((buf[1] & 0xFF) << 8) | ((buf[2] & 0xFF) << 16) |
24+
((buf[3] & 0xFF) << 24);
25+
}
26+
27+
Status BlockDecompressor::ValidateLength(int32_t compressed_len, int32_t original_len) {
28+
if (original_len < 0 || compressed_len < 0 || (original_len == 0 && compressed_len != 0) ||
29+
(original_len != 0 && compressed_len == 0)) {
30+
return Status::IOError(
31+
fmt::format("Input is corrupted, compressed_len={}, , original_len={}", compressed_len,
32+
original_len));
33+
}
34+
return Status::OK();
35+
}
36+
37+
} // namespace paimon

0 commit comments

Comments
 (0)