Apache Druid 中文翻译 - 段(Segments)

Segments

Apache Druid将其索引(index)存储在(Segments)文件中,该段文件按时间进行分区(partitioned)。在默认设置中,Druid为每个时间间隔(time interval)创建一个段文件,其中时间间隔可通过配置 granularitySpec.segmentGranularity实现。为了使Druid在繁重的查询负载下正常运行,将段文件的大小配置为建议的300MB-700MB范围内很重要。如果你的段文件超过该范围,然后再考虑要么改变时间间隔(time interval)的粒度或分区你的数据(partitioning your data),并调整了您的partitionsSpec.targetPartitionSize (建议将参数调整为500万行)。有关更多信息,请参见下面的分片部分和“ 批量提取”文档的“分区规范”部分。

A segment file's core data structures 段文件的核心数据结构

 下面,我们描述了段文件的内部结构,该结构基本上是柱状的:每列的数据都布置在单独的数据结构中。通过分别存储每列,Druid可以通过仅扫描查询实际需要的那些列来减少查询延迟。共有三种基本列类型:时间戳列,维度列和指标列,如下图所示:

Apache Druid 中文翻译 - 段(Segments)_第1张图片

timestamp和metric列很简单:在幕后每个都是由LZ4算法压缩的整数或浮点值的数组。一旦查询知道需要选择的行,它就简单地解压缩这些行,取出相关的行,然后应用所需的聚合运算符。与所有列一样,如果查询不需要一列,则该列的数据将被跳过。

维度列有所不同,因为它们支持筛选和分组操作,因此每个维度都需要以下三个数据结构:

  1. 一个将列值(始终被视为字符串)映射到整数ID的字典
  2. 一个使用1中的字典编码的列值列表
  3. 对于列中的每个不同值,一个位图指示哪些行包含该值。

为什么要使用这三个数据结构?字典仅将字符串值映射为整数id,以便可以紧凑地表示(2)和(3)中的值。(3)中的位图-也称为倒排索引,允许快速过滤操作(特别是,位图便于快速应用AND和OR运算符)。最后,group byTopN 查询需要(2)中的值列表。换句话说,仅基于过滤器汇总指标的查询不需要使用到存储在(2)中的维度值列表(注:因为不需要根据维度进行分组)。

为了具体了解这些数据结构,请考虑上面示例数据中的“ page”列。下图说明了表示该维度的三个数据结构。

1: Dictionary that encodes column values
  {
    "Justin Bieber": 0,
    "Ke$ha":         1
  }

2: Column data
  [0,
   0,
   1,
   1]

3: Bitmaps - one for each unique value of the column
  value="Justin Bieber": [1,1,0,0]
  value="Ke$ha":         [0,0,1,1]

请注意,位图与前两个数据结构不同:前两个在数据大小上呈线性增长(在最坏的情况下),而位图部分的大小则是数据大小*列基数的乘积。压缩对于位图十分有用,因为我们知道对于“列数据”中的每一行,只有一个位图的条目为非零。这意味着高基数列将具有极为稀疏、高度可压缩位图。德鲁伊使用特别适合位图的压缩算法(例如咆哮的位图压缩)来利用这一点。

Multi-value columns 多值列

如果数据源使用多值列,则段文件中的数据结构看起来会有所不同。让我们想象一下,在上面的示例中,第二行同时标记了“ Ke $ ha”  “ Justin Bieber”主题。在这种情况下,这三个数据结构现在看起来如下:

1: Dictionary that encodes column values
  {
    "Justin Bieber": 0,
    "Ke$ha":         1
  }

2: Column data
  [0,
   [0,1],  <--多值列的行值拥有数组形式的值
   1,
   1]

3: Bitmaps - one for each unique value
  value="Justin Bieber": [1,1,0,0]
  value="Ke$ha":         [0,1,1,1]
                            ^
                            |
                            |
    Multi-value column has multiple non-zero entries
 

注意列数据和Ke $ ha位图中第二行的更改。如果一行的一个列有多个值,则其在“列数据”中的输入是一组值。此外, “列数据”中具有值的行在位图中将具有n个非零值条目。

SQL兼容的空处理

默认情况下,Druid字符串维列的null值 将会被替换为'',而数值和度量列完全不可能出现null,因为null会强制转换为0。Druid还提供了与SQL兼容的null处理模式,但是需要在系统级别启用该模式,将druid.generic.useDefaultValueForNull设置为false,Druid在摄取时间创建的段,其字符串列可以区分''null,并且数字列可以存储null而不是0

字符串维度列在此模式下不包含任何其他列结构,而是仅保留该null值的其他字典条目。但是,数值维度列将与附加位图一起存储在段中,该位图的设置位显示有null值的列。除了需要稍微增加段大小外,由于需要检查空位图,因此与SQL兼容的空处理也可能在查询时产生性能成本。该性能开销仅发生在实际上包含空值的列上。

命名约定

段标识符通常使用段数据源、间隔开始时间(ISO 8601格式)、间隔结束时间(ISO 8601格式)和版本来构造。如果另外将数据分片超出时间范围,则段标识符还将包含分区号。

一个示例段标识符可以是:datasource_intervalStart_intervalEnd_version_partitionNum

段组件

在幕后,一个段由几个文件组成,下面列出。

  • version.bin

    4个字节,以整数表示当前段版本。例如,对于v9段,版本为0x0、0x0、0x0、0x9

  • meta.smoosh

    具有有关其他smoosh文件内容的元数据(文件名和偏移量)的文件

  • XXXXX.smoosh

    这些文件中有一些是串联的二进制数据

    这些smoosh文件代表在一起“ smooshed”的多个文件,以减少必须打开以容纳数据的文件描述符的数量。它们是最大2GB的文件(以匹配Java中内存映射的ByteBuffer的限制)。这些smoosh文件包含数据中每个列的单独文件,以及index.drd带有有关该段的额外元数据的文件。

    还有一个称为__time的特殊列,它表示段的时间列。希望随着代码的发展,这种特殊性越来越小,但就目前而言,它就像我妈妈一直告诉我的那样特殊。

在代码库中,段具有内部格式版本。当前的句段格式版本为v9

列格式

每列存储为两部分:

  1. Jackson序列化的ColumnDescriptor
  2. 该列的其余二进制文件

ColumnDescriptor本质上是一个对象,它使我们能够使用Jackson的多态反序列化来添加新的有趣的序列化方法,而对代码的影响最小。它由一些有关该列的元数据组成(它是什么类型,它是多值的,等等),然后是可以反序列化二进制其余部分的序列化/反序列化逻辑列表。

分片数据以创建细分

分片

对于同一数据源,在相同的时间间隔内可能存在多个段。这些段形成一个block间隔。根据shardSpec用于分片数据的类型,仅当a block完成时,Druid查询才可能完成。也就是说,如果一个块由3个段组成,例如:

sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_0

sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_1

sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_2

在查询间隔2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z完成之前,必须装入所有3个段。

该规则的例外是使用线性分片规范。线性分片规范不会强制“完整性”,即使分片未加载到系统中,查询也可以完成。例如,如果您的实时摄取创建了3个使用线性分片规范进行分片的段,并且系统中仅加载了两个段,则查询将仅返回这2个段的结果。

模式变更

Replacing segments 替换段

Druid使用数据源、时间间隔、版本和分区号唯一地标识段。如果在一段时间内创建了多个段,则分区号仅在段ID中可见。例如,如果您有按小时分的段,但一个小时内的数据量超过单个段所能容纳的数据,则可以在同一小时内创建多个段。这些段将共享相同的数据源,间隔和版本,但分区号线性增加。

foo_2015-01-01/2015-01-02_v1_0
foo_2015-01-01/2015-01-02_v1_1
foo_2015-01-01/2015-01-02_v1_2
 

另外,在上述中,实例中的片段dataSource = foointerval = 2015-01-01/2015-01-02version = v1,和partitionNum = 0。如果在以后的某个时间点,您使用新的架构重新索引数据,则新创建的段将具有更高的版本ID。

foo_2015-01-01/2015-01-02_v2_0
foo_2015-01-01/2015-01-02_v2_1
foo_2015-01-01/2015-01-02_v2_2
 

Druid批处理索引(基于Hadoop或基于IndexTask)可确保每个间隔的原子更新。在我们的示例中,在将所有v22015-01-01/2015-01-02都加载到Druid集群中之前,查询仅使用v1段。一旦v2加载了所有段并可以查询,所有查询将忽略v1段并切换到这些v2段。过段时间后,v1段将会从集群之中卸载

请注意,跨越多个段间隔的更新仅是每个间隔内的原子。在整个更新过程中,它们不是原子的。例如,您具有如下段:

foo_2015-01-01/2015-01-02_v1_0
foo_2015-01-02/2015-01-03_v1_1
foo_2015-01-03/2015-01-04_v1_2
 

v2分段一旦构建,就会被加载到集群中,并v1在分段重叠的时间段内替换分段。在完全加载v2段之前,您的群集可能包含v1v2段的混合。

foo_2015-01-01/2015-01-02_v1_0
foo_2015-01-02/2015-01-03_v2_1
foo_2015-01-03/2015-01-04_v1_2

在这种情况下,查询可能会混合使用v1v2指标。

段之间的模式不同

同一数据源的Druid段可能具有不同的架构。如果一个段中存在一个字符串列(维度),但另一个段中不存在,则涉及这两个段的查询仍然有效。缺少维的段查询将表现得好像维只有空值。同样,如果一个段中有一个数字列(指标),而另一部分则没有,则对缺少指标的段查询通常会“做正确的事”。此缺失指标上的聚合的行为就像该指标缺失一样。

你可能感兴趣的:(Apache Druid 中文翻译 - 段(Segments))