干货丨时序数据库分区教程(二)

1.分区原则

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

1.1 选择合适的分区字段数据库

DolphinDB分区字段的数据类型能够是整型、日期类型和SYMBOL类型。注意,STRING、FLOAT和DOUBLE数据类型不能够做为分区字段。数组

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

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


1.2 分区粒度不要过大app

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

DolphinDB的子任务以分区为单位。所以分区粒度过大会形成没法有效利用多节点多分区的优点,将原本能够并行计算的任务转化成了顺序计算任务。分布式

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

综上各类因素,建议一个分区未压缩前的原始数据大小控制在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])

除了使用范围分区的方法,列表分区也是解决数据分布不均匀的有效方法。

3.时序类型分区

时间是实际数据中最多见的一个维度。DolphinDB提供了丰富时间类型以知足用户的需求。当咱们以时间类型字段做为分区字段时,在时间取值上须要预留足够的空间以容纳未来的数据。下面的例子,咱们建立一个数据库,以天为单位,将2000.01.01到2030.01.01的日期分区。注意,只有当实际数据写入数据库时,数据库才会真正建立须要的分区。

dateDB = database("dfs://testDate", VALUE, 2000.01.01 .. 2030.01.01)

DolphinDB使用时间类型做为分区字段时,还有一个特殊的优势。数据库定义的分区字段类型和数据表实际采用的时间类型能够不一致,只要保证定义的分区字段数据类型精度小于等于实际数据类型便可。好比说,若是数据库是按月(month)分区,数据表的字段能够是month, date, datetime, timestamp和 nanotimestamp。系统自动会做数据类型的转换。

4.不一样表相同分区的数据存放于同一节点

在分布式数据库中,若是多个分区的数据表要链接(join)一般十分耗时,由于涉及到的分区可能在不一样的节点上,须要在不一样节点之间复制数据。为解决这个问题,DolphinDB推出了共存储位置的分区机制。DolphinDB确保同一个分布式数据库里全部表在相同分区的数据存储在相同的节点上。这样的安排,保证了这些表在链接的时候很是高效。DolphinDB当前版本对采用不一样分区机制的多个分区表不提供链接功能。

dateDomain = database("", VALUE, 2018.05.01..2018.07.01)
symDomain = database("", RANGE, string('A'..'Z') join `ZZZZZ)
stockDB = database("dfs://stockDB", COMPO, [dateDomain, symDomain])

quoteSchema = table(10:0, `sym`date`time`bid`bidSize`ask`askSize, [SYMBOL,DATE,TIME,DOUBLE,INT,DOUBLE,INT])
stockDB.createPartitionedTable(quoteSchema, "quotes", `date`sym)

tradeSchema = table(10:0, `sym`date`time`price`vol, [SYMBOL,DATE,TIME,DOUBLE,INT])
stockDB.createPartitionedTable(tradeSchema, "trades", `date`sym)

上面的例子中,quotes和trades两个分区表采用同一个分区机制。

DolphinDB是为OLAP设计的系统,主要是解决海量结构化数据的快速存储和计算,以及经过内存数据库和流数据实现高性能的数据处理。DolphinDB不适合数据频繁更改的OLTP业务系统。DolphinDB的数据写入与Hadoop HDFS相似,快速在每一个分区或文件的末尾批量插入数据。插入的数据会压缩存储到磁盘,通常压缩比例在20%~25%。数据一旦追加到基于磁盘的数据表后,不能快速更新或删除某些符合条件的记录,必须以分区为单位对数据表进行修改。这也是分区原则中提到单个分区不宜过大的缘由之一。

5.多副本机制

DolphinDB容许为每个分区保留多个副本,默认的副本个数是2,能够修改控制节点的参数dfsReplicationFactor来设置副本数量。

设置冗余数据的目的有两个: (1)当某个数据节点失效或者或磁盘数据损坏时,系统提供容错功能继续提供服务; (2)当大量并发用户访问时,多副本提供负载均衡的功能,提升系统吞吐量,下降访问延时。

DolphinDB经过两阶段事务提交机制,确保数据写入时,同一副本在多节点之间的数据强一致性。

在控制节点的参数文件controller.cfg中,还有一个很是重要的参数dfsReplicaReliabilityLevel。 该参数决定是否容许多个副本驻留在同一台物理服务器的多个数据节点上。在development阶段,容许在一个机器上配置多个节点,同时容许多个副本驻留在同一台物理服务器(dfsReplicaReliabilityLevel=0), 可是production阶段须要设置成为1,不然起不到容错备份的做用。

 // 每一个表分区或文件块的副本数量。默认值是2。
dfsReplicationFactor=2

 // 多个副本是否能够驻留在同一台物理服务器上。 Level 0:容许; Level 1:不运行。默认值是0。
dfsReplicaReliabilityLevel=0

6.事务机制

DolphinDB对基于磁盘(分布式文件系统)的数据库表的读写支持事务,也就是说确保事务的原子性,一致性,隔离性和持久化。DolphinDB采用多版本机制实现快照级别的隔离。在这种隔离机制下,数据的读操做和写操做互相不阻塞,能够最大程度优化数据仓库读的性能。

为了最大程序优化数据仓库查询、分析、计算的性能,DolphinDB对事务做了一些限制:

首先,一个事务只能包含写或者读,不能同时进行写和读。

其次,一个写事务能够跨越多个分区,可是同一个分区不能被多个writer并发写入。也就是说当一个分区被某一个事务A锁定了,另外一个事务B试图再次去锁定这个分区时,系统马上会抛出异常致使事务B失败回滚。

7.多Writer并行写入

DolphinDB提供了强大的分区机制,单个数据表能够支持几百万的分区数量,这为高性能的并行数据加载创造了条件。特别是当将海量的数据从别的系统导入到DolphinDB时,或者须要将实时数据以准实时的方式写入到数据仓库时,并行加载显得尤其重要。

下面的例子将股票报价数据(quotes)并行加载到数据库stockDB。stockDB以日期和股票代码作复合分区。数据存储在csv文件中,每一个文件保存一天的quotes数据。

//建立数据库和数据表
dateDomain = database("", VALUE, 2018.05.01..2018.07.01)
symDomain = database("", RANGE, string('A'..'Z') join `ZZZZZ)
stockDB = database("dfs://stockDB", COMPO, [dateDomain, symDomain])
quoteSchema = table(10:0, `sym`date`time`bid`bidSize`ask`askSize, [SYMBOL,DATE,TIME,DOUBLE,INT,DOUBLE,INT])
stockDB.createPartitionedTable(quoteSchema, "quotes", `date`sym)

def loadJob(){
	fileDir='/stockData'

    // 到路径下取出数据文件名
	filenames = exec filename from files(fileDir)

	// 加载数据库
	db = database("dfs://stockDB")

	// 对每一个文件,经过文件名产生jobId前缀。
	// 经过函数submitJob提交后台程序调用loadTextEx将数据加载到stockDB数据库中。
	for(fname in filenames){
		jobId = fname.strReplace(".txt", "")
		submitJob(jobId,, loadTextEx{db, "quotes", `date`sym, fileDir+'/'+fname})
	}
}

//经过pnodeRun将loadJob这个任务发送到集群的每一个数据节点进行并行加载。
pnodeRun(loadJob)

当多个writer并行加载数据时,要确保这些writer不会同时往同一个分区写入数据,不然会致使事务失败。在上面的例子中,每个文件存储了一天的数据,而quotes表的一个分区字段是日期,从而确保全部加载数据的做业不会产生有重叠的事务。


欢迎访问 官网下载DolphinDB试用版

相关文章
相关标签/搜索