TsFile C++ 架构与逻辑报告

最终版 (final branch) — 合并自 experiment (写入优化)、tablet_refine (Tablet 写入优化)、read_opt (读取优化)


1. 项目概览

TsFile C++ 是 Apache TsFile 时序数据文件格式的 C++ 实现,提供高性能的时序数据读写能力。

代码规模:157 个头文件 + 54 个源文件 = 211 个文件

1.1 目录结构

src/
├── common/                     # 公共基础设施
│   ├── allocator/              # 内存管理 (ByteStream, PageArena, MyString)
│   ├── cache/                  # LRU 缓存
│   ├── config/                 # 全局配置
│   ├── constant/               # TsFile 常量定义
│   ├── container/              # 容器 (BitMap, Array, HashTable, List)
│   ├── datatype/               # 日期转换等数据类型工具
│   ├── logger/                 # 日志 (elog)
│   ├── mutex/                  # 互斥锁封装
│   └── tsblock/                # TsBlock 列式数据块
│       └── vector/             # 列向量 (FixedLengthVector, VariableLengthVector)
├── compress/                   # 压缩 (LZ4, Snappy, LZO, Uncompressed)
├── cwrapper/                   # C 语言封装层
├── encoding/                   # 编码 (Plain, RLE, TS2Diff, Gorilla, Sprintz, Dictionary, ZigZag)
├── file/                       # 文件 I/O (ReadFile, WriteFile, TsFileIOReader/Writer)
├── parser/                     # 路径解析 (ANTLR4 生成)
├── reader/                     # 读取引擎
│   ├── block/                  # TsBlock 读取器
│   ├── filter/                 # 时间/值过滤器
│   └── task/                   # 查询任务
├── utils/                      # 工具函数
└── writer/                     # 写入引擎

2. 核心数据结构

2.1 Tablet — 写入数据容器

Tablet 是用户向 TsFile 写入数据的主要接口。

内存布局:
┌──────────────┐
│ timestamps_  │ → int64_t[] 时间戳数组
├──────────────┤
│ value_matrix_│ → ValueMatrixEntry[] 列值数组
│   ├─ bool*   │   (每列一个 entry,类型由 union 区分)
│   ├─ int32*  │
│   ├─ int64*  │
│   ├─ float*  │
│   ├─ double* │
│   └─ StringColumn*  ← [优化] Arrow 风格连续字符串存储
├──────────────┤
│ bitmaps_     │ → BitMap[] 空值位图 (bit=1 表示 null)
└──────────────┘

StringColumn 优化 (来自 experiment 分支):

  • 替代原来的 String* 数组 (每个字符串独立分配)
  • 采用 Arrow 风格的 offsets[] + buffer 连续存储
  • string[i] = buffer + offsets[i], len = offsets[i+1] - offsets[i]
  • 支持 append() 自动扩容,reset() 零拷贝重置
  • 支持 set_column_string_repeated() 批量填充相同字符串

批量写入 API:

  • set_timestamps(ptr, count) — 批量拷贝时间戳
  • set_column_values(idx, data, bitmap, count) — 批量拷贝列数据
  • set_column_string_repeated(idx, str, len, count) — 批量填充字符串列

2.2 TsBlock — 读取数据容器

TsBlock 是列式内存数据块,用于读取路径返回数据。

┌─────────────┐
│ TupleDesc   │ → 列描述 (列名 + 数据类型)
├─────────────┤
│ Columns[]   │ → FixedLengthVector / VariableLengthVector
│   └─ data   │   每列存储对应类型的数组
│   └─ nulls  │   空值位图
├─────────────┤
│ row_count   │ → 当前行数
│ capacity    │ → 最大容量
└─────────────┘

ColAppender → 向列追加数据
RowAppender → 管理行提交
ColIterator → 读取列数据

2.3 ByteStream — 分页式字节流

ByteStream 是一个链式分页缓冲区,用于编码和 I/O。

┌──────┐   ┌──────┐   ┌──────┐
│ Page │──→│ Page │──→│ Page │──→ NULL
│ buf  │   │ buf  │   │ buf  │
└──────┘   └──────┘   └──────┘
  ↑ head                  ↑ tail

特性:
- OptionalAtomic<uint32_t> total_size_ (可选原子操作,支持并发写入)
- 支持 wrap_from() 零拷贝包装外部缓冲区
- page_mask_ 位运算取模优化 (代替 %)
- AllocModID 追踪内存分配模块

2.4 BitMap — 空值位图

BitMap 用于标记 null 值 (bit=1 表示 null)。

[优化] 来自 read_opt 分支:
- count_set_bits(): 使用 __builtin_popcount 快速统计 null 数量
- next_set_bit(from, total): 使用 __builtin_ctz 快速跳过非 null 字节
  → 用于读取时跳过 null 行,时间复杂度与 null 字节数成正比

3. 写入路径 (Write Path)

3.1 整体架构

用户接口                    写入引擎                   文件格式
┌──────────┐           ┌───────────────┐          ┌──────────────┐
│ Tablet / │  write_   │ TsFileWriter  │  flush   │ TsFileIO     │
│ TsRecord │──tablet──→│               │─────────→│ Writer       │
└──────────┘  write_   │  ThreadPool   │          │              │
              table    │  (6 threads)  │          │ WriteFile    │
                       └───────┬───────┘          └──────────────┘
                               │
                 ┌─────────────┼─────────────┐
                 ↓             ↓             ↓
          ┌─────────────┐ ┌────────────┐ ┌──────────────┐
          │ ChunkWriter │ │ TimeChunk  │ │ ValueChunk   │
          │ (non-align) │ │ Writer     │ │ Writer       │
          └──────┬──────┘ └─────┬──────┘ └──────┬───────┘
                 ↓              ↓               ↓
          ┌─────────────┐ ┌────────────┐ ┌──────────────┐
          │ PageWriter  │ │ TimePage   │ │ ValuePage    │
          │             │ │ Writer     │ │ Writer       │
          └─────────────┘ └────────────┘ └──────────────┘

3.2 写入模式

三种写入模式:

模式方法Chunk 类型说明
Tree 非对齐write_tree() / write_tablet()ChunkWriter每个 measurement 独立时间戳
Tree 对齐write_tablet_aligned()TimeChunkWriter + ValueChunkWriter共享时间戳列
Table 模型write_table()TimeChunkWriter + ValueChunkWriter按 TAG 列自动分组设备

Table 写入流程:

  1. split_tablet_by_device() — 根据 TAG 列 (ID columns) 找到设备边界
  2. find_all_device_boundaries() — 使用 uint64 位图 + __builtin_ctzll 快速扫描边界
  3. 对每个设备子区间,调用 time_write_column_batch() + value_write_column_batch()

3.3 批量写入优化 (experiment 分支)

write_tablet() / write_table()
    │
    ├─ [快速路径] 无 null → write_column_batch()
    │   └─ chunk_writer->write_batch(timestamps, values, count)
    │       └─ page_writer_.write_batch(timestamps, values, count)
    │           └─ 直接 memcpy 或批量编码
    │
    └─ [慢速路径] 有 null → write_column() (逐行)
        └─ for each row: chunk_writer->write(timestamp, value, is_null)

value_write_column_batch():
    └─ value_chunk_writer->write_batch(timestamps, values, bitmap, start, count)
        └─ 支持所有基本类型的批量写入
        └─ STRING/TEXT/BLOB 回退到逐行

3.4 ThreadPool (experiment 分支)

// 写入路径使用通用线程池 (6 线程)
common::ThreadPool thread_pool_{6};

// 支持 std::future 获取结果
auto future = thread_pool_.submit([&] { return do_work(); });
auto result = future.get();

4. 读取路径 (Read Path)

4.1 整体架构

用户接口                     读取引擎                    文件格式
┌──────────────┐        ┌─────────────────┐        ┌───────────────┐
│ TsFileReader │───────→│ QueryExecutor   │───────→│ TsFileIO      │
│              │  query │ QueryDataSet    │  read  │ Reader        │
└──────────────┘        └────────┬────────┘        │               │
                                 │                 │ ReadFile      │
               ┌─────────────────┤                 └───────────────┘
               ↓                 ↓
      ┌─────────────────┐  ┌───────────────────┐
      │ TsBlockReader   │  │ QdsWithTime       │
      │ (Table 模型)    │  │ Generator         │
      │                 │  │ (Tree 模型)       │
      └────────┬────────┘  └───────────────────┘
               ↓
      ┌──────────────────────────┐
      │ SingleDeviceTsBlockReader│
      │ ├─ MeasurementColumnCtx  │
      │ │  ├─ SingleMeasurement  │ → 单列
      │ │  └─ VectorMeasurement  │ → 多列 (Aligned)
      │ └─ IdColumnCtx           │ → TAG 列
      └──────────┬───────────────┘
                 ↓
      ┌─────────────────────────┐
      │ TsFileSeriesScanIterator│ ← 时间序列扫描迭代器
      │ ├─ ChunkReader          │    (非对齐)
      │ └─ AlignedChunkReader   │    (对齐)
      └─────────────────────────┘

4.2 读取优化 (read_opt 分支)

4.2.1 Aligned 快速路径

当所有查询列都来自 aligned chunk group 时:

has_next() → has_next_aligned()
    │
    └─ 避免逐行 merge-sort
    └─ 批量 bulk_copy_into() 直接拷贝 TsBlock 数据
    └─ 利用 available_rows() 计算可批量拷贝的行数

4.2.2 行级 Offset/Limit 下推

TsFileSeriesScanIterator:
    set_row_range(offset, limit)
        │
        ├─ should_skip_chunk_by_offset(cm)
        │   └─ 利用 chunk 统计信息 (count) 跳过整个 chunk
        │
        └─ get_next_page() 6-param overload
            └─ row_offset, row_limit 传入 AlignedChunkReader
            └─ 利用 page 统计信息跳过整个 page

4.2.3 多值列合并读取

AlignedChunkReader:
    load_by_aligned_meta_multi(time_meta, value_metas[])
        │
        └─ 一次加载 time chunk + N 个 value chunk
        └─ get_next_page_multi() → 批量解码多列
        └─ 避免 N 次独立 chunk 加载开销

4.2.4 DecodeThreadPool

// 读取路径使用轻量级解码线程池
class DecodeThreadPool {
    submit(task);   // 提交解码任务
    wait_all();     // 等待所有解码完成
};

5. 编码子系统

5.1 编码器层次

Encoder (base)
├── PlainEncoder           # 无编码,直接存储
├── RleEncoder             # Run-Length Encoding
├── Ts2DiffEncoder         # 时间序列二阶差分 + zigzag
├── GorillaEncoder         # Gorilla 压缩 (浮点数 XOR)
├── SprintzEncoder         # Sprintz 编码
│   ├── Int32SprintzEncoder
│   ├── Int64SprintzEncoder
│   ├── FloatSprintzEncoder
│   └── DoubleSprintzEncoder
├── DictionaryEncoder      # 字典编码
└── ZigZagEncoder          # ZigZag 变长编码

Decoder (对称结构)
├── PlainDecoder
├── RleDecoder
├── Ts2DiffDecoder
├── GorillaDecoder
├── SprintzDecoder
├── DictionaryDecoder
└── ZigZagDecoder

5.2 批量编码支持

PlainEncoder:
    write_batch(values, count)  → 直接 memcpy

其他编码器:
    支持 write_batch() → 内部循环调用 encode()

5.3 SIMD 支持

通过 simde (SIMDe) 库提供跨平台 SIMD 支持,用于编码/解码加速。


6. 压缩子系统

Compressor (base)
├── UncompressedCompressor  # 无压缩
├── LZ4Compressor           # LZ4 压缩
├── SnappyCompressor        # Snappy 压缩
└── LZOCompressor           # LZO 压缩

7. 内存管理

7.1 分配器体系

AllocBase (g_base_allocator)
    │
    ├─ mem_alloc(size, mod_id)    # 带模块标记的分配
    ├─ mem_free(ptr)              # 释放
    ├─ mem_realloc(ptr, size)     # 重分配
    │
    └─ PageArena                  # 页面级内存池
        ├─ alloc(size)            # 从当前页分配
        └─ reset()               # 整体回收 (不逐个释放)

AllocModID (内存模块标记):
    MOD_DEFAULT, MOD_TABLET, MOD_CW_PAGES_DATA,
    MOD_TSFILE_WRITER, ...

7.2 ByteStream 内存追踪

ByteStream(AllocModID mid)
    └─ get_mid() → 传播给内部分配
    └─ SerializationUtil 使用 mid 进行所有中间分配

8. 文件格式

8.1 TsFile 结构

┌───────────────────┐
│ Magic ("TsFile")  │ 6 bytes
├───────────────────┤
│ Chunk Group 1     │ ← 一个设备的数据
│   ├─ Chunk 1      │   ← 一个 measurement 的数据
│   │  ├─ Header    │
│   │  ├─ Page 1    │   ← 一个数据页
│   │  ├─ Page 2    │
│   │  └─ ...       │
│   ├─ Chunk 2      │
│   └─ ...          │
├───────────────────┤
│ Chunk Group 2     │
├───────────────────┤
│ ...               │
├───────────────────┤
│ Index Area        │ ← 时间序列索引
│   ├─ MetaData     │
│   ├─ BloomFilter  │
│   └─ IndexTree    │
├───────────────────┤
│ Footer            │
│   └─ Magic        │
└───────────────────┘

8.2 Aligned Chunk Group

对齐写入时,一个设备的所有 measurement 共享时间戳:

Aligned Chunk Group:
├── TimeChunk          → 时间戳列 (只存一份)
│   ├── TimePage 1
│   └── TimePage 2
├── ValueChunk (col1)  → 值列1
│   ├── ValuePage 1
│   └── ValuePage 2
└── ValueChunk (col2)  → 值列2
    ├── ValuePage 1
    └── ValuePage 2

9. Final 分支合并内容总结

来源分支主要变更
experimentTablet StringColumn 优化、批量写入 API (set_timestamps, set_column_values, set_column_string_repeated)、write_column_batch/time_write_column_batch/value_write_column_batch 快速路径、ThreadPool (6 线程写入并发)、ByteStream 构造器参数化 mid、SerializationUtil 使用 mem_alloc 替代 malloc
tablet_refineTablet 写入优化 (已被 experiment 完全覆盖)
read_optBitMap count_set_bits/next_set_bit 优化、AlignedChunkReader 6-param get_next_page (offset/limit 下推)、aligned fast path (has_next_aligned + bulk_copy_into)、多值列合并读取 (load_by_aligned_meta_multi)、DecodeThreadPool、TsFileSeriesScanIterator row_range 支持

10. 关键数据流

写入流程 (Table 模型)

1. 用户创建 Tablet,调用 set_timestamps() + set_column_values()
2. TsFileWriter::write_table(tablet)
3. split_tablet_by_device() → 按 TAG 列分组
4. 对每个设备:
   a. do_check_schema_table() → 获取/创建 ChunkWriter
   b. time_write_column_batch() → 批量写入时间戳
   c. value_write_column_batch() → 批量写入各列值
5. check_memory_size_and_may_flush_chunks() → 内存超限则 flush
6. flush() → TsFileIOWriter 写入磁盘
7. close() → 写入索引和 footer

读取流程 (Table 模型)

1. TsFileReader::query(columns, filter)
2. QueryExecutor → SingleDeviceTsBlockReader
3. 对每个 measurement:
   a. 创建 MeasurementColumnContext
   b. 初始化 TsFileSeriesScanIterator
   c. 加载 TimeseriesIndex
4. has_next():
   a. [aligned fast path] → bulk_copy_into() 批量拷贝
   b. [merge path] → 逐行合并多列
5. next() → 返回 TsBlock
6. 用户遍历 TsBlock 的 ColIterator