Skip to content

Commit 40a7d05

Browse files
authored
feat(realtime): support user-defined-key deduplication for append tables (#393)
1 parent 5d467db commit 40a7d05

67 files changed

Lines changed: 5240 additions & 154 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎include/paimon/defs.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,10 @@ struct PAIMON_EXPORT Options {
645645
/// Supported values are "none" and "full". Default value is "none".
646646
static const char REALTIME_STORE_STATS_MODE[];
647647

648+
/// "realtime.deduplicate-key-fields" - Comma-separated user-defined deduplicate key fields
649+
/// used for last-write-wins deduplication on an append table. No default value.
650+
static const char REALTIME_DEDUPLICATE_KEY_FIELDS[];
651+
648652
/// "scan.timestamp" - Optional timestamp string used in case of "from-timestamp" scan mode,
649653
/// as an alternative to "scan.timestamp-millis".
650654
/// It will be automatically converted to timestamp in unix milliseconds, using local time zone.

‎include/paimon/file_store_commit.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ class PAIMON_EXPORT FileStoreCommit {
9898
/// Concurrent rollback or partition deletion must be fenced by the upstream coordinator.
9999
/// Configure catalog writers with `WriteContextBuilder::WithCatalog()` too.
100100
///
101+
/// In deduplicate mode each commit attempt resolves logical deleted offsets against the
102+
/// latest snapshot and merges them with its deletion vectors. Keep the complete progress
103+
/// when retrying; serializing only CommitMessage discards logical deletions.
104+
///
101105
/// @param realtime_commits Commit messages and left-closed, right-open offset ranges to
102106
/// commit.
103107
/// @param commit_identifier Identifier of the streaming commit operation.

‎include/paimon/file_store_write.h‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,16 @@ class PAIMON_EXPORT FileStoreWrite {
5252
virtual ~FileStoreWrite() = default;
5353

5454
/// Support write an input `RecordBatch` to internal buffer or file.
55-
/// @note Real-time writers require a non-nullable int64 `_REALTIME_OFFSET` field before the
56-
/// table write fields. Its values must be strictly increasing within each batch and
57-
/// monotonically increasing for each partition-bucket across batches; gaps are allowed.
58-
/// The field is used for snapshot progress and is not written to data files.
55+
/// @note Real-time writers require a non-nullable int64 `_REALTIME_OFFSET`. Its values must be
56+
/// strictly increasing within each batch and monotonically increasing for each
57+
/// partition-bucket across batches; gaps are allowed. Append-only and primary-key modes
58+
/// receive it as an internal prefix and do not write it to data files. Deduplicate mode
59+
/// requires it in the table schema and persists it as a normal data-file column.
5960
/// @note If a field in table schema is marked as non-nullable (`nullable = false`),
6061
/// the corresponding array in `batch` must have zero null entries.
62+
/// @note A failure while writing or flushing a real-time batch invalidates the writer and its
63+
/// `RealtimeContext`. Recreate both and let upstream replay input from the durable
64+
/// recovery offset persisted in the snapshot; retrying the failed writer is unsupported.
6165
virtual Status Write(std::unique_ptr<RecordBatch>&& batch) = 0;
6266

6367
/// Slices the current in-memory real-time data into a sealed segment so it can be reclaimed
@@ -111,7 +115,9 @@ class PAIMON_EXPORT FileStoreWrite {
111115
/// Each range is returned atomically with the commit message generated from the same sealed
112116
/// segments. Repeated calls return incremental progress. The upstream coordinator must retain
113117
/// every result until it is committed and include all earlier prepared-but-uncommitted
114-
/// progress when a later checkpoint subsumes it.
118+
/// progress when a later checkpoint subsumes it. In deduplicate mode the result also retains
119+
/// an immutable deleted-offset bitmap; physical deletion vectors are generated at commit,
120+
/// not at prepare. Preserve the complete progress, not just its CommitMessage.
115121
///
116122
/// @param commit_identifier Identifier of this prepare-commit operation in streaming mode.
117123
/// @return Real-time commit messages with their partition-bucket offset ranges.

‎include/paimon/realtime/arrow_realtime_store_factory.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ namespace paimon {
2626
/// Factory for Paimon's default Arrow-backed `RealtimeStore`.
2727
class PAIMON_EXPORT ArrowRealtimeStoreFactory : public RealtimeStoreFactory {
2828
public:
29-
/// Creates an Arrow-backed store for one partition and bucket.
29+
/// Creates an Arrow-backed append or primary-key store for one partition and bucket.
30+
/// The framework uses the append implementation as the delegate of its DEDUPLICATE wrapper.
3031
Result<std::shared_ptr<RealtimeStore>> Create(RealtimeStoreCreateRequest&& request) override;
3132
};
3233

‎include/paimon/realtime/realtime_commit_progress.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "paimon/commit_message.h"
2525
#include "paimon/realtime/offset_range.h"
2626
#include "paimon/realtime/realtime_context.h"
27+
#include "paimon/utils/roaring_bitmap64.h"
2728
#include "paimon/visibility.h"
2829

2930
namespace paimon {
@@ -41,6 +42,10 @@ struct PAIMON_EXPORT RealtimeCommitProgress {
4142
RealtimePartitionBucket partition_bucket;
4243
/// Left-closed, right-open offset range represented by the commit message.
4344
OffsetRange offset_range;
45+
/// Immutable logical deletions for deduplicate mode; null for other modes. Commit resolves
46+
/// these offsets against the latest snapshot and rebuilds physical DVs on every retry.
47+
/// This bitmap is not embedded in CommitMessage serialization.
48+
std::shared_ptr<const RoaringBitmap64> deleted_offsets = nullptr;
4449
};
4550

4651
} // namespace paimon

‎include/paimon/realtime/realtime_store.h‎

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ class Predicate;
4646
enum class PAIMON_EXPORT RealtimeStoreMode {
4747
APPEND_ONLY,
4848
PRIMARY_KEY,
49+
/// Experimental mode whose persisted data and index formats may change in future releases.
50+
DEDUPLICATE,
4951
};
5052

51-
/// Parameters used by a `RealtimeStoreFactory` to create a store.
53+
/// Parameters used to create a real-time store.
5254
struct PAIMON_EXPORT RealtimeStoreCreateRequest {
53-
/// Schema whose ownership is transferred to the factory. Append mode receives the complete
54-
/// append store-write schema: [_REALTIME_OFFSET, table write fields]. Primary-key mode receives
55-
/// the real-time primary-key store-write schema:
56-
/// [_SEQUENCE_NUMBER, _VALUE_KIND, _REALTIME_OFFSET, table write fields].
55+
/// Physical write schema whose ownership is transferred to the consumer. Its layout is
56+
/// mode-specific and contains a non-null int64 `_REALTIME_OFFSET` field.
5757
std::unique_ptr<::ArrowSchema> write_schema;
5858
/// Table options available to the store implementation.
5959
std::map<std::string, std::string> options;
6060
/// Memory pool for allocations retained by the store.
6161
std::shared_ptr<MemoryPool> memory_pool;
62-
/// Table mode implemented by the store.
62+
/// Requested store mode.
6363
RealtimeStoreMode mode = RealtimeStoreMode::APPEND_ONLY;
6464
/// Statistics collected by the store for query pruning.
6565
StatisticsMode statistics_mode = StatisticsMode::NONE;
@@ -69,6 +69,9 @@ struct PAIMON_EXPORT RealtimeStoreCreateRequest {
6969
/// File system used to access `temp_directory`. The default store retains shared ownership
7070
/// for its lifetime. Custom stores may ignore this hint.
7171
std::shared_ptr<FileSystem> file_system = nullptr;
72+
/// User-defined deduplicate key field names when key-based deduplication is configured; empty
73+
/// otherwise.
74+
std::vector<std::string> deduplicate_key_fields = {};
7275
};
7376

7477
/// A record batch and its application-assigned offset bounds.
@@ -77,9 +80,9 @@ struct PAIMON_EXPORT RealtimeStoreCreateRequest {
7780
/// [_REALTIME_OFFSET, table write fields], and offsets are strictly increasing before the batch
7881
/// enters the store. Primary-key batches use the real-time primary-key store-write schema, are
7982
/// sorted by full primary key then sequence number, and retain the original offset in
80-
/// `_REALTIME_OFFSET`. `offset_range` is the left-closed, right-open envelope from the first
81-
/// application offset through one past the last; offsets may have gaps, so its count is not the
82-
/// batch row count.
83+
/// `_REALTIME_OFFSET`.
84+
/// `offset_range` is the left-closed, right-open envelope from the first application offset through
85+
/// one past the last; offsets may have gaps, so its count is not the batch row count.
8386
struct PAIMON_EXPORT RealtimeWriteBatch {
8487
/// Input batch whose ownership is transferred to `RealtimeStore::Write`.
8588
std::unique_ptr<RecordBatch> batch;
@@ -170,9 +173,8 @@ class PAIMON_EXPORT RealtimeStore {
170173
/// Creates readers that expose all rows in a sealed segment for Paimon file writing.
171174
///
172175
/// The returned readers collectively expose every sealed row exactly once. Append-mode readers
173-
/// preserve write order and contain `_REALTIME_OFFSET` followed by the table write fields.
174-
/// Primary-key readers contain the real-time primary-key store fields; each reader's complete
175-
/// stream is sorted by full primary key then sequence number.
176+
/// preserve write order. Primary-key reader streams are sorted by full primary key then
177+
/// sequence number. Returned batches match the mode-specific physical write schema.
176178
/// Returned readers have independent mutable read state and may be operated concurrently with
177179
/// one another without external synchronization.
178180
virtual Result<std::vector<std::unique_ptr<BatchReader>>> CreateCommitReaders(
@@ -215,7 +217,6 @@ class PAIMON_EXPORT RealtimeStoreFactory {
215217
virtual ~RealtimeStoreFactory() = default;
216218

217219
/// Creates a store configured with the supplied schema, statistics, options, and memory pool.
218-
/// Creates a store for the requested table mode.
219220
/// The factory consumes `request`, including ownership of `request.write_schema`.
220221
virtual Result<std::shared_ptr<RealtimeStore>> Create(RealtimeStoreCreateRequest&& request) = 0;
221222
};

‎src/paimon/CMakeLists.txt‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ set(PAIMON_COMMON_SRCS
8686
common/global_index/bitmap_global_index_result.cpp
8787
common/global_index/global_index_result.cpp
8888
common/global_index/global_indexer_factory.cpp
89+
common/global_index/key_serializer.cpp
8990
common/io/buffered_input_stream.cpp
9091
common/io/byte_array_input_stream.cpp
9192
common/io/byte_array_output_stream.cpp
@@ -402,7 +403,12 @@ set(PAIMON_CORE_SRCS
402403
core/operation/write_context.cpp
403404
core/operation/write_restore.cpp
404405
core/realtime/arrow_realtime_store.cpp
406+
core/realtime/arrow_deduplicate_realtime_store.cpp
405407
core/realtime/arrow_realtime_store_factory.cpp
408+
core/realtime/realtime_key_offset_index.cpp
409+
core/realtime/realtime_offset_file_index_lookup.cpp
410+
core/realtime/realtime_deduplicate_state.cpp
411+
core/realtime/realtime_deduplicate_writer.cpp
406412
core/realtime/realtime_offset_batch_reader.cpp
407413
core/realtime/realtime_primary_key_reader.cpp
408414
core/realtime/realtime_append_only_writer.cpp
@@ -850,14 +856,19 @@ if(PAIMON_BUILD_TESTS)
850856
core/manifest/index_manifest_entry_serializer_test.cpp
851857
core/manifest/index_manifest_file_handler_test.cpp
852858
core/memory/writer_memory_manager_test.cpp
859+
core/realtime/arrow_deduplicate_realtime_store_test.cpp
853860
core/realtime/arrow_realtime_store_test.cpp
854861
core/realtime/primary_key_realtime_store_test.cpp
862+
core/realtime/realtime_deduplicate_state_test.cpp
863+
core/realtime/realtime_key_offset_index_test.cpp
855864
core/realtime/realtime_offset_batch_reader_test.cpp
865+
core/realtime/realtime_offset_file_index_lookup_test.cpp
856866
core/realtime/realtime_store_read_pipeline_test.cpp
857867
core/realtime/realtime_schema_layout_test.cpp
858868
core/realtime/realtime_primary_key_reader_test.cpp
859869
core/realtime/realtime_context_test.cpp
860870
core/realtime/realtime_reader_test.cpp
871+
core/realtime/realtime_utils_test.cpp
861872
core/mergetree/levels_test.cpp
862873
core/mergetree/lookup_file_test.cpp
863874
core/mergetree/lookup_levels_test.cpp

‎src/paimon/common/defs.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ const char Options::REALTIME_ENABLED[] = "realtime.enabled";
170170
const char Options::REALTIME_SPILL_ENABLED[] = "realtime.spill-enabled";
171171
const char Options::REALTIME_READ_VIEW_TTL[] = "realtime.read-view-ttl";
172172
const char Options::REALTIME_STORE_STATS_MODE[] = "realtime.store.stats-mode";
173+
const char Options::REALTIME_DEDUPLICATE_KEY_FIELDS[] = "realtime.deduplicate-key-fields";
173174
const char Options::SCAN_TIMESTAMP[] = "scan.timestamp";
174175
const char Options::SCAN_TAG_NAME[] = "scan.tag-name";
175176
const char Options::WRITE_ONLY[] = "write-only";

‎src/paimon/common/global_index/CMakeLists.txt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ set(PAIMON_GLOBAL_INDEX_SRC
3030
sorted_file_meta_selector.cpp
3131
sorted_index_file_meta.cpp
3232
btree/lazy_filtered_btree_reader.cpp
33-
key_serializer.cpp
3433
rangebitmap/range_bitmap_global_index.cpp
3534
rangebitmap/range_bitmap_global_index_factory.cpp
3635
offset_global_index_reader.cpp

‎src/paimon/common/global_index/key_serializer.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@
2626
#include "paimon/memory/bytes.h"
2727
#include "paimon/memory/memory_pool.h"
2828
#include "paimon/predicate/literal.h"
29+
#include "paimon/visibility.h"
2930
namespace paimon {
3031

3132
/// Provides core methods to serialize, deserialize, and compare global index keys.
32-
class KeySerializer {
33+
class PAIMON_EXPORT KeySerializer {
3334
public:
3435
~KeySerializer() = default;
3536

0 commit comments

Comments
 (0)