QuestDB 完全学习指南:2026 时序数据库"卷王",一文讲透 859 万行/秒写入与亚毫秒查询
如果你只听说过 InfluxDB 和 TimescaleDB,那这篇可能会刷新你对时序数据库性能上限的认知。
2026 年 8 月,TSBS 官方基准测试上,QuestDB 9.3.3 在 32 核 256G 的机器上跑出了 859 万行/秒 的持续写入------是同场 ClickHouse 的 4.9 倍、TimescaleDB 的 8 倍、InfluxDB 的 16 倍;"查每个设备最新一条"的 lastpoint 查询,QuestDB 用时 1.7 毫秒,TimescaleDB 是 20.5 毫秒,差了 12 倍。
更离谱的是,它用的还是标准 SQL,不是什么自创查询语言;数据还能直接躺在 Parquet/Iceberg/Arrow 这些开放格式里,不搞厂商锁定。
这篇指南面向后端工程师、数据工程师、时序数据库学习者和准备面试的同学,从架构原理到 Schema 设计、从时序 SQL 杀手锏到部署调优、从竞品对比到面试考点,16 章一次讲透。全文零代码堆砌,所有语法都用"关键字 + 参数表格 + 场景选型"的方式讲清楚原理和坑。
全文目录
- 第一篇 全景与基础:第 1 章 2026 格局与定位 / 第 2 章 架构解剖 / 第 3 章 快速上手
- 第二篇 Schema 与写入:第 4 章 Schema 设计 / 第 5 章 四种写入协议
- 第三篇 SQL 核心查询:第 6 章 时序 SQL 扩展 / 第 7 章 标准 SQL 与表达式 / 第 8 章 高级查询实战
- 第四篇 高级功能:第 9 章 数组/物化视图/实时视图 / 第 10 章 存储分层与开放格式 / 第 11 章 Pub/Sub 与流处理
- 第五篇 工程化与运维:第 12 章 SDK 与生态 / 第 13 章 部署扩容与 HA / 第 14 章 性能调优 / 第 15 章 竞品深度对比 / 第 16 章 新特性路线图与面试考点
第 1 章 时序数据库 2026 格局:为什么 QuestDB 被称为"时序卷王"
1.1 时序数据大爆发:四类场景撑出一个独立赛道
时序数据(Time-Series Data)这几年的增长曲线,比大多数业务数据都陡。主流场景可以归成四大类:
| 场景 | 典型数据源 | 数据特征 | 对数据库的核心诉求 |
|---|---|---|---|
| 金融行情与交易 | 股票/期货/加密货币 tick、L2 订单簿 | 纳秒级时间戳、单标的每秒数十万笔、精度即金钱 | 极致写入吞吐、纳秒精度、ASOF 对账、高精度小数 |
| IoT 传感器 | 工厂设备、车联网、能源电表、智慧楼宇 | 百万级设备、秒级/毫秒级上报、大量缺失值 | 高并发写入、乱序容忍、降采样、最新值查询 |
| 可观测性 | 应用 metrics、主机监控、链路指标 | 指标名+标签组合爆炸、查询多为最近时间窗 | 高基数不退化、Prometheus 生态对接、压缩率 |
| AI 特征回流 | 在线推理特征、embedding 向量、模型监控 | 数组型特征、实时闭环、要喂给 Spark/AI 引擎 | 原生数组、Arrow 零拷贝、开放格式互访 |
这四类数据有几个共同的"脾气":写入远多于读取、只追加不修改、时间天然有序、查询集中在"最近窗口 + 降采样聚合 + 每组最新值"、冷热分层极其明显。
拿通用 MySQL/PostgreSQL 硬抗会怎样?索引膨胀、VACUUM 痛苦、按时间分区要手工维护、降采样查询全表扫;拿通用 OLAP 的 ClickHouse 上呢?写入和分析都能打,但"每组最新一条"这种时序典型查询不是它的强项,也缺少时序专用语法。时序数据库(TSDB)这个细分品类,就是被这些刚需喂出来的。
1.2 五大时序产品横评矩阵(2026 年 8 月视角)
| 维度 | QuestDB 10.0 | TimescaleDB 2.29 | InfluxDB 3 Core | Prometheus 2.x | ClickHouse 26.7 |
|---|---|---|---|---|---|
| 产品定位 | 时序专用分析库 | Postgres 时序扩展 | 时序平台(监控/IoT) | 指标监控告警专用 | 通用 OLAP |
| 查询语言 | 标准 SQL(PG 方言) | 完整 SQL(PG 超集) | SQL/InfluxQL/Flux 并存 | PromQL | SQL(丰富方言) |
| 存储架构 | 内存映射列存 + 并行 WAL + Parquet | PG 堆表 + hypertable chunk | 列式 Parquet 底层(3.x 重写) | 自定义 TSDB 块格式 | MergeTree 列存 |
| TSBS 写入(rows/s) | 859 万 | 108 万 | 54.1 万 | 单机约百万级 | 175 万 |
| lastpoint 最新值查询 | 1.7 ms | 20.5 ms | 毫秒级但非专用路径 | 内置索引极快 | 相对慢 |
| JOIN 能力 | ASOF/LT/HORIZON/WINDOW/LATERAL 全家桶 | 任意关系 JOIN | 弱(3.x 改善中) | 基本没有 | 任意 JOIN 强 |
| 高基数标签 | 无 series 概念,加标签只是加行 | 可以但索引膨胀 | 3.x 已大幅改善 | 高基数易 OOM,老问题 | 强 |
| 生态 | Grafana/Telegraf/OTel/Arrow | 整个 PG 生态 | DevOps/IoT 生态最深 | 监控告警生态统治级 | BI/数仓生态 |
| 开源协议 | Apache 2.0 | Apache 2.0 + TSL 企业版 | MIT/Apache 双协议 | Apache 2.0 | Apache 2.0 |
| 最适场景 | 金融 tick、实时分析、SQL 派团队 | 已用 PG、要强关系 JOIN | 监控/IoT 全家桶 | 指标抓取+告警 | 通用大宽表分析 |
一句话总结这张表:Prometheus 守住监控告警基本盘,TimescaleDB 赢在"我就是 Postgres",InfluxDB 赢在 DevOps 生态存量,ClickHouse 赢在通用分析,而 QuestDB 把"时序专用性能 + 标准 SQL + 开放格式"这三件事同时拉满。
1.3 QuestDB 的一句话卖点
用标准 SQL,把金融级 tick 的写入吞吐和亚毫秒查询做到硬件极限,并且数据全程躺在开放格式里,不绑架用户。
拆成三个关键词:
- 标准 SQL:兼容 PostgreSQL Wire Protocol,8812 端口直连任何 psql、JDBC、psycopg、Grafana PG 数据源,学习成本几乎为零;时序扩展(SAMPLE BY、LATEST ON、ASOF JOIN)都是在 SQL 上做加法。
- 硬件极限性能:零 GC Java + C++/Rust 热路径、内存映射列存、SIMD 向量化、分区级并行,官方口径比 InfluxDB 3 Core 写入快 12--36 倍、复杂分析查询快 43--418 倍;比 TimescaleDB 写入快 6--13 倍、复杂查询快 16--20 倍。
- 开放格式:原生支持 Parquet 分区、Iceberg 互访、Arrow 线上传输,数据可以随时被 Spark/Flink/DuckDB/pandas 读走,不存在"进来了出不去"的厂商锁定。
1.4 开源 Apache 2.0 与 Enterprise 4.0 的功能边界
QuestDB 开源版极其慷慨------核心引擎、全部时序 SQL、WAL、ILP、PG wire、REST、QWP、Parquet 分区转换都在开源版里。企业版卖的是"运维和组织能力"。
| 功能 | 开源版 (Apache 2.0) | Enterprise 4.0 |
|---|---|---|
| 核心引擎 / 全部 SQL 扩展 | ✅ | ✅ |
| ILP / PG wire / REST / QWP 协议 | ✅ | ✅ |
| WAL、乱序写入 O3 | ✅ | ✅ |
| Parquet 分区转换(ALTER TABLE) | ✅(手动) | ✅ |
| Web Console、Grafana 插件 | ✅ | ✅ |
| 主从复制、只读副本 | ❌ | ✅ |
| SWITCH ROLE 主从在线切换(免重启) | ❌ | ✅ |
| 冷存储自动下沉 S3/GCS | ❌ | ✅ |
| Pub/Sub 实时订阅(500 订阅者) | ❌ | ✅(10.0 快随) |
| Kubernetes Operator | 社区 Helm | Operator 0.2.1 |
| 备份恢复、replica-first 迁移 | 基础快照 | ✅ 工具链 |
| RBAC、审计、商业支持 | ❌ | ✅ |
选型建议:中小团队、单机房、能接受自己做快照备份,开源版完全够用;金融级生产、要求跨机房 HA 和在线主从切换、需要对象存储降冷本,再考虑 Enterprise。
1.5 版本演进时间线:7.x 到 10.0 每一步都在补什么
| 版本/时间 | 里程碑 | 解决了什么问题 |
|---|---|---|
| 7.x(2022--2023) | ILP 协议成熟、SQL 扩展成型 | 写入入口与 Influx 生态打通;SAMPLE BY/LATEST ON 立住口碑 |
| 8.0(2024) | WAL 架构落地、乱序写入重写 | 并发写入不再互相阻塞;O3(out-of-order)支持从"能忍"变"好用" |
| 9.0--9.2(2024--2025) | JOIN 家族(ASOF/LT/HORIZON/WINDOW)、Parquet | 时序对齐查询补齐;三层存储闭环 |
| 9.3.5(2026-04) | LATERAL JOIN 、SQL 标准 UNNEST 、统计窗口函数(stddev/variance/covariance/correlation)、多右表 HORIZON JOIN、DST 修正的 SAMPLE BY(breaking change);SYMBOL 列 Hash Join 改整数比较、大分区 JOIN 加速、WAL commit batching | 关系表达能力向标准 SQL 靠拢;夏令时聚合不再错桶;性能再榨一轮 |
| 10.0(2026-08-06) | QuestDB Wire Protocol(QWP) 、原生 ARRAY 类型转正、Live Views 实时视图(beta)、DECIMAL 金融高精度、TIMESTAMP_NS 纳秒时间戳 | 二进制列式协议统一读写;L2 订单簿/向量原生存储;实时推送闭环 |
| 10.0.1(2026-08-24) | 10.0 首个补丁版 | 生产稳定性修复 |
| Enterprise 4.0(2026-08) | K8s Operator 0.2.1、SWITCH ROLE 在线切换、S3/GCS 冷存、Pub/Sub、replica-first 迁移 | 云原生运维与 HA 补齐 |
避坑指南
- 不要拿 7.x 时代的老博客指导 10.0 部署:WAL 在 8.x 就是默认表类型了,老教程里"建表要加 WAL 关键字"的说法已过时。
- 9.3.5 的 SAMPLE BY 时区处理是 breaking change:升级后跨夏令时的桶对齐结果会变,依赖旧行为做报表的要回归测试。
- 10.0 的 Live Views 还是 beta,生产实时链路别全压上去,可以先用物化视图兜底。
加分点
- 知道"QWP 一个协议同时管写入和 Arrow 读取"这件事,已经超过 90% 只会 ILP 的使用者。
- 能说出"开源版可以手动转 Parquet、企业版自动下沉 S3/GCS"的差异,说明你真的读过 10.0 的发布说明。
面试考点
- QuestDB 相比 InfluxDB/TimescaleDB 的核心差异是什么?(标准 SQL + 列存时序引擎 + 开放格式,背熟 1.2 的表)
- 8.0 的 WAL 架构解决了什么?(并发写互不阻塞 + 乱序写入)
- 10.0 三个最重磅特性?(QWP、原生 ARRAY、Live Views)
1.6 什么时候该上 QuestDB,什么时候别凑热闹
技术选型最怕"因为新所以上"。给一张决策清单,两边都摆清楚:
强烈建议上的信号:数据带时间戳、写入量是读取量的成百上千倍、查询集中在"最近窗口 + 降采样 + 每组最新值"、团队不想学第二门查询语言、需要把数据随时喂给 Spark/pandas/AI 引擎、单表数据量在千万到千亿行量级且还在涨。金融行情、车联网、能源计量、广告实时特征、游戏战报埋点,都是它的主场。
别急着上的信号:核心需求是多表事务和外键约束(这是 OLTP 的活)、数据模型实体关系复杂、JSON 文档操作为主、需要大量 UPDATE/DELETE 做状态回写、团队想要一个"什么都能干"的库。这类需求老老实实用 Postgres 或 MySQL;QuestDB 的 append 哲学和弱更新能力会让你很难受。
还有一个常见误区:拿 QuestDB 当日志全文检索引擎。它能存日志(STRING + SYMBOL 标签),但全文检索、倒排、模糊匹配不是它的设计目标,日志检索请交给 ES/ClickHouse。它擅长的是日志里结构化指标部分的时序分析。
加分点:能在面试里主动说"QuestDB 不擅长什么"(事务、更新、全文检索、通用 OLTP),比只会吹性能显得可信得多。
第 2 章 架构解剖:为什么 QuestDB 能这么快
面试被问"QuestDB 为什么快",只答"列存"是不及格的。它的快是一整套设计叠加出来的:零 GC 的运行时、内存映射的列文件、不可变的追加分区、SIMD 向量化、多核并行、极短的读写路径。这一章逐层拆。
2.1 运行时:零 GC Java,热路径交给 C++/Rust
QuestDB 的引擎主体是 Java 写的,但它做了两件反直觉的事:
- 数据路径上几乎不分配 Java 对象:核心数据结构走堆外内存、对象池化、缓冲区复用,GC 线程基本碰不到热点数据,所以你看不到 Java 系统那种周期性 STW 抖动。
- 最热的路径用 C++/Rust 重写:向量化扫描、聚合、JOIN、ILP 解析这些 CPU 密集环节是 native 代码,直接吃满 SIMD 指令集和 cache 局部性。
结果就是:拿到了 Java 生态的开发效率,又规避了 JVM 在数据密集场景的最大短板。
2.2 存储:内存映射 + 时间分区 + 列式
- 内存映射(mmap):列文件直接映射进进程地址空间,读数据靠操作系统 page cache,没有 read 系统调用、没有内核态到用户态的数据拷贝。热数据在内存里就是"文件本身",查询路径短到离谱。
- 时间分区列式存储 :表按时间切成分区(HOUR/DAY/MONTH 等),每个分区内每一列一个独立文件。查询只扫需要的列、只碰需要的分区------列裁剪 + 分区裁剪两个裁剪同时生效。
- Append-only 不可变分区:数据一旦落成分区就不再修改(更新走追加新版本 + 合并),不可变意味着无锁、无 MVCC 版本膨胀、缓存友好,也天然适合并行扫描和冷数据下沉。
- 数据路径零第三方依赖:不依赖 ZooKeeper、Kafka、HDFS,单个二进制就能跑,运维面积极小。
2.3 三层存储:WAL → 原生列分区 → Parquet
这是 8.x 到 10.0 逐步成型的核心架构,务必理解:
| 层级 | 形态 | 作用 | 生命周期 |
|---|---|---|---|
| 第一层:并行 WAL | 预写日志(多 WAL 线程并发写) | 吸收高并发写入、保证持久性、9.3.5 起 commit batching 攒批提交 | 秒级,随后合并进分区 |
| 第二层:原生列式分区 | QuestDB 自有列格式,mmap 可读 | 热数据/温数据的主力查询层,性能最高 | 天到月级,按保留策略回收 |
| 第三层:Apache Parquet | 开放列存格式 | 冷数据压缩归档;开源版用 ALTER TABLE 手动转,Enterprise 自动把冷分区下沉到 S3/GCS 并透明查询 |
长期归档,可被外部引擎直接读 |
写入与查询为什么互不干扰:写入永远只先进 WAL(顺序追加,极快),查询读的是不可变分区 + WAL 的合并视图,两者没有写写互斥、也没有读写锁竞争。这就是 8.0 WAL 架构带来的最大红利------9.3.5 还加了 WAL commit batching,把多个写入者的提交攒成一批 fsync,高并发下尾延迟进一步压低。
乱序写入(O3 buffer):迟到的数据不会被拒之门外,先进内存中的 out-of-order buffer 排序、再按时间戳合并进正确位置。只要乱序幅度在配置的 lag 窗口内,写入者完全无感知。
2.4 数据类型全家桶
| 类别 | 类型关键字 | 说明 | 典型场景 |
|---|---|---|---|
| 布尔 | BOOLEAN |
true/false | 设备开关、告警标记 |
| 整数 | BYTE / SHORT / INT / LONG |
8/16/32/64 位 | 计数器、数量;LONG 最常用 |
| 大整数 | LONG128 / LONG256 |
128/256 位 | 哈希值、超大 ID、链上地址量级运算 |
| 浮点 | FLOAT / DOUBLE |
32/64 位 | 传感器读数、价格序列(注意精度) |
| 高精度 | DECIMAL |
定点小数(10.0) | 金融成交价、金额,杜绝浮点误差 |
| 字符串 | STRING |
变长、无上限提示 | 高基数/自由文本(日志片段、请求 ID) |
| 字符串 | VARCHAR(n) |
有界变长 | 高基数但长度可预期的编码 |
| 字符串 | CHAR |
单字符 | 状态标记 |
| 字典串 | SYMBOL |
字典编码的整数化字符串 | 低基数重复字符串:标的代码、设备 ID、租户、机房 |
| 时间 | TIMESTAMP |
纳秒精度时间戳 | designated timestamp 首选 |
| 时间 | TIMESTAMP_NS |
显式纳秒类型(10.0) | Arrow 互操作、纳秒语义显式化 |
| 时间 | DATE / INTERVAL |
天精度日期 / 时间区间 | 按天统计、区间表达 |
| 地理 | GEOHASH |
定精度地理编码 | 车辆轨迹、区域聚合 |
| 数组 | ARRAY |
原生 N 维数组(10.0 转正) | L2 订单簿档位、向量 embedding、波形数组 |
| 其他 | UUID / IPv4 / BINARY |
专用类型 | 追踪 ID、IP 分析、二进制负载 |
类型选型直觉:金额用 DECIMAL 别用 DOUBLE ;重复出现的分类字符串用 SYMBOL,一次性的自由文本用 STRING/VARCHAR;时间戳统一 TIMESTAMP(纳秒),不要用 LONG 存时间------会丧失全部时序函数和分区裁剪能力。
2.5 与 Postgres 系行存扩展的本质差异
TimescaleDB 本质是"PostgreSQL + 时序插件":底层还是 PG 的堆表、行存、MVCC、VACUUM、B-tree 索引,hypertable 只是把表按时间切成 chunk。它的好处是完整 PG 生态,代价是存储引擎不是为时序从头设计的。
| 对比点 | QuestDB | Postgres 系扩展(TimescaleDB) |
|---|---|---|
| 存储模型 | 列存、时间分区列文件、mmap | 行存堆表 + chunk + 索引 |
| 并发控制 | 不可变分区 + WAL,几乎无锁 | MVCC,需要 VACUUM 回收 |
| 降采样 | SAMPLE BY 原生向量化执行 | 连续聚合物化视图 |
| 最新值 | LATEST ON 反向物理扫描,1.7 ms | DISTINCT ON / 索引扫描,20.5 ms |
| 扩展生态 | 时序专用 + 开放格式 | 整个 PG 生态(外键、存储过程、JSONB...) |
一句话:TimescaleDB 是"给 Postgres 打时序补丁",QuestDB 是"从磁盘格式开始就为时序重写"。前者赢在生态兼容,后者赢在性能上限。
加餐:向量化执行到底省了什么
SIMD 向量化是"快"的清单里最容易被一句带过、其实最硬核的一环。传统 Volcano 执行模型是"一次一行、一行里逐列函数调用",一条聚合查询的时间全耗在函数调用开销和类型分支判断上,CPU 流水线和缓存全部喂不饱。向量化反过来:一次取一批同类型列值(比如 1024 个 DOUBLE 连续摆放在内存里),用 SIMD 指令一条指令算 4--8 个值,聚合循环里没有虚函数、没有分支、数据在 cache line 里顺序流动。叠加列式存储天然连续的内存布局(mmap 列文件里同一列的值物理相邻),扫描聚合的 CPU 效率可以逼近理论内存带宽。SAMPLE BY 的均值/最大值、过滤条件的批量判定、统计函数的方差累加,都是靠这套路径跑到每秒数亿行级的扫描速度。面试时能把"列存 → 内存连续 → SIMD 批量 → 省掉逐行开销"这条因果链讲出来,就是引擎层理解合格的信号。
避坑指南
- 别把 QuestDB 当通用关系库用:没有外键、没有存储过程、UPDATE/DELETE 不是强项(设计哲学是 append + dedup)。
- 内存规划别按普通 Java 应用那套"堆给大":QuestDB 的性能来自 page cache,堆只给一小部分,大头内存留给操作系统缓存列文件。
- LONG256 不是给普通业务当主键用的,它面向哈希/加密场景,别滥用。
加分点
- 能讲清"WAL 不可变分区 + mmap"如何让读写互不干扰,是架构理解到位的标志。
- 知道 9.3.5 的 WAL commit batching,说明你关注的是 2026 年的最新实现而不是二手教程。
加餐:一张表记住 Schema 决策
| 决策点 | 选这个 | 不选这个 |
|---|---|---|
| 时间列 | TIMESTAMP 纳秒 + designated | LONG 存时间戳 |
| 重复分类字符串 | SYMBOL | STRING/VARCHAR |
| 近唯一字符串 | VARCHAR/STRING | SYMBOL(字典爆炸) |
| 金额 | DECIMAL | DOUBLE |
| 分区(日增 < 千万行) | DAY | HOUR(碎片)/ MONTH(裁剪粗) |
| 分区(日增千万--亿行) | HOUR | DAY(单分区过大) |
| 重传防护 | DEDUP UPSERT KEYS | 事后删重复 |
| 新表写入模型 | 默认 WAL | legacy 非 WAL(仅独占回填) |
面试考点
- QuestDB 为什么快?(零 GC + mmap 列存 + 不可变分区 + SIMD + 并行,五个点按 2.1--2.3 展开)
- 三层存储分别是什么?(并行 WAL / 原生列分区 / Parquet 冷存)
- 为什么不建议用 LONG 存时间戳?(丧失 designated timestamp 的排序、裁剪、时序函数能力)
2.6 一次查询的旅程:从 SQL 到结果集发生了什么
把"为什么快"落到一次具体查询上。假设你在 Web Console 跑一个"SAMPLE BY 1h 求每个标的最近 7 天平均价":
第一步,SQL 进入解析器生成逻辑计划,优化器先做分区裁剪 :时间条件"最近 7 天"直接换算成 7 个 DAY 分区(或几十个 HOUR 分区),其余分区的目录连打开都不打开。第二步做列裁剪 :查询只涉及时间列、symbol 列、价格列,磁盘上其余几百个列文件完全不碰------mmap 下不碰就等于零 IO。第三步,命中分区被分配给多个线程并行扫描,每个线程对自己分区内的列做向量化聚合:SIMD 指令一次处理多个值,symbol 分组按整数 ID 走数组下标而不是哈希字符串。第四步,SAMPLE BY 分桶因为数据物理有序,顺序扫描即可完成,无需排序。第五步,结果以列式批次(QWP 下是 Arrow IPC)返回,前端直接渲染。
对比传统行存库:同样查询要读整行(列裁剪失效)、走 B-tree 索引回表、分组要哈希字符串、还要排序------每个环节都慢一个量级。这就是 1.7 ms 和 20.5 ms 差距的来源。
2.7 一次写入的旅程:WAL 如何让灌库不打架
写入侧同样拆一遍。客户端一批数据通过 ILP/QWP 到达:写入线程先把行序列化追加到并行 WAL ------多个写入连接各写各的 WAL 段,顺序 IO,互不阻塞,这一步完成后客户端就收到成功(持久性已保证)。后台的 WAL 应用线程把攒够的批次(9.3.5 的 commit batching 在这里攒批 fsync)合并进内存中的分区结构:有序数据直接追加到最新分区尾部,迟到数据进 O3 buffer 排序后插入正确位置。分区达到滚动条件后在后台 flush 成不可变的列文件,此后永不再改,进入 mmap 可读状态。
关键设计:查询永远基于"不可变分区 + WAL 未合并部分"的合并视图,读路径不等待写、写路径不阻塞读,没有读写锁,也没有 MVCC 的版本链和 VACUUM。写入者唯一的"等待"是 WAL 的顺序 fsync,而 commit batching 把一群人的 fsync 合并成一次,尾延迟被压平。
面试考点(加餐):WAL 在这里和 MySQL/PG 的 WAL/redo log 有何不同?------后者的 WAL 服务于"崩溃恢复 + 事务回滚",QuestDB 的 WAL 还承担了"并发写互不阻塞 + 乱序排序 + 批量提交"的职责,是写入吞吐的核心机构,不只是恢复日志。
第 3 章 快速上手:安装、四端口与第一张时序表
3.1 三种安装方式对比
| 方式 | 命令/来源 | 适合场景 | 注意点 |
|---|---|---|---|
| Docker | 官方 questdb/questdb 镜像 | 本地试用、CI、生产容器化 | 挂载数据卷做持久化;显式映射 9000/8812/9009 |
| Homebrew | brew 安装 questdb 后启动 | macOS 本地开发 | 版本可能略滞后于 Docker 官方镜像 |
| 二进制 | 官网 tar.gz 解压,单进程启动 | Linux 裸机/虚拟机生产 | 无外部依赖,一个目录就是全部数据 |
Docker 资源建议:CPU 核数越多越好(查询按分区并行);内存分配上,JVM 堆只给几个 GB(约总量的 10%--25%),50% 以上留给 page cache;磁盘优先 NVMe SSD,文件系统 ext4/xfs 均可,建议关闭 swap 避免列缓存被换出。
3.2 四个端口,一次记牢
| 端口 | 协议 | 用途 | 备注 |
|---|---|---|---|
| 9000 | HTTP/REST | REST API + Web Console;ILP over HTTP、CSV 导入 /imp 也走这里 | 浏览器访问的入口 |
| 8812 | PostgreSQL Wire Protocol | 用任何 psql/JDBC/psycopg/BI 工具直连执行 SQL | 标准 SQL 的主入口 |
| 9009 | TCP(ILP legacy) | InfluxDB Line Protocol 原始 TCP 写入 | 官方现在推荐走 HTTP 的 ILP,TCP 为兼容保留 |
| 9003 | HTTP | health 健康检查 | K8s 探针、负载均衡探活用 |
记忆口诀:9000 看页面和导数据,8812 跑 SQL,9009 灌数据(老路子),9003 看死活。
3.3 Web Console:被低估的自带神器
打开 9000 端口就是 Web Console,零安装、零配置,三个核心功能:
- SQL Editor:编辑器带补全和语法高亮,查询结果直接出表格,还能一键切成图表(折线/柱状/仪表盘),探索数据时比敲 psql 快得多。
- Schema Designer:可视化建表,勾选列、选类型、指定 designated timestamp、选分区粒度,自动生成 DDL,新手不用背语法。
- 导入向导:CSV 拖进去自动推断 schema、预览类型映射、选择目标表,走的是 REST /imp 通道,导几十万行 CSV 几分钟搞定。
3.4 第一张时序表的"三件套"
不管什么场景,建表时把这三件事配齐,就拿到了 QuestDB 90% 的性能红利:
- Designated Timestamp :用
TIMESTAMP(ts)把某一列指定为表的逻辑时间轴。数据物理上按它排序,分区裁剪、SAMPLE BY、LATEST ON、ASOF JOIN 全部依赖它。没有它,这张表就退化成普通列存表。 - PARTITION BY 时间粒度 :新手一律先用
PARTITION BY DAY,后面第 4 章讲按数据量换 HOUR/MONTH 的精确规则。 - DEDUP UPSERT KEYS :用
DEDUP UPSERT KEYS(时间戳列, 关键标识列)声明去重键,重传/补数时相同键自动覆盖而不是产生重复行。
一张典型的行情/指标表骨架就是:时间戳列(TIMESTAMP 并 designated)+ 一个 SYMBOL 标识列(标的代码/设备 ID)+ 若干 DOUBLE/DECIMAL 数值列 + 可选 BOOLEAN/STRING 属性列,按天分区,按 (ts, symbol) 去重。
3.5 Grafana 初体验
QuestDB 有官方原生 Grafana 插件,数据源里直接选 QuestDB 类型即可(也可以用 PostgreSQL 数据源连 8812 兜底)。配好之后:
- 仪表盘变量可以用
$__timeFilter这类宏自动下推时间条件,命中分区裁剪; - 官方提供公共 demo 数据源和示例仪表盘(行情 K 线、IoT 设备大屏、可观测指标看板),clone 下来就能改成自己的;
- SAMPLE BY 配 Grafana 的
$__interval宏,缩放时间范围时自动调整降采样粒度,体验比手写字典表好太多。
加餐:Web Console 之外的两个开发利器
一是 9000 端口的 REST 查询接口:任何能发 HTTP 的环境(curl、浏览器、低代码平台、Serverless 函数)都能直接提交 SQL 拿 JSON/CSV 结果,应急排障和轻量集成不用装任何驱动------很多团队的内部小工具就是一个 fetch 请求拼 SQL。二是 EXPLAIN 在 Console 里直接可视化执行路径,分区裁剪命中几个分区、JOIN 走哪条路径一目了然,调优期养成"先 EXPLAIN 再跑"的习惯(第 14 章详述)。
避坑指南
- Docker 跑起来数据写没了?九成是没挂数据卷,容器重建即丢数据。
- 用 PG 协议连不上?先确认 8812 而不是 5432,QuestDB 不监听 PG 默认端口。
- Web Console 图表出不来?检查结果集是否有 designated timestamp 列,图表依赖时间轴。
- 生产环境 9009 裸 TCP 写 ILP 前先评估 HTTP ILP:官方推荐路径已转向 HTTP,连接管理和负载均衡都更友好。
加分点
- 建表时主动加 DEDUP KEYS,而不是等查出重复数据再补救------这是生产思维和玩具思维的分水岭。
- 知道 Grafana 原生插件和 PG 数据源两条路都通,排障时多一个后手。
面试考点
- QuestDB 四个端口分别是什么?(9000 REST/Console、8812 PG wire、9009 ILP、9003 health)
- 建表三件套是什么?(designated timestamp、PARTITION BY、DEDUP UPSERT KEYS)
- 为什么 JVM 堆不能给太大?(性能依赖 page cache 缓存 mmap 列文件)
3.6 首周上手 FAQ:新手最常问的 8 个问题
| 问题 | 一句话答案 |
|---|---|
| Docker 启动后数据在哪? | 挂载的数据卷目录里;不挂卷删容器即丢数据 |
| 忘记密码/账号怎么回事? | 默认无认证,仅监听本机;暴露公网必须自配反向代理加认证,别裸奔 |
| Web Console 能写 SQL 建表吗? | 能,和导入向导生成的 DDL 等价,生产建议 DDL 纳入版本管理 |
| psql 连不上? | 端口是 8812 不是 5432;用户名/库名随意填占位即可通过 |
| 数据写进去查不到? | 八成时间戳单位错了(纳秒 vs 毫秒),先查 min(ts)/max(ts) |
| 表能改吗? | 支持加列、改分区相关 DDL(WAL 表在线执行),但换类型/换时间轴代价大,建模期想清楚 |
| 一张表多少列算多? | 几十列正常;上百列且列很稀疏就该垂直拆分 |
| 需要 ZK/Kafka 依赖吗? | 不需要,单二进制自包含,这是它运维轻的核心原因 |
Docker 资源上再给个起步配方:本地试用给 2C4G 即可;生产起步 8C32G + NVMe,堆内存限制设 4--6G,其余留给 page cache;32C256G 是 TSBS 基准机型,对应日均十亿行以上的量级。
第 4 章 Schema 设计:时序库的命脉,90% 的性能问题在建表时就注定了
普通数据库"建表错了还能靠索引救",时序库不是------designated timestamp、分区粒度、SYMBOL 选型这三个决策一旦在海量数据上定型,后期改造成本极高。这一章是全书最重要的章节之一。
4.1 Designated Timestamp:整张表的时间轴
建表时用 TIMESTAMP(ts) 子句把某一列指定为 designated timestamp,它不是一个普通字段,而是表的"物理脊柱":
- 物理排序依据:数据在磁盘上按该列有序存放,LATEST ON 才能反向扫描、ASOF JOIN 才能二分对齐;
- 分区裁剪依据:PARTITION BY 切分、查询时时间条件跳过无关分区,全靠它;
- 时序函数前提:SAMPLE BY、LATEST ON、tick 间隔语法都只认 designated timestamp 列。
缺失后果清单 :SAMPLE BY 报错或退化为全表扫;LATEST ON 不可用;时间条件无法触发分区裁剪,查询从毫秒级掉到分钟级;ASOF JOIN 无法工作。一句话:没有 designated timestamp 的表,只是个慢一点的普通列存表。
设计建议:一张表只有一根时间轴;不要把业务事件时间(如交易发生时间)和入库时间混用,选业务时间做 designated timestamp,入库迟到交给 O3 buffer 处理。
4.2 PARTITION BY 选型:分区粒度决定一切
QuestDB 按时间把表切成目录级分区,粒度选择遵循一个朴素原则:让每个分区落在"几百 MB 到几 GB"的黄金区间。太小则分区爆炸(打开开销、元数据膨胀),太大则裁剪粒度粗、并行度低。
| 每日写入量级 | 推荐分区粒度 | 理由 |
|---|---|---|
| 10 万--1000 万行/天 | DAY | 单分区几十 MB--数百 MB,绝大多数项目的默认值 |
| 1000 万--1 亿行/天 | HOUR | 按天会到数 GB--十几 GB,按小时切回黄金区间 |
| < 10 万行/天 | MONTH 或 YEAR | 按天分区会碎成一地小文件,9.3.5 虽优化了宽表分区打开开销,但没必要 |
黄金法则的两个侧面:
- 分区是并行单位:一次查询能并行扫多少个分区,直接影响多核利用率。分区太少(比如全年只有 12 个月分区),32 核也只能 12 路并行。
- 分区是冷存/删除单位:TTL 回收、转 Parquet、下沉 S3 都是按分区整建制操作,粒度对齐运维节奏(按天归档比按小时归档好管理)。
经验做法:拿一天的样本数据量 × 行平均字节估算单分区大小,落在 300 MB--3 GB 就对了。监控场景指标列多但行小,金融 tick 单行字节大,同样行数需要的粒度不同。
4.3 SYMBOL:时序库里最值得讲透的类型
SYMBOL 是 QuestDB 对"重复出现的字符串"的字典编码类型:写入时字符串存入字典表,列里实际存的是整数 ID。
| 维度 | SYMBOL | VARCHAR / STRING |
|---|---|---|
| 存储 | 字典编码,列内存整数 | 原文存储 |
| 比较/JOIN | 整数比较(9.3.5 起 Hash Join 也按整数比) | 字符串逐字节比较 |
| GROUP BY | 按整数分组,极快 | 按字符串哈希,慢且费内存 |
| 适合 | 低基数、高重复:股票代码、币种、设备型号、租户 ID、机房、城市 | 高基数、近唯一:订单号、请求 ID、自由文本 |
| 内存开销 | 字典常驻内存,基数越大越吃内存 | 不额外吃字典内存 |
| 索引 | 内置 symbol index,等值过滤走索引 | 无 |
选型规则一句话:同一个值会在表里出现成百上千次以上 → SYMBOL;每个值基本只出现一两次 → VARCHAR/STRING。
高基数陷阱:把设备唯一 ID(百万级设备、每个值高频出现)设成 SYMBOL 是对的;但如果把"订单号/请求 trace ID"设成 SYMBOL,字典会膨胀到 GB 级常驻内存,查询反而被拖垮------这是新手上生产最典型的 OOM 来源。9.3.5 的 Hash Join 整数比较优化,也是建立在"symbol 基数健康"的前提上的。
补一个高基数场景的认知差:QuestDB 没有 InfluxDB 那种"series(测量集+标签组合)"概念。InfluxDB 里每多一个标签组合就是一条新 series,高基数下 series 爆炸直接拖垮引擎;QuestDB 里加一个新 symbol 值"只是多加一行数据",结构不变,所以百万设备规模下没有 series 退化问题------这是面试对比 InfluxDB 时的高频得分点。
4.4 WAL 表 vs 非 WAL 表
8.0 之后 WAL 表是默认形态,新表不用显式声明。要点:
- WAL 表支持多连接/多线程并发写入互不阻塞,支持乱序写入、支持表级 DDL 与写入并发进行;
- 非 WAL(legacy)表写入路径更短但同表写互斥,只适合单线程批量灌数的历史回填场景;
- 9.3.5 的 WAL commit batching 会把多个写入者的提交合并成批落盘,高并发写入下 fsync 次数大幅下降------这是 9.3.5 写入尾延迟改善的关键机制之一。
生产建议:一切新表用默认 WAL;只有做一次性大批量历史数据导入、且能独占写入时,再考虑非 WAL 表榨取导入速度。
4.5 DEDUP UPSERT KEYS:时序版 Upsert
时序数据"重传"是常态:网络重试、补数任务、采集端 at-least-once 语义都会产生重复行。建表时声明 DEDUP UPSERT KEYS(ts, symbol) 一类键值后:
- 相同键的后续写入自动覆盖旧行(upsert 语义),而不是追加重复;
- 键通常是"designated timestamp + 一个或多个 SYMBOL 标识列";
- 去重在 WAL 合并阶段完成,写入者无感知,不影响吞吐。
坑点:键设计错了(比如漏了多维度标识)会把本该并存的两行误合并;键里不要放高基数字符串列。先想清楚"什么才叫同一条观测",再定键。
4.6 TTL 与数据保留
时序数据天然冷热分明,保留策略与三层存储配合:
- 原生分区可以按分区整建制删除(DROP PARTITION 是元数据操作,瞬间完成,不产生 VACUUM);
- 冷分区用
ALTER TABLE转成 Parquet 继续留在本地(开源版)或由 Enterprise 自动下沉 S3/GCS; - 设计时就定好"热数据留多少天原生列存、温数据转 Parquet 留多久、冷数据归档到对象存储几年",生命周期与分区粒度对齐(DAY 分区配按天策略最省心)。
4.7 建表设计 Checklist
- designated timestamp 列已用
TIMESTAMP(ts)指定,语义是业务事件时间? - 分区粒度估算过单分区大小,落在几百 MB--几 GB?
- 每个字符串列过一遍"SYMBOL 还是 VARCHAR":高重复低基数 → SYMBOL,近唯一 → VARCHAR/STRING?
- SYMBOL 列基数评估过字典内存占用(万级/十万级正常,百万级要警惕,千万级 rethink)?
- DEDUP KEYS 能唯一定位一条观测?
- 金额类列用了 DECIMAL 而不是 DOUBLE?
- 时间戳统一纳秒 TIMESTAMP,没有用 LONG 偷存?
- TTL/冷存策略在设计文档里写明,而不是等磁盘满了再想?
避坑指南
- 上线后改 SYMBOL/VARCHAR 类型不是一次轻量 DDL,海量数据下等于重写表------建表期花一小时选型,省上线后三天迁移。
- 别给一张表塞几百列"以防万一":宽表会拖慢每次扫描的列裁剪收益和分区打开速度(9.3.5 虽优化了宽表分区开销),冷热字段、高频低频字段考虑垂直拆分。
- 时区在建模期就要统一:服务端、采集端、查询端混用 UTC 和本地时间,SAMPLE BY 桶会对不上。
面试考点
- SYMBOL 的原理和适用边界?(字典编码 + 整数比较 + 索引;低基数高重复,高基数用 VARCHAR)
- QuestDB 为什么不怕高基数标签而 InfluxDB 怕?(无 series 概念,新 symbol 值只是新行)
- 分区粒度怎么选?(单分区几百 MB--几 GB;10万--1000万行/天用 DAY,1000万--1亿用 HOUR,更低用 MONTH/YEAR)
- DEDUP UPSERT KEYS 的键怎么设计?(timestamp + 标识列,能唯一定位一条观测)
4.8 三个行业的建表案例:把 checklist 落成具体设计
理论讲完,看三个典型行业怎么落表(均为设计描述,不贴 DDL):
案例一:加密货币 tick 行情表。时间轴用纳秒 TIMESTAMP(撮合时间),按 HOUR 分区(头部币种单小时即达数百万行);symbol 列放交易对(BTC-USDT 这类,基数几十到几千,SYMBOL 完美命中);价格列全部用 DECIMAL 而不是 DOUBLE(浮点误差在高频交易里是真金白银);量列 LONG;L2 订单簿用 ARRAY 列存十档价量快照;DEDUP KEYS 取(ts, symbol, 交易所),因为同一时刻多交易所同币种是并存的不同行,漏了交易所维度会误合并。冷数据按周转 Parquet 下沉对象存储做历史回测。
案例二:工厂设备传感器表。时间轴毫秒 TIMESTAMP,按 DAY 分区;device_id(十万级)、产线、工厂三个维度都用 SYMBOL(组合基数高但单值重复极高,且没有 series 概念,放心用);温度、压力、电流用 DOUBLE,开关状态 BOOLEAN;上报间隔不齐 + 离线补传是常态,O3 max-lag 配 24 小时容忍跨班次补数;DEDUP KEYS(ts, device_id, 指标名)。大屏查最新状态走 LATEST ON,5 分钟趋势走 SAMPLE BY 5m,模拟量缺失用 LINEAR 插值,停机时段状态量用 PREV。
案例三:SaaS 业务指标表(多租户可观测) 。时间轴秒/毫秒 TIMESTAMP,按 DAY 分区;tenant_id 用 SYMBOL(查询永远带租户过滤,走 symbol index 天然隔离);metric_name 用 SYMBOL(几百个指标名);高基数的 user_id/request_id 类 label 一律放 STRING/VARCHAR 或干脆不入库------这是从 Prometheus 高基数 OOM 迁移过来的团队最容易犯的错;计数类指标缺失桶 FILL(0)。
4.9 SYMBOL 容量评估:字典到底能放多大
给个工程估算直觉:symbol 字典常驻内存,每个唯一值存字符串内容 + 哈希索引,粗估每条几十到上百字节。十万级 symbol 约 MB 级,毫无压力;百万级约百 MB 到几百 MB,可以接受(这正是 QuestDB 相对 InfluxDB series 模型的底气);千万级开始吃掉数 GB 且分组/JOIN 收益下降,应该重新审视模型------大概率是把"事实值"错当成了"维度值"(订单号、trace ID 不是维度)。评估动作很简单:上线前用 select count(distinct 列) 采样统计每列基数,乘以百字节估算字典内存,和容器内存上限对比。
避坑指南(加餐):SYMBOL 还有个隐蔽成本------自动建表时 ILP 的 tag 无条件 SYMBOL 化,采集端加一个新 label(比如临时调试加的 trace_id 标签)就可能让字典连夜膨胀。生产环境建议重要表预先 DDL 建好、关闭自动建表或对采集 label 做白名单。
第 5 章 数据写入:四种协议实战与乱序机制
QuestDB 提供四条写入路径,2026 年的格局是:QWP 是 10.0 起的主力新路径,ILP 是生态最广的时序通道,PG wire 最通用,REST /imp 最适合批量文件。
5.1 ILP(InfluxDB Line Protocol):生态最广的时序通道
ILP 是 InfluxDB 发明的行文本协议,QuestDB 全量兼容,Telegraf 等采集器零改造就能对接。一行数据由四部分组成:表名 + tag 集合 + field 集合 + 纳秒时间戳,空格和逗号分隔。
映射规则(背下来,排障全靠它):
| ILP 部分 | 映射到 QuestDB | 类型规则 |
|---|---|---|
| measurement(行首表名) | 目标表 | 表不存在自动建 |
| tags(逗号分隔的 k=v) | SYMBOL 列 | 全部字典化------tag 一定是低基数维度 |
| field 整数值 | LONG | 数值后缀 i 显式整数 |
| field 浮点值 | DOUBLE | 默认数值类型 |
| field 布尔值 | BOOLEAN | t/f 或 true/false |
| field 字符串值 | STRING | 引号包裹 |
| 行尾时间戳 | designated TIMESTAMP | 纳秒精度,不传则用服务端时间 |
关键坑:tag 一律变 SYMBOL 。把高基数字段(如 user_id、trace_id)写进 tag 集,等于亲手制造 4.3 节的字典爆炸------高基数维度放 field 侧落成 STRING/VARCHAR。传输方式上,传统走 9009 端口 TCP(legacy),官方现在推荐 HTTP 版 ILP(走 9000),负载均衡、连接管理、认证都更成熟。
Telegraf 对接:Telegraf 自带 QuestDB 输出插件,配置目标地址即可,metrics 的 label 自动成 tag→SYMBOL、value 成 field。Prometheus 生态可通过 remote write 转换接入,OpenTelemetry 也有对应 collector 导出路径。
5.2 PostgreSQL Wire 协议:最通用的 SQL 通道
8812 端口讲 PG wire 协议,意味着:
- 任何 pg 驱动(psycopg2/psycopg3、JDBC、node-postgres、Go pq/pgx)直接连;
- 标准 INSERT 语句批量写入,事务、预处理语句都可用;
- ORM 的原生 SQL 通道、dbt、Airflow、Superset 全部无痛接入。
适用场景:业务系统侧写数据、低频/中频写入、已经在用 PG 技术栈的团队。极限吞吐不如 ILP/QWP(SQL 解析层更厚),但通用性无敌。小技巧:用多值 INSERT 攒批、配合 WAL commit batching,也能跑到很高吞吐。
5.3 REST /imp:CSV 批量导入
9000 端口的 /imp 端点专门吃 CSV:HTTP 上传文件 → 服务端流式解析 → 自动建表或追加已有表。Web Console 的导入向导底层就是它。
- 支持 URL 参数指定表名、分区粒度、designated timestamp、是否覆盖、类型覆盖(overwrite/partitionBy/timestamp 等);
- 适合:历史数据迁移、数仓批量回灌、离线文件补数;
- 实践:先小样本导入核对类型推断(尤其时间戳格式和 SYMBOL 判定),再全量;千万级行以内体验很好,更大规模考虑拆文件并行。
5.4 QWP(QuestDB Wire Protocol):10.0 的主力二进制通道
10.0 引入的 QWP 是这套系统面向 2026 年的回答:一个二进制列式协议,同时承担写入和读取。
- 线上数据编码为 Arrow IPC record batches :写入时客户端直接以 Arrow 列式内存投递,读取时结果也是 Arrow 列式批次返回,客户端零转换、零行列重组;
- 对 pandas/pyArrow/Polars/Rust/Java 这类列式生态尤其友好:DataFrame 发出去是 Arrow,查回来还是 Arrow,没有序列化税;
- 10.0 起官方各语言客户端逐步把 QWP 作为默认/推荐路径,PG wire 继续保留作通用兼容。
直觉类比:ILP 是"文本行协议",PG wire 是"行式 SQL 协议",QWP 是"列式 Arrow 协议"------数据在客户端就是列存的,进引擎还是列存的,全程不转身。
5.5 四协议横向对比
| 协议 | 端口 | 形态 | 吞吐 | 延迟 | 易用性 | 最佳场景 |
|---|---|---|---|---|---|---|
| ILP(HTTP/TCP) | 9000 / 9009 | 文本行 | 极高 | 极低 | 采集器配置即用 | 指标/IoT 持续灌数、Telegraf/OTel |
| PostgreSQL wire | 8812 | SQL 行式 | 中高 | 低 | 任何 pg 驱动 | 业务写入、BI/工具对接 |
| REST /imp | 9000 | CSV 文件 | 高(批量) | 批处理 | 拖文件即可 | 历史迁移、离线回灌 |
| QWP | 9000 体系 | 二进制 Arrow 列式 | 最高 | 最低 | 新客户端 SDK 内置 | 高频实时、DataFrame/AI 特征链路 |
5.6 批量写入最佳实践
| 实践项 | 建议值/做法 | 原理 |
|---|---|---|
| batch 大小 | 每批 1 万--10 万行(或数百 KB--数 MB)起步压测 | 太小→网络/提交开销摊不开;太大→内存峰值和失败重传代价高 |
| flush 间隔 | 100 ms--1 s 攒批发送 | 配合 WAL commit batching 攒批落盘 |
| 并行连接数 | 按 CPU 核数压测,通常 4--16 个写入连接 | WAL 支持并发写,但过多连接增加合并压力 |
| 表数量 | 同类数据合表 + SYMBOL 区分,别几万张表 | 每张表独立 WAL/分区/文件句柄 |
| 时间戳 | 客户端统一纳秒、统一 UTC | 避免单位错配(见 5.8) |
5.7 乱序写入与 O3 buffer
真实时序数据很少严格有序:离线网关重传、车载设备缓存补传、跨地域采集延迟都会让迟到数据"插队"。QuestDB 的 O3(out-of-order)机制:
- 迟到数据先进内存 O3 buffer 按时间戳排序,再合并进正确的分区位置;
- 容忍窗口由 max-lag 类配置控制(允许"最多迟到多久"的数据),窗口内乱序完全无感知;
- 超过窗口的极晚数据处理成本变高------所以配置要略大于真实 P99 迟到时长,但别无限大(内存和合并成本随窗口增长)。
对比参照:很多时序库乱序支持要么没有、要么靠重写分区;QuestDB 从架构层面把乱序当一等公民,这也是它能在 IoT 补数场景口碑好的原因。
5.8 写入避坑清单
加餐:批量写入的失败重试设计
写入端工程化还有一个细节:批量失败怎么重试。原则是"批次可重放 + 去重兜底"------每个批次保留原始 payload,失败整批重试而不是逐条重试(逐条重试在百万行场景会把重试风暴放大),依赖 DEDUP KEYS 保证重放不产生重复行。对 ILP 客户端,发送缓冲区内未确认批次在连接断开后可安全重发;对 QWP/PG 通道,事务边界内的批次要么全进要么全不进。再配合客户端本地队列 + 背压(下游 WAL 积压时降速而不是无限堆积),一套生产级写入管道就成型了。
- 时间戳单位:ILP 默认纳秒。毫秒时间戳直接当纳秒写,数据会落到 1970 年附近,查"最近一天"永远查不到------新手第一坑。
- 纳秒精度的代价:精度到纳秒意味着同键去重窗口极细,确认你的 DEDUP KEYS 语义匹配业务(同一事件多次上报纳秒不同会被当成不同行)。
- tag/field 放错边:高基数进 tag = 字典爆炸(5.1 已述)。
- 重复数据:没建 DEDUP 的表重传必重复,且时序库删重复行很痛------建表时配好 UPSERT KEYS。
- 自动建表的类型惊喜:ILP/ILP 自动建表靠首批数据推断,首批没出现的列、类型推断偏差(整数被推成浮点)都会长期定型,重要表先用 DDL 显式建好。
- 连接风暴:短连接高频建连比攒批长连接差一个数量级,写入端务必复用连接。
面试考点
- ILP 的 tag 和 field 分别映射成什么?(tag→SYMBOL,field→按值类型映射 LONG/DOUBLE/BOOLEAN/STRING)
- QWP 为什么快?(二进制 Arrow IPC 列式,读写两端零转换)
- 乱序写入怎么实现的?(O3 buffer 内存排序 + 按时间合并进分区,max-lag 控制容忍窗口)
- 高基数维度为什么不能放 ILP tag?(tag 强制 SYMBOL 字典编码,基数爆炸吃内存)
5.9 历史数据回填:批量灌数的正确姿势
新项目上线常要迁移几年历史数据,这和日常实时写入打法不同:
- 通道首选 REST /imp 传 CSV 或 ILP 批量文件,单文件百万行级并行上传;历史数据天然带准确时间戳,全部走 designated timestamp 列。
- 回填时乱序幅度可能极大(几年数据乱序到达),策略是按时间排序后分段灌或临时调大 O3 lag,灌完恢复;更稳妥的做法是按分区(如按月)顺序导入,每个分区内部基本有序,O3 压力最小。
- 回填期间 DEDUP 正常生效,重跑任务天然幂等------这是建表配 UPSERT KEYS 的第二个红利(第一个是防采集重传)。
- 回填和实时写入可并行(WAL 表),但建议限速,避免历史合并抢占实时查询的 IO 和 page cache;大回填放业务低峰。
5.10 排障实录:写入量上不去的排查顺序
真实调优过的一批写入吞吐问题,按出现频率排序:一是短连接 ,每批新建 TCP 连接,吞吐差一个数量级,改连接池立刻翻倍;二是批太小 ,每行一批或每秒一批,网络往返和 fsync 摊不开,攒到万行/百毫秒级;三是时间戳让服务端生成 (ILP 不带时间戳),高并发下服务端时钟调用和分配成瓶颈,客户端自带纳秒时间戳;四是表太多 ,几百张表每张表独立 WAL 和合并线程,合并线程池打满,合并成合表加 SYMBOL;五是O3 窗口配到无上限,迟到数据在 buffer 里无限堆积合并不过来,WAL 积压监控告警------回到合理 max-lag。走完这五步,绝大多数写入性能问题收敛。
第 6 章 时序 SQL 扩展:QuestDB 的六大杀手锏语法
标准 SQL 能做的它都能做,标准 SQL 做不了的时序动作,QuestDB 用六个扩展语法补上:SAMPLE BY、LATEST ON、tick 间隔、ASOF/LT JOIN、HORIZON/WINDOW JOIN、LATERAL JOIN。这一章是全书密度最高的一章。
6.1 SAMPLE BY:时间分桶降采样
SAMPLE BY 把按秒/毫秒涌入的明细按时间桶聚合(1 分钟 K 线、5 分钟均值、每小时计数),是时序查询出场率最高的语法。核心要素:
- 分桶单位:紧跟 SAMPLE BY 的时间大小,如 1m(1 分钟)、5m、1h、1d、1w;配合聚合函数(avg、sum、max、min、count、first、last)使用。
- FILL 策略:没有数据的空桶怎么填,决定了报表"缺数据"时的形态:
| FILL 策略 | 含义 | 空桶结果 | 适用场景 |
|---|---|---|---|
| NONE | 不填,空桶直接消失 | 该行不存在 | 默认;只关心有数据的时段,画图时断线段 |
| NULL | 填 NULL | 聚合列为 NULL | 要保留时间轴连续性,由前端/BI 处理空值 |
| PREV | 前值填充(持续最后值) | 用上一桶值 | 状态量:设备在线状态、最新报价延续 |
| LINEAR | 线性插值 | 前后桶线性推算 | 模拟量传感器:温度、压力等连续物理量 |
| 常数(如 FILL(0)) | 填指定常数 | 0 或给定值 | 计数/事件量:没事件就是 0(订单数、错误数) |
- ALIGN TO CALENDAR TIME ZONE :让桶边界对齐自然日历(按天/月聚合时对齐当地时间零点)并指定时区;
WITH OFFSET可进一步偏移桶起点。 - DST 夏令时(9.3.5 重要变更) :9.3.5 起 SAMPLE BY 做了 DST 修正 ------夏令时切换的那一天,按日历对齐的桶不再产生"23 小时/25 小时的畸形桶",时区换算结果与人类日历一致。注意这是 breaking change:升级后跨 DST 的历史聚合结果会变,报表要回归。
原理直觉:SAMPLE BY 能快,是因为数据按 designated timestamp 物理有序------分桶聚合就是一次顺序扫描 + 向量化聚合,不需要排序、不需要哈希表(配合 PARTITION BY 的分组键时走 symbol 整数分组)。
常见坑:FILL 必须和 SAMPLE BY 同语境;对"没事件就是 0"的场景忘记 FILL(0),图上会断线条误导业务;时区不指定则按服务端时区,跨国团队务必显式 TIME ZONE。
6.2 LATEST ON ... PARTITION BY:每组最新一行
"每个设备的最新状态""每只股票的最新价"是时序查询的半壁江山。QuestDB 的写法是 LATEST ON ts PARTITION BY symbol:
- 原理 :数据物理按时间有序,执行器从分区尾部反向扫描 ,每个 PARTITION BY 的键碰到第一条就返回,立刻停止------不是全表扫 + 排序 + 去重,而是"倒着找、找到就停"。
- 性能 :TSBS lastpoint 基准 1.7 ms;对照 TimescaleDB 用 DISTINCT ON 的 20.5 ms,12.1 倍差距主要来自这个物理设计。
- 等价写法对比:
| 实现方式 | QuestDB | 标准 SQL/Postgres | 代价 |
|---|---|---|---|
| 每组最新行 | LATEST ON ts PARTITION BY k | DISTINCT ON (k) ... ORDER BY k, ts DESC | 标准写法要排序全量 |
| 窗口函数替代 | 也可用 ROW_NUMBER() OVER(PARTITION BY k ORDER BY ts DESC) | 同左 | 要全分区编号再过滤,读放大 |
坑:LATEST ON 只能用于 designated timestamp 列;PARTITION BY 的键优先选 SYMBOL(整数化分组最快);想过滤"最新值满足某条件"的设备,外层套 WHERE 即可。
6.3 Tick 间隔语法:时间条件速查
QuestDB 提供一套以 $ 开头的自然时间表达,WHERE 里直接写,自动换算时间戳边界:
| 表达式 | 含义 | 典型用途 |
|---|---|---|
WHERE ts IN '$today' |
今天 0 点到现在 | 当日大盘 |
WHERE ts IN '$yesterday' |
昨天全天 | 昨日报表 |
WHERE ts IN '$last 1h' / $last_hour |
最近 1 小时 | 实时监控窗 |
WHERE ts IN '$last 7d' |
最近 7 天 | 周趋势 |
WHERE ts IN '$now - 5m' |
最近 5 分钟(相对此刻) | 大屏刷新 |
WHERE ts IN '$prev_month' |
上月整月 | 月度复盘 |
直觉:它本质是帮你写好"时间戳 BETWEEN 边界",并和分区裁剪联动------Grafana 里用宏、应用代码里用这套表达,时间窗查询既不容易写错又能稳稳命中裁剪。
6.4 ASOF JOIN 与 LT JOIN:时间对齐的艺术
金融对账、IoT 关联分析的高频动作:"把交易成交时刻最近的那条报价/基准数据对齐过来"。普通 JOIN 做不到(时间戳不会精确相等),ASOF JOIN 专门干这个:
- ASOF JOIN :对左表每行,在右表找 时间戳 ≤ 本行时间戳的最近一条 匹配行(沿时间轴"往回摸")。匹配键通常是 symbol + 时间。
- TOLERANCE:容差参数,限制"往回摸"的最大距离,如 TOLERANCE 1 分钟------超过 1 分钟没有报价就不给对齐(结果 NULL),防止拿一小时前的陈旧报价做成交对账。
- LT JOIN :严格小于版本------只匹配时间戳严格更早的记录,排除"同一时刻"的行,避免事件自关联时把自己匹配上。
| JOIN | 匹配语义 | 场景 |
|---|---|---|
| ASOF | 右表时间 ≤ 左表时间,取最近 | 成交价对齐最近报价、传感器读数对齐最近标定值 |
| ASOF + TOLERANCE | 同上但限制回溯距离 | 金融对账(报价过期不采用) |
| LT | 右表时间 < 左表时间(严格) | 事件序列自关联、状态变更前后对比 |
坑:ASOF 要求两表都有 designated timestamp 且匹配键值有序;TOLERANCE 不设可能静默对齐到极陈旧数据,金融场景务必设;9.3.5 对 ASOF/HORIZON/WINDOW JOIN 在大分区表上做了专门加速,升级后这类查询直接受益。
6.5 HORIZON JOIN 与 WINDOW JOIN:多表横向拼接与滑窗关联
- HORIZON JOIN(横向连接) :把多张表按时间轴横向并排拼接 ------同一时刻来自不同表/不同来源的列摊成一行宽结果,类似沿时间轴的"水平 union columns"。9.3.5 起支持多个右表一次拼接。场景:行情表 + 资金流向表 + 指数表并排做实时看板;不同采集系统同时间戳指标对齐。
- WINDOW JOIN(窗口连接) :在时间维度开一个滑动窗口做关联------左表每行匹配右表中"时间落在本行前后指定窗口内"的所有行(或聚合)。场景:告警发生前 5 分钟的全部日志、下单前后 N 秒的行情波动分析。
直觉区别:ASOF 是"摸一条最近的",WINDOW 是"捞一个时间段内的全部",HORIZON 是"把多列并排贴齐"。
6.6 LATERAL JOIN(9.3.5 新贵):top-N per group
9.3.5 引入标准 SQL 的 LATERAL JOIN :右侧子查询可以引用左侧表的列,等于对左表每一行(或每个分组)跑一次相关子查询。
- 头号场景:每组 Top-N------"每个板块成交额前 3 的股票""每个设备最新 10 条告警""每个用户最近一笔订单",左表给分组,右表子查询按左表 symbol 过滤 + ORDER BY + LIMIT。
- 在这之前要用窗口函数 ROW_NUMBER 包一层再过滤,读放大高;LATERAL 让优化器可以按组提前截断。
- 配合 UNNEST(6.7/7.4)还能做"数组展开后每组取最新 N 个元素"这类 10.0 数组场景。
坑:LATERAL 子查询的相关条件必须能用上索引/排序(symbol 等值 + 时间倒序),否则退化为嵌套循环全表扫。
6.7 时序 JOIN 选型总表
| 需求 | 用什么 | 关键词 |
|---|---|---|
| 时间桶聚合成 K 线/均值 | SAMPLE BY | FILL / ALIGN TO CALENDAR |
| 每组最新一条状态 | LATEST ON PARTITION BY | 反向扫描 |
| 成交对齐最近报价 | ASOF JOIN | TOLERANCE |
| 严格更早的前序事件 | LT JOIN | 严格小于 |
| 多表同刻并排 | HORIZON JOIN | 多右表(9.3.5) |
| 时间窗内全部关联 | WINDOW JOIN | 滑动窗口 |
| 每组 Top-N | LATERAL JOIN | 相关子查询 + LIMIT |
加餐:SAMPLE BY 的性能心智模型
可以把 SAMPLE BY 理解成"时间轴上的流式折叠":因为数据物理有序,引擎不需要哈希表也不需要排序,扫到哪一行就往当前时间桶的累加器里折叠一次,桶边界跨过就输出一行。FILL 策略在输出阶段补空桶,不增加扫描成本。这意味着 SAMPLE BY 的耗时基本只和"扫描的分区数 × 命中的列数"成正比,和桶的粒度几乎无关------1 分钟桶和 1 小时桶扫同样的数据耗时接近,区别只在结果行数。理解这一点就明白为什么"按天聚合查一个月"和"按分钟聚合查一个月"在 QuestDB 上差距不大,而在行存库里后者可能慢几十倍。
避坑指南
- 所有时序 JOIN 都依赖两表 designated timestamp + 物理有序,对非时间轴列乱 JOIN 会走普通 Hash Join,别指望时序加速。
- ASOF 的 TOLERANCE、SAMPLE BY 的 TIME ZONE 是两个"不写就埋雷"的参数。
- 9.3.5 的 DST 修正是 breaking change,跨时区报表升级后必须核对。
面试考点
- LATEST ON 为什么快?(物理有序 + 反向扫描 + 找到即停,免全表扫)
- ASOF 和普通 JOIN 的区别?(时间不等值,取 ≤ 当前时间的最近记录;TOLERANCE 限距)
- FILL 五种策略分别什么时候用?(计数用常数 0、状态用 PREV、连续模拟量用 LINEAR、保轴用 NULL)
- LATERAL JOIN 解决什么?(top-N per group,右子查询引用左列)
6.8 时区与夏令时专题:SAMPLE BY 最容易翻车的角落
跨时区团队几乎必踩。QuestDB 的 TIMESTAMP 内部统一存纳秒 UTC 时间戳,时区只影响"桶边界怎么切"和"展示成什么文字"。三件事必须做对:
第一,采集端全部用 UTC 纳秒时间戳,不要存本地时间------夏令时切换那天本地时间会有重复/缺失的小时,本地时间戳语义本身就是坏的。第二,SAMPLE BY 按天/月聚合时显式 ALIGN TO CALENDAR TIME ZONE 指定业务时区,比如"北京时区的自然日"和"UTC 自然日"差 8 小时,不指定就按服务端时区,换部署区域后报表悄悄错位。第三,理解 9.3.5 的 DST 修正:欧美时区夏令时切换日有 23 小时和 25 小时的"天",旧版本 SAMPLE BY 按固定 24 小时硬切会切出畸形桶,9.3.5 起按真实日历对齐,桶数正确但历史查询结果会变------升级后把跨 DST 的报表抽样核对一遍。
6.9 时序 JOIN 的组合拳:真实问题往往要连用
实战中 JOIN 很少单独出场。举两个组合范式:
范式一:行情-成交-波动率联动。成交流先 ASOF JOIN 报价流(TOLERANCE 5 秒)拿到成交时买卖盘口,再 WINDOW JOIN 成交前 60 秒的 tick 计算短时波动(stddev,9.3.5 统计函数),最后 SAMPLE BY 1m 输出"每分钟成交均价 + 盘口偏离 + 波动率"。ASOF 对齐点值、WINDOW 捞窗口集、SAMPLE BY 聚合,三个语法一条链路。
范式二:设备告警归因。告警表(状态变更事件)用 LATERAL JOIN 关联子查询"该设备告警前 10 条传感器读数"(右子查询引用左表 device_id,按时间倒序 LIMIT 10),再对这 10 条做 avg/max 判断是突变还是缓变;LATEST ON 同时给出设备当前状态做降噪。LATERAL 负责 top-N 取数、窗口函数负责趋势判断。
写复杂时序查询的通用心法:先想清楚"每行结果要关联哪个时间点(ASOF/LT)、哪个时间段(WINDOW)、还是同刻并排(HORIZON)",再套聚合。语法选错比写错更常见。
第 7 章 标准 SQL 与表达式:会写 SQL 就能上手
除了时序扩展,QuestDB 覆盖了分析师常用的全套标准 SQL。这章给速查表和方言差异清单。
7.1 基础语句注意点
- SELECT/WHERE/GROUP BY/ORDER BY/LIMIT 语义与标准 SQL 一致;LIMIT 支持 LIMIT n 与 LIMIT offset, n 两种形式。
- ORDER BY 对 designated timestamp + symbol 的组合有物理序红利,尽量按时间轴排序。
- GROUP BY 优先按 SYMBOL 列分组(整数化);字符串自由列分组慢。
- WHERE 里的时间条件尽量用 tick 表达或显式时间戳范围,确保分区裁剪;对 symbol 的等值过滤走 symbol index。
7.2 窗口函数全家桶
| 函数 | 作用 | 时序典型用法 |
|---|---|---|
| ROW_NUMBER() | 分区内顺序编号 | 每组 Top-N(LATERAL 的替代) |
| RANK() / DENSE_RANK() | 排名(跳号/不跳号) | 涨幅榜、成交额排名 |
| LAG() / LEAD() | 取前/后第 N 行 | 环比、相邻 tick 价差、停留时长 |
| 聚合 OVER(PARTITION BY ...) | 移动/累计聚合 | 累计成交量、分组内滚动均值 |
| FIRST_VALUE / LAST_VALUE | 窗口首尾值 | 开盘价、区间末状态 |
| FRAME(ROWS/RANGE BETWEEN) | 帧边界 | 最近 N 条移动平均 |
9.3.5 新增统计窗口/聚合函数 :stddev(标准差)、variance(方差)、covariance(协方差)、correlation(相关系数)。场景:两支收益率序列相关性、传感器波动度、量价相关分析------以前要外接计算的统计量现在库内直出。
7.3 聚合函数表
| 函数 | 说明 | 备注 |
|---|---|---|
| count / sum / avg / min / max | 基础聚合 | 向量化执行 |
| first / last | 时间序首/末值 | 配合 SAMPLE BY 做开收盘价 |
| ARRAY_AGG | 聚合成数组 | 10.0 起配合 ARRAY 类型,把同组值收集成数组 |
| stddev / variance | 标准差/方差 | 9.3.5 |
| covariance / correlation | 协方差/相关系数 | 9.3.5 |
| count_distinct 近似/精确 | 去重计数 | 基数统计 |
7.4 UNNEST:数组/JSON 展开(9.3.5 标准件)
9.3.5 补齐 SQL 标准的 UNNEST:把数组(10.0 的 ARRAY 列)或嵌套结构展开成行,每行一个元素,常与 LATERAL 配合------"订单簿数组展开成逐档位行""embedding 数组拆成分量"。它让 ARRAY 类型从"能存"进化到"能查、能关联",是 10.0 数组能力闭环的关键一块。
7.5 条件与常用函数
- 条件:
CASE WHEN ... THEN ... ELSE ... END、COALESCE(...)(取首个非 NULL)、NULLIF(a,b)(相等返回 NULL)------FILL 之外做缺失值处理的主力。 - 字符串:length、upper/lower、substring、split_part、starts_with、concat 等常用件齐全;注意字符串运算不要施加在本该 SYMBOL 化的维度列上。
- 数学:round、abs、ceil/floor、power、sqrt、log、三角函数等。
- 日期时间:date_trunc、dateadd/datediff、year/month/day/hour 提取、to_timestamp/str 互转、now()。
7.6 与标准 SQL / Postgres 方言差异清单
| 差异点 | QuestDB | 说明 |
|---|---|---|
| UPSERT | DEDUP UPSERT KEYS(建表声明) | 不是 ON CONFLICT 语法 |
| 时间分桶 | SAMPLE BY | PG 里要用 date_trunc + GROUP BY |
| 最新行 | LATEST ON | PG 里是 DISTINCT ON |
| 时间对齐 | ASOF/LT/HORIZON/WINDOW JOIN | PG 无原生等价 |
| 更新删除 | 弱(append 哲学) | 别当 OLTP 用 |
| 外键/存储过程/触发器 | 无 | 关系完整性靠应用层 |
| JSON 类型 | 无 JSONB;数组走 ARRAY + UNNEST | 不要期待 PG 的 JSON 生态 |
| 类型名 | TIMESTAMP 默认纳秒 | PG 的 timestamp 是微秒 |
加餐:NULL 语义的三个坑
时序数据里 NULL 比业务库多得多(设备离线、采集缺失、JOIN 未命中)。三个高频坑:一是聚合函数自动忽略 NULL,avg 不会把缺失当 0,语义正确但和"没数据"业务含义可能不同,计数场景配合 FILL(0);二是 ASOF JOIN 超出 TOLERANCE 未命中返回 NULL,下游算术运算会传播 NULL,用 COALESCE 显式兜底;三是 UNNEST 展开含 NULL 的数组会产生 NULL 行,统计时注意过滤。养成"聚合后看一眼 NULL 占比"的习惯,很多报表数字错误都源于静默忽略。
避坑指南
- 从 PG 迁查询时,ON CONFLICT、DISTINCT ON、jsonb 操作符是三大重写点。
- LAG/LEAD 计算环比时注意 PARTITION BY 要带上 symbol,否则跨标的串味。
- 窗口函数能实现 LATEST/LATERAL 的效果但读放大更高,能用原生时序语法就用原生。
7.7 从其他方言迁移:改写案例对照
| 你在别处的写法 | QuestDB 改写 | 注意 |
|---|---|---|
Postgres 的 DISTINCT ON (k) ... ORDER BY k, ts DESC |
LATEST ON ts PARTITION BY k | 性能差 12 倍,必改 |
PG 的 date_trunc('hour', ts) + GROUP BY |
SAMPLE BY 1h | 顺带获得 FILL 能力 |
PG 的 ON CONFLICT ... DO UPDATE |
建表 DEDUP UPSERT KEYS | 去重键在 DDL 里声明 |
| PG 的 generate_series 补时间轴 | FILL(NULL/PREV/LINEAR) | SAMPLE BY 原生补桶 |
| Flux/InfluxQL 的 last/mean/window | LATEST ON / avg / SAMPLE BY | 全部回到标准 SQL |
| 相关子查询取 top-N | LATERAL JOIN(9.3.5) | 相关条件要能走有序键 |
| jsonb_array_elements 展开 | UNNEST(ARRAY 列) | 10.0 数组 + 9.3.5 UNNEST |
迁移工作流建议:先把视图/报表按"最新值类、降采样类、对齐关联类、分组排名类"四类归档,分别对应 LATEST、SAMPLE BY、ASOF/家族、窗口/LATERAL,改写模式非常固定,一两个下午就能把核心报表迁完。
第 8 章 高级查询实战:金融、IoT、可观测三大场景
语法背完不算会,这章用场景把语法串起来。
8.1 金融场景
| 需求 | 语法组合 | 要点 |
|---|---|---|
| 每标的最新价 | LATEST ON ts PARTITION BY symbol | 1.7 ms 级,大屏直刷 |
| 1 小时 OHLC K 线 | SAMPLE BY 1h + first/max/min/last | open=first、high=max、low=min、close=last;非交易时段 FILL 策略按业务定 |
| 成交对账行情 | 成交流 ASOF JOIN 报价流 + TOLERANCE | TOLERANCE 设秒级,过期报价弃用 |
| 涨跌幅/波动 | LAG 窗口 + stddev(9.3.5) | 相邻 tick 收益、滚动波动率 |
| 板块涨幅榜 | GROUP BY 板块 + RANK/DENSE_RANK | 板块列用 SYMBOL |
| L2 订单簿 | ARRAY 列存档位(10.0)+ UNNEST 展开 | 五档/十档买卖价量整体存取 |
| 金额精度 | DECIMAL 类型(10.0) | 成交价、金额杜绝 DOUBLE 浮点误差 |
金融场景的隐藏加分项:纳秒 TIMESTAMP 让同秒内多笔交易可排序;DECIMAL 让对账不差一分钱;L2 订单簿原生数组让"一行一个快照"取代"十行 JOIN 拼档位"。
8.2 IoT 场景
| 需求 | 语法组合 | 要点 |
|---|---|---|
| 设备最新状态大屏 | LATEST ON ts PARTITION BY device_id | 百万设备秒级刷新 |
| 5 分钟聚合 | SAMPLE BY 5m + avg/max | 降采样减载 |
| 缺失插值 | FILL(LINEAR) | 温度压力等模拟量;状态量用 FILL(PREV) |
| 无事件即 0 | FILL(0) | 告警计数、产量计数 |
| 异常设备筛选 | 外层 WHERE 包 LATEST/聚合 | 最新温度 > 阈值的设备列表 |
| 补数/重传 | O3 buffer + DEDUP KEYS | 乱序无感知、重传不重复 |
| 设备维度隔离 | device_id 作 SYMBOL | 百万基数也只是加行,无 series 爆炸 |
8.3 可观测场景
| 需求 | 做法 |
|---|---|
| 百万指标降采样 | SAMPLE BY 配 Grafana $__interval,缩放自适应粒度 |
| 多租户隔离 | tenant_id 作 SYMBOL,查询必带等值过滤(走 symbol index) |
| SLO 计算 | SAMPLE BY 聚合成功/总请求 → CASE/比值;可用窗口函数算滚动可用率 |
| Prometheus 接入 | Prometheus remote write → QuestDB;Telegraf/OTel 同理 |
| 高基数 label | label 进 field/STRING 侧,不进 tag(避免 SYMBOL 字典膨胀) |
8.4 高频陷阱总表
| 陷阱 | 现象 | 解法 |
|---|---|---|
| 未命中 designated timestamp | 时间条件查询全表扫,毫秒变分钟 | WHERE 必带时间轴列范围;确认建表指定了 TIMESTAMP(ts) |
| SYMBOL 基数爆炸 | 字典吃满内存、OOM、查询变慢 | 高基数字符串改 VARCHAR/STRING;tag/field 放对边 |
| SAMPLE BY 时区/DST | 跨时区/夏令时桶错位 | 显式 TIME ZONE;9.3.5 起 DST 修正,升级回归 |
| 跨分区 JOIN 膨胀 | ASOF/HORIZON 大范围匹配拖垮查询 | 时间窗收窄、TOLERANCE 收紧、symbol 键带上 |
| 时间戳单位错配 | 数据"消失"(落到 1970 或未来) | 全链路统一纳秒 UTC |
| 宽表几百列 | 扫描和分区打开变慢(9.3.5 已缓解但非免疫) | 冷热/主次字段垂直拆表 |
| 忘记 FILL | 报表断线条、0 值缺失误导 | 按指标语义选 NONE/NULL/PREV/LINEAR/常数 |
8.7 可观测场景的 SLO 计算范式
补一个可观测团队的高频实操:SLO(服务等级目标)计算。原始数据是请求事件流(状态码、延迟),在 QuestDB 里按 SAMPLE BY 1m 聚合出每分钟总请求数与错误数,错误率 = 错误/总数(注意整数除法坑,转 DOUBLE 再除);SLO 达标判断用 CASE WHEN 错误率 > 阈值 THEN 1 ELSE 0 END 标记坏分钟;跨窗口的 SLO 燃烧率用窗口函数 SUM OVER 最近 N 分钟滚动累加;多租户场景 tenant_id SYMBOL 等值过滤天然隔离。相比 Prometheus 里用 PromQL 写长算式,SQL 写法的好处是可测试、可 JOIN 业务维度(比如按发布版本 HORIZON JOIN 关联发布事件,直接定位哪次上线把错误率打上去了)、可沉淀成物化视图供复盘。
加分点
- 金融对账主动给 ASOF 加 TOLERANCE,体现"数据会过期"的业务理解。
- IoT 报表区分"模拟量 LINEAR、状态量 PREV、事件量常数 0"的 FILL 选择,是老手标志。
8.5 端到端案例 A:量化行情看板的一条 SQL 链路
需求:交易团队要一个实时看板,展示 20 个币种的最新价、1 分钟 K 线、相对 1 小时前的涨跌幅、以及成交时盘口偏离度。数据链路:交易所 WebSocket → 采集服务(QWP/ILP 写入,纳秒时间戳,DECIMAL 价格)→ QuestDB。看板四个面板各走一条查询路径:最新价用 LATEST ON PARTITION BY 交易对,1.7 ms 级,前端 1 秒刷一次也毫无压力;1 分钟 K 线用 SAMPLE BY 1m 配合 first/max/min/last,非交易时段加密货币 7×24 小时无缺桶,传统市场用 FILL(PREV) 延续收盘价;涨跌幅用 LAG 窗口取 60 桶前的收盘价做比值,或者 ASOF 对齐 1 小时前的 tick;盘口偏离度是成交流 ASOF JOIN 订单簿快照流(ARRAY 列用 UNNEST 展开取中间价),TOLERANCE 3 秒。
工程要点:DECIMAL 保证逐笔对账不偏差,纳秒时间戳保证同秒多笔可排序,QWP 让 Python 研究侧直接拿 Arrow DataFrame 做因子分析------看板、对账、研究三件事共用一张表。这个架构里没有 Kafka、没有 Flink、没有 Redis 缓存层,库本身把实时查询扛了,这是 QuestDB 在量化小团队里口碑发酵的典型路径。
8.6 端到端案例 B:工厂 IoT 大屏与异常设备筛选
需求:十万台设备的工厂,大屏要全部设备实时状态,5 分钟刷新聚合曲线,缺数据要有合理填充,还要自动捞出异常设备清单。链路:设备 → 边缘网关(离线缓存,恢复后补传)→ MQTT → Telegraf(QuestDB 输出插件)→ ILP 写入,时间戳网关侧打毫秒 UTC。
关键设计:device_id、工厂、产线三个 SYMBOL 列;O3 max-lag 设 24 小时容忍跨班次补传;SAMPLE BY 5m 降采样,温度/压力用 FILL(LINEAR)(物理量连续)、运行状态用 FILL(PREV)(状态持续)、产量计数用 FILL(0)(没产出就是零)。异常筛选是一条嵌套查询:内层 LATEST ON 取每设备最新读数,外层 WHERE 温度 > 阈值或最近 1 小时标准差(stddev)超基线,再 ASOF JOIN 设备档案表(维表,SYMBOL 键)带出责任人------慢查询排查看第 14 章,这条链路在十万设备下全部是毫秒级,核心就是每次查询都命中分区裁剪 + symbol 整数比较。
避坑指南:IoT 项目最常见的翻车是把"设备型号""固件版本"这类变更缓慢的维度也塞进高频表------它们会在每行重复,正确做法是放独立维表用 ASOF/HORIZON 关联,高频表只留高频变化的列。
第 9 章 数组、物化视图与实时视图:10.0 把 QuestDB 推向新场景
9.1 原生 ARRAY 类型:L2 订单簿与向量的一等公民
10.0 把 beta 的 ARRAY 类型转正:原生 N 维数组列,数组元素可以是数值等基础类型,存储上按列式组织。
- L2 订单簿:一个时间点的五档/十档买卖价量,整体作为一行的数组列存下,查询时整快照取出或用 UNNEST 展开成逐档位行------传统方案要么 JSON 文本(解析慢、无法聚合),要么拆多行 JOIN(写入放大),数组列一次解决。
- 向量/embedding 回流:AI 在线推理产出的 embedding 直接以数组写入时序表,和产生它的业务指标同表共存,喂给下游训练/分析时 Arrow 直出。
- 波形/频谱:传感器一段采样窗口的波形数组、设备振动序列,"一行一个采样窗口"。
- 配套能力:
ARRAY_AGG把同组行聚合成数组、UNNEST(9.3.5)把数组展开成行、数组长度/下标访问等函数;QWP 下数组以 Arrow List 类型零拷贝传输。
成熟度建议:10.0 已转正可用于生产新业务,但数组级的复杂运算(如库内向量距离检索)仍在演进,重计算留给下游 AI 引擎,库里负责"存得快、取得快、展开方便"。
9.2 物化视图:定时刷新的预聚合
物化视图把高频聚合(每分钟 K 线、每小时统计)预先算好存成结果表,查询直接命中,避免每次扫明细。
- 刷新方式为定时/周期性刷新 (按调度重算或增量维护),对日历和时区感知------按天聚合对齐自然日,配合 SAMPLE BY 的时区/DST 语义。
- 适用:报表、看板这类"聚合逻辑固定、查询频率高、可容忍分钟级延迟"的场景。
- 与 TimescaleDB 连续聚合的对比:理念相同(预计算降采样),QuestDB 的版本与 SAMPLE BY/时区语义深度绑定;不追求流式逐秒更新,实时诉求看 9.3 的 Live Views。
9.3 Live Views:实时视图(10.0 beta)
Live Views 是 10.0 的实时答案:订阅一个查询,结果集发生变化时主动推送变更,而不是轮询。
- 原理直觉:在写入链路上对订阅查询做增量维护,新数据进来时算出"结果差量"推给订阅者,类似数据库内建的持续查询/流式物化视图。
- 场景:实时大屏(最新价、设备状态)无需轮询、实时风控规则命中推送、在线特征实时更新。
- 与第 11 章 Pub/Sub 的关系:Pub/Sub(Enterprise)解决"数据行扇出给大量订阅者"的传输层问题,Live Views 解决"查询结果增量"的计算层问题,两者组合构成实时闭环。
- 成熟度:beta 阶段,适合评估和非核心链路;生产实时盘先用"物化视图 + 短周期刷新 + Grafana 轮询"兜底,等 GA 再切。
9.4 DECIMAL 与 TIMESTAMP_NS:金融级精度补齐
- DECIMAL:定点十进小数,金额、成交价、费率这类"差一分钱就是事故"的字段从此告别 DOUBLE 浮点误差;配合纳秒时间戳,金融 tick 的"价 + 时"双精度都到位。
- TIMESTAMP_NS:显式纳秒时间戳类型,让纳秒语义在 schema 层显式化,并与 Arrow 的时间戳纳秒单位对齐(QWP 互操作不再需要单位约定)。常规 TIMESTAMP 本身已是纳秒精度,TIMESTAMP_NS 更多服务于类型系统严谨性和跨系统互操作。
9.5 成熟度与生产建议总表
| 特性 | 版本/状态 | 生产建议 |
|---|---|---|
| ARRAY 数组 | 10.0 转正 | 可用于新业务(订单簿/向量/波形),复杂数组运算谨慎 |
| Live Views | 10.0 beta | 评估与非核心链路;核心实时盘用物化视图兜底 |
| DECIMAL | 10.0 | 金融金额字段直接上 |
| TIMESTAMP_NS | 10.0 | Arrow 互操作、纳秒语义显式化场景使用 |
| 物化视图 | 稳定 | 报表看板标配,注意时区日历对齐 |
| LATERAL/UNNEST/统计窗口 | 9.3.5 稳定 | 放心用 |
9.6 物化视图与 Live Views 的取舍决策表
| 维度 | 普通物化视图(定时刷新) | Live Views(10.0 beta) | 应用层轮询 |
|---|---|---|---|
| 实时性 | 分钟级延迟 | 秒级/增量推送 | 取决于轮询间隔 |
| 资源开销 | 刷新时批量重算/增量 | 写入链路持续增量维护 | 重复查询浪费 |
| 成熟度 | 稳定 | beta | 稳定但笨 |
| 适合 | 报表、日周月报、固定看板 | 实时大屏、风控推送、在线特征 | 低频探索 |
| 数据新鲜度语义 | "截至上次刷新" | "结果集持续最新" | "截至上次轮询" |
工程建议:90% 的看板需求"1 分钟刷新"完全够用,物化视图 + Grafana 定时刷新是最稳的组合;Live Views 留给真正秒级敏感的场景(风控告警、做市商盘面),且先在非核心链路验证 beta 稳定性;别为了"实时"而实时------很多业务方嘴上要实时,实际看的是日报。
9.7 DECIMAL 迁移指南:老表 DOUBLE 怎么办
金融团队从 DOUBLE 切 DECIMAL 的常见路径:新建 DECIMAL 列双写一段时间 → 校验两列差值(历史数据无法回溯精度,只能保证切换后一致)→ 查询切换 → 老列下线。建表期就直接上 DECIMAL 是零成本的正确决策,事后改列在任何数据库都是大工程。另一个细节:DECIMAL 的精度和标度(总位数、小数位数)按业务定,加密货币报价 8 位小数、法币金额 2 位、费率 6 位,别用一个默认精度糊弄。
第 10 章 存储分层与开放格式:数据不被绑架的战略设计
QuestDB 最被低估的长期价值,是"开放格式三重锁定":Parquet(文件层)+ Iceberg(表格式层)+ Arrow(内存/传输层)。你的数据从落盘到传输到归档,没有一个字节是私有格式。
10.1 Parquet:冷分区的开放列存
- 开源版可用
ALTER TABLE把指定分区转换为 Parquet:分区从原生列格式变成标准 Parquet 文件,仍在本地、仍可透明查询,但压缩率更高、且任何支持 Parquet 的工具(Spark、DuckDB、pandas、Flink)都能直接读。 - Enterprise 更进一步:按生命周期策略自动把冷分区下沉到 S3/GCS 对象存储,查询时透明回读,本地磁盘只留热数据------存储成本结构从"全 NVMe"变成"热 NVMe + 冷对象存储"。
10.2 Iceberg:让外部引擎把表当表读
Iceberg 是表格式(table format)层的开放标准。QuestDB 的数据以 Iceberg 表的形态对外可访后:
- Spark/Flink 可以把 QuestDB 的表当成自己的表做批流处理,不需要导出/导入;
- AI 特征工程链路(Spark 做特征、训练框架读 Parquet/Iceberg)与在线时序库共享一份数据;
- 避免"数据进了 TSDB 就只能用它的 API 读"的孤岛问题。
10.3 Arrow 与 QWP:内存格式的统一
Arrow 是内存中的列式标准,QWP 线上用 Arrow IPC record batches 编码(见 5.4)。三重格式的分工:
| 格式 | 所在层 | 作用 |
|---|---|---|
| Arrow | 内存/网络传输 | QWP 读写零拷贝、客户端 DataFrame 零转换 |
| Parquet | 磁盘文件 | 冷分区高压缩归档、通用工具可读 |
| Iceberg | 表元数据 | Spark/Flink 等引擎把整张表当原生表 |
10.4 冷热生命周期设计建议
- 热层(0--N 天):原生列分区在 NVMe,毫秒查询,支撑实时大屏和在线接口;
- 温层(N 天--M 月):转 Parquet 留本地(开源)或自动下沉对象存储(Enterprise),查询频率低但要可查;
- 冷层(M 月以上):对象存储 Parquet/Iceberg,合规归档 + 大数据/AI 引擎按需分析;
- 分区粒度与生命周期对齐(DAY 分区配按天策略),TTL 删除走整分区 DROP,瞬间完成无 VACUUM。
避坑指南
- 转 Parquet 是按分区的重写操作,挑低峰执行;转换后该分区的写入语义按归档处理(分区是不可变单元)。
- 对象存储冷查延迟远高于本地,冷数据不要放进实时大屏的查询路径。
- 开放格式互访时注意类型映射(纳秒时间戳、DECIMAL、ARRAY 与下游引擎的类型对齐)。
面试考点
- Parquet/Iceberg/Arrow 分别解决什么?(文件归档 / 表格式互访 / 内存传输零拷贝)
- 开源版和企业版冷存的差异?(开源手动 ALTER 转 Parquet 留本地;企业自动下沉 S3/GCS)
10.5 Parquet 转换的工程细节与成本账
开源版把分区转 Parquet 的操作虽然是一条 ALTER TABLE,但工程上有几个细节要知道:
转换以分区为单位原子进行,转换中的分区查询不受影响(完成后切换读取路径);转换是重写操作,消耗 IO 和临时空间(同分区量级),挑低峰执行;转换后该分区成为归档单元,不再接收新写入(迟到数据超出保留策略应在转换前落定);Parquet 分区的查询性能略低于原生列格式(少了 mmap 的极致路径和部分索引),所以只转冷分区,热分区永远保持原生格式------这正是企业版自动下沉策略替你做的判断(按分区年龄自动搬运)。
成本账:典型时序场景热数据占 10%--20% 存储量却承载 95%+ 查询。全 NVMe 存三年是一个预算,"热 NVMe 一个月 + Parquet 本地三个月 + S3/GCS 三年"是另一个预算,对象存储成本通常是 NVMe 的十分之一以下。金融合规要求"tick 数据存五年"这类场景,冷存下沉直接把存储账单砍掉七八成,这是 Enterprise 冷存功能的商业价值所在。
10.6 开放格式的真实互操作案例
讲个具体的数据流:QuestDB 里是实时行情(热),按周转 Parquet、按月注册成 Iceberg 数据集。量化研究员在 Spark 里直接用 Iceberg connector 读历史 tick 做因子回测,数据零搬运;pandas/DuckDB 用户直接扫 S3 上的 Parquet 拉自选币种数据;在线推理服务通过 QWP 订阅 Live Views 拿最新特征,推理结果(embedding 数组)再写回 QuestDB 的 ARRAY 列。整个闭环里没有任何"导出成私有格式再转换"的环节------这就是开放格式三重锁定的实际意义:数据库是数据的管理者,不是数据的狱卒。
第 11 章 Pub/Sub 与流处理:实时闭环与边界
11.1 Enterprise Pub/Sub:500 订阅者、1.15 亿行/秒扇出
10.0 快随的 Enterprise Pub/Sub 把"订阅数据流"做进了引擎:
- 规模 :单表支持 500 个订阅者 ,聚合出口带宽达 115M rows/s(约 1.15 亿行/秒扇出);
- 无慢订阅者拖累:订阅者消费慢不阻塞写入和其他订阅者(背压隔离),这是自实现轮询/Kafka 分发最头疼的问题;
- 数据编码走 Arrow/QWP 列式通道,订阅端拿到的是结构化 record batch,不是裸文本。
11.2 实时特征闭环
典型闭环:在线系统写入业务事件/特征 → Pub/Sub 订阅流 → 实时聚合/推理(Live Views 或外部消费者)→ 结果回写 QuestDB → 在线服务 LATEST ON 毫秒读取。整个"特征回流"链路不引入重型流平台即可跑通中小规模实时 AI 应用。
11.3 与 Kafka/Flink 的分工边界
QuestDB 原生流能力不是要取代 Kafka/Flink,边界要划清:
| 维度 | 用 QuestDB 原生(Pub/Sub + Live Views) | 上 Kafka + Flink |
|---|---|---|
| 订阅规模 | 数百订阅者以内 | 消费者组海量、跨多系统分发 |
| 计算复杂度 | 查询结果增量、轻聚合、最新值 | 复杂窗口 JOIN、CEP、状态机、Exactly-once |
| 数据角色 | 订阅的是"库内查询/表变更" | 消息总线,下游任意系统 |
| 运维投入 | 零额外组件 | 需要独立集群和流作业运维 |
| 典型场景 | 实时大屏、在线特征推送、规则告警 | 全公司事件中枢、跨团队数仓流、重计算 ETL |
经验法则:数据已经在 QuestDB 里、订阅者是查询型消费者、逻辑能用 SQL 表达 → 原生;需要多系统扇出、复杂有状态计算、消息回放/多租户总线 → Kafka/Flink。 CDC 视角看,Pub/Sub 也提供了"表变更订阅"的 CDC 式能力,但定位是实时查询推送而非通用数据库 CDC 管道。
11.4 CDC、订阅与回放:三种"拿变化"的方式辨析
实时团队常问"QuestDB 能不能当 CDC 源"。精确辨析三种机制:
| 机制 | 拿到什么 | 粒度 | 回放能力 |
|---|---|---|---|
| Pub/Sub 订阅 | 写入的数据流(行级变更) | 行 | 从订阅点起,历史回放能力有限 |
| Live Views | 查询结果的增量变化 | 结果集差量 | 跟随查询语义 |
| 分区/Parquet 归档 | 全量不可变分区 | 文件级 | 任意时刻全量可重放(Iceberg/Parquet 时间旅行) |
实战含义:要"实时通知"用前两者;要"可重放、可审计、可回溯重算",靠不可变分区 + 开放格式------append-only 架构在这点上反而占优:数据从不更新删除,历史分区永远是事件真相,下游随时能从头重放。需要数据库级完整 CDC(行级 before/after 镜像、删除事件)的场景,QuestDB 不是为此设计的,这类需求放在 OLTP 数据库 + Debezium 链路上。
11.5 实时闭环的延迟预算
给个参考量级:写入到 WAL 完成是毫秒内;Pub/Sub 推送到订阅者端到端通常个位数毫秒到几十毫秒(取决于批攒策略);Live Views 增量推送在秒级以内;物化视图刷新分钟级。做系统设计时把这个预算和业务 SLA 对齐:做市风控要毫秒级,用 QWP 直写直读 + 原生查询;实时大屏秒级,Live Views 或 1 秒轮询 LATEST;运营看板分钟级,物化视图。架构师的价值就在于把组件用在它承诺的延迟区间内。
第 12 章 客户端 SDK 与生态集成
12.1 官方客户端矩阵
| 语言 | 客户端 | 通道 | 备注 |
|---|---|---|---|
| Python | questdb 官方包 | ILP(写)+ PG/QWP(读) | pandas/pyArrow 友好,QWP 下 DataFrame 零转换 |
| Java | questdb 官方 JAR | ILP/QWP/PG | JVM 生态企业接入主力 |
| C / C++ | 官方库 | ILP/QWP | 低延迟交易系统、native 热路径 |
| Rust | 官方 crate | ILP/QWP | 与引擎热路径同语言,性能优先 |
| Go | 官方包 | ILP/PG | 后端服务常用 |
| Node.js | 官方包 | ILP/PG | 全栈/看板服务 |
| 任意语言 | PostgreSQL 驱动 | PG wire 8812 | 兜底通道,会连 Postgres 就会连 QuestDB |
Python 工作流值得单独说:写入端 ILP 批量送数;分析端走 PG 或 QWP 查询,结果直接是 pandas DataFrame(QWP/Arrow 通道下零行列转换);配合 Jupyter 做时序研究,体验是"列存数据库 + DataFrame 生态"无缝衔接。
12.2 采集与可观测生态
| 系统 | 接入方式 |
|---|---|
| Telegraf | 官方 QuestDB 输出插件,metrics 直送 ILP(tag→SYMBOL,注意基数) |
| Prometheus | remote write 对接(经适配/采集层转 ILP),Grafana 侧用 QuestDB 数据源 |
| OpenTelemetry | OTel collector 导出到 QuestDB(指标链路), traces/logs 重场景仍建议专业后端 |
| Grafana | 官方原生数据源插件,支持宏变量、SAMPLE BY 联动;也可用 PG 数据源兜底 |
| Superset / Redash / Metabase | 走 PG wire,当作 Postgres 数据源即可 |
| Spark / Flink | Iceberg/Parquet 开放格式互访(第 10 章),无需专有连接器 |
| dbt / Airflow | PG wire + 标准 SQL 调度;Airflow 也可用 PG operator 做调度 |
Grafana 公共 demo:官方提供在线 demo 实例和预置仪表盘(行情、IoT、监控),配数据源时拿来参考变量和 SAMPLE BY 粒度设置,比从零搭快得多。
12.3 生态选型避坑
- BI 工具连不上先查端口(8812 而非 5432)和驱动类型(选 PostgreSQL)。
- Telegraf/Prometheus 的高基数 label 映射成 SYMBOL 前先评估基数(4.3/5.8 反复强调的第一大坑)。
- OTel traces(高基数字符串为主)不是 QuestDB 的主场,metrics 才是。
面试考点
- 不会某语言 SDK 怎么办?(走 8812 PG wire,任何 pg 驱动都能连)
- Spark 怎么读 QuestDB 数据?(Iceberg/Parquet 开放格式直接读,不需要专有连接器)
12.4 Python 生态深度工作流
数据团队最关心 Python 侧体验,展开讲:
写入用官方 ILP 客户端:pandas DataFrame 通过 sender.dataframe() 一类接口批量发送(内部按列类型映射:object 低基数列建议先转 SYMBOL 语义、datetime64 自动纳秒时间戳),攒批参数按 14.1 调。读取两条路:走 PG wire 时用 psycopg + pandas read_sql,体验和连 Postgres 完全一致;走 QWP 时结果直接是 Arrow 表,to_pandas() 零拷贝转换,亿行级聚合结果落地 DataFrame 没有序列化中间层。研究闭环典型写法:Jupyter 里一条 SAMPLE BY 查询拿 K 线 → DataFrame 算因子 → 因子结果批量写回新表 → Grafana 面板验证,全程不离开 Python + SQL。
两个坑:第一,DataFrame 的时间列时区语义,pandas 默认 naive datetime 按 UTC 处理最省心,别混本地时区;第二,ARRAY 列通过 Arrow 读出是 list 类型,配合 numpy 直接转数组算向量距离,比走 JSON 字符串快两个数量级。
12.5 dbt 与 Airflow 的对接姿势
数仓团队关心的工程化:dbt 通过 PG wire 连接(adapter 选 postgres),模型里写标准 SQL + QuestDB 时序函数都行,但注意 dbt 的 incremental 模型语义要适配 append + DEDUP 模型------增量合并靠目标表的 DEDUP KEYS,而不是 dbt 的 merge 语句(QuestDB 无 ON CONFLICT 语法),配置上用 append-only 策略最顺。Airflow 用 PostgresOperator 跑 SQL 任务、或调 REST /imp 做每日 CSV 回灌,物化视图刷新调度也挂 Airflow DAG。物化视图本身支持时区感知的调度,简单周期让库自管,跨系统依赖编排放 Airflow。
第 13 章 部署、扩容与高可用
13.1 单机生产配置
QuestDB 单机能力极强,大多数生产负载一台机器就够,配置要点:
| 项目 | 建议 | 原理 |
|---|---|---|
| CPU | 核数优先,16--32 核起步 | 查询按分区并行、写入多 WAL 线程,核数=并行度 |
| 内存 | JVM 堆占总量 10%--25%(几 GB 级),50%+ 留给 page cache | mmap 列文件靠操作系统缓存,堆给大了反而抢缓存 |
| 磁盘 | NVMe SSD,容量按增长 ×1.5 余量 | 顺序写 + 随机读混合,IOPS 和带宽都要 |
| 文件系统 | ext4/xfs,关闭 atime;关闭 swap | 减少写入放大;避免列缓存被换出 |
| ulimit | 文件句柄、内存映射数量调高 | 分区/列文件众多,mmap 数量大 |
| 部署 | Docker 挂载数据卷 / 二进制 + systemd | 数据目录独立挂载 |
13.2 Docker 与 Kubernetes
- Docker:单容器 + 数据卷挂载 + 四端口映射,资源 limit 里 CPU 给满、内存按"堆小缓存大"的比例拆。
- Kubernetes:社区提供 Helm chart;Enterprise 配 Kubernetes Operator 0.2.1,管理副本、滚动升级、故障转移、冷存声明式配置。
- 无状态/有状态边界:QuestDB 是有状态服务(数据盘),StatefulSet + PVC 是基本姿势;备份走快照或企业版工具链。
13.3 主从复制与 SWITCH ROLE 在线切换(Enterprise 4.0)
- 架构:主节点写入,复制到只读副本,副本可承担读流量和容灾;
- SWITCH ROLE :4.0 的招牌运维能力------主从角色在线切换、无需重启,维护/故障迁移时业务侧只是短暂的连接语义切换;切换有明确的超时语义(等待复制追平的窗口),客户端通过标准 PG/QWP 协议感知新主;
- 对比"重启换主"的老套路:免重启意味着缓存不丢、连接池不重建、SLA 可控。
13.4 备份恢复与 replica-first 迁移
- 开源版:文件系统快照(数据目录一致性快照)、分区级 Parquet 归档;
- Enterprise:备份恢复工具链;replica-first cutover 迁移------新环境先作为副本同步追平,再 SWITCH ROLE 切流,迁移 downtime 压到秒级,这也是跨版本升级、跨机房搬迁的标准打法。
13.5 容量规划速查
| 指标 | 估算方式 | 硬件映射参考 |
|---|---|---|
| 写入吞吐 | 峰值 rows/s × 平均行字节 | 单机 8 核可打数十万--百万级 rows/s;32vCPU 基准机达 859 万 rows/s(TSBS 优化负载) |
| 存储增长 | 日增行 × 行字节 ÷ 压缩比(原生列存约 10x 级,Parquet 更高) | 热数据留 NVMe;冷数据转对象存储 |
| 查询 QPS | lastpoint 类极轻(ms 级),大聚合按扫描分区数估 | 读扩展走只读副本横向加机器 |
| 内存 | page cache ≥ 热数据工作集 | 256G 内存机器可缓存大量热分区 |
13.6 监控
- 9003 /health:存活/就绪探针,K8s liveness/readiness 直接用;
- metrics 端点:暴露 JVM、写入速率、WAL 积压、O3 buffer 使用、分区数等指标,可自行抓取进 Prometheus/Grafana;
- 关键告警项:WAL 积压持续增长(写入快于合并)、O3 buffer 接近上限(乱序 lag 超配)、磁盘使用、page cache 命中率骤降、分区数异常膨胀。
避坑指南
- 最常见的生产配置错误是"JVM 堆给了内存的 70%"------page cache 没地方,查询全走磁盘,性能腰斩。
- 磁盘只算热数据没算冷数据和 Parquet 归档,导致 TTL 到期前写满。
- K8s 里用空目录/临时卷存数据,Pod 重建数据丢失(必须 PVC)。
13.7 扩容路径:从一台机器到一个集群
QuestDB 的扩容哲学是"单机纵向优先,读扩展横向":
第一步,单机榨干:32 核 256G + NVMe 的机器在 TSBS 上扛住了 859 万行/秒写入,绝大多数公司的全量时序负载到不了这个量级------先按 14 章调优确认单机构成瓶颈,再谈分布式。第二步,读扩展:Enterprise 只读副本横向加机器,大屏/BI/分析师查询全打副本,主库专注写入。第三步,业务分库:按业务域/租户拆库(行情库、设备库、指标库各自独立实例),配合冷热分层把冷数据甩给对象存储。要清醒认识到:QuestDB 不是 ClickHouse 那种自带分布式分片表的 MPP 架构,它的集群故事是"复制 + 分库"而非"透明分片",设计之初就按业务域规划表的归属,比事后拆库省心得多。
13.8 升级策略与版本生命周期
实操经验:小版本(如 10.0 → 10.0.1)直接滚动升级,关注 release notes 的 bugfix;大版本(9.x → 10.0)先在副本/影子环境跑一轮真实查询回归,重点核对结果集变化类变更------9.3.5 的 DST 修正就是这类,跨时区报表必须抽样比对。升级路径上善用 replica-first 迁移(Enterprise):新版本起副本同步追平 → 验证查询 → SWITCH ROLE 切流,回滚也只是再切一次,这是免重启切换之外的第二个运维红利。开源版则靠快照恢复做回滚预案,升级前必备份数据目录。
避坑指南(加餐):不要跳大版本升级(8.x 直上 10.x),WAL 格式、系统表结构跨大版本可能变化,按官方升级文档逐大版本走;升级前先在测试机用生产数据快照验证启动和查询,别拿生产当小白鼠。
第 14 章 性能调优实战:把 859 万行/秒的潜力兑现出来
14.1 写入调优
| 手段 | 做法 | 原理 |
|---|---|---|
| 攒批 | 单批 1 万--10 万行,100 ms--1 s flush | 摊薄网络与提交开销 |
| 长连接复用 | 写入端连接池化,杜绝短连接风暴 | 建连成本远大于写一批 |
| WAL commit batching | 9.3.5 自动攒批 fsync,确保多写入者并发提交 | 高并发下 fsync 次数数量级下降 |
| 并行连接 | 4--16 个并发写入连接起步压测 | 多 WAL 线程并行吃核 |
| 协议选择 | 极限吞吐上 QWP/ILP,别用单条 INSERT | 列式/文本协议路径短于 SQL 解析 |
| 表收敛 | 同类数据合表 + SYMBOL 区分 | 每表独立 WAL/句柄/合并线程 |
| O3 窗口 | max-lag 略大于 P99 迟到,不要无限大 | 窗口越大内存和合并成本越高 |
| 时间戳 | 客户端纳秒 UTC,别让服务端补时间 | 减少歧义与服务端开销 |
14.2 查询调优
| 手段 | 做法 | 原理 |
|---|---|---|
| 验证分区裁剪 | 执行计划确认只扫目标时间分区 | 时间条件没命中裁剪 = 全表扫 |
| SYMBOL 整数比较 | JOIN/GROUP BY/过滤尽量走 SYMBOL 键 | 9.3.5 Hash Join 已按整数比较 |
| JOIN 选型 | 时序对齐用 ASOF/HORIZON 而非普通 JOIN | 专用路径有序扫描,避免大哈希表 |
| 时间窗收窄 | TOLERANCE/窗口范围只给业务需要的 | 跨大分区范围匹配是 JOIN 膨胀首因 |
| 宽表垂直拆分 | 冷热列、主次字段分表 | 列裁剪少读无关列,分区打开更快 |
| 用 LATEST 代替窗口函数 | 每组最新值别用 ROW_NUMBER 包一层 | 反向扫描 vs 全量编号 |
| 预聚合 | 固定报表上物化视图 | 空间换时间 |
| 降采样下推 | Grafana 用 $__interval 联动 SAMPLE BY |
时间跨度大时自动粗粒度 |
14.3 执行计划与慢查询定位
- 用 EXPLAIN 查看执行路径:重点确认三件事------扫描的分区范围是否被裁剪、JOIN 走的是时序专用路径还是 Hash Join、过滤条件是否下推到 symbol index/时间轴。
- 慢查询定位流程:先看 WHERE 有没有时间轴条件(没有就是全表扫)→ 再看分组/JOIN 键是不是 SYMBOL → 再看 JOIN 范围是否跨了过多分区 → 最后评估是否该上物化视图。
- 常见反模式:对 STRING 列 GROUP BY(改 SYMBOL)、LATERAL 子查询相关条件没走有序键(退嵌套循环)、SAMPLE BY 不带时间过滤(扫全历史分桶)。
14.4 TSBS 基准复现要点
2026 年 8 月 TSBS 的数字有明确硬件前提,引用和复现时别脱离上下文:
| 项 | 前提 |
|---|---|
| 规模 | 10 万 hosts(设备) |
| 并发 | 32 workers |
| 机型 | AWS r8a.8xlarge:32 vCPU / 256 GB 内存 |
| 结果(写入) | QuestDB 9.3.3 8.59M rows/s;ClickHouse 26.7 1.75M;TimescaleDB 2.29 1.08M;InfluxDB 1.12 541K |
| 结果(lastpoint) | QuestDB 1.7 ms vs TimescaleDB 20.5 ms(12.1 倍) |
| 官方对比口径 | 比 InfluxDB 3 Core 写入快 12--36 倍、复杂分析查询快 43--418 倍;比 TimescaleDB 写入快 6--13 倍、复杂查询快 16--20 倍 |
复现要点:内存比例按"堆小缓存大"、NVMe、核数给足、批量/并行参数按 14.1 调;拿 4 核 8G 的小机器质疑基准数字没有意义------并行列存的扩展性正是靠大核数大内存兑现的。压缩率参考:InfluxDB 3 约 10--20x、TimescaleDB 20--50x、QuestDB 约 10--15x(QuestDB 不以压缩率为第一卖点,它用极致读写性能换一部分压缩率)。
14.5 调优 Checklist
- 建表三件套齐(designated timestamp / PARTITION BY / DEDUP)?
- SYMBOL/VARCHAR 基数健康(无高基数字典)?
- 写入攒批 + 长连接 + 合理并行?
- 堆:缓存 内存比例正确?
- 慢查询过一遍 EXPLAIN 三看(裁剪/JOIN 路径/过滤下推)?
- 固定报表上物化视图、Grafana 联动 SAMPLE BY?
- 冷数据有 Parquet/对象存储下沉策略?
- WAL 积压、O3 buffer、磁盘有监控告警?
面试考点
- 查询慢怎么排查?(时间条件→分区裁剪→SYMBOL 键→JOIN 范围→物化视图,按 14.3 流程)
- 写入怎么调优?(攒批、长连接、commit batching、并行、协议选型、表收敛)
14.6 真实压测方案:用你自己的数据说话
官方基准只能当广告,选型要自压。给一个半天能跑完的压测方案:
数据集用业务真实负载的影子流量或抽样(行宽、基数分布、乱序比例必须真实------合成数据最容易骗人,随机字符串会把 SYMBOL 基数压成天文数字)。写入压测:从 1 个连接/1000 行批起步,逐步加连接和批大小,画吞吐-延迟曲线,找到拐点(通常在连接数 ≈ 核数一半、批 1--10 万行区间),同时盯 WAL 积压和 O3 buffer 监控。查询压测:四类标准查询必测------LATEST 最新值(看 P99 是否毫秒级)、SAMPLE BY 降采样(扫 1 天/7 天/30 天三个跨度,验证线性度)、ASOF 对齐(带 TOLERANCE)、大跨度 GROUP BY(symbol 分组数拉满)。对比测试同机部署竞品、同数据集、同查询语义,重点看 P99 而不是平均值------时序大屏的体验由尾延迟决定。
14.7 调优案例:一次 40 倍提速的复盘
记录一个典型案例的调优链条:某 IoT 平台"设备最新状态"接口 P99 是 800 ms。排查第一步 EXPLAIN 发现查询没带时间范围导致扫了全部 300 个分区------加 WHERE 最近 1 天条件后降到 120 ms;第二步发现 PARTITION BY 用的 device_id 字符串列(VARCHAR),改成 SYMBOL 后分组走整数比较,40 ms;第三步接口其实只要最新一条,从窗口函数 ROW_NUMBER 改写为 LATEST ON,2 ms。最终 400 倍提升里没有任何黑魔法,就是 14.2 表格的前三行。90% 的"QuestDB 慢"都是没用上它的设计红利:没裁剪、没 SYMBOL、没用对时序语法。
面试考点(加餐):怎么证明调优有效?------EXPLAIN 对比扫描分区数和 JOIN 路径 + 压测 P99 前后对比 + 监控确认 WAL/缓存水位正常,数据说话。
第 15 章 QuestDB vs 竞品深度对比:选型不再拍脑袋
15.1 vs TimescaleDB:时序新贵与 PG 老牌
| 维度 | QuestDB 10.0 | TimescaleDB 2.29 |
|---|---|---|
| 架构 | 零 GC Java + C++/Rust,mmap 列存,不可变分区 | Postgres 扩展,行存堆表 + hypertable chunk + MVCC |
| 写入(TSBS) | 8.59M rows/s | 1.08M rows/s |
| lastpoint | 1.7 ms | 20.5 ms(12.1 倍差距) |
| SQL 完整度 | 标准 SQL + 时序扩展;无外键/存储过程/JSONB | 完整 Postgres:外键、存储过程、JSONB、生态全继承 |
| 关系 JOIN | ASOF/LT/HORIZON/WINDOW/LATERAL | 任意 PG JOIN,关系能力全面 |
| 运维 | 单二进制无依赖;page cache 调优 | PG 运维体系成熟(VACUUM、备份、主从工具链齐全) |
| 压缩 | 约 10--15x | 20--50x(原生压缩更强) |
| 团队技能 | 会 SQL 即可 | 需要/已有 Postgres DBA 经验 |
客观结论 :TimescaleDB 的优势是完整 Postgres 生态、成熟关系 JOIN、成熟运维体系 ------已经重度使用 PG、需要事务/外键/JSONB、团队有 PG DBA,它是稳妥选择。QuestDB 的优势是写入与查询延迟一个数量级的领先 + 时序语法开箱即用 + 开放格式------追求极致吞吐/亚毫秒查询、SQL 派新栈、金融 tick 场景,选 QuestDB。
15.2 vs InfluxDB 3:表模型 vs series 模型
| 维度 | QuestDB | InfluxDB 3 Core |
|---|---|---|
| 数据模型 | 表 + 列,标准关系结构 | measurement + tag/field,series 概念 |
| 高基数 | 新 symbol 值只是新行,无 series 爆炸 | 3.x 用 Parquet 重写已大幅改善,但 series 心智仍在 |
| 查询语言 | 标准 SQL | SQL/InfluxQL/Flux 三代并存,Flux 学习曲线陡 |
| JOIN | 时序 JOIN 全家桶 + 关系能力 | 弱,跨测量关联不是主场 |
| 写入性能 | 快 12--36 倍(官方口径) | --- |
| 复杂查询 | 快 43--418 倍(官方口径) | --- |
| 生态 | Grafana/Telegraf/OTel/Arrow | DevOps/IoT 监控生态最深,Telegraf 原生、存量用户大 |
| 压缩 | 10--15x | 10--20x |
客观结论 :InfluxDB 的护城河是监控/IoT 生态存量和采集器一体化体验 ,Flux/InfluxQL 有大量存量资产;纯监控埋点、团队已深度绑定 TICK 生态,它依然顺手。需要标准 SQL、跨表 JOIN 分析、高基数业务维度、金融级精度,QuestDB 明显更合适。
15.3 vs ClickHouse:专用时序 vs 通用 OLAP
| 维度 | QuestDB | ClickHouse 26.7 |
|---|---|---|
| 定位 | 时序专用 | 通用 OLAP 大宽表分析 |
| 写入(TSBS) | 8.59M rows/s | 1.75M rows/s(该负载下) |
| 时序语法 | SAMPLE BY/LATEST/ASOF 原生 | 无专用时序语法,用通用 SQL 拼 |
| 最新值查询 | 反向扫描 1.7 ms | 非强项,需聚合/去重视图 |
| UPDATE/DELETE | append 哲学,弱 | 有 mutation/轻量更新,更灵活 |
| 生态 | 时序/金融/IoT | BI/数仓/日志分析生态庞大,函数库极丰富 |
| 复杂度 | 单二进制,运维轻 | 分布式集群能力强但运维复杂 |
客观结论 :ClickHouse 是通用分析平台,宽表聚合、日志分析、Ad-hoc OLAP、函数丰富度是它的天下;但"时序专用工作负载"(最新值、时间对齐、降采样)它不是按这个目标设计的。数据以时序为核心、查询模式是时序三板斧 → QuestDB;分析需求五花八门、团队要一个万能 OLAP → ClickHouse;两者也常共存(QuestDB 扛热时序,冷数据 Parquet 落湖后 ClickHouse/Spark 再分析)。
15.4 vs Prometheus:通用时序分析 vs 指标监控专用
Prometheus 是指标抓取+告警的事实标准:Pull 模型、PromQL、本地 TSDB、Alertmanager 一体化,运维监控无人能替。但它不是分析型数据库:JOIN 能力基本为零、高基数 label 易 OOM、长期存储靠远端、PromQL 不是 SQL、做业务分析报表很别扭。
结论:监控告警留 Prometheus (它的告警生态不可替代),业务时序分析、金融/IoT 数据平台、跨维度 JOIN 报表上 QuestDB;Prometheus 可通过 remote write 把数据送进 QuestDB 做长期存储和分析,两者互补不互斥。
15.5 选型决策树
- 核心负载是监控告警(主机/应用指标 + 告警规则)? → Prometheus(长期存储可 remote write 到 QuestDB)。
- 已经全家桶 Postgres、要强事务/外键/JSONB、团队是 PG DBA? → TimescaleDB。
- 已深度绑定 Influx/Telegraf/Flux 存量,纯 DevOps 监控? → InfluxDB 3。
- 要一个万能 OLAP 做五花八门的宽表分析/日志分析? → ClickHouse。
- 金融 tick / IoT 平台 / 业务时序,追求极致写入与亚毫秒查询、团队会 SQL、要 JOIN 和开放格式? → QuestDB。
- 预算/团队有限、想一个引擎同时扛写入+实时分析+AI 特征回流? → QuestDB 开源版起步,HA/冷存/Pub-Sub 长大了再上 Enterprise。
15.6 混合架构:很多时候答案是"组合"
成熟团队很少用单一组件包打天下,给三种经过验证的组合模式:
组合一:Prometheus + QuestDB。Prometheus 继续负责抓取、告警、短期存储(它的 Alertmanager 和 PromQL 告警生态不可替代),remote write 把指标长期化到 QuestDB,容量规划、多租户长期分析、跨指标 JOIN 报表在 QuestDB 做。监控团队不改变现有工作流,数据平台拿到长期分析能力。
组合二:QuestDB + ClickHouse/数据湖。QuestDB 扛热时序写入和实时查询(毫秒级那部分),冷分区转 Parquet 落湖/进 ClickHouse,复杂的多源 Ad-hoc 分析、用户行为 + 时序的大宽表关联在 OLAP 侧做。QuestDB 负责"快而专",ClickHouse/湖负责"广而全"。
组合三:Kafka/Flink + QuestDB。公司已有事件总线的,Flink 做清洗、富化、复杂窗口计算后把结果 sink 到 QuestDB 做服务层查询;QuestDB 不承担全公司消息中枢,只做"时序结果数据的快速查询终点"。这条路径在大厂最常见。
判断依据回到 11.3 的边界表:SQL 能表达的订阅和聚合在库内做,复杂有状态计算和跨系统分发交给流平台,监控告警留给 Prometheus,开放格式保证它们之间数据自由流动。
15.7 成本视角的选型补一刀
预算敏感场景别忘了算总账:QuestDB 开源版免费且单机吞吐密度极高------同样的写入量,859 万 vs 108 万 rows/s 意味着承载同等负载的机器数量差出近 8 倍,机器成本、运维人力、许可费用(Timescale 多节点/Influx 企业版均有商业条款)叠加,三年 TCO 差距可能比性能差距更有说服力。这也是金融和 IoT 公司在 2025--2026 年明显加大 QuestDB 评估力度的现实原因:性能故事最终都是成本故事。
第 16 章 2025--2026 新特性、路线图与面试考点 TOP15
16.1 10.0 特性清单(2026-08)
| 特性 | 内容 |
|---|---|
| QWP(QuestDB Wire Protocol) | 二进制列式协议统一读写,Arrow IPC record batches,客户端零转换 |
| 原生 ARRAY 类型 | N 维数组转正,L2 订单簿/向量 embedding/波形 |
| Live Views(beta) | 查询订阅、结果变更增量推送 |
| DECIMAL | 金融高精度定点数 |
| TIMESTAMP_NS | 显式纳秒时间戳,Arrow 单位对齐 |
| Pub/Sub(Enterprise 快随) | 500 订阅者、115M rows/s 扇出、慢订阅者隔离 |
| Enterprise 4.0 | Operator 0.2.1、SWITCH ROLE 免重启切换、S3/GCS 冷存、replica-first 迁移 |
16.2 9.x 关键回顾
- LATERAL JOIN(9.3.5):右子查询引用左列,top-N per group;
- UNNEST(9.3.5):标准数组/JSON 展开,与 ARRAY 类型闭环;
- 统计窗口函数(9.3.5):stddev/variance/covariance/correlation;
- 多右表 HORIZON JOIN 、DST 修正的 SAMPLE BY(breaking change);
- 性能:SYMBOL Hash Join 整数比较、ASOF/HORIZON/WINDOW 大分区加速、宽表分区打开开销降低、WAL commit batching;
- 架构主线:8.0 WAL 落地、9.x Parquet 三层存储成型。
16.3 路线图前瞻方向
- Pub/Sub 的开源化演进(实时订阅能力下放 OSS);
- 向量/相似搜索方向:原生 ARRAY + 开放格式与 AI 场景合流,库内向量检索是自然延伸;
- GPU 加速:向量化执行从 SIMD 走向 GPU 的想象空间;
- 持续强化标准 SQL 兼容度与云原生 Operator 运维。
(路线图为方向性判断,具体以官方发布为准。)
16.4 四阶段学习路线
| 阶段 | 目标 | 动手内容 |
|---|---|---|
| 入门 | 跑起来、会查 | Docker 起实例 → Web Console 导 CSV → 三件套建表 → SAMPLE BY/LATEST 玩熟 |
| SQL | 时序语法精通 | 六大 JOIN/扩展语法逐个场景练(金融对账、IoT 大屏)→ Grafana 搭仪表盘 |
| 运维 | 上生产 | 容量规划、WAL/O3 监控、备份、Parquet 冷存、压测调优(第 13/14 章) |
| 架构 | 知其所以然 | 读 mmap/列存/WAL/Parquet 设计 → TSBS 复现 → 跟踪 QWP/Arrow/Iceberg 源码与 RFC |
学习节奏建议:入门阶段 1--2 天即可见效,重点是把三件套建表和 SAMPLE BY/LATEST 练成本能;SQL 阶段建议 1--2 周,用一份真实数据集(金融 tick 或设备监控均可)把六大 JOIN 各实现一个业务问题;运维阶段结合本职项目做一次容量规划和压测;架构阶段不必强求读源码,但 mmap、WAL、列式编码、Arrow 这四个关键词值得各找一篇深度材料啃透------它们不只属于 QuestDB,也是理解整个现代数据基础设施(ClickHouse、DuckDB、DataFusion 同理)的通用钥匙。求职的同学额外建议:准备一个"用 QuestDB 替代某监控/时序方案"的对比项目,性能数字用自己压测的,比简历上罗列技术名词有说服力得多。
16.5 高频面试考点 TOP15
- QuestDB 为什么快? 零 GC Java + C++/Rust 热路径、mmap 列存、不可变分区、SIMD 向量化、分区并行、读写互不干扰。
- Designated timestamp 是什么,没有会怎样? 表的时间轴,物理排序/分区裁剪/时序函数基础;缺失则丧失大部分性能优势。
- SYMBOL 原理与选型? 字典编码整数化,低基数高重复;高基数用 VARCHAR,tag 全映射 SYMBOL 防基数爆炸。
- 为什么 QuestDB 不怕高基数标签? 无 series 概念,新 symbol 值只是加行。
- 乱序写入怎么处理? O3 buffer 内存排序后按时间合并,max-lag 控窗口。
- LATEST ON 为什么快? 物理有序反向扫描,找到即停,1.7 ms vs DISTINCT ON 20.5 ms。
- ASOF JOIN 原理,TOLERANCE 干嘛? 取时间 ≤ 当前的最近记录;TOLERANCE 限回溯距离防陈旧数据。
- SAMPLE BY 的 FILL 五策略? NONE/NULL/PREV/状态、LINEAR/模拟量、常数 0/计数。
- 三层存储? 并行 WAL → 原生列分区 → Parquet 冷存(企业版自动下沉 S3/GCS)。
- 与 InfluxDB 核心区别? 表模型 vs series 模型、标准 SQL vs Flux/InfluxQL、JOIN 能力、高基数。
- 与 TimescaleDB 核心区别? 列存时序引擎 vs PG 行存扩展;性能 vs 生态完整度。
- 四种写入协议? ILP(生态)、PG wire(通用)、REST /imp(CSV)、QWP(10.0 列式 Arrow 主力)。
- QWP 是什么? 二进制列式协议,Arrow IPC 统一读写,客户端零转换。
- 分区怎么选? 单分区几百 MB--几 GB;10万--1000万行/天 DAY,1000万--1亿 HOUR,更低 MONTH/YEAR。
- 生产内存怎么配? 堆占 10%--25%,大头留给 page cache 缓存 mmap 列文件。
写在最后
QuestDB 的故事其实很朴素:把时序数据这一件事,从磁盘格式到查询语法到传输协议全部重做一遍,把性能交给硬件、把标准还给 SQL、把数据交给开放格式。在 2026 年这个时序数据爆炸、AI 特征回流、金融量化平民化的节点上,它的选择显得越来越聪明------不发明新语言逼用户学习,不锁数据逼用户续费,只在"快"和"开放"两件事上卷到极致。