自动发现并复制多租户 SQLite 数据库

原作:Litestream 官方文档贡献者,Directory Watcher。本文根据 2026 年 10 月 5 日读取的完整指南译编。目录监视功能要求 Litestream 0.5.4 或更新版本,并依赖平台的 fsnotify 支持;本次未安装 Litestream、访问对象存储或运行恢复命令。

多租户应用经常为每个租户创建一个 SQLite 数据库。租户随时加入或退出时,逐库修改复制配置容易遗漏。Litestream 提供两种目录模式:静态目录复制只在启动时扫描,新数据库需要重启才能被纳入;目录监视器持续观察文件系统事件,在匹配的新数据库出现后自动启动复制,也能停止已删除数据库的复制任务。

这种机制适合每租户一库的 SaaS、按需创建数据库的服务,以及短时间内大量创建数据库的场景。自动发现减少配置维护工作,但并不替代权限隔离、备份保留策略或恢复演练。

SQLite租户目录经过文件系统事件发现和数据库校验后复制到独立远端路径,恢复时使用明确副本URL并先写入隔离目录
未完纪编辑部原创流程示意图;并非实际备份状态。

开启目录监视

在目录配置中加入 watch: true:

dbs:
  - dir: /var/lib/app/tenants
    pattern: "*.db"
    watch: true
    replica:
      type: s3
      bucket: my-backup-bucket
      path: tenants
字段 含义 默认值
dir 要监视的目录 必填
pattern 数据库文件的 glob 模式 必填
recursive 是否监视子目录 false
watch 是否启用实时发现 false

pattern 使用 glob,而不是正则表达式。*.db 匹配 .db 结尾的文件,*.sqlite 匹配 .sqlite 结尾的文件;tenant-*.db 可匹配 tenant-001.db、tenant-abc.db。字段可匹配文件名,并不意味着匹配到的每个文件都会被无条件当成数据库。

平面目录、嵌套租户与多种规则

全部数据库放在同一目录

dbs:
  - dir: /var/lib/app/databases
    pattern: "*.db"
    watch: true
    replica:
      url: s3://mybucket/databases

本地目录形如:

/var/lib/app/databases/
├── tenant-001.db
├── tenant-002.db
└── tenant-003.db

每个数据库得到独立副本前缀:

s3://mybucket/databases/
├── tenant-001.db/
├── tenant-002.db/
└── tenant-003.db/

每个租户有自己的子目录

将 recursive 设置为 true:

dbs:
  - dir: /var/lib/app/tenants
    pattern: "*.db"
    recursive: true
    watch: true
    replica:
      url: s3://mybucket/tenants

本地结构:

/var/lib/app/tenants/
├── acme-corp/
│   └── data.db
├── globex/
│   └── data.db
└── initech/
    └── data.db

远端保留相对于监视根目录的路径,因此不同租户的 data.db 不会仅因同名而合并:

s3://mybucket/tenants/
├── acme-corp/data.db/
├── globex/data.db/
└── initech/data.db/

多种数据库或不同复制频率

为不同目录和后缀分别建立条目,可各自配置副本目标与同步间隔:

dbs:
  - dir: /var/lib/app/tenants
    pattern: "*.db"
    watch: true
    replica:
      url: s3://mybucket/primary
      sync-interval: 1s

  - dir: /var/lib/app/analytics
    pattern: "*.sqlite"
    watch: true
    replica:
      url: s3://mybucket/analytics
      sync-interval: 10s

这里主业务数据库使用 1 秒同步间隔,分析库使用 10 秒。配置示例中的 bucket 和路径必须换成自己的目标;同步间隔也不能直接当作端到端恢复点目标,因为网络、存储、调度和失败重试都会影响实际进度。

新建、删除与重启时会发生什么

新文件创建后,监视器先检查是否匹配 pattern,再验证 SQLite 数据库标头;通过后通常在数秒内开始复制,并在日志和指标中出现。不合法的文件被忽略,避免把普通文件误当成数据库。文件头验证不是数据库全量一致性检查。

删除本地数据库时,监视器停止该库复制并释放资源,远端副本仍然保留。递归模式下删除整个子目录,也只会停止其中各库并清理监视,不删除远端数据。应结合对象存储生命周期规则安排过期清理;租户注销、合规删除和恢复保留策略要明确区分。

Litestream 重启后会先扫描已有数据库,为匹配项启动复制,然后重新进入目录监视。曾经复制过但本地已不存在的数据库,不会在重启时被自动从对象存储清理。

按明确副本 URL 恢复

watch 模式没有在配置文件中逐一声明每个数据库,因此通常的 litestream restore <db-path> 无法按该本地路径找到对应定义。恢复时直接提供远端副本 URL:

litestream restore -o /path/to/database.sqlite s3://mybucket/databases/database.sqlite

若副本需要访问凭据,原文展示以下环境变量配置。其中 your-access-key 和 your-secret-key 只是示意占位值,不是真实凭据;切勿把实际密钥写进文章、源码或可留存的 shell 历史。部署时通过受控的凭据注入方式提供,并使用所需存储范围内的最小权限。

export LITESTREAM_ACCESS_KEY_ID=your-access-key
export LITESTREAM_SECRET_ACCESS_KEY=your-secret-key

litestream restore -o /var/lib/app/databases/tenant-001.db \
  s3://mybucket/databases/tenant-001.db
布局 本地数据库 对应副本 URL
平面,url=s3://mybucket/databases /var/lib/app/databases/tenant-001.db s3://mybucket/databases/tenant-001.db
递归,url=s3://mybucket/tenants /var/lib/app/tenants/acme/data.db s3://mybucket/tenants/acme/data.db

编辑补充:原命令直接把结果写入应用监视目录。实际恢复先安排应用停写与复制协调,选一个不被 watch 的临时恢复目录,验证目标路径不存在或已按流程备份,恢复并校验后再切换。这样可减少尚未完成的恢复文件被监视器自动发现的风险。这里给出的是操作边界,未代替读者执行生产切换。

先列出可用的远端库

原文分别给出 S3、GCS 和 Azure 的列表命令:

# For S3
aws s3 ls s3://mybucket/databases/

# For GCS
gsutil ls gs://mybucket/databases/

# For Azure Blob Storage
az storage blob list --container-name mycontainer --prefix databases/

批量恢复:原脚本的限制

Litestream 没有一个内建命令能恢复目录内所有库。原文用 shell 遍历副本目录:

#!/bin/bash
export LITESTREAM_ACCESS_KEY_ID=your-access-key
export LITESTREAM_SECRET_ACCESS_KEY=your-secret-key

# List and restore all databases from S3
for db in $(aws s3 ls s3://mybucket/databases/ | awk '{print $2}' | sed 's/\/$//'); do
  echo "Restoring $db..."
  litestream restore -o "/var/lib/app/databases/$db" "s3://mybucket/databases/$db"
done

静态审核发现:for db in $(...) 依赖 shell 按空白拆词,awk 又假设固定列。它只适用于经过控制的简单平面名称,不能可靠处理含空格的键、嵌套租户目录或特殊字符。它还会直接写入监视目录,没有逐项验证输出路径与覆盖条件。代码中的访问密钥示意也不应替换成硬编码生产秘密。

不要把这个简短循环直接作为通用批量恢复器。修订方向是使用提供方结构化 JSON 列表、正确遍历分页与相对路径、拒绝绝对路径和 .. 路径穿越,解析规范化后的目标必须仍在隔离恢复根目录内;每项恢复前检查目标冲突,分别记录成功和失败,再安排切换。本文保留原文示例以说明机制,没有伪造一份经过测试的修复脚本。

恢复到指定时间点

同样使用明确副本 URL,并给出 timestamp:

litestream restore \
  -timestamp 2026-01-01T12:00:00Z \
  -o /var/lib/app/databases/tenant-001.db \
  s3://mybucket/databases/tenant-001.db

时间采用示例中的 UTC 格式。可恢复时间范围取决于远端实际保留的数据与可用恢复链,写一个时间戳并不保证那个时间点存在。

静态复制与监视模式对比

行为 watch: false watch: true
发现新库 需要重启 自动
发现删除 需要重启 自动
启动时目录为空 报错 允许
资源消耗 较低 略高
适用场景 固定数据库集合 动态创建数据库

排查未发现的数据库

日志可出现如下消息;这是原文示例,不是本次环境日志:

INFO added database to replication path=/var/lib/app/tenants/tenant-001.db

若没有出现,依次检查:后缀与 glob 是否匹配;dir 是否正确且可访问;嵌套布局是否开启 recursive;文件是否是具有合法 SQLite 标头的数据库,而不是空文件或损坏文件。Permission denied 要核对运行账户的目录与数据库访问权限,不应通过对整个目录开放写权限来掩盖问题。

性能和平台限制

监视器使用 fsnotify 事件而非轮询,只校验符合模式的文件,并只读取文件头做 SQLite 识别,因此原文认为额外开销较小。作者称测试过超过 20 个并发创建数据库的场景;这只是原文测试范围,不是容量上限,也不是本次复测结果。

watch 必须与 dir 一起使用,不能加到单库 path 配置上;pattern 不是正则表达式;并非每个平台都提供需要的 fsnotify 支持。继续阅读可参考 目录复制、配置参考及故障排查,实际入口以原页链接为准。

来源与归属:Litestream 官方文档贡献者;中文译编:未完纪编辑部。阅读原文。全文翻译、转载及配图按权利人授权使用;原作者及项目归属保留。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容