1. 模型文件已经生成,为什么平台还需要数据库
一次训练结束后,对象存储里可能出现一个权重文件。只有这个文件,还回答不了下面的问题:
它使用了哪版数据?
由哪个任务产生?
训练参数和代码版本是什么?
是否通过评估?
目前部署到哪个环境?回答这些问题的数据是元数据(metadata,描述其他数据的数据,回答谁、何时、用什么产生)。AI 平台里的 MySQL 更适合保存这些业务关系和可查询元数据,而不是大文件本身。大文件继续放在对象存储(object storage,以对象为核心、几乎无限扩展的存储服务) 或共享文件系统,Kubernetes 保存当前工作负载状态。
MySQL 任务、版本、权限、状态、关联关系
Object Storage 数据集文件、日志归档、检查点、模型产物
Kubernetes Job、Pod、节点和当前运行状态三层各保存一种“事实”:MySQL 是平台业务记录,对象存储是实际产物,Kubernetes 是当前运行态。它们只在自己那一层保存事实,避免把“现在在跑什么”误当成“历史上发生过什么”。
2. 一条可追溯链路需要哪些对象
可以从最小关系开始:
DatasetVersion
→ TrainingJob
→ TrainingAttempt
→ Artifact
→ ModelVersion
→ Evaluation
→ DeploymentRevisionTrainingJob 表达用户提交的一次业务任务,TrainingAttempt 表达底层的一次执行。Pod 因节点故障重建时,业务任务可以保持同一个,执行次数则增加。
这也是“业务对象”和“底层对象”分离的关键:面向用户的始终是 TrainingJob,它稳定、可追踪;底层执行每次都重新计数,因为它们携带的主机、镜像、节点都不同。
下面是说明字段:
CREATE TABLE training_job (
id BIGINT PRIMARY KEY,
project_id BIGINT NOT NULL,
dataset_version_id BIGINT NOT NULL,
recipe_version VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL,
request_key VARCHAR(128) NOT NULL,
created_at DATETIME(6) NOT NULL,
started_at DATETIME(6),
finished_at DATETIME(6),
version INT NOT NULL DEFAULT 0,
UNIQUE KEY uk_project_request (project_id, request_key),
KEY idx_project_status_created (project_id, status, created_at)
);这不是项目真实迁移脚本。唯一请求键用于表达重复提交边界,联合索引对应“按项目和状态查看最近任务”的查询场景。
两个字段值得单独解释。request_key(请求键) 是用来实现幂等(idempotent,同一个请求执行多次与执行一次效果相同) 的手段:用户在界面重复点击提交,两次请求携带同一个 request_key,数据库唯一键会让第二次写入失败或直接跳过,避免产生重复训练任务。而 version 为下面的条件更新提供版本前提。
3. 状态更新必须带着前置条件
状态不能由任何回调自由覆盖。假设 Worker 报告训练开始:
UPDATE training_job
SET status = 'RUNNING',
started_at = COALESCE(started_at, CURRENT_TIMESTAMP(6)),
version = version + 1
WHERE id = ?
AND status = 'QUEUED'
AND version = ?;受影响行数为 0,说明状态或版本已经变化。调用方应该重新读取,判断是重复消息、过期回调还是不允许的跳转。
这类条件更新(conditional update,把旧状态和版本带进 WHERE 的写入方式) 把并发校验放到真正写入的时刻。Java 代码提前检查状态可以给出友好错误,SQL 条件负责防止检查之后又被其他请求修改——“检查”与“写入”之间隔着一段距离,只有把检查放进写入本身,才能消除这段距离里的竞态。
4. Kubernetes Job 名称不能成为唯一身份
Kubernetes Job 可能删除、重建或迁移到另一个集群。平台自己的任务编号应该保持稳定:
training_job.id = 1042
attempt #1 → cluster-a / Job train-1042-a1 → node lost
attempt #2 → cluster-a / Job train-1042-a2 → succeeded每次执行保存集群、命名空间、Job UID、镜像摘要、开始结束时间和退出原因。业务状态由受控规则推进,不能只轮询当前 Pod Phase 后直接覆盖。
Kubernetes API 是运行状态来源,MySQL 是平台业务记录来源。两者之间发生短暂不一致时,需要可重试同步和幂等处理——同步任务可能重复执行,因此写入要能安全重入。
5. 大日志和模型不适合塞进关系表
数据库可以保存 URI、摘要和内容校验信息:
Artifact
type: CHECKPOINT
uri: s3://ai-artifacts/jobs/1042/checkpoint-8000/
checksum: ...
size_bytes: ...
created_by_attempt: 2完整训练日志、检查点和模型文件留在对象存储。MySQL 记录它们属于谁、由什么产生、当前是否可用。文件内容不属于关系型数据库的职责,关系型数据库保存的是“这份文件在业务里的位置”。
只保存一个可变的 model_path 会丢失历史。发布新模型时,创建新的 ModelVersion 和 DeploymentRevision,旧记录继续保留。版本化而不是覆盖,是可追溯性的前提。
6. 查询变慢时先固定业务问题
训练任务页常见查询是:
EXPLAIN
SELECT id, dataset_version_id, status, created_at
FROM training_job
WHERE project_id = ? AND status = ?
ORDER BY created_at DESC
LIMIT 20;分析时保存 SQL、参数类型、表结构、索引和数据分布。EXPLAIN 给出的是优化器选择的执行计划,不等于实际延迟——它受数据分布、统计信息、缓存和并发影响,需要结合真实查询验证。
索引(index) 能加速条件查询,但会增加写入、存储和维护成本。这一步要问的是“这个查询是否真的频繁、是否真的慢”,而不是先加索引。性能问题也可能来自连接池等待、锁等待、网络和一次请求触发大量关联查询。页面慢不能直接推导成“缺少索引”——慢的原因要先定位,再开药方。
7. 备份需要覆盖关系与产物
只备份 MySQL,恢复后可能找不到对象存储中的模型;只备份对象存储,又会失去任务和版本关系。恢复目标要同时覆盖:
- 数据库快照和增量日志。
- 对象存储的版本、复制或备份策略。
- 两类数据之间可核对的标识和校验值。
- Kubernetes 清单和平台配置,但不把它们当作业务历史备份。
数据库快照与对象存储备份之间存在时间差,恢复时要能对上“同一时刻”的关系:校验值、时间戳和唯一标识就是用来对齐这两类数据的。恢复是否可用,要通过实际恢复流程验证。拥有备份文件不等于能够在目标时间内恢复服务。
把 MySQL 放回 AI 平台后,会发现它首先承担着控制面(control plane,负责创建、编排与记录任务的业务逻辑层) 的事实:任务如何创建,数据和模型怎样关联,一次失败重试如何留下历史。运行面(Kubernetes)管当前状态,数据面(对象存储)管产物,控制面(MySQL + Java)管业务关系。三层各司其职,备份与恢复也要同时覆盖它们。
相关阅读
- AI 平台的 Redis 保存什么:缓存、进度与临时状态
- 从 Docker Compose 到 Kubernetes:AI 数据平台为什么需要统一运行底座
- 从 CRUD 到 AI 数据平台:一条标注数据如何走到交付