chore(deploy): 数据库与各服务增加资源限制和优雅关闭保护

- postgres 增加 mem_limit/cpus/shm_size/stop_grace_period 与 PG
  性能参数(shared_buffers 等,env 可覆盖);backend/web 增加
  内存/CPU 限额与更长的优雅关闭宽限(生产与开发编排同步)
- ubuntu-deploy.sh 增加部署前磁盘空间预检,防止备份或迁移
  中途磁盘写满
- .env.server.example 补充资源变量;文档补充容器资源限制说明
This commit is contained in:
yml2213
2026-08-30 13:59:19 +08:00
parent 02c9de8b9c
commit 6dcdec43f1
5 changed files with 90 additions and 0 deletions
+13
View File
@@ -33,6 +33,19 @@ DATABASE_STATEMENT_TIMEOUT_MS=15000
DATABASE_SLOW_QUERY_THRESHOLD_MS=500 DATABASE_SLOW_QUERY_THRESHOLD_MS=500
# PostgreSQL 原生慢查询日志阈值(毫秒);-1 关闭 # PostgreSQL 原生慢查询日志阈值(毫秒);-1 关闭
POSTGRES_LOG_MIN_DURATION_STATEMENT_MS=500 POSTGRES_LOG_MIN_DURATION_STATEMENT_MS=500
# PostgreSQL 容器资源与性能参数(按服务器内存调整;默认值按 2G 内存机器设置,
# 4G+ 机器可将 shared_buffers 提到 512MB、effective_cache_size 提到 2GB):
POSTGRES_MEM_LIMIT=1g
POSTGRES_CPUS=2
POSTGRES_SHARED_BUFFERS=256MB
POSTGRES_EFFECTIVE_CACHE_SIZE=1GB
POSTGRES_WORK_MEM=8MB
POSTGRES_MAINTENANCE_WORK_MEM=128MB
# 后端与 Web 容器限额(内存紧张的小机器可相应收紧)
BACKEND_MEM_LIMIT=512m
BACKEND_CPUS=1
WEB_MEM_LIMIT=256m
WEB_CPUS=0.5
# Logging # Logging
LOG_LEVEL=info LOG_LEVEL=info
+27
View File
@@ -197,6 +197,32 @@ check_compose_config() {
docker compose config >/dev/null || fail "Docker Compose 配置校验失败。" docker compose config >/dev/null || fail "Docker Compose 配置校验失败。"
} }
# 部署前磁盘空间预检:备份目录所在文件系统至少要有首份备份 + 现有备份量 2 倍的余量。
# 不足时直接失败,避免备份或迁移过程中磁盘写满导致数据库不可用。
ensure_disk_space() {
local backup_root="${PROJECT_ROOT}/backups"
local floor_kib=1048576 # 1GiB
local existing_kib=0 require_kib free_kib
mkdir -p "${backup_root}" 2>/dev/null || true
if [[ -d "${backup_root}" ]]; then
existing_kib="$(du -sk "${backup_root}" 2>/dev/null | awk '{print $1}' || true)"
existing_kib="${existing_kib:-0}"
fi
require_kib=$((existing_kib * 2))
[[ ${require_kib} -lt ${floor_kib} ]] && require_kib=${floor_kib}
free_kib="$(df -Pk "${PROJECT_ROOT}" 2>/dev/null | awk 'NR==2 {print $4}' || true)"
free_kib="${free_kib:-0}"
log "磁盘空间预检:可用 ${free_kib} KiB,备份目录已占用 ${existing_kib} KiB,要求至少 ${require_kib} KiB"
if [[ ${free_kib} -lt ${require_kib} ]]; then
fail "磁盘可用空间不足(可用 ${free_kib} KiB,需 ≥ ${require_kib} KiB)。请清理磁盘或调小 DB_BACKUP_KEEP 后重试。"
fi
}
pull_base_images() { pull_base_images() {
log "拉取基础镜像" log "拉取基础镜像"
docker compose pull --ignore-buildable docker compose pull --ignore-buildable
@@ -482,6 +508,7 @@ main() {
validate_env_file validate_env_file
confirm_reset_database confirm_reset_database
ensure_data_dir ensure_data_dir
ensure_disk_space
deploy_stack deploy_stack
wait_for_health wait_for_health
print_applied_migrations print_applied_migrations
+12
View File
@@ -16,8 +16,20 @@ services:
"pg_stat_statements.track=all", "pg_stat_statements.track=all",
"-c", "-c",
"log_min_duration_statement=${POSTGRES_LOG_MIN_DURATION_STATEMENT_MS:-500}", "log_min_duration_statement=${POSTGRES_LOG_MIN_DURATION_STATEMENT_MS:-500}",
"-c",
"shared_buffers=${POSTGRES_SHARED_BUFFERS:-256MB}",
"-c",
"effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}",
"-c",
"work_mem=${POSTGRES_WORK_MEM:-8MB}",
"-c",
"maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-128MB}",
] ]
restart: unless-stopped restart: unless-stopped
mem_limit: ${POSTGRES_MEM_LIMIT:-1g}
cpus: ${POSTGRES_CPUS:-2}
shm_size: 512mb
stop_grace_period: 60s
logging: *json-log-rotation logging: *json-log-rotation
environment: environment:
TZ: ${TZ:-Asia/Shanghai} TZ: ${TZ:-Asia/Shanghai}
+21
View File
@@ -8,6 +8,7 @@ services:
postgres: postgres:
image: docker.m.daocloud.io/library/postgres:16-bookworm image: docker.m.daocloud.io/library/postgres:16-bookworm
# 开启 pg_stat_statements,便于定位生产环境累计最耗时的 SQL。 # 开启 pg_stat_statements,便于定位生产环境累计最耗时的 SQL。
# 内存类参数按 2G 内存机器给出默认值,请按服务器内存调整(见 .env.server.example)。
command: command:
[ [
"postgres", "postgres",
@@ -17,8 +18,21 @@ services:
"pg_stat_statements.track=all", "pg_stat_statements.track=all",
"-c", "-c",
"log_min_duration_statement=${POSTGRES_LOG_MIN_DURATION_STATEMENT_MS:-500}", "log_min_duration_statement=${POSTGRES_LOG_MIN_DURATION_STATEMENT_MS:-500}",
"-c",
"shared_buffers=${POSTGRES_SHARED_BUFFERS:-256MB}",
"-c",
"effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}",
"-c",
"work_mem=${POSTGRES_WORK_MEM:-8MB}",
"-c",
"maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-128MB}",
] ]
restart: unless-stopped restart: unless-stopped
# 让 PostgreSQL 优雅关闭有足够时间完成检查点;无限制时 OOM 可能拖垮整机。
mem_limit: ${POSTGRES_MEM_LIMIT:-1g}
cpus: ${POSTGRES_CPUS:-2}
shm_size: 512mb
stop_grace_period: 60s
logging: *json-log-rotation logging: *json-log-rotation
environment: environment:
TZ: ${TZ:-Asia/Shanghai} TZ: ${TZ:-Asia/Shanghai}
@@ -46,6 +60,10 @@ services:
DEBIAN_MIRROR: ${DEBIAN_MIRROR:-mirrors.aliyun.com} DEBIAN_MIRROR: ${DEBIAN_MIRROR:-mirrors.aliyun.com}
NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com} NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com}
restart: unless-stopped restart: unless-stopped
# 30s 让后端在 SIGTERM 后完成优雅关闭(HTTP 连接收尾 + 数据库连接池释放)。
mem_limit: ${BACKEND_MEM_LIMIT:-512m}
cpus: ${BACKEND_CPUS:-1}
stop_grace_period: 30s
env_file: env_file:
- .env - .env
depends_on: depends_on:
@@ -101,6 +119,9 @@ services:
ALPINE_MIRROR: ${ALPINE_MIRROR:-mirrors.aliyun.com} ALPINE_MIRROR: ${ALPINE_MIRROR:-mirrors.aliyun.com}
NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com} NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com}
restart: unless-stopped restart: unless-stopped
mem_limit: ${WEB_MEM_LIMIT:-256m}
cpus: ${WEB_CPUS:-0.5}
stop_grace_period: 20s
logging: *json-log-rotation logging: *json-log-rotation
depends_on: depends_on:
backend: backend:
+17
View File
@@ -38,3 +38,20 @@ ORDER BY query_start;
- `DATABASE_MAX_CONNECTIONS` 按后端实例数总量评估;扩容后端实例时不能简单地每实例增加连接数。 - `DATABASE_MAX_CONNECTIONS` 按后端实例数总量评估;扩容后端实例时不能简单地每实例增加连接数。
- CPU 限额只能防止数据库拖垮整机,不能替代 SQL 优化。 - CPU 限额只能防止数据库拖垮整机,不能替代 SQL 优化。
- 数据量持续增长后,排行榜和历史统计应迁移到汇总表、缓存或只读副本,不应继续依赖在线全量聚合。 - 数据量持续增长后,排行榜和历史统计应迁移到汇总表、缓存或只读副本,不应继续依赖在线全量聚合。
## 容器资源限制
Compose 对 postgres 与各服务已配置资源限额,默认值按 2G 内存机器设置,均可在 `.env` 覆盖(见 `.env.server.example``POSTGRES_*` / `BACKEND_MEM_LIMIT` / `WEB_MEM_LIMIT` 等):
| 服务 | 限额 | 说明 |
| --- | --- | --- |
| postgres | `mem_limit` 1g、`cpus` 2、`shm_size` 512mb | `shm_size` 不足会导致并行查询/大排序报错;`stop_grace_period` 60s 保证检查点能完整落盘 |
| backend | `mem_limit` 512m、`cpus` 1 | `stop_grace_period` 30s 保证优雅关闭(HTTP 收尾 + 连接池释放)能跑完 |
| web | `mem_limit` 256m、`cpus` 0.5 | Caddy 静态服务,低占用 |
| dev postgres | 同生产默认 | 仅 postgres 受限;backend/frontend 开发容器不设限,避免 tsx watch / npm install 内存波动被误杀 |
调整原则:
- 内存限额不要低于 RSS 峰值的 1.3 倍,否则会触发 OOM 杀容器(`restart: unless-stopped` 会崩溃循环)。
- 内存类 PG 参数遵循大致比例:`shared_buffers` ≈ 内存的 1/8~1/4`effective_cache_size` ≈ 内存的 1/2~3/4,且两者之和不宜超过可用内存的 80%。
- 修改限额后 `docker compose config` 会以明文展示最终值,可用它核对插值结果;变更仅对重建/重启的容器生效。