blob: a34aa2e8103f0c55555ba826de25914a8f19c7e0 [file] [view]
# 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 分支)
```cpp
// 写入路径使用通用线程池 (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
```cpp
// 读取路径使用轻量级解码线程池
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 分支合并内容总结
| 来源分支 | 主要变更 |
|---------|---------|
| **experiment** | Tablet 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 构造器参数化 midSerializationUtil 使用 mem_alloc 替代 malloc |
| **tablet_refine** | Tablet 写入优化 (已被 experiment 完全覆盖) |
| **read_opt** | BitMap 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)、DecodeThreadPoolTsFileSeriesScanIterator 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
```