美文网首页Druid
Druid-Druid中Segment

Druid-Druid中Segment

作者: 李小李的路 | 来源:发表于2020-02-28 16:40 被阅读0次
  • 基于apache-druid-0.17

概述

  • Druid将索引存储在按时间分区的Segment文件中。在基本的设置中,会为每个时间间隔(time interval)创建一个segment文件。time interval可以由granularitySpec中的参数segmentGranularity决定。对于Druid来说,在大量的查询之下依旧表现优秀。Segment的文件大小推荐在300MB-700MB。如果你的Segment文件大于这个范围,那么可以考虑更改时间间隔的粒度,或者对数据进行分区,并在partitionsSpec中调整targetPartitionSize(此参数的一个良好起点是500万行)。

Segment文件核心的数据结构

  • 描述Segment文件的内部结构,它本质上是柱状的:每一列的数据以单独的数据结构布局。通过单独存储每一列,Druid可以通过只扫描那些确实需要的列来减少查询延迟。有三种基本的列类型:时间戳列、维度列和度量列,如下图所示:


    segment内部数据结构
  • 时间戳和度量列很简单:在数据内部,它们都是用LZ4压缩的整数或浮点值数组。一旦查询知道需要选择哪些行,它只需对这些行进行解压,提取相关行,并应用所需的聚合函数操作。与所有列一样,如果查询不需要列,则跳过该列的数据。
  • 维度列是不同的,因为它们支持筛选和分组操作,所以每个维度需要以下三个数据结构:
    • 1-将值(通常被视为字符串)映射为整数id的字典;
    • 2-列值的列表,使用1中的字典进行编码;
    • 3-对于列中的每个不同的值,使用bitmap指示哪些行包含该值。
  • 为什么是这三种数据结构?字典简单地将字符串值映射为整数id,以便(2)和(3)中的值能够紧凑地表示。(3)中的bitmap——也称为反向索引,允许快速过滤操作(具体来说,位图便于快速应用AND和OR操作符)。最后,group by和TopN查询需要(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]
  • 注意,bitmap与前两个数据结构不同:前两个数据结构在数据大小上是线性增长的(在最坏的情况下),而bitmap部分的大小是数据大小*列基数的乘积。压缩将在这里帮助我们,因为我们知道,对于“列数据”中的每一行,将只有一个非零项的bitmap。这意味着高基数列将具有非常稀疏的bitmap,因此具有高度可压缩的bitmap。Druid利用这种压缩算法,特别适合bitmap,例如roaring bitmap compression

Multi-value列

  • 如果一个数据源使用了多值列,那么段文件中的数据结构看起来就有些不同。让我们想象一下,在上面的例子中,第二行被标记为“Ke$ha”和“Justin Bieber”主题。在这种情况下,三个数据结构现在看起来如下:
1: Dictionary that encodes column values
  {
    "Justin Bieber": 0,
    "Ke$ha":         1
  }

2: Column data
  [0,
   [0,1],  <--Row value of multi-value column can have array of values
   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 bitmap数据结构 的更改。如果一个行中有一个以上的值对应一个列,那么它在“列数据”中的条目就是一个值数组。此外,在“列数据”中有n个值的行在bitmap中有n个非零值项。

Sql兼容null值处理

  • 默认情况下,Druid的字符串维度列使用' 'null,数值和度量列不能表示空值,而是强制空值为0。但是,Druid也提供了一个SQL兼容的空处理模式,配置参数为:druid.generic.useDefaultValueForNull,必须在系统级启用,通过这个设置,当设置为fasle时,将允许Druid在提取数据的时候创建Segment,其字符串列可以区分' 'null值中,数字列可以代表null值行,而不是0。
  • 在此模式下,字符串维度列不包含任何附加的列结构,而只是为空值保留一个附加的字典项。然而,数值列将与一个额外的bitmap一起存储在Segment中,Bitmap的集合位表示空值行。除了稍微增加了Segment大小之外,由于需要检查空值Bitmap,SQL兼容的空处理还会在查询时产生性能成本。这种性能开销只出现在实际包含null的列上。

命名约定

  • Segment的标识符通常使用Segment数据源、间隔开始时间(ISO 8601格式)、间隔结束时间(ISO 8601格式)和版本来构造。如果将数据分片到时间范围之外,则段标识符也将包含分区号。
  • eg:datasource_intervalStart_intervalEnd_version_partitionNum

Segment组成

  • 一个Segment有以下几个文件组成;
  • version.bin:
    • 4个字节表示当前段版本为整数。E.g., for v9 segments, the version is 0x0, 0x0, 0x0, 0x9
  • meta.smoosh:
    • 带有关于其他smoosh文件内容的元数据(文件名和偏移量)的文件;
  • XXXXX.smoosh:
    • 存在一些二进制的文件:
    • smoosh文件表示多个文件“smoosh”在一起,以最小化必须打开来存放数据的文件描述符的数量。它们是最大2GB的文件(以匹配Java中映射的ByteBuffer的内存限制)。smoosh文件包含数据中每个列的单独文件和索引。带有关于Segment的额外元数据的drd文件。
    • 还有一个名为_time的特殊列,它引用段的时间列。随着代码的发展,这将变得越来越不特别,但现在它就像我妈妈总是告诉我的那样特别。

列的格式

  • 每一个列存储为两部分:
    • 1-A Jackson-serialized ColumnDescriptor;
    • 2-The rest of the binary for the column;
  • ColumnDescriptor本质上是一个对象,它允许我们使用Jackson的多态反序列化来添加新的、有趣的序列化方法,并且对代码的影响最小。它包括关于列的一些元数据(它是什么类型的,它是多值的,等等),然后是一个序列化/反序列化逻辑列表,可以反序列化二进制的其余部分。

分片数据创建Segment

分片

  • 对于相同的datasource,多个Segment可能在相同的时间间隔(interval time)。这些Segment在一个区间内构成一个block。取决于用来切分数据的shardSpec类型,Druid的查询只有在一个block完成的情况下才能完成。也就是说,如果一个block包含三个segment,例如:

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:00的查询完成之前,必须加载所有3个Segment。
  • 这个规则的例外是使用线性分片规范。线性分片规范并不强制“完整性”,即使系统中没有加载分片,查询也可以完成。例如,如果您的实时摄取创建了3个线性切分规范的Segment,并且系统中只加载了其中的两个Segment,那么查询将只返回这两个Segment的结果。

结构变化

替换Segment

  • Druid使用数据源、间隔、版本和分区号唯一地标识Segment。只有在为某个时间粒度创建多个Segment时,分区号才在Segment中id中可见。例如,如果您有每小时的Segment,但是您在一个小时内拥有的数据比单个Segment所能容纳的数据更多,那么您可以为同一小时创建多个Segment。这些Segment将共享相同的数据源、时间间隔和版本,但分区数将线性增加。
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 = foo, interval = 2015-01-01/2015-01-02, version = v1, and partitionNum = 0。如果在以后的某个时候,您使用新的模式重新索引数据,那么新创建的Segment将具有更高的版本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或基于indextquest)保证了每隔一段时间的原子更新。在我们的例子中,在所有的v2段(2015-01-01 - 2015-01-02)加载到一个Druid集群之前,查询只使用v1段。一旦所有的v2段被加载并可查询,所有的查询都会忽略v1段并切换到v2段。不久之后,v1片段将从集群中卸载。
  • 注意,跨越多个段间隔的更新只是每个间隔内的原子更新。它们在整个更新中不是原子性的。例如,你有如下Segment:
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 Segment将在构建后立即加载到集群中,并在Segment重叠期间替换v1 Segment。在v2 Segment 完全加载之前,您的集群可能混合了v1和v2 的Segment。
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
  • In this case, queries may hit a mixture of v1 and v2 segments.

Segment中不同的schema

  • 同一DataSource在Druid的Segment可能有不同的schemas。如果一个字符串列(维度)存在于一个Segment中而不存在于另一个Segment中,则涉及两个Segment的查询仍然有效。对于缺少维度的Segment的查询将表现为该维度只有空值。类似地,如果一个Segment有一个数字列(度量)而另一个没有,那么缺少度量的段上的查询通常会“do the right thing”。会有丢失度量的聚合行为。

相关文章

网友评论

    本文标题:Druid-Druid中Segment

    本文链接:https://www.haomeiwen.com/subject/fwrlhhtx.html