PostgreSQL 데이터 체크섬이 잡는 것과 놓치는 것

PostgreSQL 데이터 체크섬은 데이터 페이지를 읽을 때 손상을 감지합니다. 검사가 일어나는 시점, pg_checksums로 오프라인에서 켜는 방법, pg_stat_database 카운터, amcheck와 백업이 여전히 맡는 범위를 정리했습니다.

개발데이터베이스PostgreSQL

디스크, 컨트롤러, 파일 시스템은 PostgreSQL이 쓴 것과 다른 바이트를 아무 오류 없이 돌려줄 수 있습니다. initdb 문서는 데이터 체크섬을 “그렇지 않으면 조용히 지나갈 I/O 시스템의 손상”을 감지하는 데 돕는 기능이라고 설명합니다. 이 글은 무엇이 검사되는지, 검사가 언제 일어나는지, 이미 있는 클러스터에서 체크섬을 켜는 방법, 실패가 어디에 기록되는지, 그리고 체크섬이 지켜 주지 않는 것을 차례로 봅니다.

기준은 2026년 10월 18일 현재 최신 안정 버전인 PostgreSQL 18(마이너 릴리스 18.6) 문서입니다. PostgreSQL 19는 2026년 10월 15일에 RC 1이 발표된 단계라 이 글에서 다루지 않습니다.

체크섬이 붙는 곳

테이블과 인덱스는 고정 크기 페이지에 저장됩니다(페이지 구조는 HOT 업데이트 글에서 다뤘습니다). 페이지 헤더의 두 번째 필드 pd_checksum은 2바이트이고, 체크섬이 켜져 있으면 여기에 페이지 체크섬이 들어갑니다. 문서에 따르면 체크섬은 페이지를 쓸 때 갱신되고, 페이지를 읽을 때마다 검증됩니다.

대상은 데이터 페이지뿐입니다. 내부 데이터 구조와 임시 파일은 보호하지 않는다고 문서가 직접 적고 있습니다.

  • pg_xact, pg_subtrans, pg_multixact, pg_serial, pg_notify, pg_stat, pg_snapshots는 직접 체크섬되지 않습니다. 이 구조가 영구적인 경우에는 크래시 복구 때 최근 변경을 다시 만들 수 있도록 WAL 레코드가 기록되고, 그 WAL 레코드가 대신 보호됩니다.
  • 큰 쿼리가 정렬, 구체화, 중간 결과에 쓰는 임시 파일은 체크섬되지 않고, 이 파일의 변경에 대한 WAL도 기록되지 않습니다.

WAL은 따로 보호됩니다. 각 WAL 레코드에는 CRC-32C가 붙는데, 레코드를 쓸 때 값을 정하고 크래시 복구, 아카이브 복구, 복제 때 검사합니다. WAL에 기록된 전체 페이지 이미지는 데이터 체크섬 설정과 관계없이 항상 체크섬으로 보호됩니다.

체크섬은 클러스터 단위 설정입니다. 데이터베이스나 테이블 하나에만 켤 수는 없습니다.

켜져 있는지 확인하기

SHOW data_checksums;

data_checksums는 클러스터에서 체크섬이 켜져 있는지 알려 주는 읽기 전용 파라미터입니다.

기본값은 클러스터를 만든 버전에 따라 다릅니다. PostgreSQL 18부터 initdb는 기본으로 체크섬을 켜고, 새로 생긴 --no-data-checksums 옵션으로 끌 수 있습니다. 18 릴리스 노트가 이것을 기본값의 변경으로 적고 있으니, 이전 버전의 initdb로 -k 없이 만든 클러스터라면 체크섬이 꺼져 있을 수 있습니다. 같은 릴리스 노트는 pg_upgrade가 기존 클러스터와 새 클러스터의 체크섬 설정이 같기를 요구한다고도 적습니다. 체크섬이 없는 클러스터를 18로 올리려면 새 클러스터를 --no-data-checksums로 만들거나, 아래에서 볼 pg_checksums로 기존 클러스터에서 먼저 체크섬을 켜야 합니다.

비용에 대해 initdb 문서는 체크섬을 켜면 “작은 성능 저하가 있을 수 있다”고만 하고 수치는 주지 않습니다. 직접 잴 수 있는 비용 하나는 WAL 양입니다. 체크섬이 켜져 있으면 힌트 비트 갱신이 항상 WAL에 기록되고 wal_log_hints 설정은 무시됩니다. 문서는 체크섬이 없는 클러스터에서 wal_log_hints를 켜 보면 체크섬을 켰을 때 WAL 기록이 얼마나 늘어날지 시험할 수 있다고 설명합니다. wal_log_hints는 서버 시작 시에만 설정할 수 있습니다.

검사는 언제 일어나나

검증은 읽을 때 일어납니다. ignore_checksum_failure 문서에 따르면 읽는 도중 체크섬 실패를 감지하면 PostgreSQL은 보통 오류를 보고하고 현재 트랜잭션을 중단합니다.

여기서 문서가 직접 쓰지는 않은 추론이 하나 나옵니다. 한 번도 읽히지 않는 페이지는 검사되지도 않는다는 점입니다. 거의 조회하지 않는 오래된 파티션이 손상되어도 누군가 읽기 전까지는 드러나지 않을 수 있습니다. 평소 워크로드의 읽기보다 넓게 체크섬을 검증하는 도구가 두 가지 있습니다. 하나는 온라인, 하나는 오프라인에서 씁니다.

  • pg_basebackup(온라인): 원본 서버에 체크섬이 켜져 있으면 베이스 백업을 받는 동안 기본으로 체크섬을 검증하므로, 검사 범위는 백업이 전송하는 데이터입니다. 실패하면 종료 상태가 0이 아니지만, --no-clean을 준 것처럼 백업은 지워지지 않습니다. 실패는 pg_stat_database에도 기록됩니다. --no-verify-checksums로 이 검증을 끌 수 있습니다.
  • pg_checksums --check(오프라인): 문서에 따르면 클러스터의 모든 파일을 훑습니다. 서버를 깨끗하게 종료한 뒤에만 실행할 수 있습니다. 체크섬 오류가 없으면 종료 상태가 0이고, 실패가 하나라도 있으면 0이 아닙니다.

체크섬은 손상을 감지합니다. 페이지를 고쳐 준다는 설명은 문서 어디에도 없으므로, 복구는 백업 같은 다른 사본에 달려 있습니다.

이미 있는 클러스터에서 체크섬 켜기

PostgreSQL 18에서 initdb 이후에 체크섬을 켜거나 끄는 방법은 pg_checksums뿐이고, 서버가 멈춰 있어야 합니다. 아래 명령은 설명을 위한 예시입니다. 이 글을 쓰면서 실행하지 않았고, 경로는 자리 표시용입니다.

# 서버를 깨끗하게 종료한 뒤
pg_checksums --enable --progress -D /path/to/data

실행 전에 알아 둘 것들입니다.

  • 서버가 깨끗하게 종료된 상태여야 합니다.
  • 켤 때는 체크섬이 바뀌는 모든 릴레이션 파일 블록을 제자리에서 다시 쓰므로, 큰 클러스터에서는 오래 걸릴 수 있습니다. 그동안 서버나 데이터 디렉터리에 쓰는 다른 프로그램을 시작하면 안 되며, 그러면 데이터가 사라질 수 있습니다.
  • 끌 때(--disable)는 pg_control 파일만 고칩니다.
  • --no-sync는 파일이 디스크에 닿기를 기다리지 않고 돌아오므로, 이후 운영체제가 죽으면 데이터 디렉터리가 손상될 수 있습니다. 문서는 테스트용이며 운영 환경에서는 쓰지 말라고 합니다.
  • 켜거나 끄는 도중 pg_checksums가 중단되거나 강제 종료되어도 클러스터의 체크섬 설정은 그대로 남고, 같은 작업을 다시 실행하면 됩니다.

복제 환경은 따로 신경 써야 합니다. pg_rewind처럼 릴레이션 파일 블록을 노드 사이에서 그대로 복사하는 도구를 쓰는데 노드마다 체크섬 설정이 다르면, 체크섬이 맞지 않는 페이지가 생길 수 있습니다. 문서는 모든 클러스터를 멈추고 한꺼번에 똑같이 바꾸라고 권합니다. 스탠바이를 모두 없애고 프라이머리만 바꾼 뒤 스탠바이를 처음부터 다시 만드는 방법도 안전하다고 적혀 있습니다.

아무것도 바꾸지 않고 검사만 하려면 기본 모드인 --check를 씁니다. 이때도 깨끗한 종료가 필요합니다.

pg_checksums --check --progress -D /path/to/data

실패가 기록되는 곳

실행 중인 서버가 감지한 체크섬 실패와 pg_basebackup이 찾은 실패는 pg_stat_database 뷰에 기록됩니다.

SELECT coalesce(datname, '(shared objects)') AS db,
       checksum_failures,
       checksum_last_failure
FROM pg_stat_database
WHERE checksum_failures > 0;
  • checksum_failures: 그 데이터베이스(또는 공유 객체)에서 감지된 데이터 페이지 체크섬 실패 수
  • checksum_last_failure: 마지막 실패를 감지한 시각
  • 체크섬이 꺼져 있으면 둘 다 NULL입니다. 모든 행에서 이 열이 NULL이면 체크섬이 꺼진 클러스터입니다.
  • 이 뷰에는 데이터베이스마다 한 행, 그리고 공유 객체용 행이 하나 더 있습니다. 그 행은 datid가 0이고 datname이 NULL이라서 위 쿼리에서 coalesce를 썼습니다.

checksum_failures가 늘면 알림을 보내도록 해 두면, 애플리케이션 로그의 오류를 누군가 알아채기를 기다리지 않아도 됩니다. 이것은 문서가 정한 절차가 아니라 제안입니다. 오프라인 pg_checksums --check를 돌린 결과는 이 뷰에서 찾지 마세요. 이 도구는 서버가 멈춘 상태에서 실행되고, 문서는 결과를 명령 출력과 종료 상태(실패가 하나라도 있으면 0이 아님)로만 설명하며 pg_stat_database는 언급하지 않습니다.

손상 뒤에 쓰는 설정

문서는 페이지 손상에서 복구할 때 쓰는 설정 두 가지를 언급합니다. 둘 다 기본값은 off이고, 슈퍼유저나 해당 SET 권한이 있는 사용자만 바꿀 수 있습니다.

ignore_checksum_failure는 체크섬이 켜져 있을 때만 의미가 있습니다. 오류를 경고로 바꾸고 처리를 계속하므로, 블록 헤더가 멀쩡하다면 손상되지 않은 행을 꺼낼 수 있을지도 모릅니다. 헤더 자체가 손상되었으면 이 설정을 켜도 오류가 납니다. 문서는 이 설정이 크래시를 일으키거나, 손상을 퍼뜨리거나 숨기거나, 다른 심각한 문제로 이어질 수 있다고 경고합니다.

zero_damaged_pages는 손상된 페이지 헤더를 감지했을 때 그 페이지를 메모리에서 0으로 채우고 처리를 계속합니다. 그 페이지에 있던 행은 모두 사라집니다. 문서는 손상된 페이지에서 데이터를 되살릴 희망을 버리기 전까지는 켜지 말라고 합니다. 0으로 채운 페이지는 디스크에 강제로 쓰이지 않으므로, 설정을 다시 끄기 전에 그 테이블이나 인덱스를 다시 만들라고 권합니다.

두 설정 모두 손상이 생긴 뒤 데이터를 꺼내기 위한 것이고, 평소에 켜 둘 설정이 아닙니다.

체크섬이 대신하지 못하는 것

페이지가 형식에 맞고, 내부적으로 일관되고, 자기 체크섬과도 맞는데 내용은 논리적으로 틀릴 수 있습니다. amcheck 문서는 두 가지 예를 듭니다. TOAST 테이블에 대응하는 항목이 없는 본 테이블의 TOAST 값, 그리고 가장 오래된 유효 트랜잭션 ID보다 오래된 트랜잭션 ID를 가진 튜플입니다. 같은 문서는 체크섬은 못 잡고 amcheck는 잡을 수 있는 실패도 나열합니다.

  • 잘못된 연산자 클래스 구현이 만든 구조적 불일치
  • 인덱스와 테이블 사이의 불일치(heapallindexed 검증을 할 때)
  • PostgreSQL의 접근 방식, 정렬, 트랜잭션 관리 코드에 있을지 모르는 버그로 인한 손상
  • 불량 RAM이나 메모리 서브시스템 문제로 인한 손상

메모리에 대해서는 신뢰성 장에서 PostgreSQL이 정정 가능한 메모리 오류를 막지 않으며, ECC 이상의 RAM을 쓴다고 가정한다고 적습니다. 체크섬은 페이지를 쓸 때 계산되므로, 그 시점에 메모리 안의 바이트가 이미 틀렸다면 체크섬도 틀린 바이트에 맞춰 계산됩니다.

정리하면 체크섬은 저장 장치에 다녀오는 사이 바이트가 바뀐 것을 알아차리는 데 쓰입니다. 릴레이션 안과 릴레이션 사이의 논리적 일관성은 amcheck(verify_heapam, bt_index_check, bt_index_parent_check, gin_index_check)가, 손상 뒤 데이터를 되돌리는 일은 백업이 맡습니다. 프로그램이 파일에 쓸 때 운영체제가 무엇을 약속하는지는 write()와 fsync를 다룬 글에 정리해 두었습니다.

참고한 문서

링크와 내용은 2026년 10월 18일에 확인했습니다.

블로그로