4. 数据存储选型:对象存储、列式存储与时序数据库

数据存哪,怎么存,这问题看着基础,但坑特别多。我见过不少团队,模型迭代到一半发现数据读不出来,或者训练效率被 IO 卡死,回头一查,全是存储选型没想清楚。

这一章,咱们就把对象存储、列式存储和时序数据库这三类东西掰开揉碎。不聊虚的,直接说选型逻辑和实战经验。

4.1 对象存储:数据湖的基石

对象存储,说白了就是 OSS 或者 S3 这类服务。它存的是“对象”——文件加上元数据,扁平结构,没有目录树。你想想看,一个桶里几亿个文件,传统文件系统早崩了,但对象存储扛得住。

核心定位:数据湖的底座,存原始数据、模型文件、训练日志、Checkpoint。

我个人习惯把对象存储当作“冷热分离”的冷端。热数据放本地 SSD,冷数据全扔 OSS。为什么?便宜,扩容简单,而且 S3 协议是事实标准,几乎所有框架都支持。

4.1.1 选型要点

  • 兼容性:必须支持 S3 兼容 API。别搞私有协议,后面对接 Spark、Flink、PyTorch 会痛不欲生。
  • 吞吐能力:单桶带宽上限是多少?并发请求数?我遇到过单桶 QPS 打满导致训练中断的情况,后来加了前缀分区才解决。
  • 数据一致性:大部分对象存储是最终一致性。读后写能立刻读到吗?有些场景需要强一致,比如模型注册。

我的经验:训练 Checkpoint 直接写 OSS 时,一定要用分段上传(Multipart Upload)。否则大文件失败一次就得重传,浪费几个小时。

4.2 列式存储:分析查询的加速器

Parquet 和 ORC 是列式存储的代表。行存适合 OLTP,列存适合 OLAP。为什么?因为分析查询通常只读少数列,列存可以跳过无关数据,压缩率也高。

我记得有一次优化特征存储,把 CSV 换成 Parquet,查询速度提升了 10 倍,存储空间降了 60%。嗯,这差距就是这么夸张。

4.2.1 Parquet vs ORC

维度 Parquet ORC
压缩率 高,支持多种编码 更高,自带轻量索引
生态兼容 Spark、Hive、Presto 原生支持 Hive 生态更好,Spark 稍弱
谓词下推 支持,但不如 ORC 精细 支持,带统计信息更高效
写入性能 中等,适合批量 略优,适合流式写入

怎么选?如果你的技术栈是 Spark 为主,Parquet 更顺手。如果 Hive 用得重,ORC 更合适。我个人偏向 Parquet,因为生态更广,而且 PyArrow 读写方便。

4.2.2 实战避坑

  • 小文件问题:列式存储最怕小文件。每个文件都有元数据,文件多了元数据膨胀,查询变慢。我建议每个文件 256MB 到 1GB。
  • Schema 演化:模型迭代中特征会变。Parquet 支持添加列,但删除列要小心。我习惯用嵌套结构(Struct/List)预留扩展位。
  • 分区策略:按时间分区是常规操作。但分区粒度过细(比如按小时)会导致大量小目录,反而降低性能。

曾经踩过的坑:有一次我把特征表按天分区,每天 1000 个文件。跑了半年后,Hive 元数据服务直接 OOM。后来改成按周分区,文件合并到 200 个以内,问题解决。

4.3 时序数据库:监控与指标的首选

模型训练过程中,GPU 利用率、Loss 曲线、数据延迟这些指标,都是时序数据。用关系数据库存?可以,但查询慢,存储膨胀快。时序数据库(如 InfluxDB、TimescaleDB、Prometheus)专门为这类场景设计。

你想想看,每秒几万条指标写入,还要支持按时间窗口聚合、降采样、实时报警。普通数据库扛不住。

4.3.1 选型对比

特性 InfluxDB TimescaleDB Prometheus
存储引擎 自研 TSM 基于 PostgreSQL 自研 TSDB
写入性能 极高,单机百万点/秒 高,依赖 PG 调优 高,但单机有限
查询语言 Flux / InfluxQL SQL PromQL
集群能力 企业版才支持 原生 PG 集群 Thanos / Cortex 扩展
适用场景 IoT、APM、监控 金融、DevOps K8s 监控、告警

我个人习惯:训练指标用 InfluxDB,因为写入简单,查询灵活。基础设施监控用 Prometheus,跟 K8s 集成好。如果团队 SQL 能力强,TimescaleDB 也不错。

4.3.2 数据建模要点

  • Tag 和 Field 区分:Tag 是索引字段(如 GPU ID、模型名),Field 是指标值(如 loss、accuracy)。Tag 不要太多,否则索引膨胀。
  • 降采样策略:原始数据保留 7 天,之后自动聚合到分钟级。我一般用连续查询(CQ)或者 Retention Policy 实现。
  • 避免高基数:Tag 的取值数量不要超过百万级。比如把用户 ID 当 Tag,会导致性能雪崩。

我的习惯:训练时每 10 秒记录一次指标,用 InfluxDB 的批量写入接口。查询时用窗口函数做平滑,比在应用层算快得多。

4.4 整体选型框架

三种存储不是互斥的,而是协同的。我画了一张图,帮你理清关系。

数据存储选型框架 原始数据源 对象存储 (OSS/S3) 原始数据、模型文件、日志 列式存储 Parquet / ORC 时序数据库 InfluxDB / Prometheus 其他存储 MySQL / Redis / ES 特征工程 / 模型训练 / 监控告警 数据流向:原始数据 → 对象存储 → 按需转入列式存储或时序数据库 → 支撑上层应用

从图里能看出来,对象存储是中心,所有原始数据先进去。然后根据使用场景,转成列式存储做分析,或者写入时序数据库做监控。其他存储(MySQL、Redis)负责元数据和缓存。

4.5 选型决策清单

最后,我整理了一个清单,帮你快速决策:

  1. 存原始文件? → 对象存储(OSS/S3)
  2. 做大规模分析查询? → 列式存储(Parquet/ORC)
  3. 存监控指标和时序数据? → 时序数据库(InfluxDB/Prometheus)
  4. 需要事务和强一致? → 关系数据库(MySQL/PostgreSQL)
  5. 需要低延迟缓存? → Redis/Memcached

重要提醒:不要试图用一种存储解决所有问题。我见过有人用 HBase 存时序数据,结果查询慢、运维复杂。选型要匹配场景,而不是追新。

嗯,这一章的内容就这些。存储选型没有银弹,但理解了每种存储的优缺点,你就能在模型迭代中少走弯路。下次遇到数据读写的性能问题,先回头看看存储层选对了没有。