Skip to content

Split View: PostgreSQL 프로덕션 운영 완전 가이드 2025: 확장(Extensions), 튜닝, 모니터링, 고가용성

✨ Learn with Quiz
|

PostgreSQL 프로덕션 운영 완전 가이드 2025: 확장(Extensions), 튜닝, 모니터링, 고가용성

TL;DR

  • 확장이 PostgreSQL의 진정한 힘: pg_stat_statements(쿼리 분석), pgvector(AI), PostGIS(지리), TimescaleDB(시계열), pg_partman(파티셔닝)
  • 3대 튜닝 파라미터: shared_buffers (RAM 25%), work_mem (정렬/해시 메모리), effective_cache_size (RAM 50-75%)
  • Autovacuum이 핵심: 잘못 설정하면 테이블 bloat → 디스크 폭증 → 쿼리 느려짐. autovacuum_vacuum_scale_factor 튜닝 필수
  • Patroni: PostgreSQL HA의 사실상 표준. 자동 failover 30초 이내 가능
  • 백업: pgBackRest가 pg_dump보다 10x+ 빠름. WAL 아카이빙 + PITR 필수

1. PostgreSQL이 2025년에도 사랑받는 이유

1.1 핵심 이유 5가지

  1. 확장성(Extensibility) — 200+ 공식 확장. 벡터, 시계열, 지리정보까지 모두
  2. JSONB 성능 — MongoDB와 비슷하거나 더 빠른 JSON 처리
  3. MVCC — 읽기-쓰기 충돌 없음 (동시성 우수)
  4. 표준 준수 — ANSI SQL 가장 충실하게 구현
  5. 오픈소스 — Oracle/SQL Server 대비 라이선스 비용 0

1.2 NewSQL 시대에도 PostgreSQL이 표준인 이유

CockroachDB, YugabyteDB, Spanner 등 분산 SQL DB가 등장했지만, 대부분 PostgreSQL wire protocol 호환입니다. 이는 PostgreSQL 자체의 위치가 안정적임을 의미합니다.


2. 필수 확장 (Extensions) Top 10

2.1 운영 필수: pg_stat_statements

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = 'all'
pg_stat_statements.max = 10000

활용:

-- TOP 10 가장 느린 쿼리
SELECT
  substring(query, 1, 80) as query,
  calls,
  total_exec_time::int as total_ms,
  mean_exec_time::int as avg_ms,
  rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

이 쿼리 하나로 80%의 성능 문제가 해결됩니다. 어떤 쿼리가 가장 많은 시간을 잡아먹는지 즉시 보입니다.

2.2 AI 시대의 필수: pgvector

CREATE EXTENSION IF NOT EXISTS vector;

-- 768차원 임베딩 저장
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT,
  embedding vector(768)
);

-- HNSW 인덱스 (PostgreSQL 16+)
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);

-- 의미 기반 검색
SELECT id, content, 1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 10;

왜 별도 Vector DB 대신 pgvector?

  • 기존 PostgreSQL 운영 노하우 활용
  • ACID 트랜잭션 (벡터 + 메타데이터 일관성)
  • 비용 절감 (별도 인프라 불필요)

2.3 시계열: TimescaleDB

CREATE EXTENSION IF NOT EXISTS timescaledb;

CREATE TABLE metrics (
  time TIMESTAMPTZ NOT NULL,
  device_id TEXT,
  temperature FLOAT,
  humidity FLOAT
);

-- 하이퍼테이블로 변환 (자동 파티셔닝)
SELECT create_hypertable('metrics', 'time', chunk_time_interval => INTERVAL '1 day');

-- 자동 데이터 보존 (30일 후 삭제)
SELECT add_retention_policy('metrics', INTERVAL '30 days');

-- 연속 집계 (Continuous Aggregates)
CREATE MATERIALIZED VIEW metrics_hourly
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 hour', time) AS bucket,
  device_id,
  AVG(temperature) AS avg_temp
FROM metrics
GROUP BY bucket, device_id;

2.4 기타 핵심 확장

확장용도
PostGIS지리정보 (지도, 위치 검색)
pg_partman자동 파티션 관리
pg_cronDB 내 스케줄러 (cron job)
pgaudit감사 로깅 (규제 준수)
pglogical논리 복제 (다른 PG 버전 간)
citus수평 샤딩 (분산 PG)
pg_repack락 없이 테이블 재구성

3. 핵심 성능 튜닝

3.1 메모리 파라미터 (가장 중요)

파라미터권장값 (16GB RAM 서버)설명
shared_buffers4GB (RAM 25%)PostgreSQL 자체 캐시
effective_cache_size12GB (RAM 75%)OS 캐시 추정 (옵티마이저 힌트)
work_mem64MB정렬/해시당 메모리
maintenance_work_mem1GBVACUUM/CREATE INDEX용
wal_buffers16MBWAL 버퍼

계산 공식: shared_buffers + (work_mem × max_connections) ≤ RAM × 0.5

예: 16GB RAM, max_connections=100, work_mem=64MB → 4GB + (64MB × 100) = 4GB + 6.4GB = 10.4GB ✅ (16GB의 65%로 안전)

3.2 체크포인트 튜닝

# 너무 자주 체크포인트 → I/O 폭증
# 너무 드물게 체크포인트 → 복구 시간 증가

checkpoint_timeout = 15min          # 기본 5분 → 15분
checkpoint_completion_target = 0.9  # 9분에 걸쳐 완료
max_wal_size = 4GB                  # WAL 크기 한계
min_wal_size = 1GB

3.3 쿼리 플래너

random_page_cost = 1.1              # SSD인 경우 (HDD는 4.0)
effective_io_concurrency = 200      # SSD/NVMe (HDD는 1)
default_statistics_target = 100     # 통계 정확도 (높으면 느린 ANALYZE)

3.4 연결 관리

max_connections = 100               # PgBouncer가 있다면 충분

중요: PostgreSQL의 max_connections를 무작정 늘리면 안 됩니다. 각 연결이 메모리를 잡아먹습니다. PgBouncer 같은 connection pooler를 사용하세요.


4. VACUUM과 Autovacuum의 모든 것

4.1 왜 VACUUM이 필요한가?

PostgreSQL의 MVCC는 UPDATE를 "INSERT 새 행 + 옛 행 표시"로 처리합니다. 옛 행은 즉시 삭제되지 않고 "dead tuple"로 남습니다. VACUUM이 이를 청소합니다.

VACUUM 없이 일어나는 일:

  1. 테이블 크기가 계속 증가 (디스크 폭증)
  2. 인덱스가 dead tuple을 가리켜 쿼리 느려짐
  3. 트랜잭션 ID wraparound — 데이터 손상 위험!

4.2 Autovacuum 모니터링

SELECT
  schemaname,
  relname,
  n_live_tup,
  n_dead_tup,
  ROUND(n_dead_tup::numeric / NULLIF(n_live_tup, 0), 2) AS dead_ratio,
  last_autovacuum,
  autovacuum_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;

dead_ratio가 0.2 이상이면 autovacuum이 따라가지 못하는 것입니다.

4.3 Autovacuum 튜닝

autovacuum = on
autovacuum_max_workers = 5         # 기본 3 → 5
autovacuum_naptime = 30s            # 기본 1분 → 30초
autovacuum_vacuum_scale_factor = 0.05  # 기본 0.2 → 0.05 (5% dead tuple에서 트리거)
autovacuum_analyze_scale_factor = 0.02
autovacuum_vacuum_cost_limit = 2000  # 더 빨리 작업

테이블별 설정 (큰 테이블일수록 더 자주):

ALTER TABLE big_table SET (
  autovacuum_vacuum_scale_factor = 0.01,
  autovacuum_analyze_scale_factor = 0.005
);

4.4 수동 VACUUM이 필요한 경우

  • 장애 복구 후: VACUUM ANALYZE로 통계 재구성
  • 대량 DELETE 후: dead tuple이 폭증
  • 인덱스 bloat: REINDEX CONCURRENTLY
-- 락 없이 인덱스 재구성 (PG 12+)
REINDEX INDEX CONCURRENTLY idx_users_email;

-- 락 없이 테이블 재구성 (pg_repack 확장 필요)
SELECT pg_repack.repack_table('public.users');

5. 모니터링과 디버깅

5.1 실시간 활동 확인

-- 현재 실행 중인 쿼리
SELECT pid, usename, state, query_start, query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;

-- 락 대기
SELECT
  blocked.pid AS blocked_pid,
  blocking.pid AS blocking_pid,
  blocked.query AS blocked_query,
  blocking.query AS blocking_query
FROM pg_stat_activity AS blocked
JOIN pg_locks AS blocked_lock ON blocked.pid = blocked_lock.pid
JOIN pg_locks AS blocking_lock
  ON blocked_lock.locktype = blocking_lock.locktype
  AND blocked_lock.relation = blocking_lock.relation
JOIN pg_stat_activity AS blocking ON blocking.pid = blocking_lock.pid
WHERE NOT blocked_lock.granted AND blocking_lock.granted;

5.2 EXPLAIN ANALYZE 읽기

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE customer_id = 12345;

주요 지표:

  • Seq Scan vs Index Scan — 인덱스 사용 여부
  • actual rows vs estimated rows — 통계 정확도
  • Buffers: shared hit/read — 캐시 히트율
  • Execution Time — 실제 시간

5.3 pgBadger — 로그 분석 도구

# 슬로우 쿼리 로깅 활성화
log_min_duration_statement = 1000  # 1초 이상 쿼리 로깅
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '

# pgBadger로 분석
pgbadger -j 4 /var/log/postgresql/postgresql-*.log -o report.html

pgBadger는 PostgreSQL 로그를 파싱하여 HTML 보고서를 생성합니다 — 슬로우 쿼리 TOP, 시간대별 활동, 에러 분석 등.


6. 고가용성 (HA) — Patroni

6.1 Patroni 아키텍처

┌──────────┐       ┌──────────┐       ┌──────────┐
│ etcd-1   │       │ etcd-2   │       │ etcd-3└────┬─────┘       └────┬─────┘       └────┬─────┘
     └──────────────────┴──────────────────┘
        ┌───────────────┼───────────────┐
        │               │               │
   ┌────▼────┐     ┌────▼────┐     ┌────▼────┐
Patroni │     │ Patroni │     │ Patroni+ PG    │     │ + PG    │     │ + PGPrimary │     │ Replica │     │ Replica   └─────────┘     └─────────┘     └─────────┘

작동 원리:

  1. 각 PostgreSQL 노드에 Patroni 데몬 실행
  2. Patroni가 etcd에 leader lock 갱신
  3. Primary가 다운되면 lock 만료 → Patroni가 새 Primary 선출
  4. HAProxy/PgBouncer가 새 Primary로 자동 라우팅

6.2 Patroni 설정 예시

# patroni.yml
scope: postgres-cluster
namespace: /service/
name: postgres-1

restapi:
  listen: 0.0.0.0:8008
  connect_address: 192.168.1.10:8008

etcd3:
  hosts: 192.168.1.20:2379,192.168.1.21:2379,192.168.1.22:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      parameters:
        max_connections: 100
        shared_buffers: 4GB
        wal_level: replica
        hot_standby: 'on'
        max_wal_senders: 10

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 192.168.1.10:5432
  data_dir: /var/lib/postgresql/16/main
  authentication:
    replication:
      username: replicator
      password: secret

6.3 Patroni 대안

도구특징
Patroni가장 인기, etcd 필요
repmgrEnterpriseDB, 단순함
CloudNativePGK8s 네이티브 (CNCF)
Stolon컨테이너 친화적
pg_auto_failoverMicrosoft 개발, 단순

7. 백업과 복구

7.1 pg_dump vs pgBackRest

pg_dumppgBackRest
속도느림 (단일 스레드)빠름 (병렬)
증분
압축gziplz4/zstd
PITR
S3 통합
권장 용도작은 DB프로덕션

7.2 pgBackRest 설정

# /etc/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
repo1-retention-diff=4
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=YOUR_PASSWORD
repo1-s3-bucket=my-pg-backups
repo1-s3-region=us-east-1
process-max=4
log-level-console=info
start-fast=y

[main]
pg1-path=/var/lib/postgresql/16/main
pg1-port=5432
# 전체 백업
pgbackrest --stanza=main backup --type=full

# 증분 백업
pgbackrest --stanza=main backup --type=incr

# PITR 복구 (특정 시점으로)
pgbackrest --stanza=main \
  --type=time \
  --target="2025-04-15 14:30:00" \
  restore

7.3 WAL 아카이빙

# postgresql.conf
archive_mode = on
archive_command = 'pgbackrest --stanza=main archive-push %p'
wal_level = replica
max_wal_senders = 10

WAL 아카이빙이 활성화되면, **임의 시점 복구(PITR)**가 가능합니다. 이는 "어제 오후 3시 25분 30초로 되돌려줘" 같은 요구에 응답할 수 있게 합니다.


8. 흔한 함정과 해결책

8.1 인덱스 bloat

-- 인덱스 크기 확인
SELECT
  schemaname,
  relname,
  indexrelname,
  pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 10;

-- 사용되지 않는 인덱스
SELECT
  schemaname || '.' || relname AS table,
  indexrelname AS unused_index,
  pg_size_pretty(pg_relation_size(indexrelid)) AS size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;

8.2 N+1 쿼리

ORM이 가장 잘 일으키는 문제. pg_stat_statements로 동일한 쿼리가 수천 번 호출되는 것을 발견하면 N+1입니다. 해결:

  • Prisma: include로 eager loading
  • TypeORM: relations 옵션
  • Django: select_related / prefetch_related
  • SQLAlchemy: joinedload

8.3 트랜잭션 ID wraparound

-- 위험도 확인
SELECT datname, age(datfrozenxid)
FROM pg_database
ORDER BY age(datfrozenxid) DESC;

age2억(200,000,000) 가까워지면 위험. autovacuum이 잘 동작하면 자동 처리되지만, autovacuum이 막혀있으면 수동 VACUUM FREEZE 필요.

8.4 connection 폭증

PostgreSQL은 각 연결이 별도 프로세스입니다. 1,000 연결 = 1,000 프로세스 = 메모리 폭증. 해결:

  • PgBouncer: connection pooler (transaction mode 권장)
  • Pgpool-II: 더 무거운 풀러 + 로드 밸런싱
  • app side: connection pool (HikariCP, sequelize pool 등)

9. PostgreSQL 17 (2024-09) 새 기능

  • 점진적 정렬 (incremental sort) 개선
  • Logical replication: failover 슬롯 동기화
  • pg_basebackup: 점진적 백업 지원
  • VACUUM: 메모리 기반 dead tuple 추적 (속도 향상)
  • MERGE 명령: RETURNING 절 추가

퀴즈

1. shared_buffers를 RAM의 50%로 설정하면 안 되는 이유는?

: PostgreSQL은 자체 shared_buffers 외에도 OS 파일 시스템 캐시를 활용합니다. effective_cache_size로 OS 캐시를 옵티마이저에 알려줘야 합니다. shared_buffers를 너무 크게 잡으면 (1) OS 캐시 공간이 줄어 이중 캐싱 비효율, (2) 페이지 교체 알고리즘이 비효율적, (3) 백업/체크포인트가 느려집니다. RAM의 25%가 일반적으로 최적입니다.

2. autovacuum이 따라가지 못하는 징후는?

: pg_stat_user_tables에서 n_dead_tupn_live_tup의 20% 이상이면 따라가지 못하는 것입니다. 해결: (1) autovacuum_max_workers 증가, (2) autovacuum_vacuum_scale_factor 감소 (0.2 → 0.05), (3) autovacuum_naptime 감소, (4) 큰 테이블은 ALTER TABLE로 개별 설정.

3. PgBouncer가 필요한 이유는?

: PostgreSQL의 각 연결은 별도 프로세스입니다 (~10MB). 1,000 연결 = 10GB+ 메모리. 또한 프로세스 생성/소멸 비용이 큽니다. PgBouncer는 적은 수의 실제 연결을 풀에 유지하고 클라이언트 연결을 멀티플렉싱합니다. transaction pooling 모드가 가장 효율적이지만 prepared statements와 호환 문제가 있습니다.

4. WAL 아카이빙이 PITR에 어떻게 사용되나요?

: WAL(Write-Ahead Log)은 모든 데이터 변경을 순차적으로 기록합니다. WAL 아카이빙은 이 로그를 외부 저장소(S3 등)에 복사합니다. 복구 시: (1) 가장 가까운 base backup 복원, (2) 원하는 시점까지 WAL을 재생(replay). 결과: 임의 시점으로 복구 가능. pgBackRest의 --type=time이 이를 자동화합니다.

5. pg_repack과 VACUUM FULL의 차이는?

: VACUUM FULL테이블 락이 걸려 운영 중인 서비스에서는 사용 불가. pg_repack은 락 없이 테이블을 재구성합니다 — 새 테이블에 데이터를 복사하고, 이름만 swap합니다. 대용량 테이블의 bloat 제거에 필수. 단, 트리거와 같은 동시 변경은 신중히 처리해야 합니다.


참고 자료

PostgreSQL Production Operations Complete Guide 2025: Extensions, Tuning, Monitoring, HA

TL;DR

  • Extensions are PostgreSQL's true power: pg_stat_statements (query analysis), pgvector (AI), PostGIS (geo), TimescaleDB (time-series), pg_partman (partitioning)
  • Three key tuning parameters: shared_buffers (25% RAM), work_mem (sort/hash memory), effective_cache_size (50-75% RAM)
  • Autovacuum is critical: Misconfigured causes table bloat → disk explosion → slow queries. Tuning autovacuum_vacuum_scale_factor is essential
  • Patroni: De facto standard for PostgreSQL HA. Auto failover within 30 seconds
  • Backup: pgBackRest is 10x+ faster than pg_dump. WAL archiving + PITR is essential

1. Why PostgreSQL Remains Loved in 2025

1.1 Five Key Reasons

  1. Extensibility — 200+ official extensions. Vector, time-series, geo, all of them
  2. JSONB performance — Comparable or faster than MongoDB for JSON
  3. MVCC — No read-write conflicts (excellent concurrency)
  4. Standards compliant — Most faithful ANSI SQL implementation
  5. Open source — Zero license costs vs Oracle/SQL Server

1.2 Why PostgreSQL Is Standard Even in NewSQL Era

CockroachDB, YugabyteDB, Spanner emerged, but most use PostgreSQL wire protocol compatibility. This means PostgreSQL itself is in a stable position.


2. Essential Extensions Top 10

2.1 Operations Must-Have: pg_stat_statements

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = 'all'
pg_stat_statements.max = 10000

Usage:

-- TOP 10 slowest queries
SELECT
  substring(query, 1, 80) as query,
  calls,
  total_exec_time::int as total_ms,
  mean_exec_time::int as avg_ms,
  rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

This single query solves 80% of performance issues. You immediately see which queries consume the most time.

2.2 AI Era Essential: pgvector

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT,
  embedding vector(768)
);

CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);

SELECT id, content, 1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 10;

Why pgvector instead of dedicated Vector DB?

  • Leverage existing PostgreSQL ops knowledge
  • ACID transactions (vector + metadata consistency)
  • Cost savings (no separate infrastructure)

2.3 Time-Series: TimescaleDB

CREATE EXTENSION IF NOT EXISTS timescaledb;

CREATE TABLE metrics (
  time TIMESTAMPTZ NOT NULL,
  device_id TEXT,
  temperature FLOAT
);

SELECT create_hypertable('metrics', 'time', chunk_time_interval => INTERVAL '1 day');

SELECT add_retention_policy('metrics', INTERVAL '30 days');

2.4 Other Core Extensions

ExtensionPurpose
PostGISGeographic info
pg_partmanAuto partition management
pg_cronIn-DB scheduler
pgauditAudit logging
pglogicalLogical replication
citusHorizontal sharding
pg_repackLock-free table reorganization

3. Core Performance Tuning

3.1 Memory Parameters

ParameterRecommended (16GB RAM)Description
shared_buffers4GB (25% RAM)PostgreSQL's own cache
effective_cache_size12GB (75% RAM)OS cache estimate
work_mem64MBPer sort/hash memory
maintenance_work_mem1GBFor VACUUM/CREATE INDEX
wal_buffers16MBWAL buffer

Formula: shared_buffers + (work_mem × max_connections) ≤ RAM × 0.5

3.2 Checkpoint Tuning

checkpoint_timeout = 15min
checkpoint_completion_target = 0.9
max_wal_size = 4GB
min_wal_size = 1GB

3.3 Query Planner

random_page_cost = 1.1              # SSD (HDD is 4.0)
effective_io_concurrency = 200      # SSD/NVMe
default_statistics_target = 100

4. VACUUM and Autovacuum

4.1 Why VACUUM Is Needed

PostgreSQL's MVCC handles UPDATE as "INSERT new row + mark old row". Old rows aren't deleted immediately but remain as "dead tuples". VACUUM cleans them.

Without VACUUM:

  1. Table size keeps growing (disk explosion)
  2. Indexes point to dead tuples → slow queries
  3. Transaction ID wraparound — data corruption risk!

4.2 Autovacuum Monitoring

SELECT
  schemaname,
  relname,
  n_live_tup,
  n_dead_tup,
  ROUND(n_dead_tup::numeric / NULLIF(n_live_tup, 0), 2) AS dead_ratio,
  last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;

dead_ratio above 0.2 means autovacuum can't keep up.

4.3 Autovacuum Tuning

autovacuum = on
autovacuum_max_workers = 5
autovacuum_naptime = 30s
autovacuum_vacuum_scale_factor = 0.05
autovacuum_analyze_scale_factor = 0.02
autovacuum_vacuum_cost_limit = 2000

5. Monitoring and Debugging

5.1 Real-time Activity

-- Currently running queries
SELECT pid, usename, state, query_start, query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;

5.2 EXPLAIN ANALYZE

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE customer_id = 12345;

Key indicators:

  • Seq Scan vs Index Scan — index usage
  • actual rows vs estimated rows — statistics accuracy
  • Buffers: shared hit/read — cache hit rate
  • Execution Time — actual time

5.3 pgBadger — Log Analysis

log_min_duration_statement = 1000
pgbadger -j 4 /var/log/postgresql/postgresql-*.log -o report.html

6. High Availability — Patroni

6.1 How Patroni Works

  1. Each PostgreSQL node runs Patroni daemon
  2. Patroni renews leader lock in etcd
  3. Primary fails → lock expires → Patroni elects new Primary
  4. HAProxy/PgBouncer auto-routes to new Primary

6.2 Patroni Alternatives

ToolFeatures
PatroniMost popular, requires etcd
repmgrEnterpriseDB, simple
CloudNativePGK8s native (CNCF)
StolonContainer-friendly
pg_auto_failoverMicrosoft, simple

7. Backup and Recovery

7.1 pg_dump vs pgBackRest

pg_dumppgBackRest
SpeedSlow (single-thread)Fast (parallel)
IncrementalNoYes
Compressiongziplz4/zstd
PITRNoYes
S3 integrationNoYes

7.2 PITR (Point-in-Time Recovery)

pgbackrest --stanza=main \
  --type=time \
  --target="2025-04-15 14:30:00" \
  restore

This enables responses like "rewind to yesterday at 3:25:30 PM".


8. Common Pitfalls

8.1 Index Bloat

-- Unused indexes
SELECT
  schemaname || '.' || relname AS table,
  indexrelname AS unused_index,
  pg_size_pretty(pg_relation_size(indexrelid)) AS size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;

8.2 N+1 Queries

ORM's most common problem. Use pg_stat_statements to find duplicate queries called thousands of times.

8.3 Connection Explosion

PostgreSQL connection = separate process (~10MB each). Solution: PgBouncer (transaction mode recommended).


9. PostgreSQL 17 New Features (2024-09)

  • Improved incremental sort
  • Logical replication: failover slot sync
  • pg_basebackup: incremental backup support
  • VACUUM: memory-based dead tuple tracking (faster)
  • MERGE command: RETURNING clause added

Quiz

1. Why shouldn't shared_buffers be set to 50% of RAM?

Answer: PostgreSQL uses both its own shared_buffers and OS file system cache. Setting shared_buffers too high (1) reduces OS cache space causing double-caching inefficiency, (2) page replacement algorithm becomes inefficient, (3) backups/checkpoints slow down. 25% of RAM is generally optimal.

2. Signs that autovacuum can't keep up?

Answer: When n_dead_tup exceeds 20% of n_live_tup in pg_stat_user_tables. Solutions: (1) increase autovacuum_max_workers, (2) decrease autovacuum_vacuum_scale_factor (0.2 → 0.05), (3) decrease autovacuum_naptime, (4) per-table settings via ALTER TABLE.

3. Why is PgBouncer needed?

Answer: Each PostgreSQL connection is a separate process (~10MB). 1,000 connections = 10GB+ memory. Process creation/destruction is also costly. PgBouncer maintains a small pool of real connections and multiplexes client connections. Transaction pooling mode is most efficient but has prepared statements compatibility issues.

4. How does WAL archiving enable PITR?

Answer: WAL (Write-Ahead Log) records all data changes sequentially. WAL archiving copies these logs to external storage (S3, etc.). For recovery: (1) restore nearest base backup, (2) replay WAL up to the desired time. Result: point-in-time recovery. pgBackRest's --type=time automates this.

5. Difference between pg_repack and VACUUM FULL?

Answer: VACUUM FULL requires a table lock, unusable in production. pg_repack reorganizes tables without locks — copies data to a new table and swaps names. Essential for removing bloat from large tables in production.


References