blob: a8671345dce65334a8ed01e19411855877bc0648 [file] [view]
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# SQLite + TsFile 技术报告
本文说明当前实验实现;日常操作与排障见 [中文用户手册](USER_GUIDE.md),
英文版本见 [Technical report](TECHNICAL_GUIDE_EN.md)。
## 1. 实现结构
`tsfile_sqlite.cc` 实现统一虚拟表 `tsfile_hybrid`,负责参数、schema、冷热读取、
热区写入、封存和事务回调。`tsfile_sqlite_management.inc` 位于同一匿名命名空间,
提供 seal/export 管理函数和状态、校验虚拟表。集成测试位于
`../../test/sqlite/tsfile_sqlite_test.cc`,使用真实 SQLite 连接和 TsFile 读写器。
连接维护按 SQLite schema 与逻辑表名索引的表对象。每张表保存列、模式、源文件映射、
时间精度、watermark、待提交文件和 savepoint 标记。游标当前在内存中收集查询结果。
显式定义加目录、外部文件加目录、外部文件不加目录三种建表方式,最终都形成同一套
列结构,再通过 `sqlite3_declare_vtab` 声明给 SQLite。前两种可写,第三种只读。
文件推断只处理明确指定的 source_table,并扫描其最大时间;空源表通过完整 schema
元数据识别。普通源文件的隐含时间列名为 time,扩展生成文件使用 Property
`tsfile_sqlite.time_column` 保存自定义时间列名。
重连从 SQLite 恢复已登记的结构,不因外部文件变化重新推断。源文件缺失时仍能查看
结构和诊断,实际读取通过文件特征检查后才继续。
## 2. SQLite 内部状态
| 对象 | 内容 |
| --- | --- |
| `<table>_tsfile$hot` | 可写表的业务列及独立内部行号 |
| `<table>_tsfile$segments` | 文件路径、cutoff、行数、文件内部表名及内容指纹 |
| `<table>_tsfile$config` | 结构、模式、精度、目录、watermark、源文件及最大时间 |
| `<table>_tsfile$key` | 热数据的逻辑唯一键索引 |
只读表不创建 hot。内部对象与逻辑表处于相同 SQLite schema,名称全部转义;冲突时
拒绝创建。schema 以带长度的列名、TsFile 类型和类别序列保存,不因 SQLite 类型映射
丢失原类型信息。直接修改内部对象不属于受支持接口。
每个 TAG 在唯一索引中使用 `(tag IS NULL)` 和 `coalesce(tag,'')` 两个分量,最后
追加 TIME。这使 NULL 键相互冲突,同时保留 NULL 与空字符串的区别。无 TAG 时只有
TIME 键。源文件原有重复行保持原样,新数据必须晚于源表最大时间,不能覆盖历史。
热区内部行号选择不与业务列冲突的 `tsfile$rowid` 名称,业务 rowid/oid/_rowid_ 不影响
内部定位。冷行使用负的合成行号,仅用于操作内定位,不是稳定业务主键。
## 3. 查询和写入
xBestIndex 提供整数时间范围、BINARY TAG 等值和列裁剪信息。xFilter 读取热区及登记
冷文件,并使用每段保存的 source_table 映射;事务内新封存的段从本次临时路径读取。
支持的时间和 TAG 条件下推给 TsFile,剩余谓词、关联、聚合、排序和分页由 SQLite
执行,结果顺序需要显式 ORDER BY。
xUpdate 检查类型、TIME 非空、唯一键和 watermark,只写入热区。实际命中冷行的
UPDATE/DELETE,以及只读表实际写入,返回非约束类错误 SQLITE_READONLY,保证
IGNORE/FAIL 也不能留下同一语句的部分热区修改。SQLite 不调用零行 DML 的 xUpdate,
所以只读表上的无目标行操作可以作为空操作成功。
watermark 是可接受新时间的闭下界。普通空表从 INT64_MIN 开始;有源数据时为
source_max + 1。可写表的 NULL watermark 表示时间空间耗尽,与只读表通过 mode 区分。
xBegin 重新加载 SQLite 中的边界,避免另一连接封存后仍使用旧缓存允许迟到写入。
## 4. 文件归属
可写目录在建表时必须不存在或为空,不能与其他自有目录重合、嵌套或包含源文件。
扩展解析已有祖先的真实路径,以独占创建并 fsync 的 `.tsfile-owner` 记录归属。
标记绑定 SQLite 数据库规范路径及表名;内存数据库使用连接身份。改变 ATTACH 别名
不改变归属,但复制或移动数据库不能直接复用原可写目录。重连和写事务检查归属。
失败或回滚建表仅回收本次创建且拥有的标记和空目录。DROP 保留已提交文件及标记,
避免事务回滚后失去目录归属;后续清理由调用方负责。
内容特征使用文件大小和遍历全部字节的 FNV-1a 指纹,用于检测变化,不提供密码学
完整性证明。外部源文件必须由调用方保持路径和内容不变。扩展不因文件后缀就删除
未知 `.tmp` 或 `.tsfile`,也不把源文件父目录当作自己拥有的目录。
## 5. 显式 seal 与事务
管理函数只接受独立顶层 SELECT。seal 创建内部 savepoint,并通过虚拟表的零行
UPDATE 使 SQLite 注册该表的写事务回调;随后直接执行封存。因此封存参与同一连接
的外层事务及 savepoint。连接上的管理重入保护阻止嵌套管理操作。
非空封存流程如下:
1. 读取符合半开边界的热行,按可空 TAG 和时间排序。
2. 写入临时 TsFile、完成 footer 并 fsync,保存 schema 和已知精度。
3. 在 SQLite 事务中登记最终路径及指纹、删除对应热行、推进 watermark。
4. xSync 重新打开临时文件检查元数据,以不覆盖已有目录项的原子 rename 发布文件,
再 fsync 自有目录。
5. SQLite 提交登记状态,xCommit 清除本次待提交文件记录。
空封存只推进边界,不生成文件。显式 cutoff 为 int64 半开上界;export 自动封存
可以额外覆盖 INT64_MAX 并将可写时间标记为耗尽。
xRollback 只删除本次操作拥有的临时或已发布文件。xSavepoint 按 SQLite savepoint ID
记录待提交文件数,xRollbackTo 删除其后的文件并恢复缓存配置,xRelease 移除释放的
标记。回滚跨越新建表时,允许 SQLite 已删除 config,并释放本次新目录资源。
macOS 使用 `renamex_np` 的 RENAME_EXCL,Linux 使用 `renameat2` 的 RENAME_NOREPLACE。
已有目录项,包括悬空符号链接,均不会被替换。不支持该操作的文件系统或内核会报错。
文件持久化先于 SQLite 提交文件引用。进程在 SQLite 提交前中断可能留下未登记文件,
但 SQLite 仍保留旧热行,该文件不参与查询。verify 可以报告未登记的 TsFile,重连
不会猜测归属并自动删除。持久性依赖 SQLite journal/synchronous 设置和文件系统
正确执行 fsync,这不是跨文件系统的分布式事务。
## 6. 自动封存导出
export 禁止放入用户显式事务,先检查目标路径,再在内部事务中验证并捕获冷数据,
对可写表注册写事务后读取热数据。SQLite 的快照和写锁使并发冲突明确失败,不会让
一次导出静默混入边界检查之外的写入。
本次所有热行自动封存并提交后,将捕获的完整数据排序、重写到独立 staging 目录。
非空输出为一个 `part-000001.tsfile`,空表输出空目录。输出只含所选逻辑表,文件内部
表名使用逻辑表名,保留已知时间精度。源文件的其他表不会被带出。
输出完成后 fsync staging,以不覆盖方式原子发布目标目录,再 fsync 父目录。
封存提交与输出发布为两个阶段;第二阶段失败不会把冷数据变回热数据,也不会回退
watermark。错误说明封存是否已提交;父目录 fsync 失败时还会说明完整输出已发布。
普通失败清理本次已知 staging 文件,进程中断可以留下隔离目录,不发布部分结果。
封存提交后的新写入不进入本次输出,仍保留在热区。
## 7. 诊断和验证范围
`tsfile_table_info` 从当前事务视图读取配置、热行数和文件数。`tsfile_verify` 检查
登记文件是否存在、Reader 是否可解析元数据、指定表 schema 是否存在,以及全文
指纹是否一致;不逐页解码所有数据。它只扫描自有目录当前层的普通 `.tsfile`,不跟随
扫描项的符号链接,也不递归或扫描外部源文件父目录。状态为 OK、MISSING、CORRUPT、
MISMATCH、UNREGISTERED,仅报告,不注册、修复或删除文件。
集成测试覆盖三种建表、NULL 逻辑键、无 TAG、源表映射、空及多表文件、精度、INT64
耗尽、业务 rowid 列、冷热写约束、事务/savepoint、重连、并发边界、源文件变化与缺失、
目录归属、自动导出和标准 Reader 回读。回归还覆盖封存已提交后的导出失败、回滚跨越
建表、复制数据库抢占原目录,以及发布时已有目录项。
额外子进程检查在 DELETE/WAL 两种 journal 模式下,封存提交前后直接退出并重新打开
数据库,验证数据与冷热状态。这些检查不代表每个断电或文件系统故障点都已验证。
构建及测试命令见 [用户手册](USER_GUIDE.md)。
## 8. 当前限制
查询、封存及导出会在内存中收集数据,导出排序并重写完整快照,文件指纹也需要完整
读取文件;当前尚不是限制内存用量的流式实现。早期原型的 column=、隐藏管理列及
内部表布局已替换,不自动迁移原型数据库。首版不提供后台封存、compaction、自动
保留期、历史修正、批量文件注册、原地 schema 演进或完整数据库备份恢复。
Linux 特有文件发布路径需要 Linux CI 验证;本地验证环境是 macOS 和 Homebrew SQLite。