数据模型与核心概念:DataSource、时间戳、维度、指标、粒度、Segment
好,咱们今天来啃 Druid 最核心的东西——数据模型。说实话,我见过不少朋友,API 调得挺溜,但一问到数据模型就含糊。结果呢?集群一上量,性能就崩了。嗯,这章咱们就把这几个概念彻底掰扯清楚。
1. DataSource:不只是“表”那么简单
DataSource 是 Druid 里数据的逻辑容器。你可以把它想象成关系数据库里的表,但又不完全一样。
它更像一个“时间序列数据集合”。每个 DataSource 都绑定了一个时间戳列,所有数据都按时间组织。我刚开始用 Druid 时,习惯性地把不同业务的数据塞进同一个 DataSource,结果查询时各种混乱。后来学乖了——一个业务场景一个 DataSource,清晰又好管理。
关键点:DataSource 在创建时不需要预定义 schema。Druid 会根据你摄入的数据自动推断列的类型。但别高兴太早——生产环境我强烈建议你显式定义 schema,否则类型推断翻车是常有的事。
2. 时间戳:Druid 的“心脏”
没有时间戳,Druid 就废了。所有数据都必须有一个时间戳列,它决定了数据落在哪个时间桶里。
时间戳的精度默认到毫秒。但我在项目中遇到过一个问题:业务日志的时间戳精度是纳秒级的。直接摄入?Druid 会截断。解决方案是在摄入前先做一次精度转换,或者用字符串类型保留原始值。
避坑指南:我曾经接手过一个集群,查询慢得离谱。排查了半天,发现是时间戳列被误设为 String 类型,导致 Druid 无法做时间范围剪枝。记住——时间戳必须是 LONG 类型(毫秒级),否则性能直接腰斩。
3. 维度(Dimension):你的“分组依据”
维度就是用来做 GROUP BY、WHERE、FILTER 的列。比如用户 ID、城市、设备类型、页面 URL 等等。
Druid 对维度做了特殊处理——它会为每个维度值构建倒排索引。所以维度列的基数(唯一值数量)直接影响内存占用和查询速度。
我个人的经验是:高基数维度(比如用户 ID,几千万个唯一值)要谨慎处理。如果必须用,建议先做一层预聚合,或者用 HyperLogLog 近似计算。
| 维度类型 | 基数范围 | 建议 |
|---|---|---|
| 低基数 | < 1 万 | 直接使用,性能极佳 |
| 中基数 | 1 万 ~ 100 万 | 注意内存,可考虑 bitmap 压缩 |
| 高基数 | > 100 万 | 建议预聚合或使用近似算法 |
4. 指标(Metric):你要“算”的东西
指标就是你要聚合计算的数值列。比如 PV、UV、收入、响应时间等等。
Druid 的指标支持常见的聚合函数:SUM、COUNT、AVG、MIN、MAX,还有近似聚合如 HyperLogLog、ThetaSketch。
这里有个坑:AVG 在 Druid 里不是原生支持的。它其实是 SUM / COUNT 的语法糖。如果你在摄入时只存了 SUM 和 COUNT,查询时再算 AVG,这样更灵活。我习惯在摄入时就把 SUM 和 COUNT 都存下来,这样后续想怎么算都行。
小技巧:如果你需要精确去重计数(比如 UV),Druid 的 COUNT(DISTINCT) 在高基数下性能很差。换成 HyperLogLog 聚合器,误差在 2% 以内,但性能提升几十倍。
5. 粒度(Granularity):时间“切片”的粗细
粒度决定了数据按什么时间间隔做预聚合。Druid 支持从毫秒到年的各种粒度。
常见的粒度有:NONE(原始粒度)、SECOND、MINUTE、HOUR、DAY 等。
你想想看,如果你只需要按天查报表,却把粒度设成秒级,那 Druid 就得存海量的数据点,查询时还得做二次聚合。浪费资源。
我的建议:根据你的查询需求,选择最粗的粒度。比如业务只看小时级趋势,那就用 HOUR 粒度。数据量直接减少 3600 倍。
| 粒度 | 数据量(相对) | 适用场景 |
|---|---|---|
| NONE | 100% | 需要原始明细数据 |
| MINUTE | ~1.67% | 实时监控、秒级报警 |
| HOUR | ~0.028% | 小时级趋势分析 |
| DAY | ~0.0012% | 日报、周报 |
6. Segment:Druid 的“数据文件”
Segment 是 Druid 存储和查询的基本单元。每个 Segment 包含一段时间范围内的数据,并且是不可变的——一旦生成,就不会再修改。
Segment 内部结构很讲究:
- 时间列:按时间排序存储
- 维度列:用字典编码 + 位图索引
- 指标列:用列式存储 + 压缩
Segment 的大小很关键。太小了(比如几十 MB),会导致大量小文件,元数据管理开销大;太大了(比如几个 GB),查询时加载慢,内存压力大。
我个人习惯把 Segment 控制在 300 MB ~ 700 MB 之间。这个范围在大多数场景下表现最好。
避坑指南:我曾经遇到一个案例,Segment 大小只有 10 MB,结果集群里有几十万个 Segment。每次查询都要扫描大量元数据,性能惨不忍睹。后来调整了 Segment 粒度,合并成 500 MB 左右,查询速度提升了 10 倍。
7. 一张图看懂数据模型
下面这张图展示了数据从摄入到存储的完整流程,以及各个概念之间的关系。
8. 它们怎么协同工作?
说白了,整个流程是这样的:
- 你定义好一个 DataSource,指定时间戳列、维度列、指标列。
- 设置好粒度(比如按小时聚合)。
- Druid 把原始数据按时间窗口切分成多个 Segment。
- 每个 Segment 内部,维度建索引,指标做预聚合。
- 查询时,Druid 根据时间范围定位到相关 Segment,利用索引快速过滤,然后聚合指标。
嗯,这里要注意:Segment 的不可变性是 Druid 高性能的基石。因为不可变,所以可以放心地做列式存储、压缩、索引,不用考虑并发写入的问题。
个人经验:我习惯在摄入前先估算一下数据量。比如每天 1 亿条数据,如果粒度设成小时,那每个 Segment 大约 400 万条。再根据维度基数调整 Segment 大小,确保在 300-700 MB 之间。这样集群的稳定性和查询性能都比较好。
好了,这一章的核心概念就这些。理解清楚 DataSource、时间戳、维度、指标、粒度、Segment 之间的关系,后面讲摄入、查询、优化时你才能游刃有余。