backupdrill 2.1.6 → 2.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/drill.js +40 -1
  2. package/package.json +1 -1
package/dist/drill.js CHANGED
@@ -38,6 +38,24 @@ function sandboxImage(manifest) {
38
38
  const hasVector = (manifest.database.extensions ?? []).some((e) => e.name === "vector");
39
39
  return hasVector ? `pgvector/pgvector:pg${major}` : `postgres:${major}-alpine`;
40
40
  }
41
+ /**
42
+ * 沙箱 Postgres 的调参(2026-09-12):沙箱一次性、跑完即毁,生产库绝不能开的选项这里全开。
43
+ * 触因:105 表 / 12 GB dump 的演练跑了 2 小时,COPY 与建索引都在等 fsync、写整页 WAL、
44
+ * 64 MB 的排序内存反复溢磁盘。关掉持久性保证不影响演练结论(我们验证的是"能不能恢复出来",
45
+ * 不是"断电后还在不在"),但 COPY / 建索引通常快 2-5 倍,临时盘也少吃一半。
46
+ * wal_level=minimal 要求 max_wal_senders=0;shared_buffers 按 2 GB 的 worker 机器留余量。
47
+ */
48
+ const SANDBOX_TUNING = [
49
+ "fsync=off",
50
+ "synchronous_commit=off",
51
+ "full_page_writes=off",
52
+ "wal_level=minimal",
53
+ "max_wal_senders=0",
54
+ "maintenance_work_mem=256MB",
55
+ "max_wal_size=4GB",
56
+ "checkpoint_timeout=30min",
57
+ "shared_buffers=256MB",
58
+ ];
41
59
  async function startEphemeralPostgres(image) {
42
60
  log.step(`Starting ephemeral ${image}…`);
43
61
  const { stdout } = await execFileAsync("docker", [
@@ -50,9 +68,25 @@ async function startEphemeralPostgres(image) {
50
68
  "POSTGRES_DB=postgres",
51
69
  "-p",
52
70
  "127.0.0.1:0:5432", // 随机主机端口,避免撞端口
71
+ // Docker 默认 /dev/shm 只有 64 MB;maintenance_work_mem 调大后并行建索引(pgvector 的 HNSW
72
+ // 尤其)走动态共享内存,64 MB 会撞 "No space left on device" 把好备份误判失败(交叉审查
73
+ // 2026-09-12 复现)。pgvector 官方 docker 说明就要求调大 shm。
74
+ "--shm-size=1g",
53
75
  image,
76
+ "postgres",
77
+ ...SANDBOX_TUNING.flatMap((kv) => ["-c", kv]),
54
78
  ]);
55
79
  const containerId = stdout.trim();
80
+ // postgres 镜像把数据目录声明成 VOLUME → 每次 run 都建一个匿名卷。生产实测 `--rm` **没有**把它
81
+ // 带走(一次演练遗留 37.6 GB,三次失败把 59 GB 根盘灌满),所以记下卷名,销毁容器后显式删。
82
+ const volumeName = await execFileAsync("docker", [
83
+ "inspect",
84
+ "-f",
85
+ '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}}{{end}}{{end}}',
86
+ containerId,
87
+ ])
88
+ .then((r) => r.stdout.trim() || null)
89
+ .catch(() => null);
56
90
  try {
57
91
  const portOut = await execFileAsync("docker", [
58
92
  "port",
@@ -79,7 +113,7 @@ async function startEphemeralPostgres(image) {
79
113
  "postgres",
80
114
  "-q",
81
115
  ]);
82
- return { containerId, port, connString };
116
+ return { containerId, port, connString, volumeName };
83
117
  }
84
118
  catch {
85
119
  await new Promise((r) => setTimeout(r, 500));
@@ -89,6 +123,8 @@ async function startEphemeralPostgres(image) {
89
123
  }
90
124
  catch (error) {
91
125
  await execFileAsync("docker", ["rm", "-f", containerId]).catch(() => { });
126
+ if (volumeName)
127
+ await execFileAsync("docker", ["volume", "rm", "-f", volumeName]).catch(() => { });
92
128
  throw error;
93
129
  }
94
130
  }
@@ -415,6 +451,9 @@ export async function drillDump(dumpPath, manifest, snapshot, preChecks = [], op
415
451
  else {
416
452
  log.step("Tearing down ephemeral Postgres…");
417
453
  await execFileAsync("docker", ["rm", "-f", pg.containerId]).catch(() => { });
454
+ // 显式删匿名卷(--rm 不可靠,见 startEphemeralPostgres);幂等,已被 --rm 带走也不报错
455
+ if (pg.volumeName)
456
+ await execFileAsync("docker", ["volume", "rm", "-f", pg.volumeName]).catch(() => { });
418
457
  }
419
458
  }
420
459
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backupdrill",
3
- "version": "2.1.6",
3
+ "version": "2.1.7",
4
4
  "description": "Open-source CLI to back up a Supabase project — Postgres database and Storage files — to your own S3/R2/B2 bucket, with a checksummed manifest.",
5
5
  "type": "module",
6
6
  "bin": {