所有托管服务节省 15%

测试技能,享折扣

使用代码: Skills 开始使用
Linux 管理 虚拟服务器

PostgreSQL在VPS上的部署:架构、性能调优与部署指南

PostgreSQL 是一个先进的开源对象关系数据库管理系统(ORDBMS),支持 SQL 和 JSON 查询、符合 ACID 的事务处理以及可扩展的数据类型。当部署在 虚拟私有服务器 上时,它获得了专用计算资源、完整的内核级配置访问权限和网络隔离——这些是共享主机从根本上无法提供的能力。

对于生产工作负载,这种组合的重要性立竿见影:在共享主机上错误配置的 shared_buffers 值无法修复,邻居实例上失控的查询可能耗尽您的 I/O,而且没有 root 访问权限就无法安装 PostGIS 或 pg_partman 等扩展。VPS 可以同时消除这三个限制。

为什么 PostgreSQL 在开源 RDBMS 选项中表现优异

在研究 VPS 的具体优势之前,有必要了解是什么让 PostgreSQL 成为复杂工作负载中优于 MySQL/MariaDB 的首选引擎。

功能PostgreSQLMySQL 8.xMariaDB 10.x
ACID 合规性完整,包括 DDL完整完整
JSON/JSONB 索引原生 JSONB 配合 GIN 索引JSON(无二进制存储)JSON(无二进制存储)
地理空间支持PostGIS(行业标准)有限的空间类型有限的空间类型
全文搜索内置,可配置基本 FULLTEXT 索引基本 FULLTEXT 索引
表分区声明式,范围/列表/哈希支持分区支持分区
并行查询执行是(可配置工作进程)有限有限
自定义数据类型是(CREATE TYPE)否否
存储过程(PL/pgSQL)完整过程语言基本基本
预写日志(WAL)可配置,流复制二进制日志二进制日志
并发模型MVCC(无读锁)MVCCMVCC
逻辑复制是(发布/订阅)是是
外部数据包装器是(postgres_fdw 等)否否

PostgreSQL 的多版本并发控制(MVCC)模型值得特别关注:读操作永远不会阻塞写操作,写操作也永远不会阻塞读操作。对于混合 OLTP/OLAP 工作负载,这在架构上具有明显优势,因为长时间运行的分析查询否则会锁定事务表。

成本效益:按需分配资源,避免过度配置

VPS 主机方案以远低于裸机硬件的成本提供有保障的 CPU 核心、RAM 和 NVMe SSD 存储。经济逻辑很简单:PostgreSQL 的内存需求随 max_connections 和 work_mem 扩展,而非随原始服务器大小扩展。一个经过适当调优、配备 4 GB RAM 的 VPS 服务 50 个并发连接,其性能将优于一个配备 8 GB RAM 但使用默认设置且有 200 个空闲连接占用共享内存的实例。

实际的成本效益策略是:从中等级别的 VPS 开始,在两周的生产负载后分析实际的 pg_stat_activity 和 pg_stat_bgwriter 指标,然后进行垂直扩展。这种数据驱动的方法可以避免在上线时过度配置的常见错误。

一个常被忽视的成本因素:PostgreSQL 的 autovacuum 守护进程需要 CPU 余量。在共享主机上,autovacuum 经常被提供商限速,导致表膨胀和查询计划随时间退化。在 VPS 上,您可以直接控制 autovacuum_vacuum_cost_delay 和 autovacuum_max_workers。

完整 Root 访问权限与环境控制

与托管数据库服务或共享虚拟主机不同,VPS 为您提供对操作系统层的不受限访问。这不仅仅是一种便利——对于 PostgreSQL 的某些功能而言,这是硬性要求。

root 访问权限所能实现而共享环境所阻止的功能:

  • 安装 PostgreSQL 扩展(CREATE EXTENSION postgis、CREATE EXTENSION pg_trgm、CREATE EXTENSION timescaledb)
  • 修改直接影响 PostgreSQL 性能的内核参数(vm.overcommit_memory、vm.swappiness、huge_pages)
  • 使用自定义身份验证方法配置 pg_hba.conf(SCRAM-SHA-256、LDAP、基于证书的身份验证)
  • 运行 pg_upgrade 进行主要版本迁移,无需提供商介入
  • 在独立块设备上挂载专用表空间卷,实现索引与堆文件之间的 I/O 分离

Linux 上 PostgreSQL 的关键内核调优:

# Disable transparent huge pages (causes latency spikes in PostgreSQL)
echo never > /sys/kernel/mm/transparent_hugepage/enabled

# Set vm.overcommit_memory to allow PostgreSQL shared memory allocation
sysctl -w vm.overcommit_memory=2
sysctl -w vm.overcommit_ratio=80

# Reduce swappiness to prevent paging PostgreSQL shared buffers
sysctl -w vm.swappiness=1

# Persist these settings
echo "vm.overcommit_memory=2" >> /etc/sysctl.conf
echo "vm.overcommit_ratio=80" >> /etc/sysctl.conf
echo "vm.swappiness=1" >> /etc/sysctl.conf

这些设置对应用层托管数据库服务不可见,且经常是云托管 PostgreSQL 实例中无法解释的性能下降的根本原因。

性能调优:真正重要的 postgresql.conf 参数

PostgreSQL 的默认安装参数是有意保守的——它们设计用于在 2000 年代初期的 256 MB RAM 机器上运行。在配备 4–16 GB RAM 和 NVMe 存储的现代 VPS 上,默认设置使大部分硬件能力闲置。

内存配置

# postgresql.conf — tuned for a 8 GB RAM VPS, OLTP workload

# Set to 25% of total RAM
shared_buffers = 2GB

# Estimate of OS cache available to PostgreSQL (typically 50-75% of RAM)
effective_cache_size = 6GB

# Per-sort/hash operation memory (multiply by max_connections for worst case)
work_mem = 32MB

# For VACUUM, CREATE INDEX, ALTER TABLE operations
maintenance_work_mem = 512MB

# Enable huge pages if kernel supports it
huge_pages = try

work_mem 陷阱:将 work_mem = 256MB 与 max_connections = 100 结合设置意味着 PostgreSQL 理论上可以为排序操作单独分配 25.6 GB RAM——远超物理内存并触发 OOM 终止。始终按以下公式计算 work_mem:(available_RAM - shared_buffers) / (max_connections * 2)。

存储与 WAL 配置

# For NVMe SSD storage — set to 1 for spinning disks, 200 for NVMe
random_page_cost = 1.1
effective_io_concurrency = 200

# WAL configuration for durability vs. performance tradeoff
wal_buffers = 64MB
checkpoint_completion_target = 0.9
checkpoint_timeout = 10min

# For write-heavy workloads, consider asynchronous commit
# (risk: last ~1 transaction lost on crash, not data corruption)
synchronous_commit = on  # Keep 'on' for financial data

连接管理

# Avoid setting this above what your application actually needs
max_connections = 100

# Use PgBouncer in transaction pooling mode for high-concurrency apps
# A VPS allows you to install and configure PgBouncer locally

对于并发用户超过 50 的应用,PgBouncer 不是可选项。每个 PostgreSQL 后端进程消耗约 5–10 MB RAM。在 200 个连接时,空闲进程将消耗 1–2 GB。PgBouncer 在事务池模式下可将数百个应用连接多路复用到少量实际 PostgreSQL 后端上。

# Install PgBouncer on Debian/Ubuntu
apt install pgbouncer

# Minimal pgbouncer.ini configuration
cat /etc/pgbouncer/pgbouncer.ini
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
listen_port = 6432
listen_addr = 127.0.0.1
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20

安全加固:超越默认安装

VPS 上全新安装的 PostgreSQL 存在几个安全漏洞,必须在实例可从网络访问之前加以修复。

网络级隔离

除非绝对必要,PostgreSQL 不应监听公共 IP。将其绑定到 localhost,并使用 SSH 隧道或 VPN 进行远程管理。

# In postgresql.conf
listen_addresses = 'localhost'

# For replication or application servers on a private network only
# listen_addresses = '127.0.0.1,10.0.0.1'

配置 pg_hba.conf 以强制执行 SCRAM-SHA-256 身份验证(默认的 md5 在密码学上较弱):

# /etc/postgresql/16/main/pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             postgres                                peer
local   all             all                                     scram-sha-256
host    all             all             127.0.0.1/32            scram-sha-256
host    all             all             ::1/128                 scram-sha-256
# Reject all other connections by default (no catch-all line)

远程连接的 SSL/TLS 加密

如果您的应用服务器通过网络连接到 PostgreSQL,则必须对连接进行加密。将此与应用层的 SSL 证书配合使用,并配置 PostgreSQL 自身的 TLS 堆栈:

# Generate a self-signed certificate for internal use
openssl req -new -x509 -days 365 -nodes 
  -out /etc/postgresql/16/main/server.crt 
  -keyout /etc/postgresql/16/main/server.key

chmod 600 /etc/postgresql/16/main/server.key
chown postgres:postgres /etc/postgresql/16/main/server.{crt,key}
# postgresql.conf
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_min_protocol_version = 'TLSv1.2'

基于角色的访问控制

最小权限原则严格适用于数据库角色:

-- Create an application role with minimal permissions
CREATE ROLE app_user WITH LOGIN PASSWORD 'strong_password_here';
GRANT CONNECT ON DATABASE mydb TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;

-- Create a read-only analytics role
CREATE ROLE analytics_reader WITH LOGIN PASSWORD 'another_strong_password';
GRANT CONNECT ON DATABASE mydb TO analytics_reader;
GRANT USAGE ON SCHEMA public TO analytics_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_reader;

-- Never use the superuser 'postgres' role for application connections

防火墙规则

# Allow PostgreSQL only from specific application server IP
ufw allow from 10.0.0.5 to any port 5432
ufw deny 5432

# Verify
ufw status verbose

备份与恢复架构

PostgreSQL 提供两种根本不同的备份机制,各自适用于不同的恢复目标。

备份方法工具恢复类型RPORTO使用场景
逻辑备份pg_dump / pg_dumpall对象级恢复小时级中等模式迁移,选择性表恢复
物理备份pg_basebackup完整集群恢复分钟级(配合 WAL)快速灾难恢复,备用节点创建
持续归档WAL 归档 + pg_basebackup时间点恢复秒级取决于 WAL 量零数据丢失要求
快照VPS 提供商快照完整服务器恢复快照时间点快速升级前安全保障

带压缩的逻辑备份:

# Dump a single database in custom format (supports parallel restore)
pg_dump -U postgres -Fc -Z 9 mydb > /backup/mydb_$(date +%Y%m%d).dump

# Restore
pg_restore -U postgres -d mydb_restored /backup/mydb_20240115.dump

# Dump all databases including roles and tablespaces
pg_dumpall -U postgres | gzip > /backup/full_cluster_$(date +%Y%m%d).sql.gz

用于灾难恢复的物理备份:

# Take a base backup (can run while PostgreSQL is live)
pg_basebackup -U replication_user -D /backup/base -Ft -z -Xs -P

# This creates base.tar.gz and pg_wal.tar.gz
# Combined with WAL archiving, enables point-in-time recovery

使用 cron 自动化:

# /etc/cron.d/postgres-backup
0 2 * * * postgres pg_dump -Fc mydb > /backup/mydb_$(date +%Y%m%d_%H%M).dump
0 3 * * 0 postgres pg_dumpall | gzip > /backup/full_$(date +%Y%m%d).sql.gz

# Prune backups older than 30 days
0 4 * * * root find /backup/ -name "*.dump" -mtime +30 -delete

可扩展性:垂直扩展、水平扩展与读扩展

垂直扩展

在 VPS 上,垂直扩展(增加 CPU、RAM、存储)通常是在线操作,或仅需短暂重启。升级 RAM 后,按比例更新 shared_buffers、effective_cache_size 和 work_mem。增加 CPU 核心后,增大 max_parallel_workers_per_gather 和 max_parallel_maintenance_workers。

# After upgrading from 4 to 8 CPU cores
max_parallel_workers_per_gather = 4
max_parallel_maintenance_workers = 2
max_parallel_workers = 8

用于读扩展的流复制

PostgreSQL 内置的流复制可创建热备节点,该节点可服务读查询,从主节点卸载分析工作负载:

# On primary: create replication user
psql -U postgres -c "CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'rep_password';"

# In pg_hba.conf on primary
# host replication replicator 10.0.0.2/32 scram-sha-256

# In postgresql.conf on primary
# wal_level = replica
# max_wal_senders = 3
# wal_keep_size = 1GB
# On standby: initialize from primary
pg_basebackup -h 10.0.0.1 -U replicator -D /var/lib/postgresql/16/main 
  -P -Xs -R

# The -R flag creates standby.signal and populates primary_conninfo automatically

用于选择性复制的逻辑复制

逻辑复制允许将特定表复制到另一个 PostgreSQL 实例,适用于数据仓库管道:

-- On publisher
CREATE PUBLICATION analytics_pub FOR TABLE orders, customers, products;

-- On subscriber
CREATE SUBSCRIPTION analytics_sub
  CONNECTION 'host=10.0.0.1 dbname=mydb user=replicator password=rep_password'
  PUBLICATION analytics_pub;

对于需要将独立服务器用于主数据库、VPS 副本处理读流量的应用,这种架构同时提供了性能和成本效益。

值得启用的高级 PostgreSQL 功能

用于混合关系/文档工作负载的 JSONB

-- Create a table with JSONB column
CREATE TABLE events (
  id BIGSERIAL PRIMARY KEY,
  occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  payload JSONB NOT NULL
);

-- Create a GIN index for fast JSONB queries
CREATE INDEX idx_events_payload ON events USING GIN (payload);

-- Query nested JSON efficiently
SELECT * FROM events
WHERE payload @> '{"type": "purchase", "currency": "USD"}';

-- Extract and index a specific JSON key
CREATE INDEX idx_events_user_id ON events ((payload->>'user_id'));

用于大型数据集的表分区

-- Range partitioning by month (ideal for time-series data)
CREATE TABLE measurements (
  id BIGSERIAL,
  recorded_at TIMESTAMPTZ NOT NULL,
  sensor_id INT,
  value NUMERIC
) PARTITION BY RANGE (recorded_at);

CREATE TABLE measurements_2024_01
  PARTITION OF measurements
  FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

CREATE TABLE measurements_2024_02
  PARTITION OF measurements
  FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');

-- PostgreSQL automatically routes inserts and prunes partitions in queries

用于地理空间应用的 PostGIS

# Install PostGIS extension
apt install postgresql-16-postgis-3

# Enable in database
psql -U postgres -d mydb -c "CREATE EXTENSION postgis;"
-- Store and query geographic coordinates
CREATE TABLE locations (
  id SERIAL PRIMARY KEY,
  name TEXT,
  geom GEOMETRY(Point, 4326)
);

-- Find all locations within 10km of a point
SELECT name, ST_Distance(geom::geography, ST_MakePoint(-73.935242, 40.730610)::geography) AS distance_m
FROM locations
WHERE ST_DWithin(geom::geography, ST_MakePoint(-73.935242, 40.730610)::geography, 10000)
ORDER BY distance_m;

监控:生产 PostgreSQL 的可观测性堆栈

被动故障排除对于生产数据库来说是不够的。主动可观测性堆栈可在性能下降演变为中断之前发现问题。

PostgreSQL 内置统计视图

-- Identify slow queries (requires pg_stat_statements extension)
CREATE EXTENSION pg_stat_statements;

SELECT query, calls, total_exec_time / calls AS avg_ms,
       rows / calls AS avg_rows
FROM pg_stat_statements
ORDER BY avg_ms DESC
LIMIT 20;

-- Check for table bloat and vacuum status
SELECT schemaname, relname, n_dead_tup, last_autovacuum, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;

-- Monitor replication lag
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
       (sent_lsn - replay_lsn) AS replication_lag_bytes
FROM pg_stat_replication;

-- Find long-running queries
SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 minutes';

Prometheus + postgres_exporter 堆栈

# Install postgres_exporter
wget https://github.com/prometheus-community/postgres_exporter/releases/download/v0.15.0/postgres_exporter-0.15.0.linux-amd64.tar.gz
tar xzf postgres_exporter-0.15.0.linux-amd64.tar.gz
mv postgres_exporter-0.15.0.linux-amd64/postgres_exporter /usr/local/bin/

# Create a monitoring role in PostgreSQL
psql -U postgres -c "CREATE ROLE postgres_exporter WITH LOGIN PASSWORD 'monitor_pass';"
psql -U postgres -c "GRANT pg_monitor TO postgres_exporter;"

# Run the exporter
export DATA_SOURCE_NAME="postgresql://postgres_exporter:monitor_pass@localhost:5432/postgres?sslmode=disable"
postgres_exporter --web.listen-address=":9187"

将 postgres_exporter 与 Grafana 仪表板配合使用(grafana.com 上的仪表板 ID 9628 涵盖所有关键 PostgreSQL 指标),并为以下情况配置告警:复制延迟超过 30 秒、事务 ID 回绕临近、缓存命中率低于 95%,以及死元组数量超过活跃元组的 10%。

使用场景矩阵:将 PostgreSQL 配置与工作负载匹配

工作负载关键参数扩展扩展策略
OLTP(电商、SaaS)低 work_mem、PgBouncer、synchronous_commit=onpg_stat_statements、pgcrypto垂直扩展 + 读副本
分析 / OLAP高 work_mem、并行工作进程、synchronous_commit=offpg_partman、tablefunc分区 + 列式存储
时序 IoT按时间分区、autovacuum 调优TimescaleDB、pg_partman分区裁剪 + 压缩
地理空间 / GIS空间索引、effective_io_concurrencyPostGIS、pg_routing大型数据集使用独立服务器
API 后端(JSON)JSONB 上的 GIN 索引、work_mem 用于聚合pg_trgm、uuid-ossp读副本用于 GET 密集型 API
全文搜索tsvector 列、GIN 索引pg_trgm、unaccent仅索引扫描,部分索引

对于构建 API 后端或 Web 应用的团队,将 PostgreSQL 与带 cPanel 的 VPS 配合使用,可在完整数据库灵活性的同时提供托管控制面板。对于偏好 CLI 驱动管理的基础设施团队,VPS 控制面板提供了更广泛的面板选项。

在 VPS 上部署 PostgreSQL 前的实用决策清单

硬件规格:

  • 将 shared_buffers 计算为总 RAM 的 25%
  • 确认 NVMe SSD 存储——PostgreSQL 的 WAL 写入对延迟敏感
  • 为生产工作负载分配至少 2 个专用 CPU 核心

安全基线:

  • 将 listen_addresses 绑定到私有/localhost 地址
  • 在 pg_hba.conf 中将 md5 替换为 scram-sha-256
  • 为任何远程连接启用最低 TLS 1.2 的 SSL
  • 创建应用专用角色——切勿在应用代码中使用 postgres 超级用户
  • 配置 ufw 或 iptables,仅将已知源 IP 加入 5432 端口白名单

性能基线:

  • 在操作系统级别禁用透明大页
  • 设置 vm.swappiness=1 以防止共享缓冲区分页
  • 如果连接数超过 50,安装并配置 PgBouncer
  • 从第一天起启用 pg_stat_statements——事后查询分析是不可能的

备份与恢复:

  • 使用 cron 自动化 pg_dump,每月测试恢复
  • 如果 RPO 要求低于 1 小时,实施 WAL 归档
  • 将应用层备份与 VPS 提供商快照结合,实现分层保护

可观测性:

  • 在上线前部署 postgres_exporter + Prometheus + Grafana
  • 针对复制延迟、事务 ID 年龄和缓存命中率设置告警
  • 每周审查 pg_stat_bgwriter 以检测检查点压力

常见问题

在新 VPS 上应该安装哪个 PostgreSQL 版本?

始终从官方 PGDG 仓库安装最新稳定主要版本(截至 2024 年为 PostgreSQL 16),而非 Linux 发行版自带的版本。发行版软件包通常落后 1–2 个主要版本,且不接收上游功能向后移植。使用 apt.postgresql.org 或 yum.postgresql.org 进行安装。

PostgreSQL VPS 实际需要多少 RAM?

对于并发连接数低于 50、数据集低于 50 GB 的小型生产应用,4 GB RAM 是实际最低要求。设置 shared_buffers = 1GB、work_mem = 16MB,并使用 PgBouncer。对于超出可用 RAM 的数据集,在增加硬件之前应专注于索引覆盖和查询计划优化——100 GB 表上缺失的索引无法通过增加 RAM 来解决。

在同一 VPS 上同时运行 PostgreSQL 和应用是否安全?

对于中小型工作负载,是安全的。风险在于资源竞争:应用中的内存峰值可能触发 OOM 终止,进而终止 PostgreSQL。通过将 PostgreSQL 的 oom_score_adj 设置为负值(使其不太可能被终止)并使用 cgroups 限制应用的内存上限来降低此风险。

pg_dump 和 pg_basebackup 有什么区别?

pg_dump 生成单个数据库的逻辑备份——它导出 SQL 语句或自定义二进制格式,可以选择性地恢复(单个表、模式)。pg_basebackup 在二进制级别复制整个 PostgreSQL 数据目录,生成适用于灾难恢复和备用服务器初始化的完整集群备份。两者都要使用:pg_dump 用于细粒度恢复,pg_basebackup 用于完整恢复场景。

如何在 VPS 上安全地将 PostgreSQL 升级到新主要版本?

首先使用带 --check 标志的 pg_upgrade 验证兼容性,而不做任何更改。在继续之前进行完整的 pg_basebackup 备份。升级本身需要离线执行(PostgreSQL 必须停止)。对于零停机主要版本升级,使用逻辑复制:将新的 PostgreSQL 16 实例设置为 PostgreSQL 15 主节点的逻辑订阅者,等待其追上进度,然后以最短停机时间执行协调切换。