专栏首页Apache IoTDBApache IoTDB 系列教程-6:性能优化(0.8-0.10)

Apache IoTDB 系列教程-6:性能优化(0.8-0.10)

今天的内容包括建模优化、读写性能优化,会涉及一些简单的原理介绍。主要面向 0.8 - 0.10 版本。

正文 3754 字,预计阅读时间 10 分钟。

建模指南

关于存储组

现在每个存储组是一个相对独立的引擎,而且读写锁是存储组级别的。因此把存储组从1改到10,读写基本能增速8倍。单个 IoTDB 实例推荐设置 CPU 核数个存储组。存储组越多,并行度就越高。我们之后打算把锁粒度下放到设备层。

设备

设备这个概念没有在 SQL 语句里显示的定义出来,而是在服务器端处理时候默认将倒数第二层设置为设备,导致大家容易忽略这个概念。先说一下设备影响什么。

(1)区分顺序数据 和 乱序数据是以设备为粒度的。举个例子,假如一个设备在内存里写了时间戳 1-10 的数据(不论写哪些测点,时间戳都会算到这个设备头上),落盘了,再写时间戳<=10 的数据,这些数据就会被当做乱序数据缓存并落盘。

(2)设备粒度的时间范围索引。对于每个 TsFile 文件,会构造设备粒度的索引在内存里,假如所有设备都活跃,N 个 TsFile,D 个设备,就有 N*D 条索引。百万级设备的索引内存会吃不消。这个东西我们会在一两个版本内改掉。

再说一下怎么设计建模来控制设备数。对于实际应用设备和传感器层次比较简单的情况比较好说,设备下直接是传感器层,一般不会建错。对于设备下有多层结构的就要注意了。

比如我一个设备下有10个传感器(s1,s2,...,s10),每个传感器采集10个时间序列的数据(f1,f2,...f10)。这时候很容易建成 root.xxx.device.s1.f1 这种。当你建成这种时候,你以为的 device 就不再是你以为的 device 了,实际的device 变成了 root.xxx.device.s1 。 实际 device 数量就是你以为的 10 倍了。

怎么办嘞,如果设备下的子设备不多,这样建模也没啥问题,只要你心里清楚系统中实际有多少个设备就行,这样沟通不会出现偏差,便于以后排查问题。

如果子设备非常多,可以把设备后的那几层压成一层,比如 root.xxx.device.s1_f1 。由于我们是以 . 作为分隔符的,这样 s1_f1 就变成 1 层了。 实际的设备还是 root.xxx.device。

Measurement 定义

Measurement 也就是最后一层的测点。假如一个测点是 INT32 或者 INT64 类型的,而且大部分时间这个数据的值都一样,没什么变化,这时候用 RLE 编码就很好。可以大大节省磁盘空间,当然刷盘速度也会变快。压缩方式开着 SNAPPY 就挺好。

Tag & Attribute

0.10.0 引入的这两个概念,容易分不清这两个有啥区别。 虽然都是 key-value 类型的属性。但是 Tag 是可以反向查询时间序列的元数据的,假如有个 tag 的 key 是 owner,就可以用 show timeseries where owner=Thanos 查灭霸拥有的时间序列。Tag 常驻内存,有Tag到时间序列的索引。

Attribute 就是普通属性了,比如有个属性是 description="this is my series"。这些属性只能是给定时间序列的路径顺带展示一下,辅助人查看的。

因此,要根据实际需求进行区分,那些需要做反向查询的属性,就建成 tag,其他的就搞成 attribute 就行了。

读写优化

读和写关系密切,数据的写入和参数配置会影响查询性能。

写入接口

以 0.10 为例,先同类比较,insertRecords 接口肯定比 insertRecord 接口要快,这个类似 JDBC 的 executeBatch 和 execute 的区别,节省了网络通信次数。同理,insertTablets 比 insertTablet 要快,createMultiTimeseries 也比 createTimeseries 要快。

进一步,insertRecords 方法我们提供了两种,一种是传 Object 的 value,一种是传 String 的 value。如果客户端能获取 value 的类型,建议用 Object 的,会比 String 的快 25% 左右。

跨类比较的话,如果不考虑客户端做格式转化的耗时,insertTablet 比 insertRecords 要快很多,可能 8 倍以上,节省了很多对象封装的耗时,batch size 1000左右就可以。

insertTablet 这个接口默认是没排序的,如果你能保证一个 Tablet 数据的时间戳是非递减的,那就可以多加一个 sorted 为 true 的参数。就节省了客户端的排序。

在统计耗时的时候,还需要注意客户端做格式转化的耗时,可以把接口参数构造的时间和执行的时间分开统计。

查询接口

查询接口比较简单,Session 默认的 hasNext 和 next 会返回 RowRecord 结构,这个结构不一定大家都需要,可以用 SessionDataSet 的 iterator 得到一个迭代器,然后通过类似 JDBC 的接口去得到原始数据,避免很多没用的对象生成。

顺序写入

对于时序数据库,时序是一个很重要的概念,最好不要乱来。IoTDB 支持数据的乱序写入,但是乱序数据会影响查询性能,主要是对于聚合查询,原理是乱序数据会让预计算的统计信息失效,把聚合查询降维打击到读原始数据。

正常情况下,有个几倍的乱序都没问题,但是如果往一个时间段写入了过多(几万倍)的乱序数据,查询时候有可能爆内存。举个例子,内存缓冲区写了时间戳1-10的数据落盘了,然后又写了 9999 遍 1-10 的缓冲区,这样磁盘上就有 1 万个时间戳是 1-10 的数据块。查询时候需要将 1万 个数据块都读出来进行合并,内存占用就比较大了。

面对这种场景,我们会后台做数据整理来处理乱序(在0.9引入的merge,但是0.9版本有bug,0.10修掉了,但是先默认关掉了,会在0.11重新开放merge),但是如果能在客户端避免乱序,就尽量写入的时候避免掉。一个设备就按递增的顺序写入。

如果前边接了 kafka,最好注意一下,把设备 id 作为分区粒度,这样一个设备的数据都会发送到一个分区里,消费的时候同一个分区也能保证顺序。

内存缓冲区

先介绍一下每个序列在内存里能缓存多少个点的怎么算的,用 memtable 大小除以序列数,再除以每个点的大小,比如long类型就是 16字节(8字节时间戳,8字节值),float是12字节。

memtable 的大小可以从日志里看到,搜 reaches,大概日志就是 the memtable size xxx reaches the threshold。如果配置文件里的 enable_parameter_adapter 没有改为 false,这个 memtable 大小就不固定,随着注册的序列数量调整的。

内存缓冲区在一定范围内尽可能大有利于读写。平均每个序列能缓冲100万点以下是比较好的。但是不建议太大,查询时候会临时排序,如果内存中数据点过多,比如千万级,查询时候内存排序会占个十几秒。

为了避免这个问题,0.10.0 里加了个参数,avg_series_point_number_threshold ,默认是 10000,也就是内存缓冲区中每个序列最多缓存这么多点就会刷盘,这个默认参数没给好,可以改成50万或者100万。

memtable_size_threshold 这个参数越大,写入速度快,一般在几百M到一两G左右。不要设置的过小,比如几M,会严重影响写入速度。在设置这个参数时候需要注意不要超内存限制,调这个参数之前需要保证 enable_parameter_adapter 改为 false。

多数据目录

数据库的瓶颈在磁盘IO,简单的提升磁盘IO能力的就是配置多盘。IoTDB 的数据目录可以在 data_dirs 参数配置,用逗号分隔多个目录。可以每块盘一个目录。在写数据的时候会到这几个盘里找最空闲的写。

客户端优化

刚才说了存储组级别的锁,对于同一个存储组的N个写线程,这N个写线程都会抢一把锁,一个存储组对应不超过50个客户端比较好,写线程过多会导致过多的锁竞争。

线程池 SessionPool 的容量,一般搞个服务器 CPU 核数就可以了,不要过多。

客户端的内存,数据的生产和消费速率也可以监控起来,避免提交的任务积压过多,如果客户端内存满了,会出现一个现象:客户端发送请求到服务器,服务器执行和返回很快,但是客户端接收结果会很慢。

容易爆内存的点

select * from root 这个语句在序列过多时候最好不要做,这个操作会把整个库当做一张表,一下查出来所有列的一批数据,容易爆内存,我们会在0.11版本加个检查,及时拒绝。

show timeseries 在 0.10 及以前的版本会把系统所有序列在内存里拷贝一遍传给客户端,如果序列过多,最好指定前缀做个过滤。或者 show child paths 一层一层往下查。

时间序列过多(亿级),元数据可能爆内存,可以按照一条时间序列 200字节估计一下,大概1千万序列会占2G元数据(就是那个元数据树)。

总结

数据库前期需要比较多的手动调优,现在的自动调优工具还有待完善,我们的目标是越简单越好,0.11 版本会完善内存和参数配置。今天内容比较多,之后想到什么再出续集!

本文分享自微信公众号 - IoTDB漫游指南(Apache-IoTDB),作者:铁头乔

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Druid :高性能、列式的分布式数据存储

    GIthub上有两个Druid。其中一个是阿里的数据库连接池,另一个是列式存储的分布式数据存储系统。我曾经一度认为是一个东西,本文介绍后一种Druid。

    Apache IoTDB
  • Apache IoTDB 系列教程-1:数据模型

    大家周末快乐!随着最近项目落地,0.10.0 即将发布,准备写一系列教程,今天第一篇,介绍 IoTDB 的数据模型和建模方式。

    Apache IoTDB
  • Apache IoTDB 随笔 - IoTDB核心技术剖析

    【摘要】Gartner指出赋能边缘是2020年十大战略技术趋势之一,5G加速IoT领域的发展,物联网设备数据的收集,存储和计算需求与日俱增。Apache IoT...

    Apache IoTDB
  • php查询内存信息操作示例

    砸漏
  • 【面试虐菜】—— JAVA面试题(2)

    1 String = 与 new 的不同 使用“=”赋值不一定每次都创建一个新的字符串,而是从“字符串实例池”中查找字符串。使用“new”进行赋值,则每次都创建...

    用户1154259
  • C/C++ 学习笔记七(内存管理)

    工作中经常使用到C/C++,为对C有个比较深刻的了解,重新拾起学习C的任务。在看书的同时,记录下思考的过程,也记录下重要的知识点。

    Celebi
  • Angular系列教程-第五节

    1.模块 NgModule 是一个带有 @NgModule 装饰器的类。 @NgModule 的参数是一个元数据对象,用于描述如何编译组件的模板,以及如何在运...

    苦咖啡
  • 科普 | ETH2 Staking 指南 :客户端多样性为何如此重要

    免责声明:本文没有贬低任何一个客户端。每个客户端,甚至是规范,可能都存在不足和漏洞。ETH 2.0 是一个复杂的协议,实现这个协议的人也都是肉体凡胎。本文旨在强...

    区块链大本营
  • Blackhat专题:WSUS漏洞利用的理论与实践

    Paul Stone和Alex Chapman在Blackhat2015提出了一个关于Windows Server Update Service (WSUS) ...

    FB客服
  • c#基础系列2---深入理解 String

    string(严格来说应该是System.String) 类型是我们日常coding中用的最多的类型之一。那什么是String呢?^ ~ ^

    架构师修行之路

扫码关注云+社区

领取腾讯云代金券