1. 现状
metadata模块内部使用MNode实现元数据树结构,并在内存中全量存储元数据信息。
外部模块与MManager交互时,使用PartialPath、MeasurementPath等对象装载元数据信息。
PartialPath、MeasurementPath是元数据树基于MNode信息实时生成的通信对象。
2. 问题
2.1. 单任务场景
任何一个任务访问MManager获取元数据信息时,所有的通信对象都是实时生成的,因此实时生成的对象会短暂占用内存。
在某实际场景中,IoTDB通过180G内存管理1.2亿的序列,合并模块一次元数据请求会造成12G的临时内存开销,均为实时生成的PartialPath。
2.2. 多任务场景
单任务执行时会有造成临时的内存负载,在多任务场景下,内存负载会被放大。
由于PartialPath等通信对象都是实时生成,因此每个任务线程都持有自身从MManager获取的实时生成对象。
即元数据信息被每个任务线程进行了拷贝,内存开销相较于单任务场景线性增长。
3. 方案
3.1. PartialPath瘦身
PartialPath目前为了与TsFile交互的方便,直接继承了TsFile的Path类,导致PartialPath内部信息过于冗余。
因此考虑解除PartialPath对Path的继承,在与TsFile交互时实时生成。
收益:单个PartialPath的大小可减少24字节以上
3.2. PartialPath享元
线程使用PartialPath时均以强引用方式使用。
PartialPath存储时,在MNode设置弱引用的partialPath字段。
每一次获取partialPath时,优先获取当前弱引用指向的对象。
如果弱引用指向null,则表明此时该MNode的partialPath未被任何线程使用,因此需要生成PartialPath对象。
public class MNode{ WeakReference<PartialPath> partialpath = null; public String getFullPath(){ return getPartialPath().getFullPath(); } public PartialPath getPartialPath(){ PartialPath partialPath = this.partialPath.get(); if(partialPath == null){ synchronized(this){ if((partialPath = this.partialPath.get()) == null){ partialPath = this.concatPartialPath(); this.partialPath = new WeakReference(partialPath); } } } return partialPath; } }
收益:多任务并行时,无同一逻辑PartialPath多实例问题
3.3. MetadataQueryUnit抽象
对通信对象进行抽象,建立接口MetadataQueryUnit,利用MNode实现MetadataQueryUnit。
MetadataQueryUnit是一种元数据信息的访问代理,基于这个代理可以控制外部的视图和访问权限。
外部需要使用PartialPath时,实时从MetadataQueryUnit中获取,MetadataQueryUnit利用MNode生成PartialPath。
虽然MNode中的PartialPath仍是实时生成,但是这种延迟生成可大幅缩短PartialPath的生命周期,进而降低内存开销的瞬时峰值。
对外部暴露的接口示例如下(仅列举PartialPath相关内容,针对查询的业务MetadataQueryUnit将会实现更多职责):
public interface IMetadataQueryUnit{ PartialPath getPartialPath(); ... } public interface IStorageGroupQueryUnit extends IMetadataQueryUnit{ long getDataTTL(); ... } public interface IMeasurementQueryUnit extends IMetadataQueryUnit{ IMeasurementSchema getMeasurementSchema(); ... } public interface IAlignedMeasurementQueryUnit extends IMetadataQueryUnit{ ... }
根据元数据内部的具体实现,MetadataQueryUnit的实现也需多种适配方案。
例如MTree上存储在的MNode,可直接用于一种实现;Template表征的序列不存在MNode实例,则需要具体的信息来实现。
public class MetadataQueryUnit implements IMetadataQueryUnit{ IMNode node; PartialPath getPartialPath(){ return node.getPartialPath(); } } public class MetadataInTemplateQueryUnit implements IMetadataQueryUnit{ PartialPath partialPath; PartialPath getPartialPath(){ return partialPath; } } public class StorageGroupQueryUnit implements IStorageGroupQueryUnit{ IStorageGroupMNode storageGroupMNode; PartialPath getPartialPath(){ return storageGroupMNode.getPartialPath(); } long getDataTTL(){ return storageGroupMNode.getDataTTL(); } } public class MeasurementQueryUnit implements IMeasurementQueryUnit{ IMeasurementMNode measurementMNode; PartialPath getPartialPath(){ return measurementMNode.getPartialPath(); } IMeasurementSchema getMeasurementSchema(){ return measurementMNode.getSchema(); } } public class MeasurementInTemplateQueryUnit implements IMeasurementQueryUnit{ PartialPath partialPath; IMeasurementSchema measurementSchema; PartialPath getPartialPath(){ return partialPath; } IMeasurementSchema getMeasurementSchema(){ return measurementSchema; } }
收益:单个MetadataQueryUnit仅包含一个MNode引用,其存储开销远小于PartialPath,进而单次的元数据交互的瞬时峰值可大幅降低。