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.
- package/dist/drill.js +40 -1
- 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