DolphinDB分区数据库教程(二)

2022-04-28 00:00:00 数据 字段 多个 节点 分区

DolphinDB分区数据库教程(一)介绍了DolphinDB的几种分区方式,本文将会详细讲解DolphinDB的分区原则、特殊的分区方案,让用户对DolphinDB分区数据库有更深入的了解。

1.分区原则

分区的总原则是让数据管理更加高效,提高查询和计算的性能,达到低延时和高吞吐量。

1.1 选择合适的分区字段

DolphinDB分区字段的数据类型可以是整型、日期类型和SYMBOL类型。注意,STRING、FLOAT和DOUBLE数据类型不可以作为分区字段。

虽然DolphinDB支持TIME、SECOND、DATETIME类型字段的分区,但是在实际使用中要谨慎使用,避免采用值分区,以免分区粒度过细,将大量时间耗费在创建或查询几百上千万个只包含几条记录的文件目录。

分区字段应当是在业务中相当重要的。例如在证券交易领域,许多任务都与股票交易日期或股票代码相关,因此使用这两个字段来分区比较合理。


1.2 分区粒度不要过大

DolphinDB单个分区支持大记录条数是20亿条。但合理的记录条数应该远远小于这个数。一个分区内的多个列以文件形式独立存储在磁盘上,通常数据是经过压缩的。使用的时候,系统从磁盘读取所需要的列,解压后加载到内存。如果分区粒度过大,可能会造成多个工作线程并行时内存不足,或者导致系统频繁地在磁盘和工作内存之间切换,影响性能。一个经验公式是,数据节点的可用内存是S,工作线程(worker)的的数量是W,则建议每个分区解压后在内存中的大小不超过S/8W。假设工作内存上限32GB,8工作线程,建议单个分区解压后的大小不超过512MB。

DolphinDB的子任务以分区为单位。因此分区粒度过大会造成无法有效利用多节点多分区的优势,将本来可以并行计算的任务转化成了顺序计算任务。

DolphinDB是为OLAP的场景优化设计的,支持添加数据,不支持对个别行进行删除或更新。如果要修改数据,以分区为单位覆盖全部数据。如果分区过大,降低效率。DolphinDB在节点之间复制副本数据时,同样以分区为单位,分区过大,不利于数据在节点之间的复制。

综上各种因素,建议一个分区未压缩前的原始数据大小控制在100M~1G之间。当然这个数字可结合实际情况调整。譬如在大数据应用中,我们经常看到宽表设计,一个表达到几百个字段,但是在单个应用中只会使用一部分字段。这种情况下,可以适当放大上限的范围。

如果发现分区粒度过大,可以采用几种方法,(1)采用组合分区(COMPO),(2)增加分区个数,(3)将范围分区改为值分区。


1.3 分区粒度不要过小

分区粒度过小,一个查询和计算作业往往会生成大量的子任务,这会增加数据节点和控制节点,以及控制节点之间的通讯和调度成本。分区粒度过小,也会造成很多低效的磁盘访问(小文件读写),造成系统负荷过重。另外,所有的分区的元数据都会驻留在控制节点的内存中。分区粒度过小,分区数过多,可能会导致控制节点内存不足。我们建议每个分区未压缩前的数据量不要小于100M。

譬如股票的高频交易数据若按交易日期和股票代码的值做组合分区,会导致许多极小的分区,因为许多交易不活跃的股票的交易数据量太少。如果将股票代码的维度按照范围分区的方法来切分数据,将多个交易不活跃的股票组合在一个分区内,则可以有效解决分区粒度过小的问题,提高系统的性能。

2.如何把数据均匀分区

当各个分区的数据量差异很大时,会造成系统负荷不均衡,部分节点任务过重,而其他节点处于闲置等待状态。当一个任务有多个子任务时,只有后一个子任务完成了,才会把结果返回给用户。由于一个子任务对应一个分区,如果数据分布不均匀,可能会增大作业延时,影响用户体验。

为了方便根据数据的分布进行分区,DolphinDB提供了一个非常有用的函数cutPoints(X, N, [freq])。X是一个数据,N表示产生的分组数,freq是与X等长的数组,每个元素对应X中元素出现的频率。这个函数返回具有(N+1)个元素的数组,使得X中的数据均匀地分布在N个组中。

下面的例子中,需要对股票的报价数据按日期和股票代码两个维度做数据分区。如果简单的按股票的首字母进行范围分区,极易造成数据分布不均,因为极少量的股票代码以U, V, X,Y,Z等字母开头。建议使用cutPoints函数根据样本数据来划分分区。

// 将2007.08.01这天数据导入
t = ploadText(WORK_DIR+"/TAQ20070801.csv")

// 选择2007.08.01这天数据的股票代码的分布来计算分组规则
t=select count(*) as ct from t where date=2007.08.01 group by symbol

// 按照股票代码字母顺序产生128个区间。每个区间内部的数据行数在2007.08.01这天是相当的。
buckets = cutPoints(t.symbol, 128, t.ct)

// 后一个区间的结束边界由2007.08.01的数据决定。为排除2007.08.01之后之后有新的将后一个区间的结束边界替换成不会出现的大的股票代码。
buckets[size(buckets)-1] = `ZZZZZ

//buckets的结果如下:
//["A",'ABA','ACEC','ADP','AFN','AII','ALTU','AMK',..., 'XEL','XLG','XLPRACL','XOMA','ZZZZZ']

dateDomain = database("", VALUE, 2017.07.01..2018.06.30)
symDomain = database("", RANGE, buckets)
stockDB = database("dfs://stockDBTest", COMPO, [dateDomain, symDomain])

相关文章