Table of Contents |
---|
文件结构
每个数据文件 TsFile 在内存中对应一个对象 TsFileResource,主要包含以下信息
- 版本号
- 时间索引:将此 TsFile 中所有时间序列的路径 默认按照 (root - 倒数第二层)分组,每组为一个索引条目(包含:路径、起始时间、终止时间)
- Raft日志段:raft 中一个 TsFile 文件对应的 Raft 日志的起始和终止下标
- 墓碑文件路径:如果存在墓碑文件则记录
VERSION_NUMBER = 1 (1 byte) timeIndexType = 0 FileTimeIndex / 1 DeviceTimeIndex (1 byte) timeIndex (ITimeIndex)
maxPlanIndex (long) minPlanIndex (long) modFileName (String) |
各个字段用处
- 版本号
- IoTDB 版本升级时识别不同版本的 TsFileResource 结构
- 时间索引
- 查询时根据 TsFileResource 中的时间索引判断某个 TsFile 是否可能包含待查数据
- 跨文件空间数据合并时,根据 TsFileResource 的时间索引判断哪些文件需要被合并
- TTL 线程根据 TsFileResource 的时间索引定期检查 TsFile 是否整个过期,可以完全删除
- Raft 日志段
- 墓碑文件路径
一、TsFileResource及接口设计修改目标
1. 每个数据文件 TsFile 在内存中对应一个文件索引信息TsFileResource,供查询使用。
2. 目标:
(1) 总体目标:目前tsfile采用一对一的索引方式,每个tsfile都有一个索引文件TsFileResource,tsfile文件内的信息(开始时间、结束时间等)都保存在TsFileResource中。
由于TsFileResource常驻内存中,当有过多设备的情况下,就会爆内存。本次针对TsFileResouce的改进总目标为可以支持更多的设备。
(2) 阶段1目标 —— 接口化:区分【文件句柄TsFileResource】和【文件索引FileIndexEntries】,将TsFileResource中的字段放入FileIndexEntries中;
利用FileIndexEntries的接口完成“文件索引”相关功能;TsFileResource中仅保留文件句柄的功能。
二、阶段1详细设计:新增和修改的数据结构
具体而言,涉及到数据结构如下:
1. FileIndexEntries:(用于信息传递)
...
private TimeIndexEntry[] indexEntries; // TimeIndexEntry 列表,记录文件中所有的前缀路径、开始时间和结束时间
private String tsFilePath; // TsFile 路径
2. TimeIndexEntry:
...
private PartialPath path; // 前缀路径
private long startTime; // 开始时间
private long endTime; // 结束时间
3. FileIndex
...
// 获得与查询相关的FileIndexEntries
Map<tsFilePath, TimeIndexEntry[]> filterByPath(PartialPath path, Filter timeFilter)
// 为路径增加索引,适用场景:(1)文件刷盘封口;(2)版本升级
void createIndexForPaths(FileIndexEntries fileIndexEntries)
// 为路径删除索引,适用场景:(1)删除文件;(2)根据TTL标记失效的文件
void deleteIndexOfPaths(FileIndexEntries fileIndexEntries)
4. FileIndexManager
public class FileIndexManager {
private Map<PartialPath, FileIndex> seqIndices;
private Map<PartialPath, FileIndex> unseqIndices;
private final ReentrantReadWriteLock lock;
private static class IndexerManagerHolder {
private IndexerManagerHolder() {
// allowed to do nothing
}
private static final FileIndexManager INSTANCE = new FileIndexManager();
}
public static FileIndexManager getInstance() {
return IndexerManagerHolder.INSTANCE;
}
private FileIndexManager() {
seqIndexers = new ConcurrentHashMap<>();
unseqIndexers = new ConcurrentHashMap<>();
lock = new ReentrantReadWriteLock();
}
public boolean init() {...}
public void addSeqIndexer(PartialPath storageGroup, FileIndex fileTimeIndexer) { ... }
public void addUnseqIndexer(PartialPath storageGroup, FileIndex fileTimeIndexer) { ... }
public void deleteSeqIndexer(PartialPath storageGroup) { ... }
public void deleteUnseqIndexer(PartialPath storageGroup) { ... }
public FileIndex getSeqIndexer(PartialPath storageGroup) { ... }
public FileIndex getUnseqIndexer(PartialPath storageGroup) { ... }
// 将TsFileResource转化为FileIndexEntries,用于版本升级
public static FileIndexEntries convertFromTsFileResource(TsFileResource resource) {... }
4. 第二步的修改(后续)涉及到【封口文件句柄SealedTsfileResource】和【未封口文件句柄UnsealedTsfileResource】
因为封口文件句柄需要的字段较少,未封口文件句柄可以继承封口文件句柄;文件封口时再转化为封口文件句柄。
5. 总结
(1) 第一步接口化(本次)的修改中,可以从TsFileResource中转移走的字段:
...
protected long[] startTimes; // 开始时间列表
protected long[] endTimes; // 结束时间列表,未封口则为 Long.MIN_VALUE
protected Map<String, Integer> deviceToIndex; // device设备名 => 开始 / 结束时间列表index
(2) 目前TsFileResource文件句柄保留的功能和字段:
...
Map<String, String> cachedDevicePool // for reducing the String number in memory
File file // tsfile
TsFileProcessor processor 【未封口】
ModificationFile modFile // modification file
boolean closed【未封口】
boolean deleted【未封口】
boolean isMerging
Set<Long> historicalVersions // for tracking the merge history of a TsFile
TsFileLock tsFileLock
List<ChunkMetadata> chunkMetadataList // 【未封口】chunk metadata list of unsealed tsfile. Only be set in a temporal TsFileResource in a query process.
List<ReadOnlyMemChunk> readOnlyMemChunk // 【未封口】mem chunk data. Only be set in a temporal TsFileResource in a query process.
TimeseriesMetadata timeSeriesMetadata // 【未封口】get TimeseriesMetadata of unsealed file
List<TsFileResource> upgradedResources // 【升级】generated upgraded TsFile ResourceList used for upgrading v0.9.x/v1 -> 0.10/v2
UpgradeTsFileResourceCallBack upgradeTsFileResourceCallBack // 【升级】load upgraded TsFile Resources to storage group processor used for upgrading v0.9.x/v1 → 0.10/v2
boolean isSeq // 【升级】indicate if this tsfile resource belongs to a sequence tsfile or not used for upgrading v0.9.x/v1 -> 0.10/v2
TsFileResource originTsFileResource // 【未封口】current tsfile resource is a snapshot of the originTsFileResource. When we want to used the lock, we should try to acquire the lock of originTsFileResource
long maxPlanIndex // for cluster, max index of plans executed within this TsFile
long minPlanIndex // for cluster, min index of plans executed within this TsFile
三、涉及到使用文件索引的操作
【数据的插入和批量插入】
总入口: public void insert(InsertRowPlan insertRowPlan) StorageEngine.java
1. 找到对应的 StorageGroupProcessor
2. 根据写入数据的时间以及当前设备落盘的最后时间戳,找到对应的 TsFileProcessor
3. 【需修改】写入 TsFileProcessor 对应的 memtable 中:
(1) 如果是乱序文件,则更新tsfileResource中的endTimeMap (deviceToIndex, endTimes);
(2) 如果tsfile中没有该设备的信息,或新插入数据的时间小于已存startTime,则更新tsfileResource中的startTimeMap
4. 记录写前日志
5. 根据 memtable 大小,来判断是否触发异步持久化 memtable 操作。如果是顺序文件且执行了刷盘动作,则更新tsfileResource中的endTimeMap
6. 根据当前磁盘 TsFile 的大小,判断是否触发文件关闭操作
修改:第3步将endTimeMap、startTimeMap存储在TsFileProcessor中,而不是tsfileResource中
【数据的访问】
总入口(StorageEngine): public QueryDataSource query(SingleSeriesExpression seriesExpression, QueryContext context, QueryFileManager filePathsManager)
...
修改:调用filterByPath(path, timeFilter) 方法
【重启恢复流程】
StorageGroupProcessor的recover()
1. 【需修改】恢复每个分区的顺序 / 乱序文件,将上一步获得的每个分区的顺序 / 乱序 TsFile 文件作为参数,调用recoverTsFiles进行恢复,
该方法会将恢复后的顺序 / 乱序 TsFile 以 TsFileResource 的形式放入TsFileManagement里,并恢复文件时间索引信息(将TimeFileIndex信息放入FileIndexManager中),
若该 TsFile 是此分区的最后一个,且未封口,则还要为其构造TsFileProcessor对象,并加入work(Un)sequenceTsFileProcessors中
2. 若该 TsFile 文件不是最后一个文件,或者该 TsFile 文件是最后一个文件,但已经被关闭或标记被关闭,
只需将该 TsFile 文件在内存中对应的TsFileResource(文件句柄)的closed属性置成true即可。
3. 若该 TsFile 文件可以继续写入,则表示这是此分区的最后一个 TsFile,且未封口,则继续保持其未封口的状态,
需要为它构造一个TsFileProcessor对象,并将其放到workSequenceTsFileProcessors或workUnsequenceTsFileProcessors中。
4. 最后将恢复出来的 TsFile 文件在内存中对应的TsFileResource(文件句柄+文件索引)对象放入sequenceFileTreeSet或unSequenceFileList中,用于更新分区对应的版本号等
四、涉及到使用文件句柄类的操作
【数据的删除】
StorageEngine.java中的入口: public void delete(String deviceId, String measurementId, long timestamp)
1. 找到对应的 StorageGroupProcessor
2. 【保留使用文件句柄类】找到受影响的所有 working TsFileProcessor 记录写前日志
3. 【保留使用文件句柄类】找到受影响的所有 TsFileResource,在其对应的 mods 文件中记录一条记录:path,version,startTime,endTime:
(1) 如果存在 working memtable:则删除内存中的数据
...