Quickwit Metastore 本地开发指南:PostgreSQL 与 File-backed 双实现的测试、迁移与演进实践
【免费下载链接】quickwitCloud-native OSS search engine for observability项目地址: https://gitcode.com/GitHub_Trending/qu/quickwit
本篇技术指南以 quickwit-metastore 模块 为核心,系统讲解 Quickwit(云原生可观测性搜索引擎)元数据存储层的本地开发全流程:如何用 Docker 快速拉起 PostgreSQL 测试环境、如何分别运行 File-backed 与 PostgreSQL 两套后端的测试、如何使用 sqlx-cli 管理 schema 迁移,以及 deferred migrations(延迟迁移)机制的设计原理与编写规则。读完本文,你将能独立搭建 quickwit-metastore 的开发与测试环境,理解其迁移流水线,并为该模块贡献或调试代码。
一、quickwit-metastore 模块定位
quickwit-metastore是 Quickwit 中用于持久化索引元数据的抽象层。从 crate 根文档 可以看到,它负责屏蔽不同 metastore 实现的差异,目前提供两种后端:
- File-backed metastore(文件后端):元数据以文件形式存储,适用于单机、开发调试场景;
- PostgreSQL metastore(数据库后端):官方推荐用于分布式部署场景。
该层维护的元数据包括:索引配置(index configuration)、每个 split 的元信息(ID、文档数、大小、min/max 时间戳、标签集合)、各数据源的 checkpoint,以及索引创建时间等附加信息(详见 metastore 配置文档)。
从源码结构看,MetastoreResolver 负责根据metastore_uri的协议前缀将请求分发到对应工厂:MetastoreFactory 是构建MetastoreServiceClient的 trait 抽象,其中postgres后端只有在编译期开启postgresfeature 时才会注册(见 metastore_resolver.rs),否则会注册一个返回UnsupportedBackend错误的占位实现。理解这一点,就能明白为什么测试命令要区分cargo test与cargo test --features=postgres。
二、本地启动 PostgreSQL 测试环境
README 提供的第一个命令是启动一个本地 PostgreSQL 服务器,用于测试 postgres metastore 实现:
docker-compose up postgres该服务定义在仓库根目录的 docker-compose.yml 中,关键配置如下:
| 配置项 | 值 |
|---|---|
| 镜像 | postgres:${POSTGRES_VERSION:-12.17-alpine}(默认锁定最低受支持版本) |
| 端口映射 | ${MAP_HOST_POSTGRES:-127.0.0.1}:5432:5432(默认只绑定本机回环地址) |
| 用户 | ${POSTGRES_USER:-quickwit-dev} |
| 密码 | ${POSTGRES_PASSWORD:-quickwit-dev} |
| 数据库 | ${POSTGRES_DB:-quickwit-metastore-dev} |
| 数据目录 | PGDATA=/var/lib/postgresql/data/pgdata |
| 数据卷 | postgres_data:/var/lib/postgresql/data |
| 健康检查 | pg_isready(1 秒间隔、最多重试 100 次) |
docker-compose 文件头部注释(docker-compose.yml)特别说明:关键服务(如 postgres、pulsar)会刻意运行在最老的支持版本上,以验证向后兼容;如需使用最新镜像版本,可修改.env文件覆盖POSTGRES_VERSION等变量,但要注意旧数据卷可能与新版本不兼容。
数据持久化与清理
README 明确指出:PostgreSQL 的数据保存在数据卷(volume)中,两次运行之间不会被自动清理。也就是说,上一次测试遗留的库表会保留下来,这是有意为之——避免每次启动都重新建表。
清理命令在 README 中写作make rm-postgres;需要说明的是,在当前仓库的 Makefile 中,对应的实际目标是docker-rm-postgres-volume,其执行内容为:
docker volume rm quickwit_postgres_data此外make docker-clean(见 Makefile)也会连同 azurite、fake GCS、grafana、localstack 等开发服务的 volume 一并清理。你可以根据实际仓库的 Makefile 目标选择对应的清理命令。
三、测试 quickwit-metastore:从纯文件后端到 PostgreSQL
3.1 仅测试 FileBackedMetastore
如果只想运行不依赖外部服务的文件后端测试,直接在quickwit项目根目录执行:
cargo test此命令运行quickwit-metastorecrate 的单元测试,此时postgresfeature 处于关闭状态,测试只覆盖FileBackedMetastore。从 lib.rs 可以看到,测试模式下会通过metastore_for_test()构造一个基于RamStorage(内存存储)的FileBackedMetastore客户端,因此纯文件后端测试完全不需要任何外部进程。
3.2 测试包含 PostgresqlMetastore
要测试 PostgreSQL 后端,需要先启动 PostgreSQL。README 给出的标准做法是在项目根目录使用 Makefile 封装:
make docker-compose-up DOCKER_SERVICES=postgresdocker-compose-up目标(Makefile)会设置COMPOSE_PROFILES=postgres并执行docker compose up -d --remove-orphans --wait,通过 compose profile 只启动postgres这一个服务,并等待其健康检查通过后再返回。
PostgreSQL 就绪后,运行:
cargo test --features=postgres--features=postgres会启用 Cargo.toml 中定义的 feature,它级联开启quickwit-proto/postgres、quickwit-parquet-engine/postgres,并引入sea-query、sea-query-binder、sqlx等数据库相关依赖。只有开启该 feature,PostgresqlMetastore才会被编译(见 lib.rs),相关集成测试也才会执行。
测试结束后,停止并移除 PostgreSQL 容器:
docker-compose down从源码看,metastore 的测试覆盖相当全面:tests 目录 下包含index.rs、split.rs、delete_task.rs、source.rs、template.rs、shard.rs、metrics.rs、list_splits.rs、get_identity.rs等测试模块,覆盖了索引生命周期、split 管理、删除任务、源 checkpoint、模板、分片、指标等核心路径;backward_compatibility_tests 则利用 test-data 下的历史版本 JSON 快照(如 v0.7/v0.8/v0.9)验证新旧数据格式的兼容性。
四、sqlx-cli 与迁移工作流
PostgreSQL 后端的 schema 演进由sqlx迁移框架管理。README 建议(但非必需)安装 sqlx-cli 来手工操作迁移:
cargo install sqlx-cli安装完成后,可以用以下命令**应用(run)或回滚(revert)**常规迁移。迁移源目录为migrations/postgresql,数据库连接串为:
postgres://quickwit-dev:quickwit-dev@localhost:5432/quickwit-metastore-devsqlx migrate run --database-url postgres://quickwit-dev:quickwit-dev@localhost:5432/quickwit-metastore-dev --source migrations/postgresql sqlx migrate revert --database-url postgres://quickwit-dev:quickwit-dev@localhost:5432/quickwit-metastore-dev --source migrations/postgresql这两条命令分别用于在开发数据库上应用最新迁移、或回滚最近一次迁移,适合在修改迁移文件后反复验证。
对于延迟迁移(deferred migrations),则使用另一个迁移源目录:
sqlx migrate run --database-url postgres://quickwit-dev:quickwit-dev@localhost:5432/quickwit-metastore-dev --source migrations/postgresql_deferred迁移文件组织方式
migrations/postgresql下的迁移采用成对的 up/down 文件组织,目前已有编号 1 至 28 的常规迁移,例如:
1_create-indexes.up.sql/1_create-indexes.down.sql:创建indexes表、update_timestamp触发器函数,并兼容旧版 diesel 迁移(见 1_create-indexes.up.sql);2_create-splits.up.sql/2_create-splits.down.sql:创建splits表(含split_id主键、split_state、time_range_start/end、tags TEXT[]、split_metadata_json等字段),并通过触发器在 split 变更时联动更新所属索引的update_timestamp(见 2_create-splits.up.sql);- 后续迁移按需为表增加字段(如
publish_timestamp、incarnation_id、maturity_timestamp、node_id)、创建新表(delete_tasks、templates、metrics_splits、sketch_splits)或调整主键与唯一索引(如 22/23 号迁移)。
这种编号递增 + up/down 对称的模式,保证了 schema 可以双向演进,也便于 sqlx-cli 追踪当前版本。
五、Deferred migrations:长耗时迁移的优雅降级方案
5.1 为什么需要 deferred migrations
常规迁移要求在启动时同步、快速地完成,因此不适合承载CREATE INDEX CONCURRENTLY这类长时间运行、且不能放在事务块内的 DDL。为此,Quickwit 引入了第二个迁移目录migrations/postgresql_deferred,专门存放这类"长耗时、允许优雅降级"的迁移。其设计约束在 deferred migrations 说明 中有明确记载:
- 版本号全局唯一:两个目录共享同一张
_sqlx_migrations表,因此版本号必须跨目录延续同一条编号序列(当前常规迁移到 28,deferred 从 29 开始); - 必须幂等:每条迁移在任何中断场景下都必须可以安全重放;
- 不能依赖未发布的常规迁移:常规迁移绝不允许依赖 deferred 迁移的结果。
5.2 后台任务 + Postgres advisory lock 的选举机制
deferred migrations 的运行时实现位于 migrator.rs:
- 常规迁移在
run()中同步执行(run_required,见 migrator.rs); - deferred 迁移则在就绪(readiness)之后,通过
quickwit_common::spawn_named_task派发一个名为postgres_deferred_migrations的后台任务执行(见 migrator.rs); - 为避免多个 metastore 节点(pod)同时执行长迁移,后台任务先通过
SELECT pg_try_advisory_lock($1, $2)竞争一把 Postgres advisory lock,锁键是硬编码的魔数424242和1789(见 migrator.rs)。拿到锁的节点成为"迁移领导者"负责执行;拿不到锁的节点打印deferred PostgreSQL migrations handled by another node后直接退出(见 migrator.rs); - 关键实现细节:执行前会把连接从连接池
detach()出来,让 advisory lock 绑定在单个会话上——PostgreSQL 会在会话结束或连接断开时自动释放该锁,即使迁移执行期间发生 panic 或失败也不会造成锁泄漏(见 migrator.rs)。
执行结果通过指标DEFERRED_MIGRATIONS_APPLY(带success/failure标签)对外暴露,便于运维监控。
5.3 幂等编写规则与真实示例
deferred 迁移的编写有一个硬性要求:凡是不能在事务中执行的语句(如CREATE INDEX CONCURRENTLY),文件首行必须标注-- no-transaction,之后直接写 DDL。由于concurrently模式下每条语句自动提交,迁移中途被 kill 后必须能安全重跑。
以实际的 29 号 deferred 迁移 为例,它为核心压缩器(compaction planner)的扫描路径在splits表上创建一个(maturity_timestamp, split_id)的 B-tree 部分索引:
-- no-transaction CREATE INDEX CONCURRENTLY IF NOT EXISTS splits_maturity_timestamp_idx ON splits (maturity_timestamp, split_id);该 SQL 文件的注释详细解释了设计考量:planner 每个 tick 都要读取split_state = 'Published'且maturity_timestamp > now()的 split 并按时间升序取LIMIT,因此 B-tree 可以让 PostgreSQL 直接按索引顺序 seek 到"尚未成熟"的时间范围,免去额外的排序;split_id作为决胜列保证LIMIT分页的确定性;部分索引谓词必须是 IMMUTABLE,所以不能用now()出现在谓词中,而只能靠maturity_timestamp > now()的查询条件配合。对应的回滚迁移同样以-- no-transaction开头并使用DROP INDEX CONCURRENTLY IF EXISTS(见 29 号 down 迁移)。
deferred 说明文档还提醒了几个运维要点:如果某条迁移执行失败,CREATE INDEX CONCURRENTLY可能留下一个无效(invalid)索引,且不会在迁移表中登记记录,因此需要手工清理后再重试;文档也记录了一个经验教训——曾经尝试在迁移 SQL 内部先清理无效索引,但两条独立语句会被 PostgreSQL 隐式包进一个事务块,反而覆盖了-- no-transaction指令,使concurrently无法工作,因此该方案被放弃。
六、从源码看 metastore 的接入与演进保障
6.1 URI 协议到后端的分发
metastore_uri是 Metastore 的唯一配置入口。在 metastore_resolver.rs 中可以看到协议映射规则:
azure://、gs://、file://、ram://、s3://→File-backed后端(底层复用对象存储);postgres://、postgresql://→PostgreSQL后端;- 其他协议 → 返回
UnsupportedBackend错误。
其中postgres与postgresql两种协议前缀均被接受,这一点在 resolver 测试 中有明确断言。测试默认连接串为postgres://quickwit-dev:quickwit-dev@localhost/quickwit-metastore-dev,可通过环境变量QW_TEST_DATABASE_URL覆盖。
另外,resolver 还提供了resolve_read_only()方法用于解析只读连接(适用于从库/读副本场景),该能力仅 PostgreSQL 后端支持——若对 file 后端调用会直接报错,且只读连接上执行写操作(如create_index)会收到Forbidden错误(见 resolver 测试)。
6.2 生产配置要点(补充参考)
虽然本指南聚焦开发测试,但理解生产配置有助于反向理解测试环境的默认值。根据 metastore 配置文档:
- PostgreSQL URI 格式为
postgres://[user]:[password]@[host]:[port]/[dbname],部分参数可省略,但数据库必须预先创建;Quickwit 首次启动时会自动建表,升级时会在启动阶段自动执行迁移; - 每个运行 metastore 服务的节点维护独立连接池,默认
metastore.postgres.max_connections为 10,因此节点最多承载2 * max_connections = 20个在途请求;排池时需保证metastore_nodes * max_connections低于 PostgreSQL 连接数上限; - file-backed 后端每个索引一个元数据文件,路径为
[storage_uri]/[index_id]/metastore.json,可通过#polling_interval=30sURI 片段开启轮询刷新(仅支持秒);注意 file-backed 后端没有锁机制,同一时刻只允许一个实例运行。
七、开发自检清单
完成阅读后,建议按以下清单快速自检环境:
docker-compose up postgres可正常拉起 PostgreSQL,容器健康检查通过;- 未启动数据库时
cargo test全绿(只覆盖 File-backed 后端); - 启动数据库后
make docker-compose-up DOCKER_SERVICES=postgres再跑cargo test --features=postgres全绿(覆盖 PostgreSQL 后端); - 新增迁移时:常规迁移放在
migrations/postgresql,长耗时/不可事务化的 DDL 放在migrations/postgresql_deferred,编号全局递增、up/down 成对、-- no-transaction正确标注、语句幂等; - 用 sqlx-cli 对本地库反复
run/revert验证迁移可双向执行; - 结束时
docker-compose down,如需彻底重置数据卷再执行make docker-rm-postgres-volume(README 中写作make rm-postgres)。
通过上述流程,你可以在不污染任何共享环境的前提下,独立完成 quickwit-metastore 的测试、迁移开发与调试,并为该模块的后续演进打下坚实基础。
【免费下载链接】quickwitCloud-native OSS search engine for observability项目地址: https://gitcode.com/GitHub_Trending/qu/quickwit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考