数据存储 - Aim数据收集的位置
本节深入探讨Aimstack的存储结构。了解其内部存储组织对于理解如何影响查询性能非常重要。
存储结构
Aim存储的核心基础是rocksdb。这是一个由facebook维护的快速嵌入式键值存储。Aim仓库是由多个独立rocksdb数据库组成的集合,通过添加抽象层来将这些集合作为一个数据库进行管理。这个抽象层称为Container。以下是一个典型Aim项目的目录结构:
.aim
run_metadata.sqlite
meta/
index/
chunks/
aacf48e769534c32a9cc5a3c/
80483ab611a24bf5bd8fc288/
16f83a2c2f50477f8446f322/
progress/
aacf48e769534c32a9cc5a3c
80483ab611a24bf5bd8fc288
locks/
aacf48e769534c32a9cc5a3c
80483ab611a24bf5bd8fc288
seqs/
chunks/
aacf48e769534c32a9cc5a3c/
80483ab611a24bf5bd8fc288/
16f83a2c2f50477f8446f322/
progress/
aacf48e769534c32a9cc5a3c
80483ab611a24bf5bd8fc288
locks/
aacf48e769534c32a9cc5a3c
80483ab611a24bf5bd8fc288
存储主要有两个部分:
run_metadata.sqlite: 用于存储Run结构化数据的SQLite数据库,例如创建时间、名称、所属的Experiment、Tag标签等。meta/和seqs/目录:一组rocksdb存储。用于写入Run跟踪的数据,如参数、指标和对象。
在上面的树形结构中,哈希字符串(例如aacf48e769534c32a9cc5a3c)代表单个Run。
当新的Run启动时,aim将创建两个容器:
Meta 容器,用于记录参数以及收集的序列、上下文等元数据。
Sequence 用于存储数值序列的容器。
实际序列数据与元数据分离的原因在于,无论序列大小如何,都需要支持快速查询。
此外,每个容器将创建两个文件以正确管理容器状态
lock 文件,表示容器以写入模式打开。
progress 文件,表示容器可能包含未索引的数据。
这种设置允许实现并发训练作业配置,无需额外的同步例程,也没有丢失或损坏数据的风险。例如,两个作业可能运行在不同的主机上,其中aim仓库挂载在共享的NFS位置。
什么是索引容器?
每次运行都会将数据写入其独立的容器中。aim查询需要从meta容器读取运行元数据。然而,当存在数千次运行时,逐个打开每个元数据容器数据库会显著降低查询速度。此时,元数据的索引就变得至关重要。
Run 对象在训练脚本执行期间会锁定meta和sequence容器。Run会持续将数据写入自己的容器中。一旦执行完成,meta容器的数据会被索引,容器锁释放,并移除进度文件。而sequence容器的数据不会被索引,因为序列的单个点不可查询,且序列信息已在meta容器中提供。
数据如何写入/读取存储?
Run 对象提供了记录运行参数的类字典数据以及跟踪标量和对象序列的接口。Aim 拥有一个自定义编码层,将这些分层数据转换为键值对集合并写入 rocksdb。同一编码层还负责重构跟踪的数据/对象。在执行查询时,aim SDK 会遍历 index 容器中的所有运行以及具有进度文件的块的 meta 容器(请记住,进度文件表示该运行可能存在未索引的数据)。如果运行/序列匹配查询表达式,则会返回相应的运行/序列。请注意,在此之前并未从 sequence 容器中访问任何数据。序列数据本身是按需读取的。