PostgreSQL 19에서 data_checksums가 또 하나의 재시작 파라미터에서 벗어났다. 이제 실행 중인 클러스터에서, 재시작도 정지도 없이 page checksum을 켜고 끌 수 있다. SQL 함수 하나를 실행하면 background worker가 모든 page를 다시 쓰면서 checksum을 입히고, 그동안 클러스터는 평소처럼 트래픽을 받는다.

이 글은 The Build의 Christophe Pettus(크리스토프 페투스)가 정리한 “All Your GUCs in a Row: data_checksums”를 한국어로 풀고, 13년에 걸친 data_checksums의 진화사와 PostgreSQL 19가 정확히 무엇을 바꿨는지를 DBA 시선으로 본다. 그리고 “온라인"이라는 단어가 “공짜"나 “즉시"를 뜻하지 않는다는 점까지 함께 짚는다.

dbalog에는 PostgreSQL 19의 “재시작 없이 바꾼다” 계열 글이 이미 두 편 있다. wal_level이 고정값에서 동적 floor로 바뀐 이야기autovacuum_worker_slots로 worker 수를 재시작 없이 조절하는 이야기다. data_checksums의 온라인 전환은 그 흐름 위에 올라가는 또 한 칸이다.

data_checksums가 무엇을 막아주나

data_checksums는 page 단위 checksum 기능을 켜는 read-only GUC다. 켜져 있으면 PostgreSQL은 data page를 디스크에 쓸 때마다 checksum을 계산해 page 안에 함께 적고, 그 page를 다시 읽어 올릴 때 checksum을 검증한다.

값이 맞지 않으면 PostgreSQL은 깨진 데이터를 그대로 돌려주지 않고 error를 낸다. 이게 핵심이다. 막아주는 대상은 silent data corruption, 즉 조용히 번지는 손상이다.

  • bit rot — 디스크 위 데이터가 시간이 지나며 미세하게 망가지는 현상
  • 고장 직전의 디스크가 슬그머니 잘못된 비트를 돌려주는 경우
  • “썼다"고 응답해 놓고 실제로는 쓰지 않은 storage layer의 거짓말

이런 손상은 error 없이 흘러간다. checksum이 없으면 PostgreSQL은 깨진 page를 멀쩡한 데이터로 믿고 그대로 읽어 들이고, 그 위에 연산을 쌓는다. 문제를 알아챌 무렵엔 이미 backup에까지 손상이 번진 뒤다.

검증에 실패하면 PostgreSQL은 pg_stat_databasechecksum_failures 카운터를 올린다. DBA는 이 값을 모니터링해 손상이 처음 감지된 시점을 잡아낼 수 있다.

비용은 어떨까. 2013년 도입 당시엔 checksum 계산 부담이 켤 가치가 없을 만큼 크다고 봤다. 그래서 기본값이 off였다. 그 뒤로 하드웨어가 좋아지면서 오버헤드는 한 자릿수 퍼센트 초반대까지 내려왔고, 손상을 조기에 잡는 가치에 비하면 충분히 감당할 만한 수준이 됐다.

과거엔 켜기가 왜 고통이었나

문제는 비용이 아니라 켜는 방법이었다. PostgreSQL 18까지 data_checksums를 켜는 길은 둘뿐이었고, 둘 다 운영 클러스터에는 무겁다.

첫째, initdb 시점에 정하는 것이다. 클러스터를 처음 만들 때 checksum을 켜두면 그 클러스터는 평생 켜진 상태로 산다. 깔끔하지만 시점이 고약하다. 이미 몇 년째 돌고 있는 운영 클러스터에는 적용할 길이 없다. “처음부터 켰어야 했다"는 후회만 남는다.

둘째, pg_checksums로 오프라인 전환하는 것이다. PostgreSQL 12에서 추가된 이 명령은 멈춰 있는 클러스터의 checksum 설정을 바꿔준다. 강조점은 “멈춰 있는"이다.

# 반드시 클러스터를 먼저 정지한 상태에서 실행
pg_ctl -D /var/lib/pgsql/data stop
pg_checksums --enable -D /var/lib/pgsql/data
pg_ctl -D /var/lib/pgsql/data start

pg_checksums는 모든 heap과 index page를 한 장씩 읽어 checksum을 계산해 다시 쓴다. 멀티 테라바이트 클러스터라면 이 작업만 몇 시간이 걸린다. 그리고 그 몇 시간 내내 클러스터는 내려가 있어야 한다.

DBA에게 이건 사실상 “큰맘 먹고 잡는 점검 시간"이다. 서비스 중단 공지를 내고, 새벽 시간을 확보하고, 작업이 예상보다 길어질 경우까지 대비해야 한다. 그래서 많은 운영 클러스터가 checksum의 가치를 알면서도 “지금 켜기엔 다운타임이 부담"이라는 이유로 off인 채 남았다.

PostgreSQL 18에서 한 발 나아가긴 했다. initdb의 기본값이 checksum 켜짐으로 바뀌어, 새로 만드는 클러스터는 별도 조치 없이 checksum을 켠 채 출발한다. 하지만 이건 새 클러스터 이야기다. 이미 돌고 있는 클러스터의 고민은 그대로였다.

PostgreSQL 19의 온라인 전환

PostgreSQL 19는 마지막 매듭을 푼다. 클러스터를 멈추지 않고, 재시작도 없이, SQL 함수 호출만으로 checksum을 켜고 끈다. 새로 들어온 함수는 둘이다.

pg_enable_data_checksums(cost_delay integer DEFAULT 0, cost_limit integer DEFAULT 100)
pg_disable_data_checksums()

켜는 동작은 이렇게 실행한다.

SELECT pg_enable_data_checksums();

이 함수는 곧바로 반환된다. 밀리초 단위다. 하지만 그 시점에 checksum이 다 입혀진 것은 아니다. 실제 작업은 background에서 비동기로 흐른다.

내부 동작은 이렇다. background worker launcher가 데이터베이스마다 per-database worker를 띄운다. 이 worker는 storage를 가진 모든 relation의 buffer를 dirty로 표시한다. dirty page는 디스크로 다시 쓰일 때 checksum을 계산해 함께 적게 된다. 모든 데이터베이스의 모든 relation이 처리되고 나면, 그제야 data_checksums 상태가 on으로 넘어간다.

OS의 process 목록에서도 이 launcher와 worker가 보인다.

postgres: datachecksum launcher
postgres: datachecksum worker

진행 중에 data_checksums가 가질 수 있는 상태는 다음과 같다.

상태의미
offchecksum 꺼짐
inprogress-on켜는 중 — page 재작성 진행 중
on켜짐 — 모든 page 처리 완료
inprogress-off끄는 중

SHOW로 현재 상태를 확인한다.

SHOW data_checksums;
-- 진행 중:  inprogress-on
-- 완료 후:  on

끄는 동작도 같은 방식이다.

SELECT pg_disable_data_checksums();

이 모든 과정에서 클러스터는 멈추지 않는다. 읽기도 쓰기도 평소처럼 받는다. 다운타임이 사라진 것이 PostgreSQL 19의 핵심 변화다.

전환 흐름 비교

오프라인 전환과 온라인 전환의 차이를 한눈에 보면 이렇다.

flowchart TD
    A[checksum 켜야 함] --> B{PostgreSQL 버전}
    B -->|18 이하| C[클러스터 정지]
    C --> D[pg_checksums 실행]
    D --> E[클러스터 재기동]
    E --> F[on 완료]
    B -->|19 이상| G[함수 실행
클러스터 가동 중] G --> H[background worker가
page 재작성] H --> F

PostgreSQL 18 이하의 경로는 정지-작업-재기동이라는 다운타임 구간을 통과해야 한다. PostgreSQL 19는 그 구간 자체가 없다. 작업은 가동 중인 클러스터 위에서 background로 흐른다.

상태 전이로 보면 켜는 과정은 다음과 같이 움직인다.

flowchart LR
    A[off] -->|함수 실행| B[inprogress-on]
    B -->|page 재작성 완료| C[on]

여기서 DBA가 기억할 한 가지. inprogress-on은 “켜지는 중"이지 “켜짐"이 아니다. 모든 page가 처리되기 전까지는 on으로 넘어가지 않는다.

DBA 관점 — “온라인"이 “즉시"는 아니다

함수가 밀리초 만에 반환된다고 해서 작업이 끝난 것은 아니다. 이 지점에서 운영 실수가 갈린다. Christophe Pettus는 이 작업의 무게를 “minor version 업그레이드와 전체 클러스터 VACUUM FULL 사이 어디쯤"으로 보고 계획하라고 권한다.

기억할 점을 정리하면 이렇다.

  • 진짜 작업은 background에 있다. 함수 반환은 시작 신호일 뿐이다. 멀티 테라바이트 클러스터에서 모든 page 재작성은 몇 시간이 걸린다.
  • 다른 작업과 자원을 다툰다. page 재작성은 autovacuum, 평상시 워크로드, backup 작업과 같은 디스크 I/O를 두고 경쟁한다. burst IOPS를 쓰는 클라우드 인스턴스라면 burst 예산을 일찍 소진하고 throttle 구간에 들어간다.
  • 중간에 끊으면 mixed state로 남는다. 일부 page는 checksum이 입혀지고 일부는 아닌 상태다. 이 상태 자체는 안전하지만, 작업이 끝나기 전까지 data_checksumson으로 넘어가지 않는다. 오래 머물 상태는 아니다.

그래서 켜는 함수에는 throttle 제어가 붙어 있다. cost_delaycost_limit은 autovacuum의 vacuum cost 의미를 그대로 따른다. worker가 cost_limit만큼 작업 단위를 쌓을 때마다 cost_delay 밀리초씩 쉬게 해서, 평상시 워크로드에 주는 압박을 낮춘다.

-- worker가 자원을 덜 차지하도록 천천히 진행
SELECT pg_enable_data_checksums(cost_delay => 1, cost_limit => 3000);

운영 현장에서 잡을 체크리스트는 단순하다.

  • 트래픽이 한가한 시간대를 골라 시작한다.
  • autovacuum이 급한 작업을 들고 있지 않은지 확인한다.
  • backup window와 겹치지 않게 한다.
  • 진행 중에는 pg_stat_io로 I/O 부하를, replication을 쓴다면 replica lag을 함께 본다.

PostgreSQL 19의 이번 변화를 앞선 두 글과 나란히 놓으면 방향이 또렷하다. wal_level은 재시작 파라미터에서 동적 floor로, autovacuum worker 수는 재시작 없이 조절 가능하게, 그리고 이제 data_checksums는 클러스터를 멈추지 않고 켜고 끄는 대상이 됐다. “운영 중에 바꾸려면 재시작/정지가 필요하던 설정"의 목록이 한 칸씩 줄고 있다.

다만 방향이 같다고 비용까지 같진 않다. wal_level의 effective level 전환은 다음 checkpoint면 끝나지만, data_checksums의 온라인 전환은 디스크 위 모든 page를 다시 쓰는 무거운 작업이다. “재시작이 사라졌다"와 “부담 없이 켤 수 있다"는 다른 말이다. 다운타임은 없앴지만, I/O 비용과 소요 시간은 그대로 DBA의 계획표 위에 남는다.

정리

  • data_checksums는 page 단위 checksum으로 silent data corruption을 조기에 잡아주는 기능이다. 실패는 pg_stat_database.checksum_failures로 드러난다.
  • PostgreSQL 18까지는 initdb 시점에 고정하거나, 클러스터를 정지하고 pg_checksums로 오프라인 전환해야 했다. 운영 클러스터엔 다운타임이 부담이었다.
  • PostgreSQL 18부터 initdb 기본값이 켜짐으로 바뀌어, 새 클러스터는 별도 조치 없이 checksum을 켠 채 출발한다.
  • PostgreSQL 19는 pg_enable_data_checksums() / pg_disable_data_checksums()로 가동 중인 클러스터에서 재시작 없이 켜고 끌 수 있게 했다. background worker가 모든 page를 다시 쓰며, 진행 중 상태는 inprogress-on / inprogress-off로 보인다.
  • 다운타임은 사라졌지만 작업 자체는 무겁다. 멀티 테라바이트 클러스터에서 몇 시간이 걸리고 다른 I/O와 경쟁하므로, cost_delay / cost_limit throttle과 한가한 시간대 시작이 필요하다.

출처