Druid-Druid中schema设计技巧
2020-03-07 本文已影响0人
李小李的路
- 基于apache-druid-0.17
Druid的数据模型
- Druid中的数据存储在DataSource中,像传统关系型数据库RDBMS的table。
- Druid中DataSource可以通过使用或不实用
Rollup
的方式。如果使用Rollup
,druid在抽取数据的时候会预聚合,这种方式会减少数据量、存储空间、提高查询性能。当不使用Rollup
时,druid会保留原始数据,不会预聚合。 - Druid中的每一条记录必须有一个
timestamp
,数据会根据时间分区,每次查询也必须有时间过滤。查询结果还可以按照minute、hour、day等区分。 - Druid中除了时间列,其他列都是维度或度量,这遵循OLAP的标准命名。
- 生产环境中的数据源有成千上百列;
- 维度列按原样存储,因此可以在查询时对它们进行筛选、分组或聚合。它们通常是单字符串、字符串数组、单long、单double或单float。
- 度量列是预先聚合的,因此它们只能在查询时聚合(不进行筛选或分组)。它们通常存储为数字(整数或浮点数),但也可以存储为复杂对象,如HyperLogLog草图或近似分位数草图。即使在禁用rollup时,也可以在摄入时配置指标,但是在启用rollup时最有用。
类比关系性模型
- (Like Hive or PostgreSQL.)
- Druid中DataSource类似于关系型数据库中的表。Druid的
Lookup
类似于数据仓库中的维度表。 - 关系数据建模的常见实践包括规范化:将数据分割成多个表,以减少或消除数据冗余。例如,在一个“sales”表中,最佳实践关系建模需要一个“product id”列,该列是一个外键,它被放入一个单独的“products”表中,而这个表又有“product id”、“product name”和“product category”列。这可以防止产品名称和类别需要在“sales”表中引用相同产品的不同行上重复。
- 在Druid中,通常使用平表(大宽表)的方式,在查询时不需要连接。在“sales”表的例子中。在Druid中,直接在“sales”数据源中存储“product_id”、“product_name”和“product_category”是典型的维数,而不使用单独的“products”表。采用平表的方式极大的提高了性能,因为在查询的时候不需要join连接。采用平表的方式还可以从底层讲,Druid可以在查询的时候直接操作压缩编码的数据。但是采用平台的方式并没有显著增加底层的存储空间,因为Druid采用字典编码对每一行进行编码。
- 如果有必要,Druid可以使用
Lookup
功能,这个关系型数据库中的维度表功能一致。在查询的时候,使用Druid Sql的lookup
函数,而不是像关系型数据库中使用join连接。因为查询表会增加内存占用,并且在查询时产生更多的计算开销。在你已经更新维度表和抽取数据Datasource的主键映射时,才推荐使用。 - Druid进行关系类模型的提示:
- druid中的DataSource中没有主键或唯一键,跳过此部分;
- 如果不能规范化的化。如果您需要能够定期更新维度/查找表,并将这些更改反映到已经摄入的数据中,那么可以考虑使用查找进行部分规范化。
- 如果你需要连接两个大的分布式表,你必须在将数据加载到Druid之前这样做。Druid不支持两个数据源的查询时间连接。查找在这里没有帮助,因为每个查找表的完整副本都存储在每个Druid服务器上,所以对于大的表来说不是一个好的选择。
- 考虑是否要为预聚合启用rollup,或者是否要禁用rollup并按原样加载现有数据。在Druid中的Rollup类似于在一个关系模型中创建一个汇总表。
时间序列模型
- (Like OpenTSDB or InfluxDB.)
- 类似于时间序列数据库,Druid的数据模型需要一个时间戳。Druid不是一个时序数据库,但它是存储时序数据的自然选择。其灵活的数据模型允许它存储时序和非时序数据,即使在相同的数据源中也是如此。
- 为了在Druid中实现时序数据的最佳压缩和查询性能,按指标名称进行分区和排序非常重要,就像时序数据库经常做的那样。有关详细信息,请参见分区和排序。
- Druid中时序模型的提示:
- Druid并不认为数据点是“时间序列”的一部分。取而代之的是,Druid将每个点单独处理以获取和聚集。
- 创建一个维度,该维度指示数据点所属的系列的名称。这个维度通常被称为“度量”或“名称”。不要把“度量”和Druid的度量混淆了。将其放在
dimensionsSpec
中维度列表的最前面,以获得最佳性能(这有助于提高局部性;有关详细信息,请参阅下面的分区和排序)。 - 创建与您希望能够查询的聚合类型相对应的度量。通常这包括“sum”、“min”和“max”(long、float或double口味中的一种)。如果你想计算百分位数或分位数,使用Druid的近似聚合器。
- 考虑启用rollup,这将允许Druid在你的Druid数据源中将多个点合并成一行。如果您希望以不同于自然发出的时间粒度存储数据,那么这将非常有用。如果希望在同一数据源中组合时序和非时序数据,它也非常有用。
- 如果您事先不知道要摄取哪些列,请使用空维度列表来触发对维度列的自动检测。
日志聚合模型
- (Like Elasticsearch or Splunk.)
- 类似于日志聚合系统,Druid提供了反向索引来快速搜索和过滤。Druid的搜索能力通常不如这些系统,分析能力通常更发达。Druid和这些系统之间主要的数据建模区别是,当你把数据输入Druid的时候,你必须更加明确。Druid的列在前面有特定的类型,而Druid现在还不支持嵌套数据。
- druid中的日志模型提示:
- 如果您事先不知道要摄取哪些列,请使用空维度列表来触发对维度列的自动检测。
- 如果您有嵌套的数据,请使用压扁规范将其压扁。
- 如果您的日志数据主要是分析用例,那么可以考虑启用rollup。这将意味着你失去从Druid中获取单个事件的能力,但你可能获得实质性的压缩和查询性能提升。
提示
Rollup
- Druid可以对原数据进行上卷操作(Rollup),因为数据的抽取,以尽量减少原始数据的数量及数据存储。这是一种聚合或预聚合的形式。详细见
Rollup
。
Partitioning and sorting
- 对数据进行优化的分区和排序会对内存占用和性能产生很大的影响。详见分区和排序部分。
高基数维度列
- 在处理高基数列(如用户id或其他惟一id)时,请考虑使用
sketch
进行近似分析,而不是对实际值进行操作。当你使用sketch
抽取数据时,Druid不会储存原始数据,而是储存一个sketch
,它可以在查询时提供给以后的计算。sketch
的流行用例包括计数-区分和分位数计算。每个sketch
都是为一种特定的计算而设计的。 - 一般来说,使用
sketch
有两个主要目的:改进rollup和减少查询时的内存占用。 -
sketch
可以提高rollup比率,因为它们允许将多个不同的值折叠到同一个sketch
中。例如,如果您有两个相同的行,除了一个用户ID(可能两个用户同时执行了相同的操作),那么将它们存储在一个不同数量的sketch
中,而不是按原始数据存储,这意味着可以将数据存储在一行中,而不是两行。您将无法检索用户id或精确地计算不同的计数,但是您仍然能够计算近似的不同计数,并且您将减少您的存储占用空间。 - 详细信息见 approximate aggregators
字符串与数字维度
- 如果用户希望抽取一个列作为数字类型的维度(长、双或浮点),则需要在维度规范的dimensions部分指定该列的类型。如果类型被省略,Druid将抽取一个列作为默认的字符串类型。
- 字符串和数字列之间存在性能权衡。对数字列进行分组通常比对字符串列进行分组快。但与字符串列不同的是,数字列没有索引,因此它们的过滤速度可能较慢。您可以尝试为您的用例找到最佳的选择。
Secondary timestamps
- Druid的事件中必须总是包含一个主时间戳。主时间戳用于对数据进行分区和排序,因此它应该是您最常过滤的时间戳。Druid能够快速识别和检索数据对应的时间范围的主时间戳列。
- 如果您的数据具有多个时间戳,则可以将其他时间戳作为次要时间戳摄取。最好的方法是将它们作为长类型的维度以毫秒的格式摄取。如果有必要,可以使用transformSpec和timestamp_parse等表达式将它们转换成这种格式,timestamp_parse返回毫秒时间戳。
- 在查询时,可以使用诸如
MILLIS_TO_TIMESTAMP
、TIME_FLOOR
等SQL时间函数查询次要时间戳。如果采用原生的Druid查询,可以通过spec文件的方式。
嵌套维度
- 截止druid当前版本,druid不支持嵌套类型数据,对于嵌套类型的数据,需要进行
flatten
,如果有以下数据结构;
{"foo":{"bar": 3}}
- 在进行索引之前,需要对数据进行转换,
{"foo_bar": 3}
- Druid可以对JSON, Avro, or Parquet进行压平操作。详见
flattenSpec
。
计算抽取数据的条数
- 当启用rollup时,查询时的count聚集器实际上不会告诉您已摄入的行数。他们告诉你Druid数据源的行数,这可能是小于实际行数摄入。
- 在这种情况下,可以使用抽取时的计数聚合器来计数事件的数量。但是,需要注意的是,在查询这个指标时,应该使用
longSum
聚合器。一个计数聚合器在查询时将返回时间间隔内的Druid行数,这可以用来确定上卷率是多少。 - 举个例子来讲,抽取说明包含以下:
...
"metricsSpec" : [
{
"type" : "count",
"name" : "count"
},
...
* 应该采用以下方式进行查询:
```json
...
"aggregations": [
{ "type": "longSum", "name": "numIngestedEvents", "fieldName": "count" },
...