4. 配置Cromwell的数据库(H2/MySQL)

数据库配置,说白了就是给Cromwell找个“记事本”。

工作流跑到哪一步了?中间结果存哪了?这些都是靠数据库来记录的。我刚开始碰Cromwell的时候觉得这步无所谓,直接拿默认配置跑。结果工作流一中断,啥都找不回来。

嗯,从那以后我再也不敢跳过数据库配置了。

4.1 数据库到底管什么?

Cromwell自己不会“记住”任何东西。每一次任务调度、每一个输出路径,它都要往数据库里写一笔。万一数据库挂了,整个引擎就等于失忆。

我个人习惯把数据库分成两套场景,通俗点讲就是:

  • H2(开发/尝鲜):本地文件型数据库,开箱即用,擦桌子就能跑。
  • MySQL(生产/多人协作):独立服务,稳定、可控,适合团队或云端环境。

下面这张图可以帮你快速理清不同阶段该选哪个:

数据库选型决策流程 开始配置数据库 开发 or 生产环境? 开发/单机 生产/团队 H2 数据库 嵌入式,无需安装 MySQL 数据库 独立服务,稳定可靠 写入 cromwell.conf 小型测试用 H2,正式项目切 MySQL

4.2 先聊聊 H2(开箱即用版)

Cromwell 默认就带 H2。你下载完 jar 包直接跑,它会在当前目录生成一个 cromwell.db 文件。

说实话,H2 对于练手和测试完全够用。

但这里有个坑:H2 是单机文件锁。如果你同时起了两个 Cromwell 进程指向同一个 .db 文件,后一个直接报错退出。我曾经在本地调试时,忘了关掉前一个窗口,又开了一个终端,结果浪费了十几分钟排查锁文件。

H2 的配置超级简单,在 cromwell.conf 里这样写:

database {
  profile = "slick.jdbc.HsqldbProfile$"
  db {
    driver = "org.hsqldb.jdbcDriver"
    url = "jdbc:hsqldb:file:${CROMWELL_ROOT}/cromwell.db;shutdown=false;hsqldb.tx=MVCC"
    connectionTimeout = 5000
  }
}
# 如果使用 H2,默认配置是这样:
database {
  profile = "slick.jdbc.H2Profile$"
  db {
    driver = "org.h2.Driver"
    url = "jdbc:h2:file:${CROMWELL_ROOT}/cromwell.db;MVCC=true;LOCK_TIMEOUT=10000"
    connectionTimeout = 5000
  }
}
我的建议: H2 模式下的 LOCK_TIMEOUT 酌情调高到 10 秒以上。任务多了,H2 偶尔会锁住等一会儿才能释放。

4.3 切换到 MySQL(生产级配置)

一旦你的工作流开始变多,或者要跟别人共享 Cromwell 实例,H2 就不太稳了。我建议直接上 MySQL。

你想想看,H2 只是一个文件,万一磁盘满了或者文件损坏,所有正在跑的任务就全断了。MySQL 起码有主从、有备份、有事务回滚。

先得把 MySQL 服务准备好。我个人习惯用 Docker 来起一个测试用的 MySQL 实例,省得污染本地环境:

docker run --name cromwell-mysql \
  -e MYSQL_ROOT_PASSWORD=root \
  -e MYSQL_DATABASE=cromwell \
  -e MYSQL_USER=cromwell \
  -e MYSQL_PASSWORD=cromwell \
  -p 3306:3306 \
  -d mysql:8.0
注意:线上别用 root 密码当应用密码。这里只是演示方便。真实环境请单独建用户,权限最小化。

然后修改 cromwell.conf,指向 MySQL:

database {
  profile = "slick.jdbc.MySQLProfile$"
  db {
    driver = "com.mysql.cj.jdbc.Driver"
    url = "jdbc:mysql://localhost:3306/cromwell?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"
    user = "cromwell"
    password = "cromwell"
    connectionTimeout = 5000
    numConnections = 10
    maxConnectionAge = 30 minutes
  }
}

这里有几个参数我想重点说一下:

参数 说明 个人建议值
numConnections 连接池大小。撑不满会报连接耗尽。 10 ~ 20(看并发量)
maxConnectionAge 连接最大存活时间。MySQL 8.0 默认 8 小时断开空闲连接。 30 分钟或 1 小时
connectionTimeout 等待连接的超时时间(毫秒)。 5000(5秒够了)
useSSL=false 本地/内网可以不加密,省掉证书配置。 内网 false,公网必须 true

配置好之后,启动 Cromwell 时会自动建表。我第一次切 MySQL 的时候,启动日志里看到一堆 CREATE TABLE 语句飞过,心里还挺踏实。

不过,如果你之前用过 H2,旧的元数据是不会自动迁移到 MySQL 的。你得手动搞一下,或者干脆接受“老任务记录丢失”,直接从新环境开始。

高效切换的实操套路: 在开发机上用 H2 快速试错,等 pipeline 调通之后,换 MySQL 跑正式数据。同一个 cromwell.conf 文件可以保留两份配置,通过环境变量切换。我经常这么干,省得来回改文件。

4.4 MySQL 连接中容易踩的坑

反正我遇到的坑,十有八九出在连接驱动和时区上。给你列几个常见问题:

  • 驱动找不到:Cromwell 启动时如果报 ClassNotFoundException: com.mysql.cj.jdbc.Driver,代表你依赖没加全。用 Docker 镜像的话,得自己把 mysql-connector-java 的 jar 包放进 Cromwell 的 lib 目录下。
  • 时区不对:MySQL 默认用系统时区。如果你服务器是 UTC,Cromwell 跑出来的时间戳就会差 8 小时。建议在 JDBC URL 里加 serverTimezone=UTC 或 Asia/Shanghai,保持两端一致。
  • 连接数超限:并发一大,MySQL 报 Too many connections。我的经验是先把 max_connections 调到 200 以上,再配合 Cromwell 的连接池上限调整。
曾经踩过的坑: 我把 numConnections 设成了 100,但 MySQL 端的 max_connections 默认只有 151。高并发时 Cromwell 直接连不上去报 Connection refused。查了半天才发现是 MySQL 自己的连接上限被封了。

4.5 快速验证数据库是否生效

配置改完了,怎么知道 Cromwell 真的在用 MySQL 而不是 H2?

有一个最简单的办法:

# 启动 Cromwell
java -jar cromwell.jar server

# 观察启动日志
# 如果看到类似下面的内容,说明数据库配置加载了
[info] CromwellDatabase - Connecting to database with profile: slick.jdbc.MySQLProfile$
[info] CromwellDatabase - Database connection established successfully

# 然后进 MySQL 查表
mysql -u cromwell -p -h localhost cromwell

mysql> SHOW TABLES;
# 如果看到 WORKFLOW_METADATA_ENTRY、JOB_STORE 等系统表,就对了

如果启动后还是老样子,先检查 cromwell.conf 文件路径对不对。Cromwell 默认从当前目录加载这个文件。你可以通过 -Dconfig.file=/path/to/your/cromwell.conf 强制指定。

说白了,数据库配置就是 Cromwell 的“记忆体”。选 H2 还是 MySQL,不是靠感觉,而是看你项目到什么阶段了。我个人经验是: MVP 阶段 H2 完全够用,一旦要上协作、上生产,直接切 MySQL 别犹豫。