Skip to content

Commit 784b76e

Browse files
committed
feat: support real-time append writes with pluggable memory indexers
1 parent a899262 commit 784b76e

45 files changed

Lines changed: 2732 additions & 73 deletions

Some content is hidden

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

include/paimon/api.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,10 @@
3838
#include "paimon/table/source/table_scan.h" // IWYU pragma: export
3939
#include "paimon/write_context.h" // IWYU pragma: export
4040

41+
// IWYU pragma: begin_exports
42+
#include "paimon/realtime/mem_indexer.h"
43+
#include "paimon/realtime/realtime_context.h"
44+
// IWYU pragma: end_exports
45+
4146
/// Top-level namespace for Paimon C++ API.
4247
namespace paimon {}

include/paimon/file_store_commit.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "paimon/executor.h"
3131
#include "paimon/memory/memory_pool.h"
3232
#include "paimon/metrics.h"
33+
#include "paimon/realtime/realtime_commit_progress.h"
3334
#include "paimon/result.h"
3435
#include "paimon/status.h"
3536
#include "paimon/type_fwd.h"
@@ -71,6 +72,21 @@ class PAIMON_EXPORT FileStoreCommit {
7172
int64_t commit_identifier = BATCH_WRITE_COMMIT_IDENTIFIER,
7273
std::optional<int64_t> watermark = std::nullopt) = 0;
7374

75+
/// Commit sealed real-time segments and persist their partition-bucket offset progress.
76+
///
77+
/// Entries for each partition-bucket must form a contiguous range beginning after the offset
78+
/// recorded by the latest committed snapshot. Input entries may be unordered; this method
79+
/// orders them by partition, bucket, and offset before validating continuity. The resulting
80+
/// snapshot atomically publishes the data files and the updated offset map.
81+
///
82+
/// @param realtime_commits Commit messages and inclusive offset ranges to commit.
83+
/// @param commit_identifier Identifier of the streaming commit operation.
84+
/// @param watermark Optional event-time watermark.
85+
/// @return Status indicating the success or failure of the commit operation.
86+
virtual Status CommitWithProgress(const std::vector<RealtimeCommitProgress>& realtime_commits,
87+
int64_t commit_identifier,
88+
std::optional<int64_t> watermark) = 0;
89+
7490
/// Filter out all `std::vector<CommitMessage>` which have been committed and commit the
7591
/// remaining ones.
7692
///

include/paimon/file_store_write.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
#include <cstdint>
2222
#include <map>
2323
#include <memory>
24+
#include <string>
2425
#include <vector>
2526

2627
#include "paimon/commit_message.h"
2728
#include "paimon/defs.h"
2829
#include "paimon/executor.h"
2930
#include "paimon/memory/memory_pool.h"
3031
#include "paimon/metrics.h"
32+
#include "paimon/realtime/realtime_commit_progress.h"
3133
#include "paimon/result.h"
3234
#include "paimon/status.h"
3335
#include "paimon/type_fwd.h"
@@ -85,8 +87,22 @@ class PAIMON_EXPORT FileStoreWrite {
8587
///
8688
/// @return A Result containing `std::vector<std::shared_ptr<CommitMessage>>` objects,
8789
/// representing the generated commit messages.
90+
/// @note Real-time writers must use `PrepareCommitWithProgress()` so offset ranges are not
91+
/// discarded.
8892
virtual Result<std::vector<std::shared_ptr<CommitMessage>>> PrepareCommit(
8993
bool wait_compaction = true, int64_t commit_identifier = BATCH_WRITE_COMMIT_IDENTIFIER) = 0;
94+
95+
/// Generates commit messages together with partition-bucket real-time offset ranges.
96+
///
97+
/// Each range is returned atomically with the commit message generated from the same sealed
98+
/// segment.
99+
///
100+
/// @param commit_identifier Identifier of this prepare-commit operation in streaming mode.
101+
/// @return Real-time commit messages with their partition-bucket offset ranges.
102+
/// @note Calling this method on a non-real-time writer or in batch mode returns an error.
103+
virtual Result<std::vector<RealtimeCommitProgress>> PrepareCommitWithProgress(
104+
int64_t commit_identifier);
105+
90106
virtual std::shared_ptr<Metrics> GetMetrics() const = 0;
91107
virtual Status Close() = 0;
92108
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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/realtime/mem_indexer.h"
20+
21+
namespace paimon {
22+
23+
/// Factory for Paimon's default Arrow-backed `MemIndexer`.
24+
class PAIMON_EXPORT ArrowMemIndexerFactory : public MemIndexerFactory {
25+
public:
26+
/// Creates an Arrow-backed indexer for one partition and bucket.
27+
Result<std::shared_ptr<MemIndexer>> Create(
28+
::ArrowSchema* write_schema, const std::map<std::string, std::string>& options,
29+
const std::shared_ptr<MemoryPool>& memory_pool) override;
30+
};
31+
32+
} // namespace paimon
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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 <cstdint>
20+
#include <map>
21+
#include <memory>
22+
#include <optional>
23+
#include <string>
24+
#include <vector>
25+
26+
#include "paimon/reader/batch_reader.h"
27+
#include "paimon/record_batch.h"
28+
#include "paimon/result.h"
29+
#include "paimon/utils/range.h"
30+
#include "paimon/visibility.h"
31+
32+
struct ArrowSchema;
33+
34+
namespace paimon {
35+
36+
class MemoryPool;
37+
38+
/// A record batch and the contiguous offset range assigned to its rows.
39+
///
40+
/// The batch contains only the table write fields. `_OFFSET` is carried separately by
41+
/// `offset_range`; row `i` corresponds to `offset_range.from + i`. Paimon adds the physical
42+
/// `_OFFSET` column when the sealed segment is written to data files.
43+
struct PAIMON_EXPORT RealtimeWriteBatch {
44+
/// Input batch whose ownership is transferred to `MemIndexer::Write`.
45+
std::unique_ptr<RecordBatch> batch;
46+
/// Inclusive `[from, to]` offset range covered by `batch`.
47+
Range offset_range;
48+
};
49+
50+
/// Opaque handle to an immutable segment returned by `MemIndexer::SealForCommit`.
51+
///
52+
/// A plugin may store the segment in memory or in spill files. Callers use this handle only to
53+
/// request commit readers and inspect its offset range.
54+
class PAIMON_EXPORT RealtimeSegmentHandle {
55+
public:
56+
virtual ~RealtimeSegmentHandle() = default;
57+
58+
/// Returns the inclusive offset range covered by this segment.
59+
virtual Range GetOffsetRange() const = 0;
60+
};
61+
62+
/// Plugin interface for buffering real-time writes before Paimon data-file generation.
63+
///
64+
/// Paimon serializes calls to `Write` and `SealForCommit` for the same indexer. After sealing,
65+
/// `CreateCommitReaders` may read the immutable sealed segment while later `Write` calls append to
66+
/// a new building segment. Paimon retains control of file format, rolling, indexes, and
67+
/// commit-message generation.
68+
class PAIMON_EXPORT MemIndexer {
69+
public:
70+
virtual ~MemIndexer() = default;
71+
72+
/// Adds a batch to the current building segment.
73+
///
74+
/// The number of rows must equal the size of `offset_range`.
75+
virtual Status Write(RealtimeWriteBatch&& batch) = 0;
76+
77+
/// Seals the current building data and opens a new building segment.
78+
///
79+
/// Returns an immutable segment handle, or `std::nullopt` when there is no data to seal.
80+
virtual Result<std::optional<std::shared_ptr<RealtimeSegmentHandle>>> SealForCommit() = 0;
81+
82+
/// Creates readers that expose all rows in a sealed segment for Paimon file writing.
83+
///
84+
/// Concatenating the returned readers must produce every sealed row exactly once and in write
85+
/// order. Each output batch contains `_VALUE_KIND` followed by all fields from the factory's
86+
/// `write_schema`; it does not contain `_OFFSET`.
87+
virtual Result<std::vector<std::unique_ptr<BatchReader>>> CreateCommitReaders(
88+
const std::shared_ptr<RealtimeSegmentHandle>& segment) = 0;
89+
90+
/// Returns the number of bytes currently retained by the building segment.
91+
virtual uint64_t GetMemoryUsage() const = 0;
92+
93+
/// Releases resources owned by this indexer and rejects subsequent writes or seals.
94+
virtual Status Close() = 0;
95+
};
96+
97+
/// Factory for application-provided `MemIndexer` implementations.
98+
class PAIMON_EXPORT MemIndexerFactory {
99+
public:
100+
virtual ~MemIndexerFactory() = default;
101+
102+
/// Creates an indexer configured with the supplied schema, options, and memory pool.
103+
///
104+
/// `write_schema` uses the Arrow C Data Interface and contains the table fields accepted by
105+
/// `Write`. It is valid only during this call. An implementation may consume its contents by
106+
/// using an Arrow C Data Interface importer; otherwise Paimon releases them after this method
107+
/// returns.
108+
/// @param write_schema Table write schema without Paimon-generated real-time fields.
109+
/// @param options Effective table options available to the indexer.
110+
/// @param memory_pool Memory pool provided by the write context.
111+
virtual Result<std::shared_ptr<MemIndexer>> Create(
112+
::ArrowSchema* write_schema, const std::map<std::string, std::string>& options,
113+
const std::shared_ptr<MemoryPool>& memory_pool) = 0;
114+
};
115+
116+
} // namespace paimon
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 <cstdint>
20+
#include <memory>
21+
#include <string>
22+
23+
#include "paimon/commit_message.h"
24+
#include "paimon/utils/range.h"
25+
#include "paimon/visibility.h"
26+
27+
namespace paimon {
28+
29+
/// A real-time commit message and its partition-bucket offset progress.
30+
///
31+
/// Offsets are scoped to one partition and bucket. `offset_range` is inclusive and covers all
32+
/// rows represented by `commit_message`. The progress fields are not embedded in
33+
/// `CommitMessage` serialization.
34+
struct PAIMON_EXPORT RealtimeCommitProgress {
35+
/// Paimon commit message generated from one sealed segment.
36+
std::shared_ptr<CommitMessage> commit_message;
37+
/// Partition path such as `dt=2`, or an empty string for an unpartitioned table.
38+
std::string partition;
39+
/// Bucket containing the sealed segment.
40+
int32_t bucket;
41+
/// Inclusive offset range represented by the commit message.
42+
Range offset_range;
43+
};
44+
45+
} // namespace paimon
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 <cstdint>
20+
#include <map>
21+
#include <memory>
22+
#include <string>
23+
24+
#include "paimon/result.h"
25+
#include "paimon/visibility.h"
26+
27+
struct ArrowSchema;
28+
29+
namespace paimon {
30+
31+
class MemIndexer;
32+
class MemIndexerFactory;
33+
class MemoryPool;
34+
35+
/// Shared context that owns the `MemIndexer` instances used by a real-time writer.
36+
///
37+
/// Applications attach one context to `WriteContext`. The context uses either the default Arrow
38+
/// implementation or an application-provided factory and keeps each created indexer available
39+
/// across multiple writes and prepare-commit operations.
40+
class PAIMON_EXPORT RealtimeContext {
41+
public:
42+
/// Creates a context backed by Paimon's default Arrow `MemIndexer`.
43+
static Result<std::shared_ptr<RealtimeContext>> Create();
44+
45+
/// Creates a context backed by an application-provided indexer factory.
46+
///
47+
/// @param factory Non-null factory used to create indexers on demand.
48+
static Result<std::shared_ptr<RealtimeContext>> Create(
49+
const std::shared_ptr<MemIndexerFactory>& factory);
50+
51+
/// Returns the stable indexer associated with a partition and bucket, creating it if needed.
52+
Result<std::shared_ptr<MemIndexer>> GetOrCreateMemIndexer(
53+
const std::string& partition, int32_t bucket, std::unique_ptr<::ArrowSchema> write_schema,
54+
const std::map<std::string, std::string>& options,
55+
const std::shared_ptr<MemoryPool>& memory_pool);
56+
57+
~RealtimeContext();
58+
59+
private:
60+
class Impl;
61+
62+
explicit RealtimeContext(std::unique_ptr<Impl>&& impl);
63+
64+
std::unique_ptr<Impl> impl_;
65+
};
66+
67+
} // namespace paimon

include/paimon/type_fwd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class FileStatus;
5656
class BasicFileStatus;
5757

5858
class RecordBatch;
59+
class RealtimeContext;
5960

6061
class FormatWriter;
6162

include/paimon/utils/special_field_ids.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ class SpecialFieldIds {
3939
static const int32_t ROW_KIND = std::numeric_limits<int32_t>::max() - 3;
4040
/// Special field ID reserved for row ID. Value: INT32_MAX - 5
4141
static const int32_t ROW_ID = std::numeric_limits<int32_t>::max() - 5;
42-
4342
/// Special field ID reserved for index score. Value: CPP_FIELD_ID_END - 1
4443
static const int32_t INDEX_SCORE = CPP_FIELD_ID_END - 1;
44+
// TODO(xinyu.lxy): Add _OFFSET to the Java codebase so compaction preserves its information.
45+
/// Special field ID reserved for real-time offset. Value: CPP_FIELD_ID_END - 2
46+
static const int32_t OFFSET = CPP_FIELD_ID_END - 2;
4547
};
4648

4749
} // namespace paimon

0 commit comments

Comments
 (0)