数据建模指南
表结构设计将极大影响写入和查询性能。 在写入数据之前,你需要了解业务中涉及到的数据类型、数据规模以及常用查询, 并根据这些数据特征进行数据建模。
设计一张表时,主要需要做出以下几个决定:
- 哪些列组成主键(决定数据排序和时间序列身份);
- 表是 append-only 还是需要去重,以及使用哪种合并模式;
- 哪些列需要索引;
- 如何在表之间组织数据(一张宽表还是多张表),以及如何通过分区扩展规模。
本文先说明 GreptimeDB 如何存储和读取数据,然后依次介绍这些设计决策。
基本概念
在阅读本文档之前,请先阅读 GreptimeDB 数据模型文档。
基数
基数(Cardinality):指数据集中唯一值的数量。可以分为"高基数"和"低基数":
- 低基数(Low Cardinality):低基数列通常具有固定值。
唯一值的总数通常不超过1万个。
例如,
namespace、cluster、http_method通常是低基数的。 - 高基数(High Cardinality):高基数列包含大量的唯一值。
例如,
trace_id、span_id、user_id、uri、ip、uuid、request_id、表的自增 ID,时间戳通常是高基数的。
语义类型
在 GreptimeDB 中,列被分为三种语义类型:Tag、Field 和 Timestamp。
时间戳通常表示数据采样的时间或日志/事件发生的时间。
GreptimeDB 使用 TIME INDEX 约束来标识 Timestamp 列。
因此,Timestamp 列也被称为 TIME INDEX 列。
如果你有多个时间戳数据类型的列,你只能将其中一个定义为 TIME INDEX,其他的定义为 Field 列。
在 GreptimeDB 中,Tag 列是可选的。
GreptimeDB 复用 PRIMARY KEY 约束来定义 Tag 列;它们共同标识一条时间序列,并定义数据在存储中的排序方式(见 GreptimeDB 如何存储和读取数据)。
Tag 列的主要用途包括:
- 定义数据在存储中的排序方式,从而提高相同 tag 数据的局部性。如果没有定义 tag 列,GreptimeDB 按时间戳对行进行排序。
- 标识唯一的时间序列,使 GreptimeDB 能在表不是 append-only 时,在同一时间序列(主键)下对行进行去重。
- 便于从使用 label 或 tag 的其他时序数据库迁 移。
GreptimeDB 如何存储和读取数据
了解 GreptimeDB 如何存储数据和执行查询,有助于理解本文后续的设计建议。 后续章节会多次引用这里介绍的概念。 关于引擎层面的细节,包括磁盘上的 SST 文件格式和完整的扫描裁剪流程,请参考存储引擎文档中的 SST 文件中的数据布局和扫描裁剪。
数据按主键和时间排序
GreptimeDB 按 (primary key, timestamp) 对行进行排序。
具有相同主键的行组成一条时间序列,并按时间顺序相邻存储。
这种局部性让扫描单条时间序列的成本更低,也有利于压缩。
关于行在磁盘上如何布局的具体示例,请参考 SST 文件中的数据布局。