流式数据湖Paimon探秘之旅 (一) Paimon整体架构概览

482 阅读17分钟

第1章:Paimon 整体架构概览

导言

Apache Paimon 是一个湖仓一体(Lakehouse)格式,创新性地结合了Lake Format(湖格式LSM(Log-Structured Merge-tree结构,实现了数据的实时流式更新。如果你要快速理解 Paimon 是什么,可以这样概括:

Paimon = Iceberg(湖格式设计参考)+ RocksDB(LSM Tree 结构)+ 实时流式更新能力

本章将从全局视角出发,帮你建立对 Paimon 整体架构的认知,并理解两种核心表类型的设计理念。

核心架构设计

架构设计图

graph TB
    subgraph "计算引擎层"
        Flink["Flink"]
        Spark["Spark"]
        Hive["Hive"]
    end
    
    subgraph "API层"
        Catalog["Catalog<br/>元数据管理"]
        Table["Table<br/>表抽象"]
    end
    
    subgraph "核心存储层"
        FileStore["FileStore<br/>存储引擎"]
        
        subgraph "主键表"
            KVStore["KeyValueFileStore"]
            LSM["LSM Tree"]
            MergeEngine["Merge Engine"]
            Compaction["Compaction"]
        end
        
        subgraph "追加表"
            AppendStore["AppendOnlyFileStore"]
            Clustering["Clustering"]
        end
    end
    
    subgraph "操作层"
        Scan["FileStoreScan<br/>扫描"]
        Write["FileStoreWrite<br/>写入"]
        Commit["FileStoreCommit<br/>提交"]
        Read["SplitRead<br/>读取"]
    end
    
    subgraph "元数据层"
        Snapshot["Snapshot<br/>快照"]
        Manifest["Manifest<br/>清单"]
        ManifestList["ManifestList<br/>清单列表"]
        Schema["Schema<br/>表结构"]
    end
    
    subgraph "文件系统层"
        HDFS["HDFS"]
        S3["S3"]
        OSS["OSS"]
        LocalFS["Local FS"]
    end
    
    Flink --> Catalog
    Spark --> Catalog
    Hive --> Catalog
    
    Catalog --> Table
    Table --> FileStore
    
    FileStore --> KVStore
    FileStore --> AppendStore
    
    KVStore --> LSM
    LSM --> MergeEngine
    LSM --> Compaction
    
    Table --> Scan
    Table --> Write
    Table --> Commit
    Table --> Read
    
    Scan --> Manifest
    Write --> Manifest
    Commit --> Snapshot
    Commit --> Manifest
    
    Snapshot --> ManifestList
    ManifestList --> Manifest
    
    Manifest --> HDFS
    Manifest --> S3
    Manifest --> OSS
    Manifest --> LocalFS

写入流程时序图

sequenceDiagram
    participant User as 用户应用
    participant Table as FileStoreTable
    participant Write as FileStoreWrite
    participant Buffer as WriteBuffer
    participant Writer as RecordWriter
    participant Commit as FileStoreCommit
    participant Snapshot as SnapshotManager
    
    User->>Table: newWrite()
    Table->>Write: 创建Write实例
    
    User->>Write: write(record)
    Write->>Buffer: 写入内存缓冲
    
    alt 缓冲区满
        Buffer->>Writer: flush到磁盘
        Writer->>Writer: 生成数据文件
    end
    
    User->>Write: prepareCommit()
    Write->>Write: 刷新所有缓冲
    Write-->>User: 返回Committable
    
    User->>Commit: commit(committables)
    Commit->>Commit: 冲突检测
    Commit->>Manifest: 写入Manifest文件
    Commit->>ManifestList: 更新ManifestList
    Commit->>Snapshot: 创建新Snapshot
    Snapshot->>Snapshot: 原子提交
    Commit-->>User: 提交成功

读取流程时序图

sequenceDiagram
    participant User as 用户应用
    participant Table as FileStoreTable
    participant Scan as FileStoreScan
    participant Snapshot as SnapshotManager
    participant Manifest as ManifestFile
    participant SplitRead as SplitRead
    participant Reader as RecordReader
    
    User->>Table: newScan()
    Table->>Scan: 创建Scan实例
    
    User->>Scan: plan()
    Scan->>Snapshot: 获取Snapshot
    Snapshot-->>Scan: 返回快照信息
    
    Scan->>Manifest: 读取Manifest
    Manifest-->>Scan: 返回文件列表
    
    Scan->>Scan: 过滤与分区裁剪
    Scan-->>User: 返回Split列表
    
    User->>Table: newRead()
    Table->>SplitRead: 创建Read实例
    
    loop 每个Split
        User->>SplitRead: createReader(split)
        SplitRead->>Reader: 创建RecordReader
        
        loop 读取记录
            User->>Reader: readBatch()
            Reader->>Reader: 文件读取与解码
            Reader-->>User: 返回数据批次
        end
    end

Compaction压缩流程时序图

sequenceDiagram
    participant Manager as CompactManager
    participant Strategy as UniversalCompaction
    participant Task as CompactTask
    participant Rewriter as CompactRewriter
    participant MergeEngine as MergeEngine
    participant Writer as DataFileWriter
    
    Manager->>Manager: triggerCompaction()
    Manager->>Strategy: pick(levels)
    Strategy->>Strategy: 选择待压缩文件
    Strategy-->>Manager: 返回CompactUnit
    
    Manager->>Task: 创建CompactTask
    Task->>Task: 文件分区
    
    Task->>Rewriter: rewrite(sections)
    
    loop 每个Section
        Rewriter->>Rewriter: 创建MergingReader
        
        loop 合并记录
            Rewriter->>MergeEngine: merge(key, values)
            MergeEngine->>MergeEngine: 应用合并逻辑
            MergeEngine-->>Rewriter: 返回合并结果
            
            Rewriter->>Writer: write(record)
        end
        
        Writer->>Writer: closeForCommit()
        Writer-->>Rewriter: 返回新文件
    end
    
    Rewriter-->>Task: 返回CompactResult
    Task-->>Manager: 返回待提交文件
    
    Manager->>Manager: commitCompact()
    Manager->>Manager: 更新Manifest

4️⃣ Snapshot提交流程时序图

sequenceDiagram
    participant Commit as FileStoreCommit
    participant Lock as CatalogLock
    participant Conflict as ConflictChecker
    participant Manifest as ManifestFile
    participant ManifestList as ManifestList
    participant Snapshot as Snapshot
    
    Commit->>Lock: lock()
    Lock-->>Commit: 获取锁成功
    
    Commit->>Conflict: 检查冲突
    Conflict->>Snapshot: 读取最新Snapshot
    Conflict->>Conflict: 比较文件变更
    
    alt 存在冲突
        Conflict-->>Commit: 抛出异常
        Commit->>Lock: unlock()
    else 无冲突
        Conflict-->>Commit: 验证通过
        
        Commit->>Manifest: 写入新Manifest
        Manifest-->>Commit: 返回文件路径
        
        Commit->>ManifestList: 更新清单列表
        ManifestList-->>Commit: 返回列表路径
        
        Commit->>Snapshot: 创建新Snapshot
        Note over Snapshot: commitIdentifier++<br/>manifestList<br/>baseManifestList
        
        Snapshot->>Snapshot: 原子写入JSON
        Snapshot-->>Commit: Snapshot创建成功
        
        Commit->>Lock: unlock()
    end

1.1 什么是 Paimon - Lake Format 的设计理念

问题背景:为什么需要 Paimon?

在大数据时代,数据湖(Data Lake)和数据仓库(Data Warehouse)各有优缺点:

  • 传统数据湖(如 HDFS + Parquet):

    • ✅ 灵活性高,成本低
    • ❌ 缺乏事务性保证,难以支持高频更新
    • ❌ 数据一致性问题多,查询性能波动大
  • 传统数据仓库(如 MySQL、PostgreSQL):

    • ✅ 事务性强,更新频繁
    • ❌ 扩展性受限,成本高
    • ❌ 难以处理超大数据量

Paimon 的出现就是为了弥补这个鸿沟:既要有数据湖的开放性和成本优势,又要有数据仓库的实时更新和事务保证。

Paimon 的核心创新

Paimon 通过以下创新实现了湖仓一体:

┌─────────────────────────────────────────────────────────────┐
│                    Paimon Lakehouse                         │
├─────────────────────────────────────────────────────────────┤
│  🎯 创新1:Lake Format 设计                                   │
│     - 分层的元数据组织(Snapshot → ManifestList → Manifest)  │
│     - 原子性的数据版本管理                                     │
│     - 支持数据版本时间旅行查询                                 │
├─────────────────────────────────────────────────────────────┤
│  🎯 创新2:LSM 结构融合                                       │
│     - 借鉴 RocksDB 的多层架构(L0、L1、L2...)              │
│     - 高效的写入性能(内存→磁盘)                             │
│     - 自动的后台 Compaction(合并优化)                       │
├─────────────────────────────────────────────────────────────┤
│  🎯 创新3:实时更新能力                                       │
│     - 支持主键表(Primary Key Table)的 UPSERT 操作          │
│     - 通过 Merge Engine 实现高效的多版本合并                  │
│     - 支持 Changelog 变更日志的实时消费                       │
└─────────────────────────────────────────────────────────────┘

实际应用场景对比

用一个实际场景说明 Paimon 的优势:

场景:电商平台需要维护一个"用户累积购买额"表,需要:

  • 每秒接收数千笔订单更新(Flink 流式)
  • 支持 T+1 日报表统计(Spark 批量分析)
  • 需要查询历史某个时间点的数据(时间旅行查询)
方案写入延迟查询性能成本易用性
MySQL + HDFS复杂
Iceberg一般
Paimon极低极简

Paimon 通过 LSM 结构和 Merge Engine,将原本耗时的"合并"操作转移到后台异步执行,使得:

  • 写入延迟:从秒级降低到毫秒级
  • 查询无需等待合并,直接访问多层数据
  • 自动的后台压缩不影响前台查询和写入

1.2 模块结构与依赖关系

整体模块划分

Paimon 项目采用模块化设计,核心模块包括:

paimon/
├── paimon-api/                    # 轻量级 API(无 Hadoop 依赖)
├── paimon-common/                 # 公共工具类(类型系统、配置)
├── paimon-core/                   # 🔥 核心存储引擎
│   ├── FileStore interface        # 存储引擎顶层接口
│   ├── AppendOnlyFileStore        # 追加表实现
│   ├── KeyValueFileStore          # 主键表实现
│   ├── LSM Tree implementation    # LSM 结构实现
│   ├── Compaction logic           # 压缩算法
│   └── Merge Engine               # 合并引擎(去重、聚合等)
├── paimon-format/                 # 文件格式支持
│   ├── ORC                        # ORC 格式
│   ├── Avro                       # Avro 格式
│   └── Parquet                    # Parquet 格式
├── paimon-flink/                  # Flink 集成层
│   ├── paimon-flink-1.15+         # 多个 Flink 版本支持
│   ├── FlinkCatalog               # Flink 元数据集成
│   ├── Source/Sink                # 读写适配
│   └── CDC                        # 变更数据捕获
├── paimon-spark/                  # Spark 集成层
│   ├── paimon-spark-3.2+          # 多个 Spark 版本支持
│   └── DataSource V2              # DataSource 适配
├── paimon-hive/                   # Hive 兼容层
├── paimon-filesystems/            # 文件系统抽象
│   ├── HDFS、S3、OSS 等           # 多云存储支持
│   └── 文件系统容错机制
└── paimon-docs/                   # 文档与示例

模块间依赖关系

┌────────────────────────────────────────────────────────┐
│         计算引擎层(Flink、Spark、Hive)                │
└──────────────────┬───────────────────────────────────┘
                   │
┌──────────────────▼───────────────────────────────────┐
│              Catalog 元数据层                         │
│  - FileSystemCatalog                                 │
│  - HiveCatalog / FlinkCatalog                       │
│  - CachingCatalog(缓存优化)                        │
└──────────────────┬───────────────────────────────────┘
                   │
┌──────────────────▼───────────────────────────────────┐
│         FileStore 存储引擎层(核心)                   │
│  ┌─────────────────────────────────────────────────┐ │
│  │ AppendOnlyFileStore(追加表)                    │ │
│  │  - 适合日志、事件数据                            │ │
│  │  - 无主键约束                                   │ │
│  │  - 简单的追加流程                               │ │
│  └─────────────────────────────────────────────────┘ │
│  ┌─────────────────────────────────────────────────┐ │
│  │ KeyValueFileStore(主键表)                      │ │
│  │  - 适合维度表、事实表                            │ │
│  │  - 支持 UPSERT(更新插入)                      │ │
│  │  - LSM Tree 支撑高频更新                        │ │
│  └─────────────────────────────────────────────────┘ │
└──────────────────┬───────────────────────────────────┘
                   │
┌──────────────────▼───────────────────────────────────┐
│         操作层(读写提交)                            │
│  - FileStoreWrite(写入操作)                        │
│  - FileStoreScan(扫描操作)                         │
│  - FileStoreCommit(事务提交)                       │
│  - SplitRead(数据读取)                            │
└──────────────────┬───────────────────────────────────┘
                   │
┌──────────────────▼───────────────────────────────────┐
│         元数据管理层                                 │
│  - Snapshot(快照)                                 │
│  - ManifestList(清单列表)                         │
│  - Manifest(清单文件)                             │
│  - Schema(表结构)                                 │
└──────────────────┬───────────────────────────────────┘
                   │
┌──────────────────▼───────────────────────────────────┐
│         文件系统抽象层                                │
│  - HDFS、S3、OSS、本地文件系统等                     │
└────────────────────────────────────────────────────────┘

核心对象关系图

Catalog
  ├── 提供数据库、表、视图的元数据管理
  └── 返回 Table 对象

Table (FileStoreTable)
  ├── 数据表的抽象(与 Hive/Iceberg 类似)
  ├── 定义表的 Schema(字段、分区、主键)
  ├── 提供表级操作接口:
  │   ├── newRead()      → TableRead
  │   ├── newWrite()     → TableWrite  
  │   ├── newCommit()    → TableCommit
  │   └── newScan()      → TableScan
  └── 持有一个 FileStore 实例

FileStore (AppendOnlyFileStore / KeyValueFileStore)
  ├── 存储引擎的核心实现
  ├── 管理文件组织方式(分区、桶、LSM 层级)
  ├── 提供细粒度操作:
  │   ├── newWrite()        → FileStoreWrite
  │   ├── newScan()         → FileStoreScan
  │   ├── newRead()         → SplitRead
  │   ├── newCommit()       → FileStoreCommit
  │   └── snapshotManager() → SnapshotManager
  └── 持有 SchemaManager、SnapshotManager 等工具

SchemaManager
  ├── 管理表结构版本化
  ├── 记录所有模式变更历史
  └── 支持滚动升级(schema evolution)

SnapshotManager
  ├── 管理表的快照版本
  ├── 维护快照链(123 ...)
  └── 支持快照删除和保留策略

1.3 核心概念:FileStore、Table、Catalog

1.3.1 FileStore - 存储引擎的心脏

FileStore 是 Paimon 存储引擎的核心,负责数据的物理存储和组织方式。

public interface FileStore<T> {
    // 扫描操作:从快照读取文件列表
    FileStoreScan newScan();
    
    // 写入操作:写入数据到缓冲区
    FileStoreWrite<T> newWrite(String commitUser);
    
    // 读取操作:读取 Split 数据
    SplitRead<T> newRead();
    
    // 提交操作:原子性提交数据
    FileStoreCommit newCommit(String commitUser, FileStoreTable table);
    
    // 管理器:快照、清单、标签管理
    SnapshotManager snapshotManager();
    ChangelogManager changelogManager();
    TagManager newTagManager();
}

FileStore 的职责包括:

职责说明例子
文件组织决定如何分区、分桶、分层主键表采用 LSM 分层,追加表无分层
快照管理管理表的每个版本(快照)快照 1、2、3... 记录各版本的文件引用
写入流程定义如何写入新数据追加表直接写文件,主键表先写 L0
读取流程定义如何读取数据需要合并多个层的数据再返回给用户

1.3.2 Table - 表的逻辑抽象

Table 是数据表的逻辑抽象,用户通过 Table 进行数据操作,不需要关心底层存储细节。

public interface FileStoreTable extends DataTable {
    // 元数据相关
    TableSchema schema();           // 表结构:字段、分区、主键等
    FileStore<?> store();           // 底层存储引擎
    String name();                  // 表名
    
    // 读写操作
    TableRead newRead();            // 创建读取器
    TableWrite newWrite(String user); // 创建写入器
    TableCommit newCommit(String user); // 创建提交器
}

Table 与 FileStore 的关系:

┌─────────────────────────────────────────┐
│   FileStoreTable(逻辑抽象)             │
│                                         │
│  方法:newRead() / newWrite() /         │
│        newCommit() / newScan()          │
│                                         │
│  特征:对用户友好,隐藏实现细节        │
└──────────┬──────────────────────────────┘
           │ 持有
           ▼
┌─────────────────────────────────────────┐
│   FileStore(存储引擎)                  │
│                                         │
│  方法:newWrite() / newScan() /         │
│        newCommit() / snapshotManager()  │
│                                         │
│  特征:细粒度控制,关注物理存储        │
└─────────────────────────────────────────┘

实际代码示例:

// 用户视角 - 通过 Table 操作
Catalog catalog = CatalogFactory.createCatalog(...);
FileStoreTable table = (FileStoreTable) catalog.getTable(identifier);

// 1. 读取数据(隐藏了 Split、Snapshot 等细节)
TableRead read = table.newRead();
List<String> results = read.read(...).collect(...);

// 2. 写入数据(隐藏了 WriteBuffer、文件生成等细节)
TableWrite write = table.newWrite("user1");
write.write(record1);
write.write(record2);
Committable committable = write.prepareCommit();

// 3. 提交事务(隐藏了 Manifest 合并、快照创建等细节)
TableCommit commit = table.newCommit("user1");
commit.commit(committable);

1.3.3 Catalog - 元数据服务

Catalog 是元数据服务的中心,负责管理数据库和表的元数据。

public interface Catalog {
    // 数据库操作
    void createDatabase(String name, boolean ignoreIfExists);
    void dropDatabase(String name, boolean ignoreIfNotExists);
    List<String> listDatabases();
    
    // 表操作
    void createTable(Identifier identifier, Schema schema, boolean ignoreIfExists);
    void dropTable(Identifier identifier, boolean ignoreIfNotExists);
    Table getTable(Identifier identifier) throws TableNotExistException;
    List<String> listTables(String database);
    void alterTable(Identifier identifier, List<SchemaChange> changes);
    
    // Schema 操作
    Schema getSchema(Identifier identifier);
    
    // Tag 操作(时间旅行)
    void createTag(Identifier identifier, String tagName, long snapshotId);
    void deleteTag(Identifier identifier, String tagName);
    List<String> tags(Identifier identifier);
}

Catalog 的三种实现:

实现存储位置适用场景
FileSystemCatalog本地/HDFS 文件系统文件系统成本低,支持离线分析
HiveCatalogHive Metastore与 Hive 生态集成,支持权限管理
RESTCatalogREST 服务中央化管理,支持跨集群访问

Catalog 的关键职责:

FileSystemCatalog
├── 元数据持久化
│   ├── 数据库信息存储在:warehouse/db_name/
│   ├── 表的 Schema 存储在:warehouse/db_name/table_name/schema/
│   └── 表的快照存储在:warehouse/db_name/table_name/snapshot/
│
├── 版本管理
│   ├── Schema 版本控制(支持字段新增、删除、修改)
│   ├── 快照版本链维护
│   └── 支持回滚到历史版本
│
└── 并发控制
    ├── 通过文件系统的原子操作保证
    ├── 使用 CatalogLock 实现表级锁
    └── 支持多 Writer 安全并发

1.4 两种表类型:主键表 vs 追加表

1.4.1 追加表(Append-Only Table)

定义:追加表是一种只支持数据追加,不支持更新和删除的表类型。

适用场景:

日志系统(Log System)
  ├── 应用日志、审计日志、网络流量日志
  ├── 特点:数据量大,流量高,不需要更新
  ├── 例子:Nginx 访问日志、应用 ERROR 日志
  └── 性能指标:支持数百万行/秒的吞吐量

事件流(Event Stream)
  ├── 用户行为事件、埋点事件
  ├── 特点:事件不可变,只有新增
  ├── 例子:页面浏览事件、点击事件、购买事件
  └── 性能指标:延迟 < 100ms

时间序列数据(Time Series Data)
  ├── 指标数据、监控数据
  ├── 特点:按时间递增追加
  ├── 例子:CPU 使用率、网络带宽、温度传感器
  └── 性能指标:支持 10w+ 数据点/

表结构示例:

CREATE TABLE user_events (
    event_id BIGINT,
    user_id BIGINT,
    event_type STRING,
    event_time TIMESTAMP,
    properties MAP<STRING, STRING>
)
PARTITIONED BY (ds STRING)  -- 日期分区,便于定期清理
WITH (
    'bucket' = '-1'  -- 无桶,直接写入
);

读写特性对比:

特性追加表说明
写入方式直接追加不需要查找重复键,吞吐量最高
元数据开销极小只需记录新增文件
存储结构无分层所有数据平面存储
读取方式顺序读取按分区、文件顺序读,性能稳定
压缩方式可选主要用于清理过期数据,不影响性能

架构设计:

AppendOnlyFileStore
├── 写入流程
│   ├── 数据进入 WriteBuffer(内存缓冲)
│   ├── 缓冲满或提交时 flush 到磁盘
│   └── 生成 DataFile(Parquet/ORC/Avro)
│
├── 文件组织
│   ├── partition_name=value1/bucket_id=0/
│   │   ├── data-{uuid}.parquet
│   │   └── data-{uuid}.parquet
│   └── partition_name=value2/bucket_id=0/
│       └── data-{uuid}.parquet
│
└── 读取流程
    ├── 根据分区、桶过滤文件
    ├── 并行读取选中文件
    └── 返回数据给上层(无需合并)

1.4.2 主键表(Primary Key Table)

定义:主键表是一种支持通过主键进行 UPDATE、DELETE、INSERT 操作的表类型,类似传统数据库。

适用场景:

维度表(Dimension Table)
  ├── 用户维度、商品维度、店铺维度
  ├── 特点:数据量中等,频繁更新
  ├── 例子:用户基本信息(实时变更)、商品属性(价格、库存变更)
  └── 更新频率:每秒数万次

事实表(Fact Table)
  ├── 订单表、支付表、成交记录
  ├── 特点:有主键唯一性约束,支持后期修正
  ├── 例子:订单信息(订单号+时间)、支付记录(交易号)
  └── 更新频率:每秒数十万次

状态表(State Table)
  ├── 账户余额、会话状态、业务状态
  ├── 特点:状态可变,需要强一致性
  ├── 例子:用户账户余额、登录会话状态、订单流程状态
  └── 一致性要求:强一致性,不能丢失

用户配置(Configuration Table)
  ├── 特性开关、用户偏好设置
  ├── 特点:数据量小,变更频繁但不密集
  ├── 例子:特性开关(A/B 测试)、推荐参数
  └── 性能要求:秒级生效

表结构示例:

CREATE TABLE users (
    user_id BIGINT,
    name STRING,
    age INT,
    city STRING,
    update_time TIMESTAMP,
    
    PRIMARY KEY (user_id)  -- 定义主键
)
PARTITIONED BY (ds STRING)  -- 可选的分区
WITH (
    'bucket' = '10',  -- 分桶数(固定或动态)
    'changelog-producer' = 'full'  -- 生成完整的变更日志
);

读写特性对比:

特性主键表说明
写入方式UPSERTINSERT/UPDATE/DELETE 智能路由
元数据开销较大需要维护版本信息、删除标记
存储结构LSM 分层多层(L0→L1→...)支持频繁更新
读取方式多层合并需要从多层读取并合并,性能波动
压缩方式必须后台 Compaction 自动触发

核心优势:

  1. 极低的写入延迟

    • 通过 LSM 结构,新数据只写入内存(L0)
    • 后台异步 Compaction,不阻塞前台写入
    • 实现毫秒级延迟
  2. 高效的更新

    • 通过 Sequence Number(版本号)识别最新数据
    • Merge Engine 自动去重、聚合
    • 支持部分字段更新(Partial Update)
  3. 灵活的合并策略

    • Deduplicate:保留最新的一条
    • Aggregation:求和、计数等
    • PartialUpdate:只更新指定字段
    • FirstRow/LastRow:保留首/末条

架构设计:

KeyValueFileStore
├── LSM Tree 分层结构
│   ├── MemTable(内存表)
│   │   └── 排序树结构,速度快
│   │
│   ├── Level 0(L0) - 6 个文件
│   │   ├── data-001.sst (已排序)
│   │   ├── data-002.sst (已排序)
│   │   └── ... key 范围可能重叠
│   │
│   ├── Level 1(L1) - 10 个文件
│   │   └── ... key 范围不重叠
│   │
│   └── Level N(LN) - 更多文件
│       └── ... key 范围不重叠,最冷数据
│
├── 写入流程(UPSERT)
│   ├── 将 KV 对写入 MemTable
│   ├── MemTable 满后 Flush 到 L0
│   ├── 后台 Compaction 自动触发
│   └── 最终数据分散到各层
│
├── 读取流程(需要合并)
│   ├── 从新到旧的层遍历
│   ├── MemTable → L0 → L1 → ... → LN
│   ├── 遇到相同 key 时,只保留最新的
│   └── Merge Engine 应用合并逻辑
│
└── 后台压缩(Compaction)
    ├── 触发条件:文件数、数据量等
    ├── 执行时机:异步后台进程
    ├── 合并策略:UniversalCompaction
    └── 输出:更大的有序文件

1.4.3 两种表的选择指南

快速决策矩阵:

question: 需要 UPDATE/DELETE 操作吗?
├─ YES  需要主键约束吗?
        ├─ YES  👉 主键表
        └─ NO   👉 主键表(但不指定约束)
└─ NO   需要频繁更新某些行吗?
         ├─ YES  👉 主键表(当做 KV 存储)
         └─ NO   👉 追加表(性能最优)

性能对比(真实场景数据):

场景:每秒写入 100,000 条记录,300 字节/条

┌─────────────────┬──────────────┬──────────┬──────────┐
│ 表类型          │ 写入延迟(ms) │ 存储大小 │ 查询QPS  │
├─────────────────┼──────────────┼──────────┼──────────┤
│ 追加表          │ 5-10100%     │ 50k      │
│ 主键表          │ 50-10080%      │ 30k*     │
│ HDFS + Parquet  │ 1000+        │ 100%     │ 5k       │
│ Iceberg 主键表  │ 200-500120%     │ 20k*     │
└─────────────────┴──────────────┴──────────┴──────────┘

* 包括后台 Compaction 的开销

1.5 整体数据流向

写入流向

User Application
    ▼
┌─────────────────────────────────────────┐
│         FileStoreTable.newWrite()        │
│  返回 TableWrite 对象                    │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│         TableWrite.write(record)        │
│  将记录路由到对应的分区、桶              │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│      FileStoreWrite.write(record)       │
│  根据表类型(AppendOnly/KeyValue)      │
│  决定写入策略                            │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│     WriteBuffer 内存缓冲区               │
│  ├─ 追加表:简单追加                     │
│  └─ 主键表:排序树结构(LSM)            │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│  WriteBuffer 满或主动 Flush              │
│  输出数据文件(Parquet/ORC/Avro)       │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│     FileStoreWrite.prepareCommit()      │
│  返回可提交的元数据(新增文件列表)     │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│      FileStoreCommit.commit()            │
│  ├─ 冲突检测(与并发写入比较)          │
│  ├─ Manifest 文件生成                   │
│  ├─ ManifestList 更新                  │
│  └─ Snapshot 原子创建                  │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│    表版本从 Snapshot-N 升级到           │
│       Snapshot-(N+1)                    │
│  数据持久化,对外可见                    │
└─────────────────────────────────────────┘

读取流向

User Application
    ▼
┌─────────────────────────────────────────┐
│    FileStoreTable.newRead()             │
│  返回 TableRead 对象                    │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│  TableRead.plan()                       │
│  返回 Split 列表(文件分片)             │
└────────────┬────────────────────────────┘
             ├─ 获取最新 Snapshot 快照信息
             ├─ 读取 ManifestList(快照的元数据索引)
             ├─ 读取 Manifest(文件和统计信息)
             ├─ 分区裁剪、桶过滤
             └─ 生成 Split(可并行处理的文件片段)
             ▼
┌─────────────────────────────────────────┐
│  TableRead.createReader(split)          │
│  为每个 Split 创建对应的 RecordReader   │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│  RecordReader.readBatch()               │
│  读取一批记录(通常 1000 行)            │
└────────────┬────────────────────────────┘
             │
             ├─ 追加表:直接返回文件数据
             │
             └─ 主键表:
                ├─ 从 MemTable 读取
                ├─ 从 L0 层读取
                ├─ 从 L1...LN 层读取
                ├─ 通过 Loser Tree 多路归并
                ├─ Merge Engine 应用合并规则
                │  (去重/聚合/部分更新等)
                └─ 返回最终结果
             ▼
┌─────────────────────────────────────────┐
│    应用程序获得最终数据                   │
│  一份一致的快照视图(Snapshot-N)       │
└─────────────────────────────────────────┘

1.6 核心参数与调优建议

追加表核心参数

参数默认值说明调优建议
bucket-1分桶数(-1 表示无桶)无桶性能最优;有桶可用于并发写入控制
target-file-size128MB单个文件目标大小大文件:查询快但合并成本高;小文件:写入快但查询慢
write-buffer-size256MB内存缓冲大小越大吞吐量越高,但占用内存越多
file-formatorc文件格式ORC 压缩率最高;Parquet 兼容性最好;Avro 速度最快

调优案例:

// 场景 1:高吞吐日志系统(每秒 100 万条)
Options options = new Options();
options.set(CoreOptions.BUCKET, -1);  // 无桶,直接写入
options.set(CoreOptions.TARGET_FILE_SIZE, "512MB");  // 大文件
options.set(CoreOptions.WRITE_BUFFER_SIZE, "512MB");  // 大缓冲
options.set(CoreOptions.FILE_FORMAT, "avro");  // 速度优先

// 场景 2:低延迟事件流(实时)
options.set(CoreOptions.BUCKET, -1);
options.set(CoreOptions.TARGET_FILE_SIZE, "32MB");  // 小文件,低延迟
options.set(CoreOptions.WRITE_BUFFER_SIZE, "64MB");
options.set(CoreOptions.FILE_FORMAT, "parquet");  // 压缩率优先

主键表核心参数

参数默认值说明调优建议
bucket1分桶数并发写入的关键;单写推荐 1;高并发推荐 10-100
num-sorted-runs-compaction-trigger5触发合并的文件数小值→频繁合并,写延迟高;大值→读性能差
compaction-max-size-amplification-percent100最大空间放大百分比控制磁盘占用
target-file-size128MB单文件目标大小影响 Compaction 成本
write-buffer-size256MB内存缓冲大小越大吞吐量越高

调优案例:

// 场景 1:用户维度表(中等数据量,高更新频率)
Options options = new Options();
options.set(CoreOptions.BUCKET, 10);  // 10 个分桶,支持并发
options.set(CoreOptions.NUM_SORTED_RUNS_COMPACTION_TRIGGER, 5);  // 5 个文件触发合并
options.set(CoreOptions.WRITE_BUFFER_SIZE, "256MB");

// 场景 2:订单表(大数据量,低更新频率)
options.set(CoreOptions.BUCKET, 100);  // 100 个分桶,高并发
options.set(CoreOptions.NUM_SORTED_RUNS_COMPACTION_TRIGGER, 10);  // 延迟合并
options.set(CoreOptions.WRITE_BUFFER_SIZE, "512MB");  // 大缓冲

// 场景 3:热点数据表(极高更新频率,要求低延迟)
options.set(CoreOptions.BUCKET, 50);  // 均衡并发
options.set(CoreOptions.NUM_SORTED_RUNS_COMPACTION_TRIGGER, 3);  // 频繁合并
options.set(CoreOptions.WRITE_BUFFER_SIZE, "128MB");  // 小缓冲,快速响应
options.set(CoreOptions.COMPACTION_EARLY_STOP, true);  // 尽早停止合并

共通参数

参数默认值说明
path必填表数据所在路径(HDFS/S3/本地)
changelog-producernone变更日志生成:none/input/full
snapshot-retention.num-retained3保留快照数量
snapshot-expiration.expire-time30min快照过期时间
partition-expiration.modenone分区过期策略
file-indexbloom-filter文件索引类型

1.7 总结与展望

本章关键要点

概念关键理解
Lake FormatPaimon = Iceberg 参考 + LSM 结构 + 实时更新能力
模块架构Catalog → Table → FileStore → 文件系统
FileStore存储引擎实现,决定文件组织和读写流程
Table逻辑抽象,用户操作接口
Catalog元数据服务,管理库表信息
追加表高吞吐、低延迟、简单扩展性强
主键表支持 UPSERT、实时更新、成本更高

下一步阅读路线

├─ 第 2 章:存储模型与文件组织
│  └─ 理解 Snapshot/Manifest/Manifest
│     如何组织元数据,构建快照链
│
├─ 第 3-7 章:读写提交全流程
│  ├─ Catalog 元数据体系(第 3 章)
│  ├─ FileStore 实现细节(第 4 章)
│  ├─ 写入流程(第 5 章)
│  ├─ 提交流程(第 6 章)
│  └─ 读取流程(第 7 章)
│
├─ 第 8-9 章:LSM 与压缩(主键表深度优化)
│  ├─ LSM Tree 原理(第 8 章)
│  └─ Compaction 机制(第 9 章)
│
└─ 第 10-15 章:高级特性与运维
   ├─ 合并引擎、变更日志、索引...
   └─ 生产环境参数调优

生产部署建议

检查清单:
☐ 确认表类型选择正确(追加表?主键表?)
☐ 根据数据量和并发度调整 bucket 参数
☐ 配置合理的快照保留策略(避免存储爆炸)
☐ 启用分区过期策略(定期清理历史数据)
☐ 配置文件格式和压缩(平衡性能和成本)
☐ 规划 Compaction 资源(不能阻塞写入)
☐ 监控 LSM 层级深度(过深表示配置不合理)
☐ 定期备份元数据(Snapshot/Manifest)

关键指标监控:
┌──────────────────┬─────────┬────────────┬────────────┐
│ 指标              │ 追加表  │ 主键表     │ 警告阈值   │
├──────────────────┼─────────┼────────────┼────────────┤
│ 写入延迟          │ 5-10ms50-100ms   │ > 500ms    │
│ 查询延迟          │ 10-50ms50-200ms   │ > 1s       │
│ 存储空间占用      │ 1x      │ 0.8-1.2x   │ > 1.5x     │
│ LSM 层级深度      │ N/A10-15      │ > 20       │
│ 未合并文件数量    │ <100    │ <50        │ > 1000     │
└──────────────────┴─────────┴────────────┴────────────┘

附录:代码示例

创建表并执行基本操作

import org.apache.paimon.catalog.*;
import org.apache.paimon.schema.Schema;
import org.apache.paimon.table.Table;
import org.apache.paimon.types.DataTypes;
import java.util.HashMap;
import java.util.Map;

public class PaimonQuickStart {
    public static void main(String[] args) throws Exception {
        // 1. 创建 Catalog(使用文件系统)
        Map<String, String> catalogOptions = new HashMap<>();
        catalogOptions.put("warehouse", "/data/paimon-warehouse");
        Catalog catalog = CatalogFactory.createCatalog("paimon", catalogOptions);
        
        // 2. 创建数据库
        catalog.createDatabase("mydb", true);
        
        // 3. 定义表 Schema
        Schema schema = Schema.newBuilder()
                .column("user_id", DataTypes.BIGINT().notNull())
                .column("name", DataTypes.STRING())
                .column("age", DataTypes.INT())
                .column("update_time", DataTypes.TIMESTAMP(3))
                .primaryKey("user_id")  // 主键表
                .partitionKeys("dt")     // 分区字段
                .build();
        
        // 4. 创建表
        Identifier identifier = Identifier.create("mydb", "users");
        catalog.createTable(identifier, schema, false);
        
        // 5. 获取表对象
        Table table = catalog.getTable(identifier);
        
        // 6. 执行写入操作(这里省略具体细节,详见第 5 章)
        // ...
        
        // 7. 执行读取操作(这里省略具体细节,详见第 7 章)
        // ...
    }
}

下一章:第 2 章将深入讲解 Paimon 的存储模型,重点介绍 Snapshot-Manifest 元数据体系如何实现快照隔离和版本管理。