业务场景
我司于2019年自研发了一套股市信号回测平台,该平台集数据采集/清洗,因子组合,信号回测等功能于一身,实现了模块化、去代码的策略构建方式,分布式计算等市面上回测平台所没有的功能,数据精度达到Tick级,针对算力要求极高的Tick级数据信号回测进行了深入优化,极大地减少了策略回测时间,为我司的策略研发提供底层支持。
该回测平台的底层数据库原来使用的是MongoDB数据库。该数据库具有查询,写入灵活的特点,因而被选用做底层数据库,然而在实际使用中发现,由于平台的分布式计算架构设计,对数据库的查询和写入性能要求很高,要求能够支撑高并发的读写请求,然而面对我600GB的Tick数据,即使采用了索引,查询优化等技术,MongoDB也难以负担多台服务器对数据的并发请求,对一个全市场的时间截面数据请求的响应时间有时可以长达几分钟,导致分布式计算的设计优点无法完整的体现出来。因此,我开始调研市面上的数据库,期望能找到能支撑高并发的读写请求,并且切换成本较低的数据库。在对比了dolphin、TDengine,并且与两位公司的负责人沟通之后,我们选择了TDengine,就是看中了TDenginge的时序数据库,以及一个设备一张表(对应一只股票一个表)这些与金融数据特性契合的特点。
系统架构图
在我们的应用场景中,有一个master节点管理分布式计算任务的创建和分配,在master节点下的每一个slave节点在被分配到任务后,需要从底层数据库获取预置数据(3800只股票的历史数据),并且模拟真实情况,按时间推移去从30多亿条Tick数据中,获取Tick时间截面下的全市场(3800只股票)数据,计算“当前Tick时刻”下的股票信号。每个slave节点采用多进程的方式运行计算任务,即一核一任务,力求压榨出服务器的极限计算性能。目前,我们拥有一个master节点,6个slave节点,每个slave节点24核48线程,所以在任务运行的情况下,最高会同时有将近250+的进程同时向数据库请求数据。master节点的内网带宽占用有时甚至会达到900Mb/s。在这样的高并发请求下,原来的MongoDB不堪重负,经常查询堵塞。
分布式计算示意图
设计
在深入了解了TDengine的特性以及使用原则后,我们设计了如下的数据模型:
- 对Tick数据建库。库下一个Tick超级表,以超级表做模板建子表,一只股票一张子表。共有3841个子表。每只股票的历史Tick都统一以时间戳为主键写入分别的一张表。每只股票以股票代码作为表名,设有6个标签:股票聚宽代码、拼音缩写、名称、上市日期、退市日期、类型。
- 超级表共有26个字段。包含了每一个Tick的市场信息,当前价,最高价,最低价,成交量,成交金额,买五卖五的挂单情况和均价线。
- 超级表结构
Field | Type | Length | Note |
=======================================================================================================
ts |TIMESTAMP | 8| |
current |FLOAT | 4| |
high |FLOAT | 4| |
low |FLOAT | 4| |
volume |FLOAT | 4| |
money |FLOAT | 4| |
a1_p |FLOAT | 4| |
a1_v |FLOAT | 4| |
a2_p |FLOAT | 4| |
a2_v |FLOAT | 4| |
a3_p |FLOAT | 4| |
a3_v |FLOAT | 4| |
a4_p |FLOAT | 4| |
a4_v |FLOAT | 4| |
a5_p |FLOAT | 4| |
a5_v |FLOAT | 4| |
b1_p |FLOAT | 4| |
b1_v |FLOAT | 4| |
b2_p |FLOAT | 4| |
b2_v |FLOAT | 4| |
b3_p |FLOAT | 4| |
b3_v |FLOAT | 4| |
b4_p |FLOAT | 4| |
b4_v |FLOAT | 4| |
b5_p |FLOAT | 4| |
b5_v |FLOAT | 4| |
average |FLOAT | 4| |
code_jq |BINARY | 11|tag |
abbr |BINARY | 5|tag |
name |NCHAR | 4|tag |
start_date |BINARY | 10|tag |
end_date |BINARY | 10|tag |
type |BINARY | 5|tag |
实践
-
数据写入
每张表的数据量在50W-200万条之间。总共35亿条+。看似数量不算特别多,但是由于一条记录本身字段就比较多,写入的时候,速度只能达到5W条/s。然而这样的速度,就已经让我们惊为天人了,确实是快。如果字段少点,速度应该能快上不少,涛思数据的工程师跟我说一般都30W条/s。 -
数据查询
由于业务特殊性,要求一次性查出指定的若干只股票在同一个时间截面下(如 2019-05-10 09:45:03)的截面数据。为此,在深入研究了超级表查询语法规则下,我们设计了如下的查询语句:
第一种是对超级表下的子表的全查询:
select last(*) from tick.tick where ts >= \'2019-05-10 09:00:00\' and ts <= \'2019-05-10 09:45:03\' group by code_jq order by code_jq desc
这个语句会找到每一张子表(股票)最接近2019-05-10 09:45:03这个时刻的一条数据,然后集合起来,输出3841条记录。
第二种是对超级表下的部分子表的查询:
select last(ts,current,[...]) from tick.tick where (code_jq = '000001.XSHE' or code_jq = '600304.XSHG' ...) and ts >= \'2019-05-10 09:00:00\' and ts <= \'2019-05-10 09:45:03\' group by code_jq order by code_jq desc
这个语句是对第一种语句的变种,通过对TAG的过滤查询,可以做到对部分子表的查询。Last函数里面的字段可以随意配置。
数据花了2天的时间写入了数据库。结果在查询测试上碰到了问题。在真实环境中,我们做一次第一种的全查询,查询时间达到了30-45s,这对我们来说是不能忍受的,通过跟涛思数据的李总沟通业务需求,李总帮助我们优化了数据库结构,优化的细节详情可见TDengine实战中的第9条细节。优化了之后,我们的数据库结构变成了一个vnode62张表,一个表分配1M的内存,这样来极致的利用服务器的性能,首次查询时间降低到了12s左右的地步,还能接受。
name | created time | ntables | vgroups |replica| days | keep1,keep2,keep(D) | tables | rows | cache(b) | ablocks |tblocks| ctime(s) | clog | comp |time precision| status |
==============================================================================================================================================================================================================================================
log | 20-03-11 17:16:41.598| 4| 1| 1| 10|30,30,30 | 32| 1024| 2048| 2.00000| 32| 3600| 1| 2|us |ready |
tick | 20-03-11 17:20:07.895| 3835| 62| 1| 10|3650,3650,3650 | 62| 4096| 1048576| 8.00000| 50| 3600| 1| 2|ms |ready |
-
开发环境测试
因为业务的原因,我们在python中,由一个父进程创建了若干个一级子进程,再由一级子进程创建二级子进程,二级子进程才真正创建taos连接,向server获取数据。从结构上来说,相对复杂,对TDengeine的性能要求较高。
首先,单进程查询测试过没有问题后,我们开始上多进程并发查询。2.0版本对多进程高并发做了架构上的优化,使得1.6版本出现的并发问题被解决了。响应并发的速度非常快,不过一旦请求数过多,服务器处理不过来,请求就会排队等待。因为回测平台对实时的要求没有那么高,这些也不是什么问题。经过压力测试后,TDengine 2.0正式投入使用。在TDengine 2.0 的新功能中,集群是一个非常大的亮点,一般集群都是作为企业付费版的功能存在,taos却选择开源,实在是不得不佩服,由于精力问题,还没有去研究集群的使用方式,后面如果用上,再来更新这方面的使用经验,相信并发的响应速度能更上一个台阶。
在与涛思数据的工程师们的配合中,我真切的感受到了他们的负责任的态度和全力以赴的工作状态,双方的配合默契又舒适,实在是能做事能打胜仗的队伍。在这期间,涛思又获得了红杉资本的千万美元融资,让人不得不叹服涛思的实力和红杉的眼光,在这里也恭喜涛思,相信你们肯定能做出震惊世界的物联网级数据库!
使用TDengine的体会
TDenginge的列式存储设计是如此高效率写入和查询的核心设计,写入速度确实令人叹为观止,而查询速度相比写入速度并没有那么让人惊艳,在我们的业务场景下,老的mongoDB经过查询优化,也能达到和TDengine类似的查询速度,查询功能我认为还有待拓展,特别是超级表查询,如Tag查询等还需要支持更多的查询方式来满足不同的业务需求。TDenging是时序数据库,所有有时间关联性的业务场景用TDengine准没错,金融方向这块,我们应该算是比较尝鲜的几家之一,这次使用能帮助TDengine变得更优秀,变得更好,为金融方向落地使用提供经验借鉴,也是一个比较好的尝试!