多版本并存(pg_lsclusters)
生产环境常常需要「老版本先跑着、新版本并行验证」。Debian / Ubuntu 的 postgresql-common 提供了一套集群(Cluster)管理工具,让同一台机器上的多个大版本、多个实例互不干扰。本节讲解 cluster 机制、四个核心命令、端口分配策略,以及如何连接不同版本的实例。
前置知识
阅读本节前,建议先了解:systemd 服务管理
基础概念
- 集群(Cluster,此处特指 Debian 概念):
postgresql-common对「一个数据目录 + 一套配置 + 一个服务实例」的封装,用<版本>-<名称>标识,如18-main postgresql-common:Debian / Ubuntu 独有的管理套件,提供/usr/bin/pg_*cluster系列命令与pg_wrapper- 包装器(Wrapper):
/usr/bin/psql等实际是pg_wrapper的软链接,会根据目标集群自动选择对应版本的二进制 - 端口分配策略(Port Allocation):由
postgresql-common在创建集群时自动挑选未占用端口,避免手动冲突 - 大版本升级(Major Version Upgrade):18 → 19 属于大版本变更,数据目录不兼容,需要
pg_upgrade或逻辑导出 - 并存(Coexistence):多个大版本的程序与数据目录物理隔离,共用同一套客户端包装器
| 发行版 | 管理机制 | 集群描述方式 | 核心命令 |
|---|---|---|---|
| Debian / Ubuntu | postgresql-common | /etc/postgresql/<版本>/<名称>/ | pg_lsclusters 等四个命令 |
| RHEL / Rocky | 每个大版本一个独立服务 | postgresql-18.service | systemctl + postmaster.pid |
| macOS Homebrew | 每个大版本一个 formula | postgresql@18 | brew services |
| Docker | 每容器一个实例 | 容器名 | docker |
| 源码编译 | 自定义 | 自行约定 | pg_ctl -D <路径> |
版本说明
postgresql-common 生成的集群目录自 PostgreSQL 9.x 起一直保持 <版本>-<名称> 格式。PostgreSQL 18 起 PGDG 的 Debian 包默认会为 postgresql-18 创建一个名为 main 的集群,因此它的完整标识是 18-main。
语法与用法
pg_lsclusters —— 列出所有集群
# 查看本机所有集群的状态、端口、数据目录与日志位置
pg_lsclustersVer Cluster Port Status Owner Data directory Log file
17 main 5432 online postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
18 main 5433 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
18 report 5434 down postgres /var/lib/postgresql/18/report /var/log/postgresql/postgresql-18-report.log| 列 | 含义 | 说明 |
|---|---|---|
Ver | 大版本号 | 如 17 / 18 |
Cluster | 集群名称 | 同一版本下可有多套,如 main、report |
Port | 监听端口 | 由 postgresql-common 自动分配 |
Status | 状态 | online / down / recovery |
Owner | 运行用户 | 通常为 postgres |
Data directory | 数据目录 | /var/lib/postgresql/<版本>/<名称> |
Log file | 日志文件 | /var/log/postgresql/postgresql-<版本>-<名称>.log |
pg_createcluster —— 创建集群
# 基本用法:创建 18 版本下名为 report 的集群
sudo pg_createcluster 18 report
# 指定端口、编码与 locale
sudo pg_createcluster 18 report \
--port=5434 \
--encoding=UTF8 \
--locale=C.UTF-8 \
--start
# 只生成配置不立即启动,便于先改配置
sudo pg_createcluster 18 report --port=5434 --no-start
# 创建后再初始化脚本(把 SQL 文件放到 initdb 钩子目录)
sudo pg_createcluster 18 report -- --data-checksumspg_ctlcluster —— 控制集群
# 启动 / 停止 / 重启 / 重载
sudo pg_ctlcluster 18 report start
sudo pg_ctlcluster 18 report stop -m fast
sudo pg_ctlcluster 18 report restart
sudo pg_ctlcluster 18 report reload
# 查看状态(不指定则显示所有匹配集群)
sudo pg_ctlcluster 18 report status
# 备库提升
sudo pg_ctlcluster 18 report promote
# 也可用带 @ 的 systemd 形式,两者等价
sudo systemctl start postgresql@18-reportpg_dropcluster —— 删除集群
# 删除集群会同时删除数据目录与配置文件,不可恢复
sudo pg_dropcluster 18 report
# 显式指定删除日志与归档(默认保留日志)
sudo pg_dropcluster 18 report --logfile --forcepg_dropcluster 是破坏性操作
它会直接 rm -rf 数据目录,没有任何确认提示。执行前务必:确认集群已停止、确认数据目录路径、确认已有可用备份。建议先在命令前加 echo 做 dry-run 检查。
四个命令对照
| 命令 | 作用 | 是否影响数据 | 常用参数 |
|---|---|---|---|
pg_lsclusters | 列出集群清单与状态 | 只读 | -h 去掉表头、--json 结构化输出 |
pg_createcluster | 创建新集群(含 initdb) | 创建数据 | --port、--encoding、--locale、--start/--no-start |
pg_ctlcluster | 启停/重载/提升集群 | 不损坏数据 | -m fast、--skip-systemctl-redirect |
pg_dropcluster | 删除集群 | 永久删除数据 | --force、--logfile |
详细说明
目录布局与包装器
postgresql-common 把所有集群的配置统一收在 /etc/postgresql/<版本>/<名称>/,而数据目录留在 /var/lib/postgresql/。这是它与 RPM 系最大的差异:配置和数据是分开的。
/etc/postgresql/
├── 17/
│ └── main/ # 配置目录(17-main 集群)
│ ├── postgresql.conf
│ ├── pg_hba.conf
│ ├── pg_ident.conf
│ ├── pg_ctl.conf
│ ├── conf.d/ # 附加配置片段目录
│ └── start.conf # 自动启动策略:auto / manual / disabled
└── 18/
├── main/
└── report/ # 配置目录(18-report 集群)
/var/lib/postgresql/
├── 17/main/ # 数据目录(17-main 集群)
└── 18/
├── main/
└── report/
/var/log/postgresql/
├── postgresql-17-main.log
├── postgresql-18-main.log
└── postgresql-18-report.log# 常用工具实际上都是 pg_wrapper 的软链接
ls -l /usr/bin/psql /usr/bin/pg_dumplrwxrwxrwx 1 root root 37 Sep 21 09:00 /usr/bin/psql -> ../share/postgresql-common/pg_wrapper
lrwxrwxrwx 1 root root 37 Sep 21 09:00 /usr/bin/pg_dump -> ../share/postgresql-common/pg_wrapper# pg_wrapper 如何选择版本:按 环境变量 → 端口 → 默认集群 的顺序判断
# 1. 用 PGVERSION 强制指定版本
PGVERSION=17 psql --version
# 2. 用 PGPORT 指定端口(wrapper 会选出监听该端口的集群对应版本)
PGPORT=5433 psql --version
# 3. 都不指定时使用默认集群(/etc/postgresql/*/ 中 postgresql-common 记录的默认版本)
psql --versionpsql (PostgreSQL) 17.6 (Ubuntu 17.6-1.pgdg24.04+1)
psql (PostgreSQL) 18.0 (Ubuntu 18.0-1.pgdg24.04+1)
psql (PostgreSQL) 18.0 (Ubuntu 18.0-1.pgdg24.04+1)start.conf 与自动启动策略
每个集群的配置目录下都有 start.conf,它决定机器启动时该集群是否自动运行:
# /etc/postgresql/18/main/start.conf
# auto = 系统启动时自动拉起(默认)
# manual = 仅手动启动,systemd 不会自启
# disabled = 明确禁止启动(类似 mask)
auto| 取值 | 系统启动时 | pg_ctlcluster ... start | 典型用途 |
|---|---|---|---|
auto | 自动启动 | 允许 | 生产主库 |
manual | 不启动 | 允许 | 测试版本、临时实例 |
disabled | 不启动 | 拒绝 | 保留数据但暂停使用 |
# 让 report 集群不随系统启动,仅在需要时手动拉起
sudo sed -i 's/^auto$/manual/' /etc/postgresql/18/report/start.conf端口分配策略
同一台机器上多个集群必须使用不同端口。postgresql-common 的分配规则是「从 5432 开始,找到第一个未被占用的端口」。
# 查看端口占用情况
pg_lsclusters -h | awk '{print $1, $2, $3}'17 main 5432
18 main 5433
18 report 5434# 系统层面确认监听
sudo ss -lntp | grep -E '543[0-9]'LISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=812,fd=6))
LISTEN 0 244 127.0.0.1:5433 0.0.0.0:* users:(("postgres",pid=1147,fd=6))
LISTEN 0 244 127.0.0.1:5434 0.0.0.0:* users:(("postgres",pid=2310,fd=6))| 分配策略 | 说明 | 建议 |
|---|---|---|
| 自动分配 | pg_createcluster 自动挑最小可用端口 | 测试环境方便 |
| 显式指定 | --port=5434 | 生产推荐,端口固定可预期 |
| 按用途分段 | 如 55xx 给报表库 | 多集群环境的清晰约定 |
端口规划建议
生产环境中端口是运维接口的一部分,不应随机。建议建立约定:如 5432 给主库、5433 给同版本第二个实例、54xx 段给测试、55xx 段给历史版本。把端口写进配置管理,不要让工具随机决定。
连接不同版本的实例
# 方式 1:用 -p 指定端口(最直观,与版本无关)
psql -h 127.0.0.1 -p 5433 -U postgres -d postgres -c "SELECT version();"
# 方式 2:用 pg_ctlcluster 对应的 socket 目录(Debian 的 socket 在 /var/run/postgresql)
psql -h /var/run/postgresql -p 5434 -U postgres -c "SHOW port;"
# 方式 3:强制 wrapper 使用指定版本(仅影响客户端二进制,不影响连接目标)
PGVERSION=17 psql -p 5433 -c "SELECT 1;"
# 方式 4:在连接串中显式给出端口(应用侧最常用)
# psql "postgresql://postgres@127.0.0.1:5433/appdb"客户端版本可以低于服务端,但不能差太多
用 17 的 psql 连 18 的服务器通常可用,但 pg_dump 必须不低于服务端版本(低版本 pg_dump 拒绝连接更高版本的服务端)。跨版本备份请使用服务端对应版本的 pg_dump:
# 用 18 版本的 pg_dump 导出 18 集群的数据
/usr/lib/postgresql/18/bin/pg_dump -p 5433 -U postgres appdb > appdb.sql实战示例
场景一:并行安装 17 与 18 并保留旧数据
# ---------- 步骤 1:安装新版本(旧版本保持不变) ----------
sudo apt install -y postgresql-18 postgresql-client-18 postgresql-contrib-18
# ---------- 步骤 2:确认两个集群都在 ----------
pg_lsclustersVer Cluster Port Status Owner Data directory Log file
17 main 5432 online postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
18 main 5433 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log# ---------- 步骤 3:安装后 apt 会自动把 5432 留给已有的 17,18 分到 5433 ----------
# 若希望调整端口,改配置后重启即可
sudo sed -i "s/^port = .*/port = 5440/" /etc/postgresql/18/main/postgresql.conf
sudo pg_ctlcluster 18 main restart
pg_lsclusters -h | awk '$1==18 {print $1, $2, $3}'18 main 5440场景二:新增一个隔离的报表库集群
# ---------- 步骤 1:创建集群(指定端口,不立即启动以便先改配置) ----------
sudo pg_createcluster 18 report \
--port=5434 \
--encoding=UTF8 \
--locale=C.UTF-8 \
--no-startCreating new PostgreSQL cluster 18/report ...
/usr/lib/postgresql/18/bin/initdb -D /var/lib/postgresql/18/report \
--auth-local peer --auth-host scram-sha-256 --no-instructions --encoding UTF8 --locale C.UTF-8
...
Ver Cluster Port Status Owner Data directory Log file
18 report 5434 down postgres /var/lib/postgresql/18/report /var/log/postgresql/postgresql-18-report.log# ---------- 步骤 2:定制专属配置(报表库需要更大 work_mem) ----------
sudo tee /etc/postgresql/18/report/conf.d/report.conf >/dev/null <<'EOF'
# /etc/postgresql/18/report/conf.d/report.conf
# conf.d 下的片段会被主配置的 include_dir 自动加载,便于按用途分类管理
work_mem = 64MB
max_parallel_workers_per_gather = 4
log_min_duration_statement = 2000
shared_buffers = 2GB
EOF
# ---------- 步骤 3:设为手动启动,避免抢占开机资源 ----------
sudo sed -i 's/^auto$/manual/' /etc/postgresql/18/report/start.conf
# ---------- 步骤 4:启动并验证 ----------
sudo pg_ctlcluster 18 report start
pg_lsclustersVer Cluster Port Status Owner Data directory Log file
17 main 5432 online postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
18 main 5440 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
18 report 5434 online postgres /var/lib/postgresql/18/report /var/log/postgresql/postgresql-18-report.log# ---------- 步骤 5:连接确认配置已生效 ----------
psql -h 127.0.0.1 -p 5434 -U postgres -c "SHOW work_mem;" -c "SHOW port;" work_mem
----------
64MB
(1 row)
port
------
5434
(1 row)场景三:删除废弃集群(含安全检查)
#!/usr/bin/env bash
# drop-cluster.sh:带多重校验的集群删除脚本
set -euo pipefail
VER="${1:?usage: $0 <version> <cluster>}"
NAME="${2:?usage: $0 <version> <cluster>}"
CONF_DIR="/etc/postgresql/${VER}/${NAME}"
DATA_DIR="/var/lib/postgresql/${VER}/${NAME}"
# 校验 1:集群必须存在
if ! pg_lsclusters -h | awk '{print $1"-"$2}' | grep -qx "${VER}-${NAME}"; then
echo "ERROR: cluster ${VER}-${NAME} not found" >&2
exit 1
fi
# 校验 2:集群必须处于停止状态,运行中删除是严重事故
if pg_lsclusters -h | awk -v v="${VER}" -v c="${NAME}" \
'$1==v && $2==c {print $4}' | grep -q "^online$"; then
echo "ERROR: cluster is online, stop it first" >&2
exit 1
fi
# 校验 3:打印将删除的路径,人工确认
echo "About to delete:"
echo " config: ${CONF_DIR}"
echo " data : ${DATA_DIR} ($(du -sh "${DATA_DIR}" 2>/dev/null | cut -f1))"
read -r -p "type the cluster name to confirm: " ans
[ "${ans}" = "${NAME}" ] || { echo "aborted."; exit 1; }
sudo pg_dropcluster "${VER}" "${NAME}" --logfile
echo "cluster ${VER}-${NAME} dropped."About to delete:
config: /etc/postgresql/18/report
data : /var/lib/postgresql/18/report (412M)
type the cluster name to confirm: report
cluster 18-report dropped.场景四:用 pg_upgrade 做跨版本数据迁移
# ---------- 步骤 1:准备目标集群(空的新版本集群) ----------
sudo pg_createcluster 18 newmain --port=5435 --no-start 2>/dev/null || true
sudo pg_ctlcluster 18 newmain stop || true
# 注意:pg_upgrade 要求目标数据目录为空,若创建时已 initdb 需清空
sudo rm -rf /var/lib/postgresql/18/newmain/*
# ---------- 步骤 2:两个集群都必须停止 ----------
sudo pg_ctlcluster 17 main stop -m fast
sudo pg_ctlcluster 18 newmain stop || true
# ---------- 步骤 3:执行升级(使用 18 的 pg_upgrade,旧库用 17 的二进制) ----------
sudo -u postgres /usr/lib/postgresql/18/bin/pg_upgrade \
--old-datadir=/var/lib/postgresql/17/main \
--new-datadir=/var/lib/postgresql/18/newmain \
--old-bindir=/usr/lib/postgresql/17/bin \
--new-bindir=/usr/lib/postgresql/18/bin \
--old-port=5432 --new-port=5435 \
--check
# --check 通过后去掉该参数正式执行
sudo -u postgres /usr/lib/postgresql/18/bin/pg_upgrade \
--old-datadir=/var/lib/postgresql/17/main \
--new-datadir=/var/lib/postgresql/18/newmain \
--old-bindir=/usr/lib/postgresql/17/bin \
--new-bindir=/usr/lib/postgresql/18/bin \
--old-port=5432 --new-port=5435Performing Consistency Checks
-----------------------------
Checking cluster versions ok
Checking database user is the install user ok
Checking database connection settings ok
...
Upgrade Complete
----------------
Optimizer statistics are not transferred by pg_upgrade.
Once you start the new server, consider running:
/usr/lib/postgresql/18/bin/vacuumdb --all --analyze-in-stages# ---------- 步骤 4:启动新集群并重建统计信息 ----------
sudo pg_ctlcluster 18 newmain start
sudo -u postgres /usr/lib/postgresql/18/bin/vacuumdb --all --analyze-in-stages
# ---------- 步骤 5:确认无误后再删除旧集群 ----------
sudo pg_dropcluster 17 mainpg_upgrade 的 --link 模式
加 --link 可以不复制数据文件,直接硬链接,速度快但旧集群会被破坏(无法回滚)。生产环境建议先做一次不带 --link 的完整演练,确认耗时与风险后再决定。
注意事项
- 同一台机器上不同集群必须使用不同端口,端口冲突会导致后者启动失败
- Debian 系的配置在
/etc/postgresql/,数据在/var/lib/postgresql/;修改配置要改前者,改了后者会在下次pg_createcluster时被覆盖 - 直接改
/var/lib/postgresql/18/main/postgresql.conf在 Debian 系中不生效,因为服务读取的是/etc/postgresql/18/main/postgresql.conf pg_dropcluster无确认提示,误执行即永久丢失数据;建议配合备份与二次确认脚本start.conf设为disabled后pg_ctlcluster start会被拒绝,这与manual不同,排障时容易困惑pg_lsclusters中显示down不代表数据有问题,只说明当前没有对应进程- 跨版本使用
pg_dump时,低版本客户端不能连高版本服务端,必须使用服务端同版本或更高版本的工具 pg_upgrade不迁移统计信息,升级后必须跑ANALYZE,否则执行计划可能严重劣化pg_upgrade --link会破坏旧集群,无法回滚;只有确认新集群可用后才能删除旧集群- 多版本并存会让默认的
psql版本不确定,脚本中应显式使用绝对路径或设置PGVERSION - RHEL 系没有
pg_*cluster命令,对应需求要用systemctl+ 单独的数据目录与端口来模拟
最佳实践
- 新集群一律显式指定
--port,并把端口规划写成文档纳入配置管理,不依赖自动分配 - 用
conf.d/片段按用途拆分配置(如report.conf、tuning.conf),避免在postgresql.conf中做大量重复修改 - 生产集群的
start.conf保持auto,测试与临时集群设为manual,明确区分 - 大版本升级遵循「先在测试机演练 → 记录耗时 → 生产执行 → 观察一周 → 再删旧集群」的流程
- 升级前务必做一次完整逻辑备份(
pg_dumpall)与基础备份,pg_upgrade不是备份的替代品 - 应用中连接串显式写明主机与端口,不依赖默认 5432,避免多实例环境连错库
- 用
pg_lsclusters的输出接入监控,统一采集状态、端口、数据目录大小 - 删除集群前用脚本做多重校验(存在性、状态、路径、人工确认),防止误删
- 客户端工具在脚本中使用绝对路径(如
/usr/lib/postgresql/18/bin/pg_dump),消除版本歧义 - 保留旧版本实例时,注意其安全补丁与生命周期;不作为业务主库的实例应及时下线
下一节
继续学习:配置文件位置与加载