Post

PostgreSQL 14에서 17로 올릴 때 달라지는 것들 — 그리고 컷오버 직후 CPU가 치솟는 이유

메이저 3개를 건너뛰는 14→17 업그레이드에서 챙겨야 할 버전별 핵심 변화(MERGE, 논리 복제, VACUUM 개편, JSON_TABLE, 증분 백업)와 실제로 걸리는 호환성 함정, 그리고 업그레이드 직후 CPU 스파이크의 구조적 원인을 정리했다.

PostgreSQL 14를 운영하다가 17로 올리기로 하면, 메이저 버전 3개(15, 16, 17)를 한 번에 건너뛰게 된다. 릴리스 노트 세 개를 다 읽는 건 부담스러우니, 결론부터 정리하면 이렇다.

  • 개발 관점 최대 변화: MERGE(15)와 SQL/JSON — 특히 JSON_TABLE(17)
  • 운영 관점 최대 변화: VACUUM 메모리 개편(17), 증분 백업(17), public 스키마 권한(15)
  • 그리고 기능과 별개로, 컷오버 직후 CPU가 치솟는 구조적 함정이 있다. 이건 마지막에 따로 다룬다.

이 글은 2026년 7월 20일 기준, 각 버전의 공식 릴리스 발표와 릴리스 노트를 바탕으로 정리했다.

PostgreSQL 15 (2022.10)

MERGE 문

SQL 표준의 MERGE가 드디어 들어왔다. 조건에 따라 INSERT, UPDATE, DELETE를 한 문장으로 처리한다. INSERT ... ON CONFLICT와 겹쳐 보이지만, MERGE는 두 테이블을 비교해서 맞추는 배치 지향 작업에 더 맞는 도구다.

public 스키마 권한 변경

보안 관점에서 가장 큰 변화다.

  • 새로 만드는 데이터베이스에서 일반 유저의 public 스키마 CREATE 권한이 제거됐다.
  • public 스키마 소유자도 부트스트랩 슈퍼유저에서 pg_database_owner로 바뀌었다.

여기서 중요한 건 적용 범위다. pg_upgrade나 덤프 복원으로 올린 기존 DB는 기존 권한이 그대로 유지된다. 업그레이드했다고 기존 DB가 갑자기 깨지지는 않는다. 대신 업그레이드 이후 새로 만드는 DB부터 동작이 달라진다. 애플리케이션이 별도 GRANT 없이 public 스키마에 테이블을 만들어 왔다면 여기서 걸린다.

그 외

  • 통계 수집기(stats collector) 프로세스가 사라지고 공유 메모리 기반 누적 통계 시스템으로 교체됐다.
  • 서버 로그 jsonlog 포맷, 정렬 성능 개선, zstd/lz4 압축 지원 확대.

PostgreSQL 16 (2023.9)

16의 중심 테마는 논리 복제다.

  • 스탠바이에서 논리 복제가 가능해졌다. 바쁜 primary 대신 standby를 논리 복제 소스로 쓸 수 있다.
  • 구독자 측에서 대형 트랜잭션을 병렬 워커로 적용할 수 있다 (max_parallel_apply_workers_per_subscription).
  • 기본 키 없는 테이블도 순차 스캔 대신 B-tree 인덱스로 대상 행을 찾을 수 있게 됐다.

쿼리·관측성 쪽도 꾸준히 좋아졌다.

  • FULL JOIN, RIGHT JOIN 병렬 실행. SELECT DISTINCT 증분 정렬, 윈도우 함수 최적화.
  • pg_stat_io 뷰 추가 — I/O를 백엔드 유형별로 훨씬 세밀하게 볼 수 있다.
  • pg_hba.conf 정규식 매칭 지원.

PostgreSQL 17 (2024.9)

VACUUM 메모리 구조 전면 개편

운영자 입장에서 체감이 가장 큰 항목이다. VACUUM의 내부 메모리 구조가 새로 설계되어, 공식 발표 기준 메모리를 최대 20배 적게 쓰면서 속도도 개선됐다. maintenance_work_mem을 크게 잡고도 불안했던 대형 테이블 vacuum 부담이 줄어든다.

JSON_TABLE

JSON_TABLE()로 JSON 데이터를 테이블 형태로 펼쳐서 일반 테이블처럼 조인할 수 있다. SQL/JSON 표준의 생성자·질의 함수도 대거 들어와서, JSON을 다루는 SQL이 눈에 띄게 깔끔해진다.

증분 백업

마지막 백업 이후 변경된 페이지만 캡처하는 증분 백업이 도입됐다. pg_combinebackup으로 전체 백업을 재구성한다. 수백 GB 이상이라면 백업 저장 비용과 백업 윈도우가 크게 줄어든다.

그 외

  • 15의 MERGERETURNING 절과 뷰 업데이트 지원이 붙어 완성형이 됐다.
  • COPY 대량 export 최대 2배 개선, ON_ERROR 옵션으로 에러 행을 건너뛰며 임포트 계속 가능.
  • pg_upgrade가 논리 복제 슬롯·구독 상태를 보존한다. 단, 이 혜택은 17→18 업그레이드부터다. 14→17 시점에는 해당 없다.
  • B-tree 다중 값 IN 검색 최적화, 고동시성 쓰기 처리량 개선, sslnegotiation=direct.

14→17에서 실제로 걸리는 호환성 변경

기능보다 이쪽이 중요하다. 실전에서 걸리기 쉬운 것만 추렸다.

1. 백업 함수 개명 + exclusive backup 제거 (15). pg_start_backup()/pg_stop_backup()pg_backup_start()/pg_backup_stop()으로 바뀌었고, exclusive backup 모드 자체가 제거됐다. pg_backup_start_time(), pg_is_in_backup()도 사라졌다. 자체 백업 스크립트가 있다면 반드시 점검해야 한다.

2. Python 2 지원 제거 (15). plpython2u/plpythonu가 제거됐다. Python 저장 프로시저는 plpython3u로 이전.

3. 유지보수 작업의 search_path 제한 (17). ANALYZE, CLUSTER, CREATE INDEX, REFRESH MATERIALIZED VIEW, REINDEX, VACUUM이 안전한 search_path로 실행된다. expression index나 materialized view가 쓰는 함수가 기본 스키마 밖 객체를 참조한다면, 함수에 SET search_path를 명시해야 한다. 업그레이드 후 REINDEX나 복원에서 갑자기 함수를 못 찾는다면 대부분 이 항목이다.

4. 업그레이드 절차. 메이저 업그레이드이므로 pg_upgrade 또는 dump/restore가 필요하다. 공식 문서도 건너뛰는 중간 버전(15, 16)의 릴리스 노트 호환성 섹션을 모두 읽으라고 권고한다. PostGIS 같은 익스텐션 버전 호환도 별도 확인 대상이다.

컷오버 직후 CPU가 치솟는 이유

업그레이드를 마치고 트래픽을 열었더니 CPU가 급증하는 사례가 많다. “17이 느려졌나?”를 의심하기 쉽지만, 대부분은 메이저 업그레이드의 구조적 함정이다. AWS와 Azure의 공식 트러블슈팅 문서가 공통으로 짚는 패턴이기도 하다.

1. 플래너 통계가 이전되지 않는다 — 1순위 원인

pg_upgrade는 데이터는 옮기지만 플래너 통계(pg_statistic)는 옮기지 않는다. 14→15든 16→17이든 전 구간 동일하다. 컷오버 직후 쿼리 플래너는 통계가 0인 상태로 동작한다. 인덱스 스캔 대신 시퀀셜 스캔을 고르고, 비효율적인 조인 전략을 선택하고, CPU가 즉시 치솟는다.

해결은 단순하다. 업그레이드 직후, 트래픽을 열기 전에 모든 DB에 ANALYZE를 돌린다.

1
vacuumdb --all --analyze-only -j 4

함정은 시간이다. 수억 행 테이블이 많은 대형 DB라면 ANALYZE 자체가 몇 시간 걸릴 수 있다. 다운타임 계획에 ANALYZE 시간까지 포함해야 하고, 급하면 vacuumdb --analyze-in-stages로 낮은 정밀도 통계부터 단계적으로 채우는 방법이 있다.

참고로 이 문제는 PostgreSQL 18에서야 해결됐다 — pg_upgrade가 통계를 보존한다. 14→17 경로에서는 혜택이 없으므로 ANALYZE가 필수다.

2. 캐시 초기화 — 일시적

업그레이드하면 버퍼 캐시와 플랜 캐시가 비워진다. 자주 쓰는 데이터가 다시 올라올 때까지 일시적으로 I/O와 CPU가 높다. 시간이 지나면 자연 해소되고, pg_prewarm으로 핵심 테이블을 미리 올려 완화할 수 있다.

3. 플랜 회귀 — 통계를 채워도 남는 경우

메이저 3개를 건너뛰면 옵티마이저 동작 자체가 많이 바뀌어서, ANALYZE 이후에도 특정 쿼리가 이전과 다른(더 나쁜) 플랜을 타는 경우가 있다. 접근 순서는 이렇다.

  • pg_stat_statements로 업그레이드 전후 상위 CPU 쿼리 비교
  • 문제 쿼리를 EXPLAIN (ANALYZE, BUFFERS)로 플랜 비교
  • 복잡한 쿼리에서 JIT 컴파일이 CPU를 태우는 케이스가 있으니 jit = off 테스트도 체크리스트에 넣기
  • 병렬화·work_mem 등 버전별 기본값 변경 점검. Aurora라면 QPM(Query Plan Management)으로 플랜 고정도 선택지

정리

관점핵심
개발MERGE (15→17 완성), JSON_TABLE과 SQL/JSON (17)
운영VACUUM 메모리 최대 20배 절감 (17), 증분 백업 (17), pg_stat_io (16)
복제스탠바이 논리 복제 (16), 병렬 적용 (16), 슬롯 보존 pg_upgrade (17)
호환성public 스키마 권한 (15), 백업 함수 개명 (15), 유지보수 search_path (17)
컷오버통계 미이전 → ANALYZE 필수, 캐시 워밍업, 플랜 회귀 점검

런북 한 줄로 요약하면: 업그레이드 → ANALYZE 완료 → 트래픽 개방. 컷오버 직후 CPU 증가의 대부분은 17이 느려서가 아니라, ANALYZE 전에 트래픽을 받아서다.

참고 자료

This post is licensed under CC BY 4.0 by the author.