minimal 备份增加 freeze 副本路径
Project CI / AI game creator shell Rust smoke (push) Successful in 2m35s
Project CI / AI game creator shell Rust crates (push) Successful in 1m14s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m4s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 7m28s
Project CI / Backend tests (push) Successful in 7m25s
Project CI / Frontend tests (push) Successful in 7m18s
Project CI / Repository checks (push) Successful in 5m51s
Project CI / Native shell tests (push) Successful in 9m16s
Project CI / AI game creator shell web tests (push) Successful in 3m41s

- database-backup-to-oss.mjs 新增 --freeze-dir:minimal 模式先把选中子集 rsync 成冻结副本(保持原始相对路径)再从副本上传,避免热备时最新 commitlog 持续追加触发 stat 漂移失败
- files-minimal systemd 模板与发布前 minimal 备份默认传入 freeze 目录
- 生产运维门禁同步 freeze 口径断言
This commit is contained in:
2026-09-21 11:37:14 +08:00
parent 2f248ea0c3
commit 90207bc1d5
4 changed files with 60 additions and 5 deletions
@@ -2,4 +2,4 @@
Environment=GENARRATIVE_DATABASE_BACKUP_FILES_CONCURRENCY=4
Environment=GENARRATIVE_DATABASE_BACKUP_UPLOAD_MAX_BYTES_PER_SECOND=20971520
ExecStart=
ExecStart=/usr/bin/node -- /opt/genarrative/current/scripts/database-backup-to-oss.mjs --env-file /etc/genarrative/api-server.env --storage-format files --mode full --minimal --retain-snapshots 2 --data-dir /stdb --work-dir /var/lib/genarrative/database-backups/files-minimal
ExecStart=/usr/bin/node -- /opt/genarrative/current/scripts/database-backup-to-oss.mjs --env-file /etc/genarrative/api-server.env --storage-format files --mode full --minimal --retain-snapshots 2 --freeze-dir /var/lib/genarrative/database-backups/files-minimal-freeze --data-dir /stdb --work-dir /var/lib/genarrative/database-backups/files-minimal
+2 -2
View File
@@ -1028,13 +1028,13 @@ const checks = [
},
{
file: 'deploy/systemd/genarrative-database-backup-files-minimal.conf',
includes: '--mode full --minimal --retain-snapshots 2',
includes: '--mode full --minimal --retain-snapshots 2 --freeze-dir',
reason:
'files-minimal 定时备份必须使用 minimal 口径并保留上游默认的最近 2 份 snapshot。',
},
{
file: 'scripts/deploy/production-stdb-publish.sh',
includes: '--minimal --retain-snapshots',
includes: '--freeze-dir',
reason:
'发布前备份默认使用 minimal 热备,避免 40G 级冷备空间门槛与停服。',
},
+51 -1
View File
@@ -107,6 +107,7 @@ function usage() {
将 SpacetimeDB 数据目录以 .tar.gz 或逐文件 catalog 形式上传到阿里云 OSS 指定 bucket。
默认 full 模式保持原有全量冷备行为;history 模式只归档已被最新 snapshot 覆盖的历史 commitlog 与旧 snapshot。
--storage-format files 不打包:按原相对路径建立 catalog,文件内容以 SHA-256 不可变对象上传;重复运行只上传新增或变化内容。
--freeze-dir <path> 仅用于 --minimal:先把选中子集 rsync 成冻结副本再上传,避免热备时最新 commitlog 追加导致 stat 漂移;副本内保持原始相对路径。
--minimal 只备份上游 retention 语义下仍需要的部分(最近 --retain-snapshots 份 snapshot + 其后 commitlog 段 + 身份/配置/控制库/模块字节);只支持 files 格式,且不停止任何服务。
archive history 必须有已验真的 full baseline statefiles history 必须复用同一 work-dir 中已发布的 full catalog state。
--defer-upload 只生成本地冷备份和 manifest,不上传;后续用 --upload-archive 异步上传。
@@ -204,6 +205,7 @@ function parseArgs(argv) {
minFreeBytes: '',
mode: 'full',
minimal: false,
freezeDir: '',
retainSnapshots: DEFAULT_RETAIN_SNAPSHOTS,
baselineState: '',
baselineManifest: '',
@@ -301,6 +303,9 @@ function parseArgs(argv) {
case '--retain-snapshots':
options.retainSnapshots = readValue();
break;
case '--freeze-dir':
options.freezeDir = readValue();
break;
case '--baseline-state':
options.baselineState = readValue();
break;
@@ -4387,7 +4392,7 @@ async function main() {
const env = loadEffectiveEnv(args.envFiles);
const isProductionLike =
existsSync(DEFAULT_PRODUCTION_DATA_DIR) && process.platform !== 'win32';
const dataDir = resolvePath(
let dataDir = resolvePath(
firstNonEmpty(
args.dataDir,
env.GENARRATIVE_DATABASE_BACKUP_DATA_DIR,
@@ -4466,6 +4471,51 @@ async function main() {
throw new Error('--minimal 只支持 --mode fullminimal 自身就是自包含快照。');
}
if (args.minimal && args.freezeDir) {
if (storageFormat !== 'files') {
throw new Error('--freeze-dir 只支持 --minimal --storage-format files。');
}
const freezeDir = resolvePath(args.freezeDir);
const plan = discoverMinimalPlan({
dataDir,
retainSnapshots: args.retainSnapshots,
});
console.log(
`[database-backup] minimal freeze: candidates=${plan.candidates.length}, size=${plan.totalSizeBytes}, freezeDir=${freezeDir}`,
);
if (!args.dryRun) {
mkdirSync(freezeDir, { recursive: true });
const listPath = join(workDir, 'minimal-freeze-files.txt');
mkdirSync(workDir, { recursive: true });
writeFileSync(
listPath,
`${plan.candidates.map((item) => item.path).join('\n')}\n`,
'utf8',
);
const result = spawnSync(
'rsync',
[
'-a',
'-r',
'--delete',
`--files-from=${listPath}`,
`${dataDir}/`,
`${freezeDir}/`,
],
{ stdio: 'inherit' },
);
if (result.status !== 0) {
throw new Error(
`minimal freeze 拷贝失败: rsync exit=${result.status ?? '<signal>'}`,
);
}
console.log(
`[database-backup] minimal freeze 完成,后续从冻结副本上传: ${freezeDir}`,
);
}
dataDir = freezeDir;
}
if (args.checkSpaceOnly) {
assertSufficientWorkDirSpace({
dataDir,
+6 -1
View File
@@ -856,7 +856,12 @@ case "${BACKUP_MODE}" in
)
if [[ "${BACKUP_MINIMAL}" == "1" ]]; then
echo "[production-stdb-publish] publish 前执行 minimal 热备(最近 ${BACKUP_RETAIN_SNAPSHOTS} 份 snapshot + 其后 commitlog),不停服务"
SYNC_BACKUP_ARGS+=(--mode full --minimal --retain-snapshots "${BACKUP_RETAIN_SNAPSHOTS}")
SYNC_BACKUP_ARGS+=(
--mode full
--minimal
--retain-snapshots "${BACKUP_RETAIN_SNAPSHOTS}"
--freeze-dir "${GENARRATIVE_STDB_PUBLISH_BACKUP_FREEZE_DIR:-/var/lib/genarrative/database-backups/publish-minimal-freeze}"
)
else
echo "[production-stdb-publish] publish 前同步执行 OSS 冷备份(storage-format=${BACKUP_STORAGE_FORMAT}),失败会阻断发布"
SYNC_BACKUP_ARGS+=(--stop-service spacetimedb.service)