This is an automated email from the ASF dual-hosted git repository.

lxy-9602 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 200e4850 perf(write): write input changelog and data files in parallel 
(#350)
200e4850 is described below

commit 200e485041a82bc3feb10d7eac3e0997fc681168
Author: lszskye <[email protected]>
AuthorDate: Wed Sep 16 22:15:02 2026 -0700

    perf(write): write input changelog and data files in parallel (#350)
---
 include/paimon/defs.h                              |   5 +
 src/paimon/common/defs.cpp                         |   2 +
 src/paimon/core/core_options.cpp                   |   9 +
 src/paimon/core/core_options.h                     |   1 +
 src/paimon/core/core_options_test.cpp              |   3 +
 src/paimon/core/mergetree/merge_tree_writer.cpp    | 252 ++++++++++++++-------
 src/paimon/core/mergetree/merge_tree_writer.h      |   3 +
 .../core/mergetree/merge_tree_writer_test.cpp      |  50 +++-
 8 files changed, 236 insertions(+), 89 deletions(-)

diff --git a/include/paimon/defs.h b/include/paimon/defs.h
index cfa08fa0..f51eb452 100644
--- a/include/paimon/defs.h
+++ b/include/paimon/defs.h
@@ -421,6 +421,11 @@ struct PAIMON_EXPORT Options {
     /// "changelog-producer.row-deduplicate" is "true".
     static const char CHANGELOG_PRODUCER_ROW_DEDUPLICATE_IGNORE_FIELDS[];
 
+    /// "changelog-producer.input.parallel-write" - Whether data and changelog 
files are
+    /// written in parallel for input mode changelog producer. Default value 
is "true".This
+    /// configuration is specific to cpp paimon.
+    static const char CHANGELOG_PRODUCER_INPUT_PARALLEL_WRITE[];
+
     /// "changelog-file.prefix" - Specify the file name prefix of changelog 
files. Default value is
     /// "changelog-".
     static const char CHANGELOG_FILE_PREFIX[];
diff --git a/src/paimon/common/defs.cpp b/src/paimon/common/defs.cpp
index de1d4577..88085f78 100644
--- a/src/paimon/common/defs.cpp
+++ b/src/paimon/common/defs.cpp
@@ -104,6 +104,8 @@ const char Options::CHANGELOG_PRODUCER[] = 
"changelog-producer";
 const char Options::CHANGELOG_PRODUCER_ROW_DEDUPLICATE[] = 
"changelog-producer.row-deduplicate";
 const char Options::CHANGELOG_PRODUCER_ROW_DEDUPLICATE_IGNORE_FIELDS[] =
     "changelog-producer.row-deduplicate-ignore-fields";
+const char Options::CHANGELOG_PRODUCER_INPUT_PARALLEL_WRITE[] =
+    "changelog-producer.input.parallel-write";
 const char Options::CHANGELOG_FILE_PREFIX[] = "changelog-file.prefix";
 const char Options::CHANGELOG_FILE_FORMAT[] = "changelog-file.format";
 const char Options::CHANGELOG_FILE_COMPRESSION[] = 
"changelog-file.compression";
diff --git a/src/paimon/core/core_options.cpp b/src/paimon/core/core_options.cpp
index 37fadc8e..bc1bb994 100644
--- a/src/paimon/core/core_options.cpp
+++ b/src/paimon/core/core_options.cpp
@@ -447,6 +447,7 @@ struct CoreOptions::Impl {
     bool force_lookup = false;
     bool lookup_wait = true;
     bool changelog_row_deduplicate = false;
+    bool input_changelog_parallel_write_enabled = true;
     bool partial_update_remove_record_on_delete = false;
     bool aggregation_remove_record_on_delete = false;
     bool table_read_sequence_number_enabled = false;
@@ -752,6 +753,10 @@ struct CoreOptions::Impl {
         PAIMON_RETURN_NOT_OK(parser.ParseList<std::string>(
             Options::CHANGELOG_PRODUCER_ROW_DEDUPLICATE_IGNORE_FIELDS, 
Options::FIELDS_SEPARATOR,
             &changelog_row_deduplicate_ignore_fields, /*need_trim=*/true));
+        // Parse changelog-producer.input.parallel-write - write data and 
input changelog
+        // files in parallel, default true.
+        
PAIMON_RETURN_NOT_OK(parser.Parse<bool>(Options::CHANGELOG_PRODUCER_INPUT_PARALLEL_WRITE,
+                                                
&input_changelog_parallel_write_enabled));
         // Parse partial-update.remove-record-on-delete - remove whole row on 
delete
         
PAIMON_RETURN_NOT_OK(parser.Parse<bool>(Options::PARTIAL_UPDATE_REMOVE_RECORD_ON_DELETE,
                                                 
&partial_update_remove_record_on_delete));
@@ -1630,6 +1635,10 @@ const std::vector<std::string>& 
CoreOptions::GetChangelogRowDeduplicateIgnoreFie
     return impl_->changelog_row_deduplicate_ignore_fields;
 }
 
+bool CoreOptions::InputChangelogParallelWriteEnabled() const {
+    return impl_->input_changelog_parallel_write_enabled;
+}
+
 std::string CoreOptions::ChangelogFilePrefix() const {
     return impl_->changelog_file_prefix;
 }
diff --git a/src/paimon/core/core_options.h b/src/paimon/core/core_options.h
index 86771e6a..6dadc680 100644
--- a/src/paimon/core/core_options.h
+++ b/src/paimon/core/core_options.h
@@ -210,6 +210,7 @@ class PAIMON_EXPORT CoreOptions {
     ChangelogProducer GetChangelogProducer() const;
     bool ChangelogRowDeduplicate() const;
     const std::vector<std::string>& GetChangelogRowDeduplicateIgnoreFields() 
const;
+    bool InputChangelogParallelWriteEnabled() const;
     std::string ChangelogFilePrefix() const;
     std::shared_ptr<FileFormat> GetChangelogFileFormat() const;
     std::optional<std::string> GetChangelogFileCompression() const;
diff --git a/src/paimon/core/core_options_test.cpp 
b/src/paimon/core/core_options_test.cpp
index 3e489a4c..be9fa823 100644
--- a/src/paimon/core/core_options_test.cpp
+++ b/src/paimon/core/core_options_test.cpp
@@ -131,6 +131,7 @@ TEST(CoreOptionsTest, TestDefaultValue) {
     ASSERT_EQ(ChangelogProducer::NONE, core_options.GetChangelogProducer());
     ASSERT_FALSE(core_options.ChangelogRowDeduplicate());
     ASSERT_TRUE(core_options.GetChangelogRowDeduplicateIgnoreFields().empty());
+    ASSERT_TRUE(core_options.InputChangelogParallelWriteEnabled());
     ASSERT_EQ("changelog-", core_options.ChangelogFilePrefix());
     ASSERT_FALSE(core_options.NeedLookup());
     ASSERT_FALSE(core_options.PrepareCommitWaitCompaction());
@@ -258,6 +259,7 @@ TEST(CoreOptionsTest, TestFromMap) {
         {Options::CHANGELOG_PRODUCER, "full-compaction"},
         {Options::CHANGELOG_PRODUCER_ROW_DEDUPLICATE, "true"},
         {Options::CHANGELOG_PRODUCER_ROW_DEDUPLICATE_IGNORE_FIELDS, "f0, f2"},
+        {Options::CHANGELOG_PRODUCER_INPUT_PARALLEL_WRITE, "false"},
         {Options::CHANGELOG_FILE_PREFIX, "test-changelog-"},
         {Options::CHANGELOG_FILE_COMPRESSION, "lz4"},
         {Options::FORCE_LOOKUP, "true"},
@@ -410,6 +412,7 @@ TEST(CoreOptionsTest, TestFromMap) {
     ASSERT_TRUE(core_options.ChangelogRowDeduplicate());
     ASSERT_EQ(std::vector<std::string>({"f0", "f2"}),
               core_options.GetChangelogRowDeduplicateIgnoreFields());
+    ASSERT_FALSE(core_options.InputChangelogParallelWriteEnabled());
     ASSERT_EQ("test-changelog-", core_options.ChangelogFilePrefix());
     ASSERT_EQ(std::optional<std::string>("lz4"), 
core_options.GetChangelogFileCompression());
     ASSERT_TRUE(core_options.NeedLookup());
diff --git a/src/paimon/core/mergetree/merge_tree_writer.cpp 
b/src/paimon/core/mergetree/merge_tree_writer.cpp
index 88df1b16..e5ac1af6 100644
--- a/src/paimon/core/mergetree/merge_tree_writer.cpp
+++ b/src/paimon/core/mergetree/merge_tree_writer.cpp
@@ -20,6 +20,7 @@
 
 #include <algorithm>
 #include <cassert>
+#include <future>
 #include <limits>
 #include <map>
 #include <unordered_set>
@@ -49,6 +50,55 @@
 
 namespace paimon {
 
+namespace {
+
+Status ValidateReaders(const 
std::vector<std::unique_ptr<KeyValueRecordReader>>& readers) {
+    if (readers.empty()) {
+        return Status::Invalid("sorted readers must not be empty");
+    }
+    for (const std::unique_ptr<KeyValueRecordReader>& reader : readers) {
+        if (reader == nullptr) {
+            return Status::Invalid("sorted readers must not contain null 
reader");
+        }
+    }
+    return Status::OK();
+}
+
+std::unique_ptr<AsyncKeyValueProducerAndConsumer<KeyValue, KeyValueBatch>>
+CreateSortMergeProducerAndConsumer(
+    std::unique_ptr<SortMergeReader>&& sort_merge_reader, int32_t 
write_batch_size,
+    const AsyncKeyValueProducerAndConsumer<KeyValue, 
KeyValueBatch>::ConsumerCreator&
+        create_consumer) {
+    assert(sort_merge_reader != nullptr);
+    std::unique_ptr<AsyncKeyValueBatchProducer> producer =
+        
std::make_unique<SortMergeReaderBatchProducer>(std::move(sort_merge_reader),
+                                                       write_batch_size);
+    return std::make_unique<AsyncKeyValueProducerAndConsumer<KeyValue, 
KeyValueBatch>>(
+        std::move(producer), create_consumer, /*consumer_thread_num=*/1);
+}
+
+Result<std::vector<std::shared_ptr<DataFileMeta>>> WriteKeyValueBatchesToFiles(
+    AsyncKeyValueProducerAndConsumer<KeyValue, KeyValueBatch>* 
producer_and_consumer,
+    RollingFileWriter<KeyValueBatch, std::shared_ptr<DataFileMeta>>* 
rolling_writer) {
+    assert(producer_and_consumer != nullptr);
+    assert(rolling_writer != nullptr);
+
+    while (true) {
+        PAIMON_ASSIGN_OR_RAISE(KeyValueBatch key_value_batch, 
producer_and_consumer->NextBatch());
+        if (key_value_batch.batch == nullptr) {
+            break;
+        }
+        
PAIMON_RETURN_NOT_OK(rolling_writer->Write(std::move(key_value_batch)));
+    }
+
+    PAIMON_RETURN_NOT_OK(rolling_writer->Close());
+    PAIMON_ASSIGN_OR_RAISE(std::vector<std::shared_ptr<DataFileMeta>> 
flushed_files,
+                           rolling_writer->GetResult());
+    return flushed_files;
+}
+
+}  // namespace
+
 Result<std::shared_ptr<MergeTreeWriter>> MergeTreeWriter::Create(
     int64_t last_sequence_number, const std::vector<std::string>& 
trimmed_primary_keys,
     const std::shared_ptr<DataFilePathFactory>& path_factory,
@@ -169,14 +219,7 @@ Status MergeTreeWriter::WriteSortedReadersToFiles(
             }
         }
     });
-    if (readers.empty()) {
-        return Status::Invalid("sorted readers must not be empty");
-    }
-    for (const std::unique_ptr<KeyValueRecordReader>& reader : readers) {
-        if (reader == nullptr) {
-            return Status::Invalid("sorted readers must not contain null 
reader");
-        }
-    }
+    PAIMON_RETURN_NOT_OK(ValidateReaders(readers));
 
     // prepare loser tree sort merge reader
     auto sort_merge_reader = std::make_unique<SortMergeReaderWithLoserTree>(
@@ -187,28 +230,18 @@ Status MergeTreeWriter::WriteSortedReadersToFiles(
         -> Result<std::unique_ptr<RowToArrowArrayConverter<KeyValue, 
KeyValueBatch>>> {
         return KeyValueMetaProjectionConsumer::Create(target_schema, pool);
     };
-    // consumer batch size is WriteBatchSize
-    std::unique_ptr<AsyncKeyValueBatchProducer> producer =
-        
std::make_unique<SortMergeReaderBatchProducer>(std::move(sort_merge_reader),
-                                                       
options_.GetWriteBatchSize());
-    auto async_key_value_producer_consumer =
-        std::make_unique<AsyncKeyValueProducerAndConsumer<KeyValue, 
KeyValueBatch>>(
-            std::move(producer), create_consumer, /*projection_thread_num=*/1);
-    ScopeGuard async_readers_guard([&]() -> void { 
async_key_value_producer_consumer->Close(); });
+    std::unique_ptr<AsyncKeyValueProducerAndConsumer<KeyValue, KeyValueBatch>>
+        producer_and_consumer = CreateSortMergeProducerAndConsumer(
+            std::move(sort_merge_reader), options_.GetWriteBatchSize(), 
create_consumer);
+    ScopeGuard producer_guard([&]() -> void { producer_and_consumer->Close(); 
});
     std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>> rolling_writer;
     PAIMON_ASSIGN_OR_RAISE(rolling_writer, CreateRollingRowWriter());
     ScopeGuard abort_writer_guard([&]() -> void { rolling_writer->Abort(); });
-    while (true) {
-        PAIMON_ASSIGN_OR_RAISE(KeyValueBatch key_value_batch,
-                               async_key_value_producer_consumer->NextBatch());
-        if (key_value_batch.batch == nullptr) {
-            break;
-        }
-        
PAIMON_RETURN_NOT_OK(rolling_writer->Write(std::move(key_value_batch)));
-    }
-    PAIMON_RETURN_NOT_OK(rolling_writer->Close());
-    PAIMON_ASSIGN_OR_RAISE(std::vector<std::shared_ptr<DataFileMeta>> 
flushed_files,
-                           rolling_writer->GetResult());
+    PAIMON_ASSIGN_OR_RAISE(
+        std::vector<std::shared_ptr<DataFileMeta>> flushed_files,
+        WriteKeyValueBatchesToFiles(producer_and_consumer.get(), 
rolling_writer.get()));
+    producer_and_consumer->Close();
+    producer_guard.Release();
     abort_writer_guard.Release();
 
     for (const std::shared_ptr<DataFileMeta>& flushed_file : flushed_files) {
@@ -336,60 +369,109 @@ Status MergeTreeWriter::FlushWriteBuffer(bool 
wait_for_latest_compaction,
             return KeyValueMetaProjectionConsumer::Create(target_schema, pool);
         };
 
-        std::unique_ptr<AsyncKeyValueProducerAndConsumer<KeyValue, 
KeyValueBatch>>
-            async_changelog_producer_consumer;
-        std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>
-            changelog_writer;
-        std::vector<std::shared_ptr<DataFileMeta>> flushed_changelog_files;
-        ScopeGuard changelog_write_guard([&]() -> void {
-            if (changelog_writer) {
-                changelog_writer->Abort();
-            }
-            if (async_changelog_producer_consumer) {
-                async_changelog_producer_consumer->Close();
-            }
-        });
         if (options_.GetChangelogProducer() == ChangelogProducer::INPUT) {
             
PAIMON_ASSIGN_OR_RAISE(std::vector<std::unique_ptr<KeyValueRecordReader>> 
raw_readers,
                                    write_buffer_->CreateRawReaders());
+            ScopeGuard raw_readers_guard([&]() -> void {
+                for (std::unique_ptr<KeyValueRecordReader>& reader : 
raw_readers) {
+                    if (reader != nullptr) {
+                        reader->Close();
+                    }
+                }
+            });
+            
PAIMON_ASSIGN_OR_RAISE(std::vector<std::unique_ptr<KeyValueRecordReader>> 
readers,
+                                   write_buffer_->CreateReaders());
+            ScopeGuard readers_guard([&]() -> void {
+                for (std::unique_ptr<KeyValueRecordReader>& reader : readers) {
+                    if (reader != nullptr) {
+                        reader->Close();
+                    }
+                }
+            });
+            PAIMON_RETURN_NOT_OK(ValidateReaders(raw_readers));
+            PAIMON_RETURN_NOT_OK(ValidateReaders(readers));
+
             auto raw_sort_merge_reader = 
std::make_unique<SortMergeReaderWithMinHeap>(
                 std::move(raw_readers), key_comparator_, 
user_defined_seq_comparator_,
                 /*merge_function_wrapper=*/nullptr);
-            std::unique_ptr<AsyncKeyValueBatchProducer> producer =
-                
std::make_unique<SortMergeReaderBatchProducer>(std::move(raw_sort_merge_reader),
-                                                               
options_.GetWriteBatchSize());
-            async_changelog_producer_consumer =
-                std::make_unique<AsyncKeyValueProducerAndConsumer<KeyValue, 
KeyValueBatch>>(
-                    std::move(producer), create_consumer, 
/*projection_thread_num=*/1);
+            raw_readers_guard.Release();
+            auto sort_merge_reader = 
std::make_unique<SortMergeReaderWithLoserTree>(
+                std::move(readers), key_comparator_, 
user_defined_seq_comparator_,
+                merge_function_wrapper_);
+            readers_guard.Release();
+
+            const int32_t write_batch_size = options_.GetWriteBatchSize();
+            std::unique_ptr<AsyncKeyValueProducerAndConsumer<KeyValue, 
KeyValueBatch>>
+                changelog_producer_and_consumer = 
CreateSortMergeProducerAndConsumer(
+                    std::move(raw_sort_merge_reader), write_batch_size, 
create_consumer);
+            std::unique_ptr<AsyncKeyValueProducerAndConsumer<KeyValue, 
KeyValueBatch>>
+                data_producer_and_consumer = 
CreateSortMergeProducerAndConsumer(
+                    std::move(sort_merge_reader), write_batch_size, 
create_consumer);
+            ScopeGuard producers_guard([&]() -> void {
+                data_producer_and_consumer->Close();
+                changelog_producer_and_consumer->Close();
+            });
+
+            // Writers retain key rows backed by the producers' source 
readers. On error, this
+            // declaration order destroys the writers before the producer 
guard closes the readers.
+            std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>
+                changelog_writer;
             PAIMON_ASSIGN_OR_RAISE(changelog_writer, 
CreateRollingChangelogWriter());
-            while (true) {
-                PAIMON_ASSIGN_OR_RAISE(KeyValueBatch key_value_batch,
-                                       
async_changelog_producer_consumer->NextBatch());
-                if (key_value_batch.batch == nullptr) {
-                    break;
+            ScopeGuard changelog_writer_guard([&]() -> void { 
changelog_writer->Abort(); });
+            std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>
+                data_writer;
+            PAIMON_ASSIGN_OR_RAISE(data_writer, CreateRollingRowWriter());
+            ScopeGuard data_writer_guard([&]() -> void { data_writer->Abort(); 
});
+
+            std::vector<std::shared_ptr<DataFileMeta>> data_files;
+            std::vector<std::shared_ptr<DataFileMeta>> changelog_files;
+            if (options_.InputChangelogParallelWriteEnabled()) {
+                
std::future<Result<std::vector<std::shared_ptr<DataFileMeta>>>> 
changelog_future =
+                    std::async(std::launch::async, [producer_and_consumer =
+                                                        
changelog_producer_and_consumer.get(),
+                                                    writer = 
changelog_writer.get()]() {
+                        return 
WriteKeyValueBatchesToFiles(producer_and_consumer, writer);
+                    });
+                Result<std::vector<std::shared_ptr<DataFileMeta>>> data_result 
=
+                    
WriteKeyValueBatchesToFiles(data_producer_and_consumer.get(),
+                                                data_writer.get());
+                Result<std::vector<std::shared_ptr<DataFileMeta>>> 
changelog_result =
+                    changelog_future.get();
+                if (!data_result.ok()) {
+                    return data_result.status();
                 }
-                
PAIMON_RETURN_NOT_OK(changelog_writer->Write(std::move(key_value_batch)));
+                if (!changelog_result.ok()) {
+                    return changelog_result.status();
+                }
+                data_files = std::move(data_result).value();
+                changelog_files = std::move(changelog_result).value();
+            } else {
+                PAIMON_ASSIGN_OR_RAISE(changelog_files, 
WriteKeyValueBatchesToFiles(
+                                                            
changelog_producer_and_consumer.get(),
+                                                            
changelog_writer.get()));
+                PAIMON_ASSIGN_OR_RAISE(data_files,
+                                       
WriteKeyValueBatchesToFiles(data_producer_and_consumer.get(),
+                                                                   
data_writer.get()));
             }
-            PAIMON_RETURN_NOT_OK(changelog_writer->Close());
-            PAIMON_ASSIGN_OR_RAISE(flushed_changelog_files, 
changelog_writer->GetResult());
-        }
-
-        // 1. flush write buffer to get sorted readers
-        // Flush write buffer to get sorted and merged data readers.
-        
PAIMON_ASSIGN_OR_RAISE(std::vector<std::unique_ptr<KeyValueRecordReader>> 
readers,
-                               write_buffer_->CreateReaders());
-        PAIMON_RETURN_NOT_OK(WriteSortedReadersToFiles(std::move(readers)));
-        if (async_changelog_producer_consumer) {
-            async_changelog_producer_consumer->Close();
-        }
-
-        new_changelog_files_.insert(new_changelog_files_.end(), 
flushed_changelog_files.begin(),
-                                    flushed_changelog_files.end());
-
-        changelog_write_guard.Release();
 
-        if (changelog_writer) {
+            data_producer_and_consumer->Close();
+            changelog_producer_and_consumer->Close();
+            producers_guard.Release();
+            changelog_writer_guard.Release();
+            data_writer_guard.Release();
+            new_changelog_files_.insert(new_changelog_files_.end(), 
changelog_files.begin(),
+                                        changelog_files.end());
+            new_files_.insert(new_files_.end(), data_files.begin(), 
data_files.end());
+            for (const std::shared_ptr<DataFileMeta>& flushed_file : 
data_files) {
+                
PAIMON_RETURN_NOT_OK(compact_manager_->AddNewFile(flushed_file));
+            }
+            metrics_->Merge(data_writer->GetMetrics());
             metrics_->Merge(changelog_writer->GetMetrics());
+        } else {
+            // Flush write buffer to get sorted and merged data readers.
+            
PAIMON_ASSIGN_OR_RAISE(std::vector<std::unique_ptr<KeyValueRecordReader>> 
readers,
+                                   write_buffer_->CreateReaders());
+            
PAIMON_RETURN_NOT_OK(WriteSortedReadersToFiles(std::move(readers)));
         }
     }
     PAIMON_RETURN_NOT_OK(TrySyncLatestCompaction(wait_for_latest_compaction));
@@ -417,28 +499,24 @@ Result<CommitIncrement> MergeTreeWriter::DrainIncrement() 
{
 
 Result<std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>>
 MergeTreeWriter::CreateRollingRowWriter() const {
-    PAIMON_ASSIGN_OR_RAISE(
-        std::shared_ptr<KeyValueDataFileWriterFactories::WriterFactory> 
factory,
-        KeyValueDataFileWriterFactories::Create(options_, schema_id_, 
write_schema_, /*level=*/0,
-                                                FileSource::Append(), 
trimmed_primary_keys_,
-                                                path_factory_, 
/*create_stats_extractor=*/true,
-                                                /*is_changelog=*/false, 
pool_));
-    return std::make_unique<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>(
-        options_.GetTargetFileSize(/*has_primary_key=*/true), 
options_.GetTargetFileRowNum(),
-        factory);
+    return CreateRollingWriter(/*is_changelog=*/false, 
options_.GetTargetFileRowNum());
 }
 
 Result<std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>>
 MergeTreeWriter::CreateRollingChangelogWriter() const {
-    PAIMON_ASSIGN_OR_RAISE(
-        std::shared_ptr<KeyValueDataFileWriterFactories::WriterFactory> 
factory,
-        KeyValueDataFileWriterFactories::Create(options_, schema_id_, 
write_schema_, /*level=*/0,
-                                                FileSource::Append(), 
trimmed_primary_keys_,
-                                                path_factory_, 
/*create_stats_extractor=*/true,
-                                                /*is_changelog=*/true, pool_));
+    return CreateRollingWriter(/*is_changelog=*/true,
+                               
/*target_file_row_num=*/std::numeric_limits<int64_t>::max());
+}
+
+Result<std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>>
+MergeTreeWriter::CreateRollingWriter(bool is_changelog, int64_t 
target_file_row_num) const {
+    
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<KeyValueDataFileWriterFactories::WriterFactory>
 factory,
+                           KeyValueDataFileWriterFactories::Create(
+                               options_, schema_id_, write_schema_, 
/*level=*/0,
+                               FileSource::Append(), trimmed_primary_keys_, 
path_factory_,
+                               /*create_stats_extractor=*/true, is_changelog, 
pool_));
     return std::make_unique<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>(
-        options_.GetTargetFileSize(/*has_primary_key=*/true),
-        /*target_file_row_num=*/std::numeric_limits<int64_t>::max(), factory);
+        options_.GetTargetFileSize(/*has_primary_key=*/true), 
target_file_row_num, factory);
 }
 
 }  // namespace paimon
diff --git a/src/paimon/core/mergetree/merge_tree_writer.h 
b/src/paimon/core/mergetree/merge_tree_writer.h
index 9f73e43b..1a2c2c3d 100644
--- a/src/paimon/core/mergetree/merge_tree_writer.h
+++ b/src/paimon/core/mergetree/merge_tree_writer.h
@@ -108,6 +108,9 @@ class MergeTreeWriter : public BatchWriter {
     Result<std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>>
     CreateRollingChangelogWriter() const;
 
+    Result<std::unique_ptr<RollingFileWriter<KeyValueBatch, 
std::shared_ptr<DataFileMeta>>>>
+    CreateRollingWriter(bool is_changelog, int64_t target_file_row_num) const;
+
     Status TrySyncLatestCompaction(bool blocking);
     Status UpdateCompactResult(const std::shared_ptr<CompactResult>& 
compact_result);
     Status UpdateCompactDeletionFile(const 
std::shared_ptr<CompactDeletionFile>& new_deletion_file);
diff --git a/src/paimon/core/mergetree/merge_tree_writer_test.cpp 
b/src/paimon/core/mergetree/merge_tree_writer_test.cpp
index 8a152e2f..e557af32 100644
--- a/src/paimon/core/mergetree/merge_tree_writer_test.cpp
+++ b/src/paimon/core/mergetree/merge_tree_writer_test.cpp
@@ -396,8 +396,12 @@ TEST_P(MergeTreeWriterTest, TestInputChangelog) {
     const DataIncrement& data_increment = 
commit_increment.GetNewFilesIncrement();
     ASSERT_EQ(1, data_increment.NewFiles().size());
     ASSERT_EQ(1, data_increment.ChangelogFiles().size());
-    ASSERT_EQ("data-" + uuid + "-1.orc", 
data_increment.NewFiles()[0]->file_name);
-    ASSERT_EQ("changes-" + uuid + "-0.parquet", 
data_increment.ChangelogFiles()[0]->file_name);
+    ASSERT_TRUE(
+        StringUtils::StartsWith(data_increment.NewFiles()[0]->file_name, 
"data-" + uuid + "-"));
+    ASSERT_TRUE(StringUtils::EndsWith(data_increment.NewFiles()[0]->file_name, 
".orc"));
+    
ASSERT_TRUE(StringUtils::StartsWith(data_increment.ChangelogFiles()[0]->file_name,
+                                        "changes-" + uuid + "-"));
+    
ASSERT_TRUE(StringUtils::EndsWith(data_increment.ChangelogFiles()[0]->file_name,
 ".parquet"));
     ASSERT_EQ(2, data_increment.NewFiles()[0]->row_count);
     ASSERT_EQ(4, data_increment.ChangelogFiles()[0]->row_count);
 
@@ -423,6 +427,48 @@ TEST_P(MergeTreeWriterTest, TestInputChangelog) {
                      expected_changelog, "parquet");
 }
 
+TEST_P(MergeTreeWriterTest, TestInputChangelogWithParallelWriteDisabled) {
+    ASSERT_OK_AND_ASSIGN(
+        CoreOptions options,
+        CoreOptions::FromMap({{Options::FILE_FORMAT, "orc"},
+                              {Options::CHANGELOG_PRODUCER, "input"},
+                              
{Options::CHANGELOG_PRODUCER_INPUT_PARALLEL_WRITE, "false"}}));
+
+    auto dir = UniqueTestDirectory::Create();
+    ASSERT_TRUE(dir);
+    auto path_factory = std::make_shared<DataFilePathFactory>();
+    ASSERT_OK(path_factory->Init(dir->Str(), "orc", options.DataFilePrefix(), 
nullptr));
+    std::string uuid = path_factory->uuid_;
+
+    ASSERT_OK_AND_ASSIGN(auto merge_writer,
+                         CreateMergeWriter(/*last_sequence_number=*/-1, 
dir->Str(), path_factory,
+                                           /*schema_id=*/1, options));
+    std::shared_ptr<arrow::Array> array =
+        arrow::ipc::internal::json::ArrayFromJSON(value_type_, R"([
+      ["Bob", 20, 0, 20.0],
+      ["Alice", 10, 0, 10.0],
+      ["Alice", 11, 0, 11.0],
+      ["Bob", 21, 0, 21.0]
+    ])")
+            .ValueOrDie();
+    WriteBatch(array, /*row_kinds=*/{}, merge_writer.get());
+    if (GetParam()) {
+        ASSERT_OK(merge_writer->FlushMemory());
+    }
+
+    ASSERT_OK_AND_ASSIGN(CommitIncrement commit_increment,
+                         
merge_writer->PrepareCommit(/*wait_compaction=*/false));
+    ASSERT_OK(merge_writer->Close());
+
+    const DataIncrement& data_increment = 
commit_increment.GetNewFilesIncrement();
+    ASSERT_EQ(1, data_increment.NewFiles().size());
+    ASSERT_EQ(1, data_increment.ChangelogFiles().size());
+    ASSERT_EQ("changelog-" + uuid + "-0.orc", 
data_increment.ChangelogFiles()[0]->file_name);
+    ASSERT_EQ("data-" + uuid + "-1.orc", 
data_increment.NewFiles()[0]->file_name);
+    ASSERT_EQ(4, data_increment.ChangelogFiles()[0]->row_count);
+    ASSERT_EQ(2, data_increment.NewFiles()[0]->row_count);
+}
+
 TEST_P(MergeTreeWriterTest, TestInputChangelogIgnoresTargetFileRowNum) {
     ASSERT_OK_AND_ASSIGN(CoreOptions options,
                          CoreOptions::FromMap({{Options::FILE_FORMAT, "orc"},

Reply via email to