目标
- 无需在有无乱序数据时分开配置
- 避免设置活跃的 partition 个数
- 考虑 PrimitiveArrayPool 内存占用
- 尽量有效利用内存,使 Chunk 尽量大
- 尽量保证创建元数据成功,为保证内存不爆,可以拒绝写入
- 尽量在有无乱序情况下,iotdb参数不需要变化都能很好适应
- 尽量不阻塞写入
- 尽量不加入固定参数
Image Added
所涉及的统计信息类
AbstractMemTable
包括以下两个内存统计值:
tvListRamCost:所有TVList被分配的内存总大小,包括TEXT值和primitive arrays中未被占用的空值
memSize:数据点实际占用的内存大小,包括TEXT值
两者的关系如图所示:
Image Added
从图中可以看出,memSize ≤ tvListRamCost
TsFileProcessorInfo
维护一个TsFileProcessor的内存占用,任何内存变动都需要向StorageGroupInfo汇报
memCost:所有ChunkMetadata占用的内存大小
StorageGroupInfo
维护一个存储组的内存占用,当内存占用的增量超过指定的阈值时向SystemInfo汇报
memoryCost:所有TsFileProcessor占用的ChunkMetadata、primitive arrays和TEXT值的内存总和,即∑ TsFileProcessorInfo.memCost + AbstractMemTable.tvListRamCost
SystemInfo
维护所有存储组的内存占用
totalStorageGroupMemCost:所有StorageGroupInfo中memroyCost的总和
写入流程各部分内存统计
RPC模块
- 一次请求的大小受限制 thrift_max_frame_size=67108864
现有问题:
- 用户设置活跃的partition比较麻烦
- 有无乱序情况的最佳配置不一样
- PrimitiveArrayPool内存占用没考虑,容易爆内存
- 开启动态参数后创建时间序列经常失败
- 动态参数计算出的memtable偏小,chunk较小,影响查询性能
- 对象内存估计不准确
- 内存中一个时间序列点数过多,上1万,拷贝排序较慢
新策略:
目标(解决1-5)
尽量保证创建元数据成功,为保证内存不爆,可以拒绝写入
尽量在有无乱序情况下,iotdb参数不需要变化都能很好适应
让chunk大小最大化
尽量不阻塞写入
尽量不加入固定参数
Image Removed
RPC模块:
...
- 一次请求的大小受限制 b.(防止许用户一条SQL写入1亿个点等场景;或者写了一个大于2GB的bytes[]).
- 并发数受限制 c。
内存写入模块:
优点:
- 所有SG共享内存,不再对每个SG单独设置一个内存上限,因此创建序列(或今后改为序列活跃情况变化)时也不需要再更新SG;好处是内存利用率可以很高;
缺点:
- rpc_max_concurrent_client_num=65535。
核心思想:
- Schema和历史resource单独分配大小;下文仅考虑其余写数据部分大小。
- 每个SG统计自身的chunk_metadata和unseal_resource大小;
- 全局ArrayPool统计buffered和out of buffer的array大小
- 系统统计总的大小
数据写入流程
写入线程:
- 如果是非空的写入线程
- 在 StorageEngine 中检查SystemInfo是否为reject状态;如果是,则该写入线程循环sleep 50ms(等待flush线程释放内存,system置回正常状态)再进行写入;如果等待max_waiting_time_when_insert_blocked后仍为reject状态,抛出写入异常;
- 进入对应的StorageGroupProcessor,获取 writeLock
- 进入对应分区的 TsFileProcessor:(1)获取已有的可写入的顺序或乱序 TsFileProcessor(2)如果没有可写入的TsFileProcessor,创建新的 TsFileProcessor
- 统计当前写入计划新增的内存占用,增加至TspInfo和SgInfo中:(1)新测点增加 chunk_metadata(2)TEXT 类型数据(3)TVList 中增加的 PrimitiveArray(4)flush内存
- 如果 SGInfo 增量超过阈值(storage_group_size_report_threshold=16M)
- 向SystemInfo进行上报(将当前 TsFileProcessor 传入);
synchronized(SystemInfo) {- 更新 SystemInfo 内存占用。
- 如果 SystemInfo 内存占用 < 总写入内存 * flush_proportion,返回 true。
- 如果 总写入内存 * flush_proportion ≤ SystemInfo 内存占用 < 总写入内存 * reject_proportion, 执行 选择Memtable提交flush流程,返回 true。
- 如果 总写入内存 * reject_proportion ≤ SystemInfo 内存占用, SystemInfo 置为 reject 状态, 执行 选择Memtable提交flush流程,记返回值为 flag
- 如果 flag = true
- 如果 SystemInfo 内存占用 < 总写入内存,则返回 true
- 如果 SystemInfo 内存占用 ≥ 总写入内存,直接抛 写入Reject 异常
- 如果 flag = false,则返回 false
}
- 判断 向SystemInfo上报 的返回结果
- 如果返回 false,则该写入线程循环 sleep (50ms) ,检查 SystemInfo 的 reject 状态如果不 reject或者该memtable被标记为shouldFlush,执行正常写入。如果等待 max_waiting_time_when_insert_blocked 后仍为reject状态,抛出写入异常
- 如果返回 true,则执行正常写入
- 如果捕获到 写入Reject 异常,reset SystemInfo,并继续向上抛
- 检查 workingMemtable 的 shouldFlush,如果为true,提交 Flush 任务,并根据文件大小判断是否需要 close。
- StorageGroupProsessor. 释放writeLock
- 如果是空的写入线程
- 进入对应的 StorageGroupProcessor,获取 writeLock
- 获取对应分区的 TsFileProcessor:如果(其 workingMemtable 不为空且 shouldFlush 为 true),则提交 flush 任务;否则直接返回。
- StorageGroupProsessor. 释放writeLock
...
- 一个insertPlan写入完成后,检查该TSP的 workingMemtable 的 shouldFlush 字段,如果为 true,再检查是否TsFile大小超过阈值,如果超过,flush memtable后将文件封口。
- TsFile关闭完成后,清空该TSPInfo,重置对应的 SGInfo 状态,并向SystemInfo报告重置后SGInfo
- 如果此时SystemInfo 为reject状态 且 `SystemInfo中统计的总内存 < 总写入内存 * reject_proportion`,将SystemInfo 置于正常状态
MTree内存控制:
注册时间序列时,如果总时间序列个数*estimate_series_size > 总内存*write_read_schema_free_memory_proportion:schema,此时拒绝注册,抛出异常。
相关参数整理
- 是否开启内存控制
enable_mem_control=true
以下参数只在开启内存控制时生效: - flush阈值(0.0--1.0)(关闭内存控制后无效)
当所有memtable实际占用大于总写入内存 * flush_proportion,触发flush
flush_proportion=0.4 - reject阈值(0.0--1.0)(关闭内存控制后无效)
当所有memtable实际占用大于总写入内存 * reject_proportion,阻塞写入,等待flush释放内存
reject_proportion=0.8 - array pool内存占总写入内存的大小比例(0.0--1.0)
buffered_arrays_memory_proportion=0.6 - sg上报阈值(bytes)(关闭内存控制后无效)
当一个sg内所有memtable的内存相比上次上报的增量大于这个值,向SystemInfo更新目前Sg的总大小
注意:当sg较多,例如1000时,需要考虑调小这个值。因为此时memtable需要写 16M * 1000 = 16G 才会向SystemInfo 汇报,比较危险。
storage_group_report_threshold=16777216 - 阻塞写入后的检查周期(ms)(关闭内存控制后无效)
当写入被SystemInfo拒绝后,客户端线程会以这个时间周期去检查SystemInfo的状态,直到flush线程释放掉一些内存,SystemInfo置回正常状态。
check_period_when_insert_blocked=50 - 阻塞写入后的最大等待时间(ms)(关闭内存控制后无效)
当写入阻塞时间超过这个值后,向客户端返回写入异常
max_waiting_time_when_insert_blocked=10000 - 预估一条序列在mtree中的大小(关闭内存控制后无效)
这个值用来限制可注册的序列个数
estimated_series_size=300
- memtable大小阈值(bytes)(开启内存控制后无效)
memtable_size_threshold=1073741824 - 写入、查询、schema、剩余内存占比
其中可注册的序列的个数由schema的内存(byte)除以estimated_series_size来确定
write_read_schema_free_memory_proportion=4:3:1:2 - array pool中的array长度
平均每个chunk的点数小于这个值时会造成内存的浪费,可以考虑调小这个值
primitive_array_size=128 - TsFile大小阈值
0代表只刷一个memtable就关文件;1代表刷两个memtable才关文件;大于1时如1G时,1个文件中会存在更多的memtable
目前设为1是考虑merge会把小文件合并为大文件;而且如果这个阈值过大且开启内存控制后,会导致内存中metadata积累较多,memtable越来越小
tsfile_size_threshold=1 - 平均chunk点数阈值(个数)
当memtable内平均每个序列的点数超过这个阈值时,触发flush
avg_series_point_number_threshold=10000
优点:
- 所有SG共享内存,不再对每个SG单独设置一个内存上限,因此创建序列(或今后改为序列活跃情况变化)时也不需要再更新SG;好处是内存利用率可以很高;
缺点:
- 部分步骤需要全局锁;目前看,假设array为k,SG info 写x延迟上报,则个memTable写入16MB后,会拿一次全局锁更新全局内存情况。
细节:关于Array Pool中分类型的数组如何管理?
...