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

suxiaogang223 pushed a commit to branch feature/paimon-jni-write-v1
in repository https://gitbox.apache.org/repos/asf/doris.git

commit b95e0706f816ea8eb414f020e63658eb0b88d2bf
Author: Socrates <[email protected]>
AuthorDate: Wed Jul 8 15:04:50 2026 +0800

    [feature](paimon) Add Paimon write architecture design document
    
    This document covers:
    - Paimon Java SDK vs Rust write path analysis
    - Doris external table write framework analysis
    - IPaimonWriteBackend abstraction layer design
    - Dual backend (JNI + FFI) interchangeable architecture
    - Structured Thrift CommitMessage design
    - v1 implementation plan with 7 steps
    
    Co-Authored-By: Claude <[email protected]>
---
 .../paimon-scanner/PAIMON_WRITE_DESIGN.md          | 2001 ++++++++++++++++++++
 1 file changed, 2001 insertions(+)

diff --git a/fe/be-java-extensions/paimon-scanner/PAIMON_WRITE_DESIGN.md 
b/fe/be-java-extensions/paimon-scanner/PAIMON_WRITE_DESIGN.md
new file mode 100644
index 00000000000..089b020ba54
--- /dev/null
+++ b/fe/be-java-extensions/paimon-scanner/PAIMON_WRITE_DESIGN.md
@@ -0,0 +1,2001 @@
+# Doris Paimon 写入功能架构设计
+
+> **设计原则**:从第一性原理出发,基于 Paimon Java SDK(功能完备)和 Paimon Rust(性能优先)的差异,设计一个统一的写入框架。
+> 
+> **核心策略**:Rust writer 和 Java writer 是**互相替代**的关系,Rust 是 Java 的功能子集。优先使用 
Rust(性能),功能缺失时回退 Java。首个版本仅引入 Java JNI writer,但在架构上必须提前为 Rust FFI writer 预留接口。
+
+## 目录
+
+1. [背景与目标](#1-背景与目标)
+2. [Paimon 写入模型分析](#2-paimon-写入模型分析)
+3. [Java SDK vs Rust 差异矩阵](#3-java-sdk-vs-rust-差异矩阵)
+4. [Doris 现有写入框架分析](#4-doris-现有写入框架分析)
+5. [架构设计](#5-架构设计)
+6. [接口定义](#6-接口定义)
+7. [数据流与交互时序](#7-数据流与交互时序)
+8. [FE 端设计](#8-fe-端设计)
+9. [BE 端设计](#9-be-端设计)
+10. [功能路由矩阵](#10-功能路由矩阵)
+11. [实施路线图](#11-实施路线图)
+
+---
+
+## 1. 背景与目标
+
+### 1.1 背景
+
+Doris 需要支持向 Paimon 表写入数据。目前有两个可用的 Paimon 客户端库:
+
+- **Paimon Java SDK** (`paimon-core`):功能最完整的参考实现,支持全量的写入、提交、Compaction、Schema 
Evolution、Lookup Changelog Producer、Deletion Vector 等功能。
+- **Paimon Rust** (`paimon-rust`):高性能实现,使用 Arrow 原生格式,零拷贝,异步 I/O。但目前功能较 Java 
版本有差距(缺少 Compaction、Lookup/Full-compaction Changelog Producer、部分 MergeEngine 与 
Deletion Vector 的组合等)。
+
+### 1.2 目标
+
+1. **v1(当前)**:基于 Java SDK 通过 JNI 实现功能完备的 Paimon 写入。同时建立 `IPaimonWriteBackend` 
抽象接口,为 Rust 后端预留设计空间。
+2. **v2(中期)**:引入 Paimon Rust 通过 FFI 加速写入。Rust 覆盖 Append-only + 
固定桶等高吞吐场景,功能缺失时自动回退 Java。
+3. **长期**:随着 Paimon Rust 功能完善,逐步扩大 Rust 后端的覆盖范围,最终在大多数场景下优先使用 Rust。
+4. **架构目标**:`IPaimonWriteBackend` 是核心抽象——Java 和 Rust 是平等的后端实现,可以互相替代,上层(Doris 
Sink)不感知差异。
+
+### 1.3 关键约束
+
+- Paimon C++ native 版本(paimon-cpp)未来会被删除,不考虑
+- Java SDK 通过 JNI 调用,Rust 通过 C FFI 调用
+- Doris BE 是 C++ 代码,需要通过 JNI(调用 Java)或 FFI(调用 Rust)来桥接
+- 写入流程包括:数据写入 → CommitMessage 生成 → Snapshot 提交
+- FE 端需要感知表类型(Paimon)来做查询规划和写入协调
+
+---
+
+## 2. Paimon 写入模型分析
+
+### 2.1 核心写入模型(两阶段提交)
+
+Paimon 的写入模型是一个**两阶段提交**过程,无论 Java 还是 Rust 都遵循相同的抽象:
+
+```
+阶段1:Write(分布式)
+  WriteBuilder → newWrite() → TableWrite
+    → write(row/batch) × N       // 每个 worker 写自己的数据
+    → prepareCommit()            // 关闭文件,生成 CommitMessage[]
+    → CommitMessage[]             // 序列化传递给 Coordinator
+
+阶段2:Commit(集中式)
+  WriteBuilder → newCommit() → TableCommit
+    → commit(CommitMessage[])    // 原子提交:写 Manifest → 创建 Snapshot
+    → abort(CommitMessage[])     // 失败时清理数据文件
+```
+
+### 2.2 Java SDK 核心接口
+
+#### WriteBuilder(工厂)
+
+```java
+// 批写入构建器
+public interface BatchWriteBuilder extends WriteBuilder {
+    BatchWriteBuilder withOverwrite();
+    BatchWriteBuilder withOverwrite(Map<String, String> staticPartition);
+    BatchTableWrite newWrite();
+    BatchTableCommit newCommit();
+}
+
+// 流写入构建器
+public interface StreamWriteBuilder extends WriteBuilder {
+    StreamWriteBuilder withCommitUser(String commitUser);
+    StreamTableWrite newWrite();
+    StreamTableCommit newCommit();
+}
+```
+
+**关键点**:`WriteBuilder` 同时创建 `TableWrite` 和 `TableCommit`。同一个 `WriteBuilder` 
实例产出的 write 和 commit 共享 `commitUser`(用于幂等提交)。
+
+#### TableWrite(数据写入)
+
+```java
+public interface TableWrite extends AutoCloseable {
+    TableWrite withIOManager(IOManager ioManager);
+    TableWrite withWriteType(RowType writeType);
+    TableWrite withMemoryPoolFactory(MemoryPoolFactory factory);
+
+    // 分区/桶计算
+    BinaryRow getPartition(InternalRow row);
+    int getBucket(InternalRow row);
+
+    // 逐行写入
+    void write(InternalRow row) throws Exception;
+    void write(InternalRow row, int bucket) throws Exception;
+
+    // 批量写入(高效路径)
+    void writeBundle(BinaryRow partition, int bucket, BundleRecords bundle) 
throws Exception;
+
+    // 压缩
+    void compact(BinaryRow partition, int bucket, boolean fullCompaction) 
throws Exception;
+
+    // 生成提交消息(在 TableWriteImpl 上,非接口方法)
+    // List<CommitMessage> prepareCommit(boolean waitCompaction, long 
commitIdentifier)
+}
+```
+
+**关键设计点**:
+- `writeBundle()` 是高性能批量写入路径:调用者预先计算好 partition 和 bucket,将一批行打包为 
`BundleRecords` 直接写入
+- `compact()` 是显式的 bucket 级压缩触发
+- `prepareCommit()` 返回 `List<CommitMessage>`,每个 `CommitMessage` 对应一个 
(partition, bucket) 的新文件列表
+- `CommitMessage` 是 `Serializable` 的,可以跨网络传输
+
+#### CommitMessage
+
+```java
+public interface CommitMessage extends Serializable {
+    BinaryRow partition();     // 分区键(二进制编码)
+    int bucket();              // 桶编号
+    Integer totalBuckets();    // 该分区的总桶数
+}
+```
+
+> **注意**:Java 的 `CommitMessage` 接口只暴露了 partition 和 bucket。实际的文件列表(newFiles, 
changelogFiles, indexFiles 等)在实现类 `CommitMessageImpl` 中,通过 
`CommitMessageSerializer` 序列化。这是一种封装设计——调用者不需要知道内部文件细节。
+
+#### BatchTableCommit
+
+```java
+public interface BatchTableCommit extends TableCommit {
+    void commit(List<CommitMessage> commitMessages);
+    void truncateTable();
+    void truncatePartitions(List<Map<String, String>> partitionSpecs);
+    void updateStatistics(Statistics statistics);
+    void compactManifests();
+}
+```
+
+### 2.3 Rust 核心接口
+
+Rust 采用更直接的 API 设计:
+
+```rust
+// 工厂
+pub struct WriteBuilder<'a> { table, commit_user, overwrite }
+impl WriteBuilder {
+    pub fn new_write(&self) -> Result<TableWrite>;
+    pub fn new_commit(&self) -> TableCommit;
+    pub fn with_overwrite(self) -> Self;
+    pub fn with_commit_user(self, user) -> Result<Self>;
+}
+
+// 写入
+pub struct TableWrite { ... }
+impl TableWrite {
+    pub fn with_overwrite(self) -> Self;
+    pub async fn write_arrow_batch(&mut self, batch: &RecordBatch) -> 
Result<()>;
+    pub async fn write_arrow(&mut self, batches: &[RecordBatch]) -> Result<()>;
+    pub async fn prepare_commit(&mut self) -> Result<Vec<CommitMessage>>;
+}
+
+// 提交
+pub struct TableCommit { ... }
+impl TableCommit {
+    pub async fn commit(&self, messages: Vec<CommitMessage>) -> Result<()>;
+    pub async fn overwrite(&self, messages: Vec<CommitMessage>,
+                           static_partitions: Option<HashMap<String, 
Option<Datum>>>) -> Result<()>;
+    pub async fn truncate_table(&self) -> Result<()>;
+    pub async fn truncate_partitions(&self, partitions: Vec<HashMap<String, 
Option<Datum>>>) -> Result<()>;
+    pub async fn abort(&self, messages: &[CommitMessage]) -> Result<()>;
+}
+
+// 提交消息(具体结构体,所有字段公开)
+pub struct CommitMessage {
+    pub partition: Vec<u8>,
+    pub bucket: i32,
+    pub new_files: Vec<DataFileMeta>,
+    pub new_changelog_files: Vec<DataFileMeta>,
+    pub new_index_files: Vec<IndexFileMeta>,
+    pub deleted_index_files: Vec<IndexFileMeta>,
+    pub deleted_files: Vec<DataFileMeta>,
+    pub check_from_snapshot: Option<i64>,
+}
+```
+
+**关键差异**:
+- Rust 的 `CommitMessage` 是**具体结构体**,所有字段直接暴露,而 Java 通过序列化器隐藏内部细节
+- Rust 使用 Arrow `RecordBatch` 作为数据载体,Java 使用 `InternalRow`
+- Rust 的写入是 async 的,Java 是同步的(但在 Flink 等框架中被异步调用)
+
+### 2.4 写入功能全景
+
+| 功能 | Java SDK | Rust | 说明 |
+|------|----------|------|------|
+| **Append-only 写入** | ✅ | ✅ | 无主键表的顺序写入 |
+| **主键表写入** | ✅ | ✅ | Sorted Merge Tree,按主键排序去重 |
+| **固定桶 (Fixed Bucket)** | ✅ | ✅ | hash(bucket_key) % N |
+| **动态桶 (Dynamic Bucket)** | ✅ | ✅ | bucket=-1,自动分配桶 |
+| **跨分区桶 (Cross Partition)** | ✅ | ✅ | 动态桶 + 跨分区主键 |
+| **Postpone 桶 (bucket=-2)** | ✅ | ✅ | 延迟桶分配,用于高吞吐写入 |
+| **MergeEngine: Normal** | ✅ | ✅ | 最后写入胜出 |
+| **MergeEngine: PartialUpdate** | ✅ | ✅ | 部分列更新(Rust 不支持 +DV) |
+| **MergeEngine: Aggregation** | ✅ | ✅ | 聚合函数(Rust 不支持 +DV) |
+| **MergeEngine: FirstRow** | ✅ | ✅ | 保留第一行 |
+| **MergeEngine: Deduplicate** | ✅ | ✅ | 按主键去重 |
+| **ChangelogProducer: None** | ✅ | ✅ | 不产生 changelog |
+| **ChangelogProducer: Input** | ✅ | ✅ | 透传 changelog |
+| **ChangelogProducer: Lookup** | ✅ | ❌ | 提交时查找旧值生成 changelog |
+| **ChangelogProducer: FullCompaction** | ✅ | ❌ | 全量压缩时生成 changelog |
+| **Compaction** | ✅ | ❌ | 自动/手动合并,层级压缩 |
+| **Deletion Vector** | ✅ | 部分 | Rust: 不支持 PartialUpdate+DV、Aggregation+DV |
+| **Schema Evolution** | ✅ | ✅ | 列添加/删除/重命名/类型变更 |
+| **Row Tracking** | ✅ | ✅ | DataEvolution 的行 ID 追踪 |
+| **DataEvolution (MERGE INTO)** | ✅ | ✅ | 基于行 ID 的部分列更新 |
+| **CoW MERGE INTO** | ✅ | ✅ | 无 PK 表的 Copy-on-Write 合并 |
+| **分区覆盖 (Overwrite)** | ✅ | ✅ | 静态/动态分区覆盖 |
+| **Truncate** | ✅ | ✅ | 清空表/分区 |
+| **Blob 类型** | ✅ | ✅ | 大对象列分离存储 |
+| **全局索引 (Global Index)** | ✅ | 部分 | Rust 仅支持 DropPartitionIndex |
+| **二次索引 (BTree Index)** | ✅ | ✅ | |
+| **文件格式: Parquet** | ✅ | ✅ | 默认 |
+| **文件格式: ORC** | ✅ | ✅ | |
+| **文件格式: Avro** | ✅ | ✅ | |
+| **文件格式: Row** | ✅ | ✅ | Arrow IPC 格式 |
+| **文件格式: Vortex** | ❌ | ✅ (feature) | Rust 特有 |
+| **Stream Writer (持续写入)** | ✅ | ❌ | Flink 流式写入模式 |
+| **Tag/Branch** | ✅ | 部分 | Rust 有 Tag 读取器但无创建 API |
+| **提交重试与幂等** | ✅ | ✅ | commit_user + commitIdentifier |
+
+---
+
+## 3. Java SDK vs Rust 差异矩阵
+
+### 3.1 架构差异
+
+| 维度 | Java SDK | Rust |
+|------|----------|------|
+| **数据载体** | `InternalRow` (自定义行格式) | Arrow `RecordBatch` (列式) |
+| **API 粒度** | 逐行 `write(InternalRow)` + 批量 `writeBundle()` | 批量 
`write_arrow_batch(RecordBatch)` |
+| **CommitMessage** | 接口 + 序列化器(封装实现) | 具体结构体(所有字段公开) |
+| **I/O 模型** | 同步(阻塞 I/O) | 异步(tokio + opendal) |
+| **内存管理** | Java GC + 直接内存(spillable) | Rust 所有权系统 |
+| **压缩** | 内置 `CompactManager` | 未实现 |
+| **可扩展性** | 通过 SPI 插件 | 通过 feature flags 和 traits |
+
+### 3.2 性能差异
+
+| 维度 | Java SDK | Rust |
+|------|----------|------|
+| **写入吞吐** | 良好 | 优秀(零拷贝 Arrow,异步 I/O) |
+| **内存效率** | 依赖 GC,可能 FGC | 确定性的所有权管理 |
+| **启动时间** | JVM 预热开销 | 原生性能,无预热 |
+| **CPU 效率** | JIT 编译优化 | 编译期优化 |
+| **跨语言调用** | JNI 开销 | FFI 开销(更低) |
+
+### 3.3 功能完备度
+
+**Java SDK 功能完备度**:⭐⭐⭐⭐⭐(参考实现,100% 功能)
+**Rust 功能完备度**:⭐⭐⭐⭐(约 75%,核心缺失:Compaction, Lookup/FullCompaction 
ChangelogProducer)
+
+---
+
+## 4. Doris 现有写入框架分析
+
+### 4.1 BE 端写入框架
+
+Doris BE 端的 Table Writer 继承体系:
+
+```
+ResultWriter (result_writer.h)
+  └── AsyncResultWriter (async_result_writer.h)
+        ├── VTabletWriter (vtablet_writer.h)        // 内部表写入
+        ├── VHiveTableWriter (vhive_table_writer.h)  // Hive 外部表
+        ├── VJdbcTableWriter (vjdbc_table_writer.h)  // JDBC 外部表
+        ├── VTVFTableWriter (vtvf_table_writer.h)    // TVF 表函数
+        ├── VIcebergTableWriter                       // Iceberg 外部表
+        └── VMaxComputeTableWriter                    // MaxCompute 外部表
+```
+
+**AsyncResultWriter** 的核心接口:
+```cpp
+class AsyncResultWriter : public ResultWriter {
+public:
+    virtual Status open(RuntimeState* state, RuntimeProfile* profile) = 0;
+    virtual Status write(RuntimeState* state, Block& input_block) = 0;  // 实际 
I/O
+    Status sink(Block* block, bool eos);  // 异步入队
+    Status start_writer(RuntimeState* state, RuntimeProfile* profile);  // 启动 
I/O 线程
+};
+```
+
+**关键设计**:
+- `sink()` → 异步队列 → `write()`:解耦计算和 I/O
+- `Block` 是 Doris 的列式数据批次
+- 每个 writer 实例对应一个 BE worker 线程
+
+### 4.2 Iceberg Table Writer 参考
+
+`VIcebergTableWriter` 是最接近 Paimon 写入需求的外部表写入器:
+
+```
+写入流程:
+  write(RuntimeState, Block)
+    → 分区计算(PartitionTransform)
+    → 确定目标 partition path
+    → 获取/创建 VIcebergPartitionWriter(每个 partition 一个)
+    → 写入 Parquet/ORC 文件
+    → 文件滚动(按 target_file_size)
+  close()
+    → 关闭所有 partition writers
+    → 收集产生的 DataFile 列表
+    → 通过 Iceberg Catalog API 提交(AddFiles)
+```
+
+**关键差异(vs Paimon)**:
+
+Iceberg 和 Paimon 的写入模型存在根本性差异,这直接影响 Doris 的写入架构设计。
+
+**Iceberg:Commit-on-Close 模型**
+
+```
+BE Worker 1                    BE Worker 2                    FE Coordinator
+  │                               │                              │
+  │ write(block) × N              │ write(block) × N             │
+  │  → 写入 Parquet 文件           │  → 写入 Parquet 文件          │
+  │  → 文件滚动                    │  → 文件滚动                   │
+  │                               │                              │
+  │ close()                       │ close()                      │
+  │  → 收集 DataFile 列表          │  → 收集 DataFile 列表         │
+  │  → 生成 TIcebergCommitData    │  → 生成 TIcebergCommitData   │
+  │     {file_path, row_count,    │     {file_path, row_count,    │
+  │      file_size, partition}    │      file_size, partition}    │
+  │                               │                              │
+  │──── TIcebergCommitData ──────│──── TIcebergCommitData ──────→│
+  │                               │                              │
+  │                               │    聚合所有 DataFile 列表      │
+  │                               │    table.newAppend()          │
+  │                               │      .appendFile(files...)    │
+  │                               │      .commit()   ← 原子提交   │
+```
+
+Iceberg writer 的输出就是**最终产物**:一堆已经写好的数据文件(Parquet/ORC)+ 文件的元信息(路径、行数、大小、分区值)。FE 
Coordinator 收集这些文件列表后,直接调用 Iceberg 的 `appendFile()` + `commit()` 
即可完成提交。文件列表是**透明的、可直接使用的**。
+
+**Paimon:Prepare-then-Commit 模型**
+
+```
+BE Worker 1                    BE Worker 2                    FE Coordinator
+  │                               │                              │
+  │ write(row) × N                │ write(row) × N               │
+  │  → Paimon 内部:               │  → Paimon 内部:              │
+  │    分区/桶路由                  │    分区/桶路由                 │
+  │    KeyValueFileWriter         │    AppendOnlyWriter          │
+  │    排序、去重、序列号分配        │    文件滚动                   │
+  │    写入数据文件 + changelog     │                              │
+  │                               │                              │
+  │ prepareCommit()               │ prepareCommit()              │
+  │  → CommitMessage[]            │  → CommitMessage[]           │
+  │     {                         │     {                        │
+  │       partition: bytes,       │       partition: bytes,      │
+  │       bucket: 3,              │       bucket: 0,             │
+  │       newFiles: [DataFileMeta │       newFiles: [DataFileMeta │
+  │         {fileName, level,     │         {fileName, level,     │
+  │          minSeq, maxSeq,      │          minSeq, maxSeq,      │
+  │          minKey, maxKey,      │          minKey, maxKey,      │
+  │          rowCount, ...}],     │          rowCount, ...}],     │
+  │       changelogFiles: [...],  │       changelogFiles: [...],  │
+  │       indexFiles: [...]       │       indexFiles: [...]       │
+  │     }                         │     }                        │
+  │                               │                              │
+  │── CommitMessage[] (序列化) ──│── CommitMessage[] (序列化) ──→│
+  │                               │                              │
+  │                               │    聚合所有 CommitMessage     │
+  │                               │    TableCommit.commit(        │
+  │                               │      allMessages)             │
+  │                               │    ├─ 合并 Manifest           │
+  │                               │    ├─ 冲突检测                │
+  │                               │    ├─ 写入新 Manifest         │
+  │                               │    └─ 原子创建 Snapshot       │
+```
+
+Paimon writer 的输出是**中间产物**:`CommitMessage` 不是简单的"文件路径列表",而是一个包含内部元数据的结构化消息:
+
+- **`newFiles` vs `deletedFiles`**:同一个 CommitMessage 可能同时包含新增文件和要删除的文件(例如 
Compaction 后:新文件是合并结果,旧文件标记删除)
+- **`minSequenceNumber` / `maxSequenceNumber`**:每个文件的序列号范围,Commit 引擎用它来检测冲突和保证 
LSM-tree 的一致性
+- **`changelogFiles`**:如果启用了 changelog producer,会产生单独的 changelog 文件,需要在 Commit 
时写入独立的 changelog manifest
+- **`indexFiles` / `deletedIndexFiles`**:动态桶模式下的哈希索引文件,需要合并到 IndexManifest
+- **`minKey` / `maxKey`**:用于 Manifest 的分区裁剪优化
+
+这些元数据**必须由 Paimon 的 Commit 
引擎来消费和处理**,外部不能简单地"收集文件列表然后提交"。`TableCommit.commit()` 做的事情远比 Iceberg 的 
`appendFile()` 复杂——它需要合并 manifest、检测冲突、分配 row ID、维护索引、写 changelog manifest 等。
+
+**对 Doris 架构的影响**:
+
+| 维度 | Iceberg 模式 | Paimon 模式 |
+|------|-------------|-------------|
+| Writer 输出 | DataFile 列表(透明) | CommitMessage(不透明内部结构) |
+| Commit 执行者 | FE 直接调 Iceberg API | 需要通过 Paimon `TableCommit` 执行 |
+| Commit 的关键输入 | 文件路径 + 行数 + 分区 | 完整 CommitMessage(含序列号、键范围、索引文件等) |
+| 提交失败处理 | 重试即可(文件已存在) | 需 abort() 清理数据文件,然后重试 |
+| 跨 BE 聚合 | 简单合并文件列表 | 相同 (partition,bucket) 的 messages 需保持原样传递 |
+| FE 需要理解的内容 | 文件元信息 | 无需理解(透传),但不能丢失任何字段 |
+
+**为什么 Paimon 不能像 Iceberg 一样 Commit-on-Close?**
+
+根本原因是 Paimon 的 LSM-tree 架构。Paimon 的表不是简单的"文件集合",而是一个分层的有序结构:
+
+1. **序列号(Sequence Number)**:LSM-tree 依赖全局递增的序列号来决定记录的可见性和冲突解决。每个 writer 
写入时分配序列号,但 Commit 时 Commit 引擎需要全局地验证序列号的连续性,合并增量到 base manifest。
+2. **Compaction**:Writer 在 `prepareCommit()` 时可能已经执行了 
Compaction,产生了新旧文件的替换关系(`compactBefore` / `compactAfter`),这些必须在 Commit 时原子地反映到 
manifest 中。如果多个 writer 各自独立 commit,会导致 manifest 冲突。
+3. **Manifest 的版本一致性**:Paimon 的 Snapshot 是一个完整的 manifest 树(base manifest list 
+ delta manifest list + changelog manifest list + index 
manifest)。这棵树必须在**一个原子操作**中创建,不能由多个 writer 各自添加叶子节点。
+
+因此,Paimon 的 Commit **必须是一个集中的、原子的操作**——这就是 "Prepare-then-Commit" 的含义:Writer 
负责准备(产生 CommitMessage),Coordinator 负责提交(消费 CommitMessage 创建 Snapshot)。
+
+### 4.3 FE 端规划
+
+```
+BaseExternalTableDataSink
+  └── IcebergTableSink    // 生成 TIcebergTableSink (Thrift)
+      // 包含:table location, schema, partition spec, file format, output 
expressions
+
+DataSink (Thrift)
+  ├── TIcebergTableSink
+  ├── THiveTableSink
+  ├── TMaxComputeTableSink
+  └── TPaimonTableSink  ← 需要新增
+```
+
+### 4.4 现有 JNI 框架
+
+Doris 已有 JNI 调用框架用于**读取**外部表:
+
+```
+be/src/format_v2/jni/
+  ├── jni_table_reader.{h,cpp}    // JNI 读取器框架
+  ├── paimon_jni_reader.{h,cpp}   // Paimon JNI 读取器
+  ├── hudi_jni_reader.{h,cpp}     // Hudi JNI 读取器
+  └── ...
+
+be/src/format/jni/
+  ├── jni_data_bridge.{h,cpp}     // C++ ↔ Java 数据桥接
+  └── jni_reader.{h,cpp}
+```
+
+**JNI 模式的写路径参考**:目前没有 JNI 写路径(只有读取),需要新建。
+
+---
+
+## 5. 架构设计
+
+### 5.1 总体架构
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│                          Doris FE                                   │
+│  ┌──────────────┐   ┌──────────────────┐   ┌─────────────────────┐ │
+│  │ Nereids      │   │ PaimonTableSink  │   │ Thrift:             │ │
+│  │ Planner      │──▶│ (FE Planner)     │──▶│ TPaimonTableSink    │ │
+│  └──────────────┘   └──────────────────┘   └─────────┬───────────┘ │
+└───────────────────────────────────────────────────────┼─────────────┘
+                                                        │ TDataSink
+                                                        ▼
+┌───────────────────────────────────────────────────────┴─────────────┐
+│                          Doris BE                                   │
+│                                                                     │
+│  ┌──────────────────────────────────────────────────────────────┐  │
+│  │              VPaimonTableWriter (AsyncResultWriter)           │  │
+│  │                                                              │  │
+│  │  write(Block) → partition/bucket routing → dispatch to       │  │
+│  │       VPaimonPartitionWriter × N (per partition+bucket)      │  │
+│  │                                                              │  │
+│  │  close() → collect CommitMessage[] from all writers         │  │
+│  └──────────────────────┬───────────────────────────────────────┘  │
+│                         │                                          │
+│                         ▼                                          │
+│  ┌──────────────────────────────────────────────────────────────┐  │
+│  │                 IPaimonWriteBackend (抽象层)                  │  │
+│  │                                                              │  │
+│  │  + open(TPaimonTableSink) → Status                          │  │
+│  │  + createWriter(partition, bucket) → IPaimonWriter           │  │
+│  │  + createCommit() → IPaimonCommitter                        │  │
+│  │  + getCommiter() → IPaimonCommitter (singleton per sink)     │  │
+│  └──────────┬───────────────────────────────┬───────────────────┘  │
+│             │                               │                      │
+│             ▼                               ▼                      │
+│  ┌──────────────────┐             ┌──────────────────┐            │
+│  │ JniWriteBackend  │             │ FfiWriteBackend  │            │
+│  │ (Java SDK)       │             │ (Rust lib)       │            │
+│  │                  │             │                  │            │
+│  │ via JNI:         │             │ via C FFI:       │            │
+│  │ BatchTableWrite  │             │ TableWrite       │            │
+│  │ BatchTableCommit │             │ TableCommit      │            │
+│  │ CommitMessage    │             │ CommitMessage    │            │
+│  │ Serializer       │             │ (direct struct)  │            │
+│  └──────────────────┘             └──────────────────┘            │
+│                                                                     │
+│  ┌──────────────────────────────────────────────────────────────┐  │
+│  │              CommitMessage (统一格式,跨后端)                  │  │
+│  │                                                              │  │
+│  │  BE 本地聚合 → FE Coordinator 全局聚合 → 统一提交              │  │
+│  └──────────────────────────────────────────────────────────────┘  │
+└─────────────────────────────────────────────────────────────────────┘
+```
+
+### 5.2 核心设计决策
+
+#### 决策 1:统一 CommitMessage 格式
+
+**设计**:在 BE 层定义一个 C++ 版本的 `CommitMessage` 结构体,同时能被 JNI 后端(Java 序列化反序列化)和 FFI 
后端(Rust 结构体)填充。
+
+```cpp
+// be/src/exec/sink/writer/paimon/paimon_commit_message.h
+struct PaimonDataFileMeta {
+    std::string file_name;
+    int64_t file_size;
+    int64_t row_count;
+    int64_t delete_row_count;
+    int64_t min_sequence_number;
+    int64_t max_sequence_number;
+    int32_t level;
+    int32_t schema_id;
+    std::string min_key;    // serialized BinaryRow
+    std::string max_key;
+    // ... 更多元数据字段
+};
+
+struct PaimonCommitMessage {
+    std::string partition;   // serialized BinaryRow bytes
+    int32_t bucket;
+    std::optional<int32_t> total_buckets;
+
+    std::vector<PaimonDataFileMeta> new_files;
+    std::vector<PaimonDataFileMeta> new_changelog_files;
+    std::vector<PaimonIndexFileMeta> new_index_files;
+    std::vector<PaimonIndexFileMeta> deleted_index_files;
+    std::vector<PaimonDataFileMeta> deleted_files;
+    std::optional<int64_t> check_from_snapshot;
+
+    // 序列化为 Thrift 格式,传递给 FE Coordinator
+    void to_thrift(TPaimonCommitMessage* t_msg) const;
+};
+```
+
+**理由**:
+- 解耦写入后端和提交逻辑
+- FE Coordinator 需要聚合所有 BE 的 CommitMessage,因此需要一个统一的序列化格式
+- 使用 Thrift(Doris 的标准 RPC 序列化)来传输 CommitMessage 到 FE
+
+#### 决策 2:IPaimonWriteBackend 抽象
+
+**设计**:定义一个 C++ 抽象接口,屏蔽 JNI 和 FFI 的差异。
+
+```cpp
+// be/src/exec/sink/writer/paimon/paimon_write_backend.h
+class IPaimonWriter {
+public:
+    virtual ~IPaimonWriter() = default;
+    // 写入一个 Block(列式批次)
+    virtual Status write(RuntimeState* state, Block& block) = 0;
+    // 关闭当前 writer,产生 CommitMessage
+    virtual Status prepareCommit(std::vector<PaimonCommitMessage>& messages) = 
0;
+    // 强制 compaction(可选,仅 Java 后端支持)
+    virtual Status compact(bool fullCompaction) = 0;
+    // 丢弃当前写入
+    virtual Status abort() = 0;
+};
+
+class IPaimonCommitter {
+public:
+    virtual ~IPaimonCommitter() = default;
+    // 提交所有 messages
+    virtual Status commit(const std::vector<PaimonCommitMessage>& messages) = 
0;
+    // 分区覆盖提交
+    virtual Status overwrite(const std::vector<PaimonCommitMessage>& messages,
+                            const std::optional<std::map<std::string, 
std::string>>& staticPartition) = 0;
+    // 清空表
+    virtual Status truncateTable() = 0;
+    // 清空分区
+    virtual Status truncatePartitions(const std::vector<std::map<std::string, 
std::string>>& partitions) = 0;
+    // 中止(删除数据文件)
+    virtual Status abort(const std::vector<PaimonCommitMessage>& messages) = 0;
+};
+
+class IPaimonWriteBackend {
+public:
+    virtual ~IPaimonWriteBackend() = default;
+
+    // 初始化
+    virtual Status open(const TPaimonTableSink& sink) = 0;
+
+    // 为特定 partition+bucket 创建 writer
+    virtual Status createWriter(const std::string& partition,
+                                int32_t bucket,
+                                std::unique_ptr<IPaimonWriter>* writer) = 0;
+
+    // 获取 committer(整个 sink 共享一个 committer)
+    virtual Status createCommitter(std::unique_ptr<IPaimonCommitter>* 
committer) = 0;
+
+    // 后端类型标识
+    virtual PaimonBackendType type() const = 0;
+
+    // 功能查询
+    virtual bool supportsCompaction() const = 0;
+    virtual bool supportsLookupChangelog() const = 0;
+    virtual bool supportsFullCompactionChangelog() const = 0;
+};
+```
+
+#### 决策 3:双后端,可互相替代
+
+Java writer 和 Rust writer 是**互相替代**的关系,不是互补关系。Rust 是 Java 的功能子集:
+
+```
+功能全集 (Java SDK)
+┌─────────────────────────────────────────┐
+│  ┌─────────────────────────────────┐    │
+│  │  功能子集 (Rust)                │    │
+│  │  · Append-only 写入             │    │
+│  │  · 固定桶 PK 写入               │    │
+│  │  · 动态桶 PK 写入               │    │
+│  │  · MergeEngine: Normal/         │    │
+│  │    PartialUpdate/Aggregation/   │    │
+│  │    FirstRow/Deduplicate         │    │
+│  │  · ChangelogProducer: None/Input│    │
+│  │  · Overwrite/Truncate           │    │
+│  └─────────────────────────────────┘    │
+│  Rust 缺失(需回退 Java):             │
+│  · Compaction                          │
+│  · Lookup/FullCompaction Changelog      │
+│  · DeletionVector + PartialUpdate/      │
+│    Aggregation 组合                     │
+│  · MERGE INTO / DataEvolution           │
+└─────────────────────────────────────────┘
+```
+
+**选择策略:优先 Rust,功能不足时回退 Java**
+
+```
+IF table options 包含 Rust 不支持的功能:
+  → Java JNI Backend  ← 功能完备保证
+ELSE:
+  → Rust FFI Backend   ← 优先选择,性能更优
+```
+
+**v1 实现策略**:
+
+```
+v1: 仅实现 Java JNI Backend,但:
+  - IPaimonWriteBackend 接口从第一天就存在
+  - VPaimonTableWriter 通过 IPaimonWriteBackend 调用
+  - Java 后端是实现细节,不是架构主体
+  - 新增 Rust 后端 = 新增一个 IPaimonWriteBackend 实现类,不碰上层代码
+
+v2: 实现 Rust FFI Backend
+  - 扩展 paimon-rust C binding
+  - 新增 FfiPaimonWriteBackend(<1000行新代码)
+  - 上层 VPaimonTableWriter 无需修改
+```
+
+| 场景 | v1 (仅 Java) | v2 (Java + Rust) |
+|------|-------------|-------------------|
+| Append-only 表 | JNI Backend | **FFI Backend** (优先) |
+| PK 表,固定桶,No Compaction | JNI Backend | **FFI Backend** (优先) |
+| PK 表,需要 Compaction | JNI Backend | JNI Backend (Rust 不支持) |
+| Lookup Changelog Producer | JNI Backend | JNI Backend (Rust 不支持) |
+| MERGE INTO | JNI Backend | JNI Backend (Rust 不支持) |
+
+### 5.3 提交流程
+
+Paimon 的两阶段提交对 Doris 的分布式架构提出了特殊挑战:
+
+```
+时间线:分布式写入 + 集中式提交
+
+BE1: write() → prepareCommit() → CommitMessage[]
+BE2: write() → prepareCommit() → CommitMessage[]
+BE3: write() → prepareCommit() → CommitMessage[]
+  │               │                    │
+  └───────────────┼────────────────────┘
+                  │  (通过 Thrift RPC 发送到 FE Coordinator)
+                  ▼
+FE Coordinator: 聚合所有 CommitMessage[]
+                  │
+                  ▼
+         TableCommit.commit(allMessages)
+                  │
+                  ▼
+         新的 Snapshot 创建
+```
+
+**关键问题**:Commit 应该在哪发生?
+
+**选择 1:FE Coordinator 执行 Commit**
+- 优点:自然的两阶段提交,FE 已有 Iceberg 的集中式提交模式
+- 缺点:FE 需要持有 Java SDK 依赖(或通过 BE 代理)
+
+**选择 2:选出一个 BE 执行 Commit(Leader BE)**
+- 优点:BE 端直接调用 JNI/FFI
+- 缺点:需要选举逻辑,增加复杂度
+
+**选择 3:混合方案** ✅ **推荐**
+
+```
+BE 端:
+  - 数据写入(全并行)
+  - prepareCommit() → CommitMessage[](序列化为 Thrift)
+
+FE Coordinator 端:
+  - 聚合所有 BE 的 CommitMessage[]
+  - 通过专用的 "Commit BE" 执行 TableCommit.commit()
+
+Commit BE(可以是任意 BE 或专用 BE):
+  - 接收 FE 发来的聚合后的 CommitMessage[]
+  - 调用 JNI Backend 的 TableCommit.commit()
+  - 返回结果给 FE
+```
+
+对于 Append-only 表(简单场景),可以直接在 FE 端通过 Java SDK 执行提交。对于需要 Compaction 的表(复杂场景),通过 
Commit BE 执行。
+
+### 5.4 架构图总览(分场景)
+
+#### 场景 1:Append-only 表写入
+
+```
+Doris Sink
+  │
+  ▼
+VPaimonTableWriter (BE)
+  │  Block[col1, col2, ...]
+  ▼
+Partition/Bucket Router
+  │  hash(bucket_key) % N → bucket
+  │  partition columns → partition path
+  ▼
+VPaimonPartitionWriter × N
+  │  每个 (partition, bucket) 一个 writer
+  │  写入数据批次
+  ▼
+IPaimonWriter (FFI Backend preferred)
+  │  Arrow RecordBatch → Rust TableWrite::write_arrow_batch()
+  │  高效:零拷贝,异步 I/O
+  ▼
+prepareCommit() → PaimonCommitMessage[]
+
+  ▼ (Thrift RPC)
+FE Coordinator
+  │  聚合所有 BE 的 messages
+  ▼
+Commit BE: IPaimonCommitter::commit(messages)
+  │  调用 TableCommit::commit()
+  ▼
+New Snapshot ✓
+```
+
+#### 场景 2:主键表写入(需要 Compaction)
+
+```
+Doris Sink
+  │
+  ▼
+VPaimonTableWriter (BE)
+  │  Block[...]
+  ▼
+Partition/Bucket Router → IPaimonWriter (JNI Backend required)
+  │  使用 JNI Backend 因为:
+  │  1. 需要 KeyValueFileWriter
+  │  2. 需要 sequence number 管理
+  │  3. 需要 MergeEngine 逻辑
+  │  4. 需要 Compaction
+  ▼
+write() → Java BatchTableWrite.write(row) × N
+  │
+  ▼
+compact() → Java BatchTableWrite.compact(partition, bucket, fullCompaction)
+  │  (定期触发或在 commit 前触发)
+  ▼
+prepareCommit() → CommitMessage[]
+  │
+  ▼ (Thrift RPC)
+FE Coordinator → Commit BE → commit()
+  ▼
+New Snapshot + Compaction result ✓
+```
+
+---
+
+## 6. 接口定义
+
+### 6.1 Thrift 定义
+
+```thrift
+// gensrc/thrift/DataSinks.thrift
+
+enum TPaimonWriteBackendType {
+    JNI = 0,       // Java SDK via JNI
+    FFI = 1,       // Rust via C FFI
+}
+
+enum TPaimonWriteMode {
+    APPEND = 0,           // INSERT INTO
+    OVERWRITE = 1,        // INSERT OVERWRITE
+    DYNAMIC_OVERWRITE = 2, // 动态分区覆盖
+    MERGE_INTO = 3,       // DataEvolution MERGE INTO
+}
+
+struct TPaimonTableSink {
+    // 表标识
+    1: required string table_location;     // Paimon 表路径
+    2: required string paimon_catalog_type; // filesystem / hive / rest
+    3: optional map<string, string> catalog_options;
+    4: optional map<string, string> table_options;
+
+    // Schema
+    5: required string schema_json;         // Paimon TableSchema JSON
+    6: required list<string> partition_keys;
+    7: required list<string> primary_keys;
+    8: required list<i32> primary_key_indices;
+
+    // 写入配置
+    9: required TPaimonWriteMode write_mode;
+    10: optional map<string, string> static_partition; // overwrite 分区 spec
+    11: required TPaimonWriteBackendType backend_type;
+
+    // 输出表达式(Doris Block → Paimon Row 映射)
+    12: required list<TExpr> output_exprs;
+
+    // 文件配置
+    13: optional string file_format;       // parquet / orc / avro
+    14: optional string file_compression;  // zstd / snappy / lz4 / none
+    15: optional i64 target_file_size;    // bytes
+
+    // 提交配置
+    16: optional string commit_user;
+    17: optional i32 commit_max_retries;
+    18: optional i64 commit_timeout_ms;
+
+    // Hadoop 配置(对于 Hive Metastore catalog)
+    19: optional map<string, string> hadoop_config;
+}
+
+struct TPaimonCommitMessage {
+    1: required binary partition;          // serialized BinaryRow
+    2: required i32 bucket;
+    3: optional i32 total_buckets;
+    4: required list<TPaimonDataFileMeta> new_files;
+    5: optional list<TPaimonDataFileMeta> new_changelog_files;
+    6: optional list<TPaimonIndexFileMeta> new_index_files;
+    7: optional list<TPaimonIndexFileMeta> deleted_index_files;
+    8: optional list<TPaimonDataFileMeta> deleted_files;
+    9: optional i64 check_from_snapshot;
+}
+
+struct TPaimonDataFileMeta {
+    1: required string file_name;
+    2: required i64 file_size;
+    3: required i64 row_count;
+    4: optional i64 delete_row_count;
+    5: required i64 min_sequence_number;
+    6: required i64 max_sequence_number;
+    7: required i32 level;
+    8: required i32 schema_id;
+    9: required binary min_key;
+    10: required binary max_key;
+    11: optional binary min_partition_key;
+    12: optional binary max_partition_key;
+    13: optional list<string> write_cols;
+    14: optional i64 first_row_id;
+    15: optional i32 file_source;
+    // ... 其他元数据
+}
+
+struct TPaimonIndexFileMeta {
+    1: required string file_name;
+    2: required i64 file_size;
+    3: required i64 row_count;
+    4: required string index_type;   // "HASH" / "BTREE" / "GLOBAL"
+    5: optional TPaimonGlobalIndexMeta global_index_meta;
+}
+
+struct TPaimonCommitResult {
+    1: required bool success;
+    2: optional i64 new_snapshot_id;
+    3: optional string error_message;
+}
+
+// 添加到 TDataSink
+struct TDataSink {
+    // ... existing fields ...
+    16: optional TPaimonTableSink paimon_table_sink
+}
+```
+
+### 6.2 C++ 抽象接口(完整版)
+
+```cpp
+// be/src/exec/sink/writer/paimon/paimon_write_backend.h
+
+namespace doris {
+
+// ──── 枚举 ────────────────────────────────────────────────
+
+enum class PaimonBackendType {
+    JNI,  // Java SDK
+    FFI   // Rust C FFI
+};
+
+enum class PaimonWriteMode {
+    APPEND,
+    OVERWRITE,
+    DYNAMIC_OVERWRITE,
+    MERGE_INTO
+};
+
+// ──── 数据结构 ────────────────────────────────────────────
+
+struct PaimonWriteOptions {
+    std::string table_location;
+    std::string commit_user;
+    PaimonWriteMode write_mode;
+    std::optional<std::unordered_map<std::string, std::string>> 
static_partition;
+    std::string file_format;       // "parquet", "orc", "avro"
+    std::string file_compression;  // "zstd", "snappy", "lz4", "none"
+    int64_t target_file_size_bytes = 128 * 1024 * 1024; // 128MB default
+    int32_t commit_max_retries = 5;
+    int64_t commit_timeout_ms = 300000; // 5min
+};
+
+// ──── IPaimonWriter ───────────────────────────────────────
+
+class IPaimonWriter {
+public:
+    virtual ~IPaimonWriter() = default;
+
+    /// 写入一个 Doris Block
+    /// @param state  运行时状态
+    /// @param block  输入数据(Doris 列式格式)
+    virtual Status write(RuntimeState* state, Block& block) = 0;
+
+    /// 准备提交:关闭文件,生成 CommitMessage
+    /// @param messages  输出:本次写入产生的 CommitMessage 列表
+    virtual Status prepareCommit(
+        std::vector<PaimonCommitMessage>& messages) = 0;
+
+    /// 强制 compaction(仅部分后端支持)
+    /// @param fullCompaction true=全量压缩, false=增量压缩
+    virtual Status compact(bool fullCompaction) {
+        return Status::NotSupported("compact not supported by this backend");
+    }
+
+    /// 丢弃本次写入的所有数据
+    virtual Status abort() = 0;
+};
+
+// ──── IPaimonCommitter ────────────────────────────────────
+
+class IPaimonCommitter {
+public:
+    virtual ~IPaimonCommitter() = default;
+
+    /// 追加提交:将所有 CommitMessage 中的数据文件原子提交
+    virtual Status commit(
+        const std::vector<PaimonCommitMessage>& messages) = 0;
+
+    /// 覆盖提交:删除旧数据,写入新数据
+    virtual Status overwrite(
+        const std::vector<PaimonCommitMessage>& messages,
+        const std::optional<std::unordered_map<std::string, std::string>>&
+            static_partition) = 0;
+
+    /// 清空整张表
+    virtual Status truncateTable() = 0;
+
+    /// 清空指定分区
+    virtual Status truncatePartitions(
+        const std::vector<std::unordered_map<std::string, std::string>>&
+            partitions) = 0;
+
+    /// 中止:删除已写入但未提交的数据文件
+    virtual Status abort(
+        const std::vector<PaimonCommitMessage>& messages) = 0;
+};
+
+// ──── IPaimonWriteBackend ─────────────────────────────────
+
+class IPaimonWriteBackend {
+public:
+    virtual ~IPaimonWriteBackend() = default;
+
+    /// 初始化后端
+    virtual Status open(const TPaimonTableSink& sink,
+                        RuntimeState* state) = 0;
+
+    /// 为特定的 (partition, bucket) 创建一个新的 writer
+    /// 同一个 (partition, bucket) 可以被多次调用(每次创建一个新的文件 writer)
+    virtual Status createWriter(
+        const std::string& partition_bytes,
+        int32_t bucket,
+        std::unique_ptr<IPaimonWriter>* writer) = 0;
+
+    /// 创建一个 committer(每个 sink 只需要一个)
+    virtual Status createCommitter(
+        std::unique_ptr<IPaimonCommitter>* committer) = 0;
+
+    /// 获取后端类型
+    virtual PaimonBackendType type() const = 0;
+
+    // ──── 功能查询 ────────────────────────────────────────
+
+    virtual bool supportsCompaction() const = 0;
+    virtual bool supportsLookupChangelogProducer() const = 0;
+    virtual bool supportsFullCompactionChangelogProducer() const = 0;
+    virtual bool supportsPartialUpdateWithDV() const = 0;
+    virtual bool supportsAggregationWithDV() const = 0;
+};
+
+// ──── 后端工厂 ────────────────────────────────────────────
+
+class PaimonWriteBackendFactory {
+public:
+    /// 根据配置创建合适的后端
+    static Status create(
+        const TPaimonTableSink& sink,
+        std::unique_ptr<IPaimonWriteBackend>* backend);
+
+    /// 根据表属性和用户偏好选择最佳后端类型
+    static PaimonBackendType selectBackendType(
+        const TPaimonTableSink& sink);
+};
+
+} // namespace doris
+```
+
+---
+
+## 7. 数据流与交互时序
+
+### 7.1 完整写入流程(时序图)
+
+```
+ FE Coordinator          BE Worker 1           BE Worker 2         Commit BE
+      │                      │                     │                   │
+      │  TDataSink           │                     │                   │
+      │  (TPaimonTableSink)  │                     │                   │
+      ├──────────────────────┤                     │                   │
+      │                      │                     │                   │
+      │                   open()                 open()                │
+      │                   IPaimonWriteBackend    IPaimonWriteBackend   │
+      │                      │                     │                   │
+      │  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐                   │
+      │  │ 写入阶段(并行)                        │                   │
+      │  │                                         │                   │
+      │  │ Block1[rows]                            │                   │
+      │  ├─────────────────────────────────────────┤                   │
+      │  │  partition/bucket 路由                  │                   │
+      │  │  write() → IPaimonWriter                │                   │
+      │  │                                         │                   │
+      │  │              Block2[rows]               │                   │
+      │  ├─────────────────────────────────────────┤                   │
+      │  │              路由 → write()             │                   │
+      │  │                                         │                   │
+      │  │ ... (更多数据) ...                      │                   │
+      │  │                                         │                   │
+      │  │ EOS (End of Stream)                     │                   │
+      │  ├─────────────────────────────────────────┤                   │
+      │  │  prepareCommit()                        │                   │
+      │  │  → CommitMessage[]                      │                   │
+      │  │                                         │                   │
+      │  │              prepareCommit()            │                   │
+      │  ├─────────────────────────────────────────┤                   │
+      │  │              → CommitMessage[]          │                   │
+      │  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘                   │
+      │                      │                     │                   │
+      │  CommitMessage[]     │   CommitMessage[]   │                   │
+      │  (Thrift RPC)        │   (Thrift RPC)      │                   │
+      │◄─────────────────────┼─────────────────────┤                   │
+      │                      │                     │                   │
+      │  聚合 & 去重         │                     │                   │
+      │  allMessages[]       │                     │                   │
+      │                      │                     │                   │
+      │                      │   commit(allMessages)                    │
+      ├──────────────────────────────────────────────────────────────────┤
+      │                      │                     │   TableCommit      │
+      │                      │                     │   .commit()        │
+      │                      │                     │   ├─ write manifests│
+      │                      │                     │   ├─ write snapshot │
+      │                      │                     │   └─ atomic rename  │
+      │                      │                     │                   │
+      │  CommitResult        │                     │                   │
+      │◄──────────────────────────────────────────────────────────────────┤
+      │                      │                     │                   │
+      │  (on success)        │                     │                   │
+      │  close() writers     │                     │                   │
+      ├──────────────────────┤                     │                   │
+      │                      │                     │                   │
+      │  (on failure)        │                     │                   │
+      │  abort(messages)     │                     │                   │
+      ├──────────────────────┼─────────────────────┤                   │
+```
+
+### 7.2 错误处理流程
+
+```
+写入阶段可能的错误:
+  1. 网络/存储 I/O 错误
+     → 重试(with exponential backoff)
+     → 超过重试次数 → 标记 task 失败 → FE 重新调度
+
+  2. 内存不足
+     → spill to disk (if spillable enabled)
+     → 触发 memory limiter → 减少并发度
+
+  3. Schema mismatch
+     → 立即失败,不回滚已写入的文件
+     → 依赖 Snapshot 的隔离性,未提交的数据不会被读到
+
+提交阶段可能的错误:
+  1. Commit conflict(并发提交)
+     → 自动重试(commit_max_retries times)
+     → 检查 idempotency (commit_user + commit_identifier)
+
+  2. 提交超时
+     → 检查是否已经提交成功(idempotency check)
+     → 如果未成功 → 根据 Snapshot 状态决定重试还是放弃
+
+  3. 写入成功但提交失败 = 孤儿文件
+     → 调用 abort(messages) 清理数据文件
+     → 下次 Snapshot expiration 时也会清理未引用的文件
+```
+
+---
+
+## 8. FE 端设计
+
+### 8.1 PaimonTableSink(FE Planner)
+
+```java
+// fe/fe-core/src/main/java/org/apache/doris/planner/PaimonTableSink.java
+
+public class PaimonTableSink extends BaseExternalTableDataSink {
+
+    private final PaimonExternalTable targetTable;
+    private List<Expr> outputExprs;
+    private PaimonWriteMode writeMode;
+    private Map<String, String> staticPartition;
+
+    public PaimonTableSink(PaimonExternalTable targetTable, 
InsertCommandContext ctx) {
+        this.targetTable = targetTable;
+        // 从 ctx 确定写入模式
+        if (ctx.isOverwrite()) {
+            this.writeMode = PaimonWriteMode.OVERWRITE;
+            this.staticPartition = ctx.getStaticPartition();
+        } else {
+            this.writeMode = PaimonWriteMode.APPEND;
+        }
+    }
+
+    @Override
+    public TDataSink toThrift() {
+        TDataSink result = new TDataSink(TDataSinkType.PAIMON_TABLE_SINK);
+
+        TPaimonTableSink paimonSink = new TPaimonTableSink();
+        paimonSink.setTableLocation(targetTable.getTableLocation());
+        paimonSink.setPaimonCatalogType(targetTable.getCatalogType());
+        paimonSink.setSchemaJson(targetTable.getPaimonSchemaJson());
+        paimonSink.setPartitionKeys(targetTable.getPartitionKeys());
+        paimonSink.setPrimaryKeys(targetTable.getPrimaryKeys());
+        paimonSink.setPrimaryKeyIndices(targetTable.getPrimaryKeyIndices());
+        paimonSink.setWriteMode(writeMode.toThrift());
+        paimonSink.setOutputExprs(Expr.treesToThrift(outputExprs));
+        paimonSink.setFileFormat(targetTable.getFileFormat());
+        paimonSink.setFileCompression(targetTable.getFileCompression());
+        paimonSink.setTargetFileSize(targetTable.getTargetFileSize());
+
+        // 选择后端类型
+        paimonSink.setBackendType(selectBackendType().toThrift());
+
+        // 生成 commit_user
+        paimonSink.setCommitUser(UUID.randomUUID().toString());
+
+        result.setPaimonTableSink(paimonSink);
+        return result;
+    }
+
+    /**
+     * 智能选择写入后端:
+     * - 如果需要 Compaction 或 Lookup/FullCompaction Changelog,必须用 JNI
+     * - 如果用户指定了 preferred backend,遵循用户选择
+     * - 默认:JNI(功能优先)
+     */
+    private TPaimonWriteBackendType selectBackendType() {
+        // 检查表是否配置了需要 Java SDK 才支持的功能
+        if (targetTable.requiresCompaction() ||
+            targetTable.getChangelogProducer().requiresJava()) {
+            return TPaimonWriteBackendType.JNI;
+        }
+
+        // 检查用户偏好
+        String configuredBackend = targetTable.getTableProperties()
+            .get("doris.paimon.write.backend");
+        if ("ffi".equalsIgnoreCase(configuredBACKend) ||
+            "rust".equalsIgnoreCase(configuredBACKend)) {
+            return TPaimonWriteBackendType.FFI;
+        }
+
+        // 默认使用 JNI(功能最完整)
+        return TPaimonWriteBackendType.JNI;
+    }
+}
+```
+
+### 8.2 PaimonWriteCommitCoordinator(FE Coordinator)
+
+```java
+// 
fe/fe-core/src/main/java/org/apache/doris/transaction/PaimonWriteCommitCoordinator.java
+
+public class PaimonWriteCommitCoordinator {
+
+    /**
+     * 协调分布式写入的提交:
+     * 1. 收集所有 BE 的 CommitMessage[]
+     * 2. 聚合去重
+     * 3. 选择一个 BE 执行提交(或 FE 本地执行)
+     * 4. 处理提交结果
+     */
+    public TPaimonCommitResult commit(
+            Database db,
+            PaimonExternalTable table,
+            List<List<TPaimonCommitMessage>> allBackendMessages) {
+
+        // Step 1: 扁平化所有 BE 的 messages
+        List<TPaimonCommitMessage> allMessages = allBackendMessages.stream()
+            .flatMap(List::stream)
+            .collect(Collectors.toList());
+
+        if (allMessages.isEmpty()) {
+            return new TPaimonCommitResult(true); // nothing to commit
+        }
+
+        // Step 2: 聚合相同 (partition, bucket) 的 messages
+        Map<Pair<byte[], Integer>, List<TPaimonCommitMessage>> grouped =
+            groupByPartitionBucket(allMessages);
+
+        // Step 3: 选择 Commit BE
+        TNetworkAddress commitBE = selectCommitBE(allBackendMessages);
+
+        // Step 4: 通过 RPC 调用 Commit BE 执行提交
+        TPaimonCommitRequest request = new TPaimonCommitRequest();
+        request.setTableLocation(table.getTableLocation());
+        request.setCommitMessages(allMessages);
+        request.setCommitUser(generateCommitUser());
+
+        try {
+            TPaimonCommitResult result = BackendServiceProxy.getInstance()
+                .paimonCommit(commitBE, request);
+            return result;
+        } catch (Exception e) {
+            // 提交失败处理
+            return handleCommitFailure(allMessages, e);
+        }
+    }
+}
+```
+
+---
+
+## 9. BE 端设计
+
+### 9.1 VPaimonTableWriter
+
+```cpp
+// be/src/exec/sink/writer/paimon/vpaimon_table_writer.h
+
+class VPaimonTableWriter final : public AsyncResultWriter {
+public:
+    VPaimonTableWriter(const TDataSink& t_sink,
+                       const VExprContextSPtrs& output_exprs,
+                       std::shared_ptr<Dependency> dep,
+                       std::shared_ptr<Dependency> fin_dep);
+
+    Status open(RuntimeState* state, RuntimeProfile* profile) override;
+    Status write(RuntimeState* state, Block& block) override;
+    Status close(Status status) override;
+
+private:
+    // 分区 & 桶路由
+    Status _routeBlock(Block& block,
+                       std::unordered_map<PartitionBucketKey, Block>& routed);
+
+    // 获取或创建 partition writer
+    Status _getOrCreateWriter(const std::string& partition, int32_t bucket,
+                             std::shared_ptr<VPaimonPartitionWriter>* writer);
+
+    // 生成 CommitMessage 列表
+    Status _prepareCommit(std::vector<PaimonCommitMessage>& messages);
+
+    TDataSink _t_sink;
+    RuntimeState* _state = nullptr;
+
+    // 抽象后端
+    std::unique_ptr<IPaimonWriteBackend> _backend;
+    std::unique_ptr<IPaimonCommitter> _committer; // 预留,实际提交不在这里
+
+    // 活跃的 partition writers
+    std::unordered_map<PartitionBucketKey,
+                       std::shared_ptr<VPaimonPartitionWriter>> _writers;
+
+    // 路由信息
+    std::vector<int> _partition_col_indices;
+    std::vector<int> _bucket_key_indices;
+    int _total_buckets = 0;
+
+    // 统计
+    int64_t _rows_written = 0;
+    int64_t _bytes_written = 0;
+};
+```
+
+### 9.2 JNI Backend 实现
+
+```cpp
+// be/src/exec/sink/writer/paimon/jni_paimon_write_backend.h
+
+class JniPaimonWriteBackend : public IPaimonWriteBackend {
+public:
+    Status open(const TPaimonTableSink& sink, RuntimeState* state) override;
+
+    Status createWriter(const std::string& partition, int32_t bucket,
+                        std::unique_ptr<IPaimonWriter>* writer) override;
+
+    Status createCommitter(
+        std::unique_ptr<IPaimonCommitter>* committer) override;
+
+    PaimonBackendType type() const override { return PaimonBackendType::JNI; }
+
+    bool supportsCompaction() const override { return true; }
+    bool supportsLookupChangelogProducer() const override { return true; }
+    bool supportsFullCompactionChangelogProducer() const override { return 
true; }
+    bool supportsPartialUpdateWithDV() const override { return true; }
+    bool supportsAggregationWithDV() const override { return true; }
+
+private:
+    // JNI 全局引用
+    jobject _write_builder;   // org.apache.paimon.table.sink.BatchWriteBuilder
+    jobject _jni_env_context; // JNI 执行环境
+
+    // 桥接方法
+    Status _callJavaWrite(jobject writer, Block& block);
+    Status _callJavaPrepareCommit(jobject writer,
+                                   std::vector<PaimonCommitMessage>& messages);
+    Status _serializeCommitMessages(jobject javaMessages,
+                                     std::vector<PaimonCommitMessage>& 
cppMessages);
+};
+```
+
+### 9.3 FFI (Rust) Backend 实现
+
+```cpp
+// be/src/exec/sink/writer/paimon/ffi_paimon_write_backend.h
+
+class FfiPaimonWriteBackend : public IPaimonWriteBackend {
+public:
+    Status open(const TPaimonTableSink& sink, RuntimeState* state) override;
+
+    Status createWriter(const std::string& partition, int32_t bucket,
+                        std::unique_ptr<IPaimonWriter>* writer) override;
+
+    Status createCommitter(
+        std::unique_ptr<IPaimonCommitter>* committer) override;
+
+    PaimonBackendType type() const override { return PaimonBackendType::FFI; }
+
+    // 功能查询 — Rust 目前的能力边界
+    bool supportsCompaction() const override { return false; }
+    bool supportsLookupChangelogProducer() const override { return false; }
+    bool supportsFullCompactionChangelogProducer() const override { return 
false; }
+    bool supportsPartialUpdateWithDV() const override { return false; }
+    bool supportsAggregationWithDV() const override { return false; }
+
+private:
+    // 动态加载的 Rust 共享库
+    void* _lib_handle;
+
+    // C FFI 函数指针
+    // 通过 paimon-rust bindings/c 提供
+    using PaimonWriteBuilderNew = void* (*)(/* args */);
+    using PaimonTableWriteNew = void* (*)(void* builder);
+    using PaimonTableWriteArrowBatch = int32_t (*)(void* writer,
+        FFI_ArrowArray* array, FFI_ArrowSchema* schema);
+    using PaimonTableWritePrepareCommit = void* (*)(void* writer); // returns 
CommitMessage[]
+    using PaimonTableCommitNew = void* (*)(void* builder);
+    using PaimonTableCommitCommit = int32_t (*)(void* commit, void* messages);
+    // ... 等等
+
+    PaimonWriteBuilderNew _ffi_write_builder_new;
+    PaimonTableWriteNew _ffi_table_write_new;
+    PaimonTableWriteArrowBatch _ffi_write_arrow_batch;
+    PaimonTableWritePrepareCommit _ffi_prepare_commit;
+    PaimonTableCommitNew _ffi_table_commit_new;
+    PaimonTableCommitCommit _ffi_commit;
+};
+```
+
+**注意**:`paimon-rust/bindings/c/` 目前只暴露了**读取** API。写入 API 需要新增。这是实施路线图中的第一步。
+
+---
+
+## 10. 功能路由矩阵
+
+### 10.1 写入模式路由
+
+原则:**优先 Rust,功能不足回退 Java**。Rust 是 Java 的功能子集。
+
+| 表类型 | v1 (仅Java) | v2 首选 | v2 回退 | 回退原因 |
+|--------|------------|---------|---------|----------|
+| Append-only(任意分区) | JNI | **FFI** | JNI | Rust 覆盖最充分 |
+| Append-only + Blob 列 | JNI | **FFI** | JNI | Rust 支持 |
+| PK 表,固定桶 | JNI | **FFI** | JNI | KeyValueFileWriter |
+| PK 表,动态桶 | JNI | **FFI** | JNI | DynamicBucketAssigner |
+| PK 表,Postpone 桶 | JNI | **FFI** | JNI | PostponeFileWriter |
+| ChangelogProducer=Input | JNI | **FFI** | JNI | Rust 支持 |
+| INSERT OVERWRITE | JNI | **FFI** | JNI | Rust 支持 |
+| Truncate table/partition | JNI | **FFI** | JNI | Rust 支持 |
+| ChangelogProducer=Lookup | JNI | JNI | - | Rust **不支持** |
+| ChangelogProducer=FullCompaction | JNI | JNI | - | Rust **不支持** |
+| PartialUpdate + DeletionVector | JNI | JNI | - | Rust **不支持** |
+| Aggregation + DeletionVector | JNI | JNI | - | Rust **不支持** |
+| MERGE INTO (有 PK) | JNI | JNI | - | Rust **不支持** |
+| MERGE INTO (无 PK) | JNI | JNI | - | Rust **不支持** |
+| 需要 Compaction | JNI | JNI | - | Rust **不支持** |
+
+**v2 回退规则**:一旦表的 options 中包含任何 Rust 不支持的功能,整个写入退回到 Java Backend。
+
+### 10.2 功能对齐路线
+
+```
+Phase 1 (v1) — Java JNI Backend + 抽象层:
+  ✅ 建立 IPaimonWriteBackend / IPaimonWriter / IPaimonCommitter 接口
+  ✅ JniPaimonWriteBackend(完整实现)
+  ✅ FfiPaimonWriteBackend(stub,所有方法返回 NotSupported)
+  ✅ 结构化 Thrift CommitMessage
+  ✅ BE 端 partition/bucket 路由
+  ✅ Append-only + 固定桶 PK 写入
+  ✅ INSERT OVERWRITE / Truncate
+  ✅ ChangelogProducer: None / Input
+  ✅ 所有 MergeEngine
+  ⬜ Compaction(Java 后端)
+  ⬜ ChangelogProducer: Lookup / FullCompaction(Java 后端)
+
+Phase 2 (v2) — Rust FFI Backend:
+  ⬜ 扩展 paimon-rust C binding(写入 API)
+  ⬜ FfiPaimonWriteBackend 从 stub 变为完整实现
+  ⬜ 零拷贝 Arrow C Data Interface
+  ⬜ Rust tokio runtime 集成
+  ⬜ PaimonWriteBackendFactory 自动后端选择
+  ⬜ 上层代码零改动(VPaimonTableWriter 不变)
+
+Phase 3(高级功能补齐):
+  ⬜ Compaction 触发与执行(Java 后端)
+  ⬜ Lookup / FullCompaction Changelog Producer(Java 后端)
+  ⬜ MERGE INTO / DataEvolution(Java 后端)
+  ⬜ 动态桶 + 跨分区桶
+
+Phase 4(Rust 功能追赶):
+  ⬜ Rust Compaction 实现
+  ⬜ Rust Lookup Changelog Producer
+  ⬜ Rust DV + PartialUpdate/Aggregation
+```
+
+---
+
+## 11. 实施路线图
+
+### Phase 1:v1 — Java JNI Backend + 预设计 Rust 接口(约 6-8 周)
+
+**目标**:基于 Java SDK 实现功能完备的 Paimon 写入,同时建立 `IPaimonWriteBackend` 抽象为 Rust 
后端预留空间。
+
+**核心原则**:虽然只实现 Java 后端,但**架构代码从第一天起就通过抽象接口调用**。新增 Rust 后端 = 新增一个 
`IPaimonWriteBackend` 的实现类(<1000行),不动上层代码。
+
+1. **Thrift 定义**
+   - 新增 `TPaimonTableSink`(含 `backend_type` 字段,v1 固定为 JNI)
+   - 新增结构化 `TPaimonCommitMessage`(含完整 DataFileMeta、IndexFileMeta)
+   - 新增 `TPaimonCommitResult`
+   - 在 `TDataSink` 中添加 `paimon_table_sink` 字段
+
+2. **BE 端 — 抽象层(关键)**
+   - **`IPaimonWriteBackend`** 接口:`open()` / `createWriter()` / 
`createCommitter()` / 能力查询
+   - **`IPaimonWriter`** 接口:`write()` / `prepareCommit()` / `compact()` / 
`abort()`
+   - **`IPaimonCommitter`** 接口:`commit()` / `overwrite()` / `truncateTable()` 
/ `truncatePartitions()` / `abort()`
+   - **`PaimonWriteBackendFactory`**:`selectBackendType()` + `create()`
+   - 这些接口从第一天起就存在,Java 后端只是第一个实现
+
+3. **BE 端 — JNI 后端实现**
+   - **`JniPaimonWriteBackend`**:实现 `IPaimonWriteBackend`
+   - **`VPaimonTableWriter`**:继承 `AsyncResultWriter`,通过 `IPaimonWriteBackend` 
接口调用(不直接依赖 JNI)
+   - Block → Arrow → Java 的数据桥接
+   - partition/bucket 路由逻辑
+
+4. **BE 端 — FFI 后端预留**
+   - **`FfiPaimonWriteBackend`**:stub 实现,所有方法返回 `Status::NotSupported("FFI 
backend not yet implemented")`
+   - 后端选择逻辑:`PaimonWriteBackendFactory` 根据 `TPaimonTableSink.backend_type` 
创建对应后端
+   - v1 中 `backend_type` 固定为 JNI,但代码路径已支持切换
+
+5. **Java 端 (be-java-extensions)**
+   - `PaimonJniWriteBackend` — JNI 入口,接收调用并转发给 Paimon SDK
+   - `BatchTableWrite` / `StreamTableWrite` 的 JNI wrapper
+   - `CommitMessage` 的序列化(Paimon 原生格式),同时转换为结构化 Thrift 格式
+
+6. **FE 端**
+   - `PaimonTableSink` — 生成 Thrift sink 描述,v1 固定 `backend_type = JNI`
+   - `PaimonWriteCommitCoordinator` / `PaimonTransaction` — 聚合 
CommitMessages,协调提交
+   - Nereids planner 集成(INSERT INTO paimon_table SELECT ...)
+
+7. **测试**
+   - 单元测试:`IPaimonWriteBackend` 接口的 mock 测试
+   - 集成测试:Doris → Paimon → Doris 读写闭环
+   - 性能测试:与直接使用 Paimon Flink 写入的对比
+
+### Phase 2:v2 — Rust FFI Backend(约 4-6 周)
+
+**目标**:引入 Rust 后端作为优先选择,加速覆盖大部分写入场景。
+
+**关键**:由于 Phase 1 已经建立了 `IPaimonWriteBackend` 
抽象,此阶段**上层代码(VPaimonTableWriter、FE Planner)无需修改**。仅需:
+
+1. **Rust 端(核心工作)**
+   - 扩展 `paimon-rust/bindings/c/`:新增写入相关的 C FFI 函数
+     - `paimon_write_builder_new()` / `paimon_table_write_new()` / 
`paimon_write_arrow_batch()`
+     - `paimon_prepare_commit()` / `paimon_table_commit_new()` / 
`paimon_commit()` / `paimon_commit_abort()`
+   - Arrow C Data Interface 支持(ArrowArray/ArrowSchema FFI,零拷贝)
+
+2. **BE 端(替换 stub)**
+   - 将 `FfiPaimonWriteBackend` 从 stub 替换为真正的实现(<1000行新代码)
+   - 动态加载 Rust 共享库(`dlopen`)
+   - Doris Block → Arrow FFI 的零拷贝转换
+   - Tokio runtime 集成(异步 I/O)
+
+3. **功能路由(自动切换)**
+   - `PaimonWriteBackendFactory::selectBackendType()` 实现自动判断:
+     - 检查表 options 是否包含 Rust 不支持的功能
+     - 是 → JNI Backend;否 → FFI Backend
+   - FE 端 `TPaimonTableSink.backend_type` 从固定 JNI 变为**自动选择**
+
+### Phase 3:高级写入功能(约 6-8 周)
+
+**目标**:对齐 Paimon 的全部写入能力
+
+1. **Compaction 支持**
+   - 触发策略配置
+   - 显式 `compact()` 调用
+   - Compaction 结果的 CommitMessage 处理
+
+2. **Changelog Producer 全支持**
+   - Lookup Changelog Producer
+   - FullCompaction Changelog Producer
+
+3. **MERGE INTO 支持**
+   - DataEvolution 表的 RowTracking 集成
+   - Copy-on-Write 合并
+
+4. **Schema Evolution**
+   - 写入时的 Schema 兼容性检查
+   - 自动 Schema Update
+
+### Phase 4:优化与 Rust 长期演进(持续)
+
+1. **性能优化**
+   - BE 端数据批处理优化
+   - 零拷贝数据路径
+   - 自适应后端选择
+
+2. **Rust 功能跟进**
+   - 与 Paimon Rust 社区协作,补齐 Compaction 等功能
+   - 逐步将更多功能从 JNI 迁移到 FFI
+
+3. **稳定性**
+   - 混沌测试
+   - 大规模数据写入测试
+   - 多版本兼容性测试
+
+---
+
+## 12. v1 实现计划
+
+> **分支**:`feature/paimon-jni-write-v1`
+> **基分支**:`master` (`6ad064501e0`)
+> **原则**:只实现 Java JNI Backend,但 `IPaimonWriteBackend` 
抽象层从第一天就存在。`FfiPaimonWriteBackend` 以 stub 形式预留。
+
+### 总体任务拆分
+
+```
+                    ┌──────────────────┐
+                    │  Step 1: Thrift   │ (无依赖)
+                    │  1-2 天          │
+                    └────────┬─────────┘
+                             │
+              ┌──────────────┼──────────────┐
+              ▼              ▼              ▼
+    ┌──────────────┐ ┌────────────┐ ┌──────────────┐
+    │ Step 2: BE   │ │ Step 4: FE │ │ Step 5: Java │  (可并行)
+    │ 抽象+JNI+Stub│ │ Planner    │ │ JNI Writer   │
+    │ 5-7 天       │ │ 2-3 天    │ │ 4-5 天       │
+    └──────┬───────┘ └─────┬──────┘ └──────┬───────┘
+           │               │               │
+           └───────────────┼───────────────┘
+                           │
+                           ▼
+                  ┌──────────────────┐
+                  │ Step 6: FE       │
+                  │ Transaction/提交  │
+                  │ 2-3 天           │
+                  └────────┬─────────┘
+                           │
+                           ▼
+                  ┌──────────────────┐
+                  │ Step 7: 集成测试  │
+                  │ 3-5 天           │
+                  └──────────────────┘
+
+总计:约 17-25 个工作日(3.5-5 周)
+```
+
+### Step 1:Thrift 定义(1-2 天)
+
+**目标**:定义 BE ↔ FE 通信协议
+
+**文件**:
+- `gensrc/thrift/DataSinks.thrift`
+
+**任务**:
+1. 新增 `TPaimonWriteBackendType` 枚举(`JNI = 0`, `FFI = 1`)
+2. 新增 `TPaimonWriteMode` 枚举(`APPEND = 0`, `OVERWRITE = 1`)
+3. 新增 `TPaimonDataFileMeta` 结构体(文件名、大小、行数、序列号、级别、键范围等)
+4. 新增 `TPaimonIndexFileMeta` 结构体(文件名、大小、行数、索引类型)
+5. 新增 `TPaimonCommitMessage` 结构体(结构化字段:`partition`, `bucket`, `new_files`, 
`new_changelog_files`, `new_index_files` 等)
+6. 新增 `TPaimonTableSink` 结构体(`db_name`, `tb_name`, `table_location`, 
`paimon_options`, `hadoop_config`, `backend_type`, `write_mode`, 
`column_names`, `serialized_table`)
+7. 在 `TDataSinkType` 中新增 `PAIMON_TABLE_SINK`
+8. 在 `TDataSink` 中新增 `paimon_table_sink` 字段
+9. 生成 Java/C++ 代码:`mvn compile -pl fe/fe-thrift -DskipTests`
+
+**验收标准**:Thrift 编译通过,生成的 `TPaimonTableSink`、`TPaimonCommitMessage` 可用。
+
+---
+
+### Step 2:BE 抽象层 + JNI Backend + FFI Stub(5-7 天)
+
+**目标**:建立 `IPaimonWriteBackend` 接口体系,实现完整的 JNI 后端,FFI 后端以 stub 预留。
+
+#### 2.1 抽象接口(1 天)
+
+**新文件**:
+- `be/src/exec/sink/writer/paimon/paimon_write_backend.h`
+
+**内容**:
+```cpp
+// 枚举
+enum class PaimonBackendType { JNI, FFI };
+
+// 数据结构
+struct PaimonCommitMessage { ... };
+struct PaimonDataFileMeta { ... };
+struct PaimonIndexFileMeta { ... };
+
+// 抽象接口
+class IPaimonWriter { ... };
+class IPaimonCommitter { ... };
+class IPaimonWriteBackend { ... };
+
+// 工厂
+class PaimonWriteBackendFactory { ... };
+```
+
+参考设计文档 Section 6.2。
+
+#### 2.2 JNI Backend 实现(2-3 天)
+
+**新文件**:
+- `be/src/exec/sink/writer/paimon/jni_paimon_write_backend.h`
+- `be/src/exec/sink/writer/paimon/jni_paimon_write_backend.cpp`
+
+**内容**:
+- `JniPaimonWriteBackend::open()`:加载 Java 类 `PaimonJniWriter`,缓存 method ID
+- `JniPaimonWriteBackend::createWriter()`:创建 Java `BatchTableWrite` 实例,返回 
`JniPaimonWriter`
+- `JniPaimonWriteBackend::createCommitter()`:创建 Java `BatchTableCommit` 实例,返回 
`JniPaimonCommitter`
+- `JniPaimonWriter::write()`:Block → Arrow RecordBatch → IPC Stream → JNI → 
Java
+- `JniPaimonWriter::prepareCommit()`:调用 Java `prepareCommit()` → 反序列化 
`byte[][]` → 结构化 `PaimonCommitMessage`
+- `JniPaimonWriter::compact()`:调用 Java `compact()`
+- `JniPaimonCommitter::commit()`:调用 Java `TableCommit.commit()`
+
+**数据路径**:
+```
+Block → ArrowRecordBatch → IPC Stream Buffer → JNI direct memory address → Java
+```
+
+**能力查询**:全部返回 `true`(Java 是功能全集)。
+
+#### 2.3 FFI Backend Stub(0.5 天)
+
+**新文件**:
+- `be/src/exec/sink/writer/paimon/ffi_paimon_write_backend.h`
+- `be/src/exec/sink/writer/paimon/ffi_paimon_write_backend.cpp`
+
+**内容**:所有方法返回 `Status::NotSupported("FFI backend not yet implemented")`。预留后续实现。
+
+#### 2.4 VPaimonTableWriter(1-2 天)
+
+**新文件**:
+- `be/src/exec/sink/writer/paimon/vpaimon_table_writer.h`
+- `be/src/exec/sink/writer/paimon/vpaimon_table_writer.cpp`
+
+**内容**:
+- 继承 `AsyncResultWriter`
+- 持有 `std::unique_ptr<IPaimonWriteBackend> _backend`
+- `open()`:调用 `PaimonWriteBackendFactory::create(sink)` 创建后端,调用 
`_backend->open(sink)`
+- `write()`:按 (partition, bucket) 路由 Block 到对应的 `IPaimonWriter`
+- 数据缓冲与批次合并
+- `close()`:调用各 writer 的 `prepareCommit()`,收集 `PaimonCommitMessage`,转发到 
`RuntimeState`
+
+#### 2.5 Pipeline Operator(0.5 天)
+
+**新文件**:
+- `be/src/exec/operator/paimon_table_sink_operator.h`
+- `be/src/exec/operator/paimon_table_sink_operator.cpp`
+
+**内容**:
+- `PaimonTableSinkLocalState` = `AsyncWriterSink<VPaimonTableWriter, 
PaimonTableSinkOperatorX>`
+- 标准模板,参考 Iceberg/Hive 的 Operator 实现
+
+#### 2.6 Build 集成(0.5 天)
+
+- 更新 `be/src/exec/sink/writer/paimon/CMakeLists.txt`(新建)
+- 注册 `PAIMON_TABLE_SINK` → `PaimonTableSinkOperatorX` 的映射
+
+**验收标准**:
+- BE 编译通过
+- `JniPaimonWriteBackend` 能成功加载 Java 类
+- `FfiPaimonWriteBackend` 正确返回 `NotSupported`
+- Unit test:mock `IPaimonWriteBackend`,验证 `VPaimonTableWriter` 的路由逻辑
+
+---
+
+### Step 3:BE CommitMessage 传递链路(1 天)
+
+**目标**:BE 的 CommitMessage 通过 RPC 传递到 FE
+
+**修改文件**:
+- `be/src/runtime/runtime_state.h`:新增 `add_paimon_commit_messages()` / 
`paimon_commit_messages()`
+- 确保 CommitMessage 在 `TReportExecStatusParams` 或等效 RPC 中传递给 FE
+- 参考 Iceberg `add_iceberg_commit_data()` 的实现模式
+
+**验收标准**:BE 写入后,FE Coordinator 能收到 `TPaimonCommitMessage` 列表。
+
+---
+
+### Step 4:FE Planner(2-3 天)
+
+**目标**:FE 端能把 INSERT INTO paimon_table 翻译为 `TPaimonTableSink`
+
+**新文件**:
+- 
`fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalPaimonTableSink.java`
+- 
`fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalPaimonTableSink.java`
+- 
`fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/LogicalPaimonTableSinkToPhysicalPaimonTableSink.java`
+- 
`fe/fe-core/src/main/java/org/apache/doris/planner/PaimonTableSink.java`(`BaseExternalTableDataSink`
 的子类)
+
+**修改文件**:
+- 
`fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java`:添加
 Paimon sink 的绑定逻辑
+- 
`fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java`:添加
 `visitPhysicalPaimonTableSink`
+- 
`fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java`:识别
 Paimon 外部表
+
+**关键逻辑**:
+- `PaimonTableSink.bindDataSink()`:
+  - 从 `PaimonExternalTable` 获取表信息(location、options)
+  - 生成 `TPaimonTableSink`(v1 固定 `backend_type = JNI`)
+  - 序列化 Paimon Table 对象(`InstantiationUtil.serializeObject` → base64)
+  - 传递 column names、hadoop config、commit user
+- Nereids 规则:`LogicalPaimonTableSink → PhysicalPaimonTableSink`
+
+**验收标准**:`EXPLAIN INSERT INTO paimon_table SELECT ...` 能正确显示 `PAIMON TABLE 
SINK`。
+
+---
+
+### Step 5:Java JNI Writer(4-5 天)
+
+**目标**:在 `be-java-extensions` 中实现 JNI 入口类
+
+**新文件**:
+- 
`fe/be-java-extensions/paimon-scanner/src/main/java/org/apache/doris/paimon/PaimonJniWriter.java`
+
+**内容**:
+1. **`open(tableLocation, options, columnNames)`**:
+   - 加载 Paimon Table(从 `serialized_table` 反序列化,或通过 Catalog 动态加载)
+   - 创建 `BatchTableWrite` 实例
+   - 初始化 `RowKeyExtractor`(用于固定桶的 bucket 计算)
+   - 处理 spill 配置(`IOManager`、`MemoryPoolFactory`)
+   - 验证桶模式:v1 支持 `HASH_FIXED`、`BUCKET_UNAWARE`;拒绝 
`HASH_DYNAMIC`、`KEY_DYNAMIC`、`POSTPONE_MODE`
+
+2. **`write(arrowAddress, arrowLength)`**:
+   - 从直接内存地址读取 Arrow IPC Stream
+   - 反序列化为 `VectorSchemaRoot`
+   - 逐行转换为 Paimon `InternalRow`(`GenericRow`)
+   - 调用 `BatchTableWrite.write(row)` 或 `write(row, bucket)`(固定桶时先计算 bucket)
+
+3. **类型转换矩阵**:
+   - Arrow → Paimon 类型映射(`Int32` ↔ `IntType`, `VarChar` ↔ `VarCharType`, 
`Timestamp` → `Timestamp.fromMicros()`, `Decimal` → `Decimal.fromBigDecimal()` 
等)
+   - 支持复杂类型:`Array` → `GenericArray`, `Map` → `GenericMap`, `Struct` → 
`GenericRow`
+   - NULL 处理
+
+4. **`prepareCommit()`**:
+   - 调用 `BatchTableWrite.prepareCommit()`
+   - 用 `CommitMessageSerializer` 序列化为 `byte[]`
+   - 按 8MB 分块打包
+   - 返回 `byte[][]`(每个元素是一个 chunk)
+
+5. **`abort()` / `close()`**:资源清理
+
+**测试文件**:
+- 
`fe/be-java-extensions/paimon-scanner/src/test/java/org/apache/doris/paimon/PaimonJniWriterTest.java`
+
+**验收标准**:
+- 单元测试:写入 100 行 → prepareCommit → 反序列化验证 CommitMessage
+- 支持 Parquet + ORC 两种格式
+- 正确支持 NULL、Decimal、Timestamp 等类型
+
+---
+
+### Step 6:FE Transaction / 提交协调(2-3 天)
+
+**目标**:FE 端聚合 CommitMessage 并执行提交
+
+**新文件**:
+- 
`fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonTransaction.java`
+
+**内容**:
+1. **`updateCommitMessages(List<TPaimonCommitMessage> messages)`**:
+   - 从 BE 收集 CommitMessage payload
+   - Base64 去重
+2. **`commit()`**:
+   - 反序列化 payload → `List<CommitMessage>`
+   - 创建 `StreamTableCommit`
+   - 调用 `committer.filterAndCommit(commitMap)`(幂等提交)
+3. **`rollback()`**:
+   - 调用 `committer.abort(messages)` 清理数据文件
+4. **`PaimonMetadataOps`** 集成:通过 `ExecutionAuthenticator` 执行
+
+**修改文件**:
+- `fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java`:处理 
`paimon_commit_messages`
+- 事务注册和生命周期管理
+
+**验收标准**:端到端 INSERT → BE 写入 → FE 收集 CommitMessage → 提交成功 → Paimon 表中可读到数据。
+
+---
+
+### Step 7:集成测试(3-5 天)
+
+**目标**:端到端验证写入正确性
+
+#### 7.1 回归测试
+
+**新文件**:
+- `regression-test/suites/paimon_write_p0/` 目录
+
+**测试用例**:
+1. `test_paimon_insert_into_append_only`:Append-only 表,INSERT INTO SELECT
+2. `test_paimon_insert_into_pk_fixed_bucket`:主键表,固定桶,INSERT INTO
+3. `test_paimon_insert_overwrite`:INSERT OVERWRITE
+4. `test_paimon_insert_into_partitioned`:分区表写入
+5. `test_paimon_write_data_types`:所有 Paimon 支持的类型(INT, BIGINT, VARCHAR, 
DECIMAL, TIMESTAMP, ARRAY, MAP, STRUCT)
+6. `test_paimon_write_nullable`:NULL 值处理
+7. `test_paimon_write_empty_batch`:空批次写入
+8. `test_paimon_commit_failure_rollback`:提交失败 → 数据文件清理
+9. `test_paimon_write_read_roundtrip`:Doris INSERT → Paimon → Doris 
SELECT,验证数据一致性
+
+#### 7.2 性能测试
+
+- 与 Paimon Flink Writer 对比(吞吐量、延迟)
+- 不同 batch size 的影响
+- 不同文件格式(Parquet vs ORC)的对比
+
+---
+
+### 任务依赖矩阵
+
+```
+Step 1 (Thrift)
+  ├──▶ Step 2 (BE 抽象+JNI+Stub)  ──▶ Step 3 (BE→FE 链路)
+  │         │                                │
+  │         └──▶ 可并行开发 ──┐               │
+  │                           ▼               │
+  ├──▶ Step 4 (FE Planner)                   │
+  │         │                                │
+  │         └──▶ Step 6 (FE Transaction) ◀───┘
+  │                    │
+  ├──▶ Step 5 (Java)  ─┘
+  │
+  └──▶ Step 7 (集成测试) ←── 所有 Step 完成后
+```
+
+### 关键风险与缓解
+
+| 风险 | 影响 | 缓解措施 |
+|------|------|----------|
+| Arrow IPC Stream 序列化开销大 | 写入性能不达预期 | 先用 IPC Stream 验证功能正确性;v2 切换到 Arrow C 
Data Interface(零拷贝) |
+| 不支持动态桶/跨分区桶 | 部分 Paimon 表无法写入 | v1 明确拒绝并给出清晰错误信息;v2 支持 |
+| 逐行 `write(row)` 开销 | PK 表写入慢 | 短期:按 bucket 分组后 `writeBundle()`;中期:Arrow C 
Data Interface + 批量写入 |
+| Thrift CommitMessage 与 Paimon 原生格式不一致 | 序列化兼容问题 | Java 端同时生成原生格式 byte[] 和结构化 
Thrift 格式 |
+| JNI 内存泄漏 | 长期运行不稳定 | 使用 `RootAllocator` + try-with-resources;JNI 全局引用管理 |
+
+---
+
+### Step 0:准备工作(开始前)
+
+1. **确认 Paimon SDK 版本**:在 `fe/be-java-extensions/paimon-scanner/pom.xml` 中固定 
paimon-core 版本
+2. **确认 Arrow 版本一致性**:BE 端(C++ Arrow)和 Java 端(Arrow Java)的版本兼容性
+3. **阅读参考代码**:
+   - `be/src/exec/sink/writer/iceberg/viceberg_table_writer.{h,cpp}` — Iceberg 
写入器参考
+   - `be/src/exec/operator/iceberg_table_sink_operator.h` — Pipeline Operator 
参考
+   - `fe/fe-core/src/main/java/org/apache/doris/planner/IcebergTableSink.java` 
— FE Planner 参考
+   - `be/src/format_v2/jni/paimon_jni_reader.{h,cpp}` — Paimon JNI 读取器参考(JNI 
调用模式)
+
+---
+
+## 附录 A:关键文件路径
+
+### Paimon Java SDK
+| 文件 | 说明 |
+|------|------|
+| `paimon-core/.../table/sink/TableWrite.java` | 写入接口 |
+| `paimon-core/.../table/sink/BatchTableWrite.java` | 批写入接口 |
+| `paimon-core/.../table/sink/BatchWriteBuilder.java` | 写入构建器 |
+| `paimon-core/.../table/sink/BatchTableCommit.java` | 批提交接口 |
+| `paimon-core/.../table/sink/StreamTableWrite.java` | 流写入接口 |
+| `paimon-core/.../table/sink/StreamWriteBuilder.java` | 流写入构建器 |
+| `paimon-core/.../table/sink/CommitMessage.java` | 提交消息接口 |
+| `paimon-core/.../table/sink/CommitMessageSerializer.java` | 提交消息序列化器 |
+
+### Paimon Rust
+| 文件 | 说明 |
+|------|------|
+| `crates/paimon/src/table/write_builder.rs` | WriteBuilder 实现 |
+| `crates/paimon/src/table/table_write.rs` | TableWrite 实现(~3000行) |
+| `crates/paimon/src/table/table_commit.rs` | TableCommit 实现(~4800行) |
+| `crates/paimon/src/table/commit_message.rs` | CommitMessage 结构体 |
+| `crates/paimon/src/table/data_file_writer.rs` | Append-only 文件写入器 |
+| `crates/paimon/src/table/kv_file_writer.rs` | PK 表 KeyValue 写入器 |
+| `crates/paimon/src/table/bucket_assigner*.rs` | 桶分配策略 |
+| `bindings/c/src/table.rs` | C FFI 绑定(当前只有读取) |
+| `bindings/c/Cargo.toml` | C 绑定 crate 配置 |
+
+### Doris
+| 文件 | 说明 |
+|------|------|
+| `be/src/exec/sink/writer/async_result_writer.h` | 异步写入器基类 |
+| `be/src/exec/sink/writer/iceberg/viceberg_table_writer.{h,cpp}` | Iceberg 
写入参考 |
+| `be/src/format_v2/jni/paimon_jni_reader.{h,cpp}` | Paimon JNI 读取器(参考) |
+| `be/src/format/jni/jni_data_bridge.{h,cpp}` | JNI 数据桥接 |
+| `gensrc/thrift/DataSinks.thrift` | Thrift sink 定义 |
+| `fe/fe-core/.../planner/IcebergTableSink.java` | Iceberg Sink FE 参考 |
+| `fe/fe-core/.../datasource/paimon/` | Paimon 数据源相关 |
+
+---
+
+## 附录 B:设计模式与交互规则
+
+### 关键抽象对应关系
+
+```
+Doris 概念          Paimon 概念             说明
+─────────────────────────────────────────────────────
+VPaimonTableWriter   BatchWriteBuilder      整个表的写入器
+VPaimonPartitionWriter  BatchTableWrite     单个 (partition, bucket) 的写入器
+Block (batch)        InternalRow / RecordBatch  数据批次
+prepareCommit()      prepareCommit()        生成 CommitMessage
+close() + RPC        TableCommit.commit()   提交
+```
+
+### CommitMessage 生命周期
+
+```
+创建:IPaimonWriter::prepareCommit()
+  ↓
+序列化(Thrift):BE → FE RPC
+  ↓
+聚合:FE Coordinator
+  ↓
+去重(idempotency):对比 commit_user + commit_identifier
+  ↓
+提交:Commit BE → IPaimonCommitter::commit()
+  ↓
+清理(success):释放 writer
+清理(failure):IPaimonCommitter::abort() 删除数据文件
+```
+
+---
+
+> **文档版本**:v3.0
+> **作者**:Doris Paimon Write Team
+> **日期**:2026-07-08
+> **状态**:从第一性原理出发的架构设计


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to