DEBUG.WIKI
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

PostgreSQL 容器报 could not resize shared memory segment

PostgreSQL 容器执行查询或维护时报共享内存无法扩展。核对容器内 /dev/shm、POSIX 内存实现和故障时用量,调整 Compose shm_size,保留镜像与数据挂载,并重建容器验证原操作。

PostgreSQL 放在 Docker 里,普通连接正常,执行查询或数据库维护时却报:

ERROR: could not resize shared memory segment "/PostgreSQL.2058389254" to 12615680 bytes: No space left on device

即使宿主机磁盘还有空间,容器仍可能报这个错误。应核对数据库容器内的 /dev/shm:在使用 POSIX 动态共享内存的 Linux 环境中,它的容量可能先于数据盘耗尽。

容器默认的 /dev/shm 容量是 64 MB,共享内存耗尽时可能出现上述报错。是否需要扩容,要看实际容器和故障发生时的用量。

确认报错来自哪个容器

以下示例适用于 Docker Compose 管理的单个 Linux PostgreSQL 容器。Docker Desktop 用户也要检查 Linux 容器内部;原生 Windows PostgreSQL、Swarm,以及共享宿主机 IPC 的部署不直接套用这份配置。

在原部署目录操作,沿用原来的项目名、全部 -f 文件及 --env-file 参数。先把示例中的 example-stack、配置路径和 db 换成实际值。这里定义一个函数,后面的命令都使用同一组 Compose 参数:

dc() {
  docker compose -p example-stack -f /path/to/compose.yaml "$@"
}
DB_SERVICE='db'
cid="$(dc ps -q "$DB_SERVICE")"
[ -n "$cid" ] || { echo '没有找到运行中的数据库容器'; exit 1; }

docker inspect --type container --format \
  'shm_bytes={{.HostConfig.ShmSize}} memory_limit_bytes={{.HostConfig.Memory}} ipc_mode={{.HostConfig.IpcMode}}' \
  "$cid"
docker exec "$cid" df -h /dev/shm
docker exec "$cid" df -i /dev/shm

shm_bytes=67108864 表示配置容量为 64 MiB。df -h /dev/shm 查看这个挂载的实际容量、已用量和剩余空间;df -i 用来排除该文件系统的 inode 耗尽。不要拿宿主机 / 或宿主机 /dev/shm 的结果替代它。

如果 ipc_mode 是 host,或者这个容器使用了单独配置的 /dev/shm 挂载,先梳理实际 IPC 和挂载方式。此时 HostConfig.ShmSize 不一定代表进程所用挂载的容量,不能仅改一个 shm_size 就算完成。

失败语句结束后,动态共享内存可能已经释放,随后查看的 df 就会显得很空。应在可控的测试负载或维护窗口中观察原操作期间的用量,并保存带时间的数据库日志。只有默认值是 64 MiB、没有故障时的用量证据,还不足以确认根因。

用现有数据库账号连接,核对 PostgreSQL 的内存实现和版本。下面的账号、库名都是占位值,密码在提示中输入:

docker exec -it "$cid" psql -U example_user -d example_db -W

进入 psql 后执行:

SHOW server_version;
SHOW dynamic_shared_memory_type;
SHOW shared_memory_type;

如果 dynamic_shared_memory_type 为 posix,故障时 /dev/shm 的剩余空间又不足,报错便与这个容量限制相符。POSIX 动态共享内存使用 shm_open;这类共享内存通常由 /dev/shm 上的 tmpfs 承载。

如果查到的是 mmap 或 sysv,应按实际实现和日志定位。特别是 mmap 动态共享内存使用数据目录中的文件,不能把所有同名报错都归到 Docker 的 64 MiB 限制。

给现有数据库服务增加共享内存容量

改配置前,保存原 Compose 文件及相关覆盖文件,确认数据库备份可用,并核对当前镜像和数据挂载:

docker inspect --type container --format \
  'image_ref={{.Config.Image}} image_id={{.Image}} mounts={{json .Mounts}}' \
  "$cid"

数据库必须使用已确认的数据卷或绑定挂载。若数据只在容器可写层中,先完成数据持久化和备份,不能直接重建容器。保持原镜像版本、数据路径、卷名、账号配置和端口不变;这次只调整共享内存容量,不顺带升级 PostgreSQL。

在原配置的数据库服务下加入 shm_size。下面只是需要合入的片段,不能用它替换整份 Compose 文件:

services:
  db:
    shm_size: "512m"

shm_size 与 image、volumes 等服务属性处于同一级,定义 Linux 容器 /dev/shm 的容量。YAML 中使用 shm_size: "512m" 这样的冒号写法,不能写成 shm_size=512m。

512 MiB 可以作为测试环境中的一个评估值,但实际容量需要覆盖业务负载下的用量,并留出余量。所需容量取决于部署和配置,不能把这个示例当成所有数据库的安全默认值。

这个容量是 tmpfs 的上限,实际用量随分配增长;扩大上限不会给机器增加物理内存。数据库主共享内存、每个进程的工作内存和其他容器仍会占用内存。尤其不要把 shm_size 简单设成 shared_buffers 的相同数值:shared_memory_type 与 dynamic_shared_memory_type 管的是不同部分,Linux 默认的主共享内存实现是 mmap。

先验证合并后的 Compose 配置,再单独查看这个字段,避免把含密码的整份配置打印到终端或工单:

dc config --quiet
dc config --format json | python3 -c \
  'import json,sys; print(json.load(sys.stdin)["services"][sys.argv[1]].get("shm_size", "未配置"))' \
  "$DB_SERVICE"

这一步能发现缩进错误、改错服务,或后加载的覆盖文件把值改回去。--format json 需要支持该选项的 Compose 插件;不支持时使用当前安装版本的配置查看方式,只摘录相关字段,不公开完整配置。

重建容器后再验证原操作

docker compose restart 不会应用 Compose 文件中的配置变更。需要重建数据库容器,安排可接受的停机窗口,并让应用做好连接中断和重连处理。

先确认本地镜像引用仍指向计划使用的版本;若标签已经被其他操作更新,先固定正确的镜像引用。下面阻止此次操作拉取或构建镜像,只重建数据库服务,不启动依赖服务:

dc up -d --no-deps --force-recreate --no-build --pull never "$DB_SERVICE"

cid="$(dc ps -q "$DB_SERVICE")"
[ -n "$cid" ] || { echo '数据库容器未运行'; exit 1; }
docker inspect --type container --format \
  'shm_bytes={{.HostConfig.ShmSize}} image_id={{.Image}} mounts={{json .Mounts}}' \
  "$cid"
docker exec "$cid" df -h /dev/shm
dc logs --since 10m "$DB_SERVICE"

docker compose up 重建时会保留挂载卷。仍要对照变更前的挂载来源和镜像 ID:保留了卷,不代表配置里写错的卷名也会自动被纠正。日志应显示数据库完成启动,而非正在初始化一个新库;如果发现数据挂载不符,先停止业务接入,按原配置恢复。

对于 512m,配置容量应为 536870912 字节,容器内 df 也应显示约 512 MiB。字段和实际挂载都正确,才能进入业务验证。

在恢复的测试环境中重跑原来失败的查询或维护操作,并观察相应时段的 /dev/shm 用量。确认原操作完成、对应报错消失,并且同类并发负载下仍有共享内存余量。单独执行一次 SELECT 1 只能证明连接可用,不能验证原来的共享内存需求。

若容量已经生效,错误仍出现,就记录故障时的用量、并发语句和完整日志,重新评估资源需求。不要因为一次失败就不断把容量翻倍;内存不足时进程可能被 OOM 杀死,放大 tmpfs 上限也可能把问题转成实际内存压力。

需要回退时,恢复此次新增的 shm_size 设置或原来的值,保持镜像和数据挂载不变,再按同样的单服务重建步骤应用。回退后重新核对挂载和启动日志;原来触发故障的操作可能再次失败,应暂缓该负载。不要删除 /dev/shm/PostgreSQL.* 文件来腾空间,也不要运行 down -v 或清理数据库卷,它们都不是这次容量调整的修复步骤。