
MSK 커넥터와 Debezium을 활용해 CDC 환경을 구축하다 보면, 결국 PostgreSQL의 WAL 내부 동작을 깊이 이해해야 하는 순간이 오게 된다. 특히 Debezium은 논리적 디코딩 방식으로 WAL을 읽기 때문에, 복제 슬롯 지연, REPLICA IDENTITY, 그리고 체크포인트와의 상호작용 등을 이해하는 게 중요하다.
본 글에서는 백엔드 개발자의 시선에서 PostgreSQL 17을 기준으로 WAL의 관리 방식과 논리적 디코딩 아키텍처를 정리한다.
1. WAL이 해결하는 문제
2. WAL 데이터 구조
3. LSN (Log Sequence Number)
4. WAL 쓰기 흐름
5. 체크포인트와 WAL 재활용
6. 스트리밍 복제 vs 논리적 복제
7. 논리적 디코딩과 복제 슬롯
8. Debezium은 WAL을 어떻게 읽는가?
9. REPLICA IDENTITY, CDC가 망가지는 1순위 함정
10. CDC 운영에서 자주 마주치는 WAL 이슈
11. 핵심 포인트 (Q&A 정리)
12. 참고 자료
1. WAL이 해결하는 문제
WAL의 출발점은 성능과 데이터 보존 사이의 절충이다. 트랜잭션이 변경한 데이터 페이지를 매번 디스크에 직접 동기화하는 작업은 무척 무겁다. 반대로 동기화를 미루면 크래시가 발생했을 때 메모리에만 있던 데이터가 모두 사라진다.
PostgreSQL은 이 문제를 다음과 같은 방식으로 해결한다.
- 메모리 우선 변경: 데이터 페이지는 우선 메모리 영역인 공유 버퍼에서만 변경하고, 데이터 파일로의 디스크 동기화는 뒤로 미룬다.
- WAL 선기록: 변경 사항을 WAL이라는 단일한 추가 전용 로그에 먼저 기록한다. 트랜잭션이 커밋되는 시점에는 무거운 데이터 파일 대신 이 WAL 파일만 디스크에 동기화한다.
- 일괄 동기화: 실제 데이터 파일은 주기적인 체크포인트 시점에만 일괄적으로 동기화한다.
- 재실행 복구: 시스템이 갑자기 종료되더라도, 마지막 체크포인트 시점부터 WAL 로그를 순서대로 다시 재생(재실행)하여 데이터의 일관성을 복구한다.
핵심 규칙 (WAL Rule)
변경된 데이터 페이지를 실제 데이터 파일에 쓰기 전에, 해당 변경 사항을 기술한 WAL 레코드에 기록해야 한다.
이 규칙 덕분에 크래시가 발생하더라도 PostgreSQL은 디스크에 저장된 마지막 WAL 위치까지 데이터를 복구할 수 있다. 이 모델은 트랜잭션의 원자성과 지속성을 보장한다. 동시성 제어 측면에서의 격리성과 런타임 일관성은 다중 버전 동시성 제어(MVCC)와 잠금 메커니즘이 주도하지만, 크래시 복구 시점의 데이터 일관성은 WAL이 뒷받침한다.
2. WAL 데이터 구조
WAL은 가변 길이 레코드들이 끝없이 이어지는 추가 전용 스트림 형태를 가진다. 디스크에는 기본적으로 16 메가바이트 크기의 세그먼트 파일 단위로 쪼개져 저장된다.

- 레코드 구성: 각 WAL 레코드는 헤더와 실제 데이터 내용을 담은 페이로드로 구성된다. 헤더에 포함된 순환 중복 검사(CRC) 필드가 레코드 전체의 무결성을 검증하며, 레코드를 읽을 때마다 매번 확인한다.
- 페이로드 내용: 테이블 페이지(힙)나 인덱스 페이지 등 변경된 블록의 구체적인 변경 내용이 들어간다. 상황에 따라 페이지 전체의 이미지가 포함되기도 한다.
- 리소스 매니저: 레코드 헤더에는 이 로그를 어떤 리소스(테이블 변경, 인덱스 변경, 트랜잭션 마커 등)가 생성했는지 분류하는 리소스 매니저 식별자가 존재한다. 논리적 디코딩 시 이 식별자를 보고 어떤 레코드를 행 변경 메시지로 변환할지 결정한다.
3. LSN (Log Sequence Number)
- LSN은 WAL 스트림 내에서의 64비트 바이트 위치 값(오프셋)이다.
- LSN은 절대로 줄어들지 않고 단조 증가하므로, 데이터베이스의 모든 변경 사항을 완벽한 시간 순서로 정렬하는 척도가 된다.
- 두 LSN의 차이를 구하면 그 사이의 정확한 바이트 크기가 나온다. Debezium이 복제 지연을 바이트 단위로 계산할 때 이 성질을 활용한다.
- PostgreSQL 내에서는 0/15CB8B0과 같은 16진수 형태의 pg_lsn 타입으로 노출된다.

💡 Debezium 관점의 팁
Debezium이 커넥터 상태로 내부 저장소에 기록하는 가장 핵심적인 정보가 바로 이 LSN이다. 커넥터가 일시적으로 중단되었다가 다시 기동 되어도 이 LSN 위치부터 WAL을 이어받아 처리하므로, 카프카 오프셋 기반 지연의 본질은 결국 PostgreSQL LSN의 차이와 같다.
4. WAL 쓰기 흐름
트랜잭션 내부에서 데이터 변경이 일어나 디스크에 영속화되기까지는 여러 백그라운드 프로세스가 역할을 분담하여 처리한다.

- walwriter: WAL 버퍼(wal_buffers)의 내용을 주기적으로 운영체제 페이지 캐시에 쓰고 디스크에 동기화한다. 트랜잭션 커밋 시 백엔드 프로세스가 직접 동기화해야 하는 부담을 줄여주어 전반적인 처리량을 끌어올린다.
- checkpointer: 체크포인트가 트리거되면 공유 버퍼에 있던 변경된 페이지들을 실제 데이터 파일에 기록하고 완전히 동기화한다.
- bgwriter: 평소에 주기적으로 공유 버퍼의 더티 페이지를 운영체제 페이지 캐시까지만 내려보낸다. 실제 디스크 동기화는 bgwriter가 직접 수행하지 않고 이후 체크포인터가 일괄적으로 처리하며, 이는 백엔드 프로세스가 새로운 페이지를 위한 공간을 쉽게 확보할 수 있도록 돕는 역할을 한다.
기본 설정인 synchronous_commit = on 상태에서는 트랜잭션이 커밋될 때 WAL의 디스크 동기화가 강제된다. 이 디스크 동기화 비용이 전체 트랜잭션 처리량의 상한선을 결정하므로, 성능 최적화를 위해 그룹 커밋이나 비동기 커밋(synchronous_commit = off) 옵션을 검토하기도 한다.
5. 체크포인트와 WAL 재활용
체크포인트는 메모리의 더티 페이지를 실제 데이터 파일에 물리적으로 일치시키는 결정적인 동기화 지점이다. 이때 내부적으로 두 가지 LSN을 명확히 구분해야 한다.

- 재실행(REDO) 포인트: 체크포인트가 시작되는 순간 결정되는 LSN이다. 체크포인트가 최종 완료되면 이 LSN 이전의 모든 변경 사항은 실제 데이터 파일에 반영되었음이 보장되므로, 크래시 복구 시 이 포인트부터 재실행을 시작한다.
- 체크포인트 레코드 LSN: 체크포인트 작업이 모두 완료되면 WAL 스트림에 최종 마무리 레코드를 기록하는데, 이때의 LSN이다. 데이터베이스가 다시 부팅될 때 이 레코드를 찾아내어 역으로 재실행 포인트의 위치를 파악한다.
체크포인트는 한 번에 모든 더티 페이지를 디스크에 쓰면 심각한 입출력 병목이 발생하므로, checkpoint_completion_target 설정에 따라 다음 체크포인트 전까지 오랜 시간에 걸쳐 천천히 분산하여 작업을 진행한다.
새로운 체크포인트가 성공적으로 완료되면 기존 재실행 포인트 이전의 WAL 세그먼트 파일들은 원칙적으로 삭제되거나 재활용 대상이 된다. 하지만 다음 중 하나라도 해당하는 조건이 있다면 디스크에서 지워지지 않고 강제로 보존된다.
- 아카이브 미완료: WAL 아카이브 파일로 복사되지 않고 대기 중인 상태인 경우 (archive_status에 ready 파일 존재)
- 복제 슬롯의 보존 요구: 특정 복제 슬롯의 restart_lsn이 해당 위치를 가리키고 있는 경우
- 최소 보존 크기 설정: wal_keep_size 설정으로 최소한의 보존 분량을 강제한 경우
⚠️ CDC 운영의 핵심 주의사항
Debezium 커넥터가 정상 동작할 때는 복제 슬롯이 LSN을 계속 갱신하므로 오래된 WAL 파일이 자연스럽게 재활용된다. 하지만 Debezium 커넥터가 다운되거나 심각한 지연이 발생하면, 복제 슬롯이 과거의 LSN 위치에 그대로 멈춰 서게 된다. 이로 인해 체크포인트가 수행되어도 오래된 WAL 파일이 절대 지워지지 않고 계속 쌓여 결국 pg_wal 디스크가 가득 차는 대형 장애로 이어진다.
6. 스트리밍 복제 vs 논리적 복제
데이터베이스 복제 아키텍처를 설계할 때 가장 자주 비교되는 주제이다. Debezium은 이 중 논리적 복제 메커니즘을 기반으로 동작한다.
| 구분 | 스트리밍 복제 | 논리적 복제 |
| 복제 단위 | 물리적 바이너리 페이지 변경 (WAL 통째로 전송) | 행 단위 변경 메시지 (WAL을 분석 및 디코딩) |
| 필수 설정 | wal_level = replica 이상 | wal_level = logical 필수 |
| 호환성 | 동일한 주(Major) 버전, 동일한 운영체제 및 아키텍처 필수 | 다른 주 버전 간 가능, 이기종 데이터베이스 간 복제 가능 |
| 주요 용도 | 고가용성 구성, 읽기 전용 부하 분산 | CDC 환경 구축, 특정 테이블 부분 복제, 무중단 업그레이드 |
| 대표적인 예시 | 대기(Standby) 서버 구성 | Debezium 커넥터 연동 |
7. 논리적 디코딩과 복제 슬롯
7.1 논리적 디코딩의 작동 원리
기본적으로 WAL 스트림 내부에는 여러 트랜잭션의 변경 사항들이 시간 순서대로 복잡하게 뒤섞여 있다. 논리적 디코딩은 이 뒤섞인 WAL 로그를 최종 커밋된 트랜잭션 단위로 다시 묶어서 행 단위의 변경 메시지로 변환한 뒤 구독자(Debezium)에게 스트리밍 프로토콜로 전달하는 기술이다. 이때 PostgreSQL 내부적으로 재정렬 버퍼라는 자료구조를 사용한다. 각 트랜잭션의 변경 분을 커밋 마커가 도착할 때까지 이 버퍼에 모아두는 역할을 한다.

- 롤백 트랜잭션 자동 필터링: 트랜잭션이 최종 롤백되면 재정렬 버퍼에 모아두었던 변경 사항을 디코딩하지 않고 그대로 폐기한다. 따라서 Debezium에는 최종 커밋된 데이터만 전달된다.
- 메모리 초과 시 디스크 스필: 임시로 모아두는 데이터의 양이 logical_decoding_work_mem(PostgreSQL 13 이상, 기본값 64MB)을 초과하면 해당 내용을 디스크에 임시 파일로 저장(디스크 스필)한다. 대용량 배치 트랜잭션이 수행될 때 복제 지연과 디스크 입출력이 폭증하는 원인이 된다.
- 인덱스 복제 제외: WAL 자체에는 인덱스 변경 사항도 기록되지만, 논리적 디코딩은 테이블의 행 변경(힙)과 트랜잭션 마커만을 분석하여 메시지를 구성하므로 인덱스 변경 정보는 최종 디코딩 메시지에서 제외된다.
7.2 복제 슬롯
복제 슬롯은 각각의 구독자(Debezium 커넥터 등)가 원본 데이터베이스와 어디까지 데이터를 주고받았는지 안전하게 동기화하기 위한 논리적 이정표이다. 내부적으로 두 가지 중요한 LSN 좌표를 추적한다.

- restart_lsn: PostgreSQL이 이 슬롯을 위해 디스크에 무조건 보존해야 하는 가장 오래된 WAL의 위치다. 이 LSN보다 과거의 WAL 파일만 안전하게 재활용될 수 있다.
- confirmed_flush_lsn: 구독자(Debezium)가 "여기까지는 내가 안전하게 수신해서 처리했다"라고 원본 데이터베이스에 최종 확인을 보내온 위치다.
💡 두 LSN의 관계
논리적 복제 슬롯에서는 일반적으로 restart_lsn ≤ confirmed_flush_lsn 관계가 성립한다. PostgreSQL이 현재 진행 중인 트랜잭션을 정상적으로 디코딩하기 위해, 구독자가 이미 확인한 시점(confirmed_flush_lsn)보다 조금 더 과거의 WAL 시작점까지 슬롯이 붙잡아두어야 하기 때문이다.
8. Debezium은 WAL을 어떻게 읽는가?

- 스냅샷 단계 (최초 시작 시) 처음 커넥터가 구동되면 복제 슬롯을 생성하는 것과 동시에 데이터의 일관성이 확보된 스냅샷을 획득한다. 트랜잭션 격리 수준을 REPEATABLE READ로 열고 대상 테이블 전체를 SELECT *로 읽어와 카프카 토픽으로 최초 발행한다. 이 스냅샷을 잡은 시점의 LSN이 곧 다음 단계인 스트리밍의 시작점이 되므로, 스냅샷이 진행되는 동안 실시간으로 들어온 변경 데이터도 유실 없이 자연스럽게 이어받을 수 있다.
- 스트리밍 단계 (실시간 변경 분 추출) 생성된 논리적 복제 슬롯을 통해 실시간으로 디코딩된 행 변경 메시지를 수신한다. 해당 내용들을 카프카의 테이블별 전용 토픽으로 전달한다. 카프카에 정상적으로 발행 완료되었음이 확인되면, PostgreSQL 서버에 처리 완료 메시지를 보낸다. 이를 통해 슬롯의 confirmed_flush_lsn이 갱신되고, 엔진은 안심하고 이전 WAL 파일들을 재활용하게 된다.
8.2 필수 PostgreSQL 설정
Debezium CDC가 정상적으로 동작하려면 원본 데이터베이스의 설정 파일들을 아래와 같이 조정해야 한다.



8.3 출력 플러그인 (Output Plugin) 선택
Debezium 2.x 버전의 PostgreSQL 커넥터는 공식적으로 두 가지 플러그인만 지원한다.
- pgoutput: PostgreSQL 10 버전부터 내장된 기본 플러그인이다. 별도의 플러그인 설치 과정이 필요 없기 때문에 AWS RDS나 Aurora, GCP Cloud SQL 같은 매니지드 데이터베이스 환경에서는 사실상 표준으로 사용된다.
- decoderbufs: 프로토콜 버퍼 형태로 데이터를 출력하는 Debezium 제공 전용 플러그인이다. 데이터베이스 서버에 직접 바이너리를 빌드하고 설치해야 하므로 매니지드 환경에서는 사용할 수 없다.
🔥 중요 운영 팁
Debezium 2.x 커넥터부터는 플러그인 이름 설정(plugin.name)의 기본값이 pgoutput으로 변경되었다. 따라서 명시하지 않아도 내장 플러그인으로 정상 기동 하지만, 클라우드 매니지드 환경과 사내 인프라 환경 전체에서 명확한 설정을 유지하기 위해 plugin.name=pgoutput을 설정 파일에 선언해 두는 방식을 권장한다.
9. REPLICA IDENTITY: CDC가 망가지는 1순위 함정
wal_level = logical 상태에서 UPDATE나 DELETE 트랜잭션이 발생할 때, WAL 레코드에는 '변경 전 데이터'를 식별하기 위한 기존 컬럼 값(변경 전 이미지)이 함께 기록되어야 한다. 이 변경 전 이미지를 어디까지 로그에 실을지 결정하는 테이블별 설정이 바로 REPLICA IDENTITY이다.
새롭게 바뀐 값(변경 후 이미지)은 언제나 기록되지만, 변경 전 이미지는 이 설정에 종속된다. 기본값인 DEFAULT 상태에서는 오직 기본키 컬럼만 변경 전 이미지로 포함시킨다. 즉, Debezium 메시지의 before 필드를 열어보면 기본키 외의 일반 컬럼들은 모두 비어 있게 된다.
REPLICA IDENTITY의 4가지 모드와 특성
| 모드 | WAL에 기록되는 변경 전 이미지 범위 | 주요 용도 및 특징 |
| DEFAULT | 기본키 컬럼들만 기록 | 기본 설정. 테이블에 기본키가 정상적으로 존재하면 동작하는 데 문제 없음. |
| USING INDEX <idx> | 지정된 고유 인덱스의 컬럼들만 기록 | 테이블에 기본키는 없지만 대체할 수 있는 고유 인덱스가 존재할 때 사용. |
| FULL | 변경 전 모든 컬럼의 값을 통째로 기록 | Debezium 메시지의 before 영역을 완벽히 채워야 할 때 사용. (대부분의 CDC 아키텍처에서 강력 권장) |
| NOTHING | 아무런 키 정보도 기록하지 않음 | 이 상태에서 테이블이 발행 대상에 등록되면, 원본 서버가 해당 테이블에 대한 UPDATE/DELETE 자체를 거부하므로 권장하지 않음. |

⚠️ FULL 설정의 비용적 트레이드오프와 토스트 함정
- WAL 크기 폭증: FULL 모드로 설정하면 아주 사소한 단 하나의 컬럼 수정 상황이 발생해도 그 행에 속한 모든 컬럼의 데이터가 WAL에 적재된다. 거대한 텍스트 필드나 대용량 바이너리 컬럼이 포함된 테이블이라면 WAL 파일의 물리적인 크기가 순식간에 늘어나 가용 디스크를 압박할 수 있다.
- 토스트 미변경 컬럼 표식의 한계: 행 내부에 외부 저장 기법(토스트)으로 관리되는 거대 컬럼이 존재할 때, 해당 트랜잭션에서 이 거대 컬럼을 건드리지 않고 다른 일반 컬럼만 수정했다면 FULL 모드라 할지라도 WAL 레코드에 거대 컬럼의 실제 값이 복사되지 않는다. 대신 시스템 성능 향상을 위해 "변경 없음"이라는 특별한 식별용 표식만 남긴다. 이로 인해 카프카 메시지 소비단에서 마치 값이 유실된 것처럼 오해하기 쉬우므로, 컨슈머 구현 시 이전 값을 그대로 유지하는 상태로 해석하도록 예외 처리를 해야 한다.
10. CDC 운영에서 자주 마주치는 WAL 이슈

- 원인: Debezium 커넥터 애플리케이션의 장애로 장시간 멈춰 있거나, 컨슈머의 처리 속도가 원본 데이터 생성 속도를 따라가지 못해 복제 슬롯이 비활성화(active = false)된 채 방치되는 경우가 가장 흔하다. 슬롯의 restart_lsn이 과거 특정 시점에 고정되어 버리면, PostgreSQL 엔진은 그 이후의 모든 WAL 파일들을 지우지 못하고 강제로 보존한다.
- 대처 및 예방:
- 커넥터 프로세스 최우선 복구: 가장 올바른 정공법이다. 다운된 태스크를 즉시 되살려 지연된 LSN 구간을 빠르게 소화하면 디스크 공간이 자연스럽게 해제된다.
- 복제 슬롯 삭제: 단시간 내에 프로세스를 복구할 수 없고 마스터 데이터베이스 전체의 가용성이 무너질 위기라면, 우선 복제 슬롯을 날려야 한다. SELECT pg_drop_replication_slot('slot_name'); 명령으로 슬롯을 삭제하면 묶여 있던 WAL 파일들이 즉시 정리된다. 다만 이 조치 이후에는 커넥터 복구 시 전체 데이터 초기 스냅샷 단계를 처음부터 다시 수행해야 한다.
- max_slot_wal_keep_size 옵션 적용: PostgreSQL 13 버전부터 제공되는 이 옵션을 통해 슬롯이 보존할 수 있는 최대 WAL 용량을 제한해 두어야 한다. 슬롯이 지정된 용량을 넘겨서까지 옛날 WAL을 붙잡고 있으면 엔진이 해당 복제 슬롯을 강제로 무효화하여 데이터베이스 다운 장애를 예방한다.

- B-1 현상: Debezium 카프카 토픽의 변경 전 필드에 기본키 외의 일반 컬럼 값이 전부 빈 값으로 반환된다.
- 원인: 해당 테이블의 REPLICA IDENTITY 옵션이 DEFAULT로 설정되어 있을 때 나타나는 정상적인 동작이다. 싱크 컨슈머 로직에서 수정 전 일반 컬럼 값을 활용해야 한다면 설정 상태를 변경해야 한다.
- 해결책: ALTER TABLE 테이블명 REPLICA IDENTITY FULL; 쿼리를 실행한다.
- B-2 현상: 애플리케이션 서비스 운영 도중 갑자기 원본 데이터베이스의 UPDATE/DELETE 로직들이 실패하며 에러가 발생한다.
- 원인: 테이블에 기본키가 없는 상태에서 REPLICA IDENTITY 설정을 DEFAULT나 NOTHING 상태로 둔 채, 해당 테이블을 CDC 대상 발행 목록에 추가했을 때 발생한다. 이 상황에서 수정이나 삭제 연산이 들어오면 PostgreSQL 엔진 자체가 트랜잭션을 거부한다. 이는 실시간 서비스에 즉각적인 장애를 유발하므로 매우 위험하다.
- 해결책: 고유성을 대체할 수 있는 유니크 인덱스가 존재한다면 ALTER TABLE ... REPLICA IDENTITY USING INDEX 인덱스명; 처리를 하고, 마땅한 인덱스가 없는 구조라면 명시적으로 FULL 모드로 변환해야 한다.

- 원인: 논리적 디코딩 기술은 트랜잭션이 완벽하게 커밋 마커를 찍기 전까지 모든 행 변경 이벤트들을 일단 메모리 재정렬 버퍼에 하나씩 차곡차곡 쌓아둔다. 이때 한 번에 대량의 행을 밀어 넣는 대형 배치 작업이 수행되면 가용 메모리 한도인 logical_decoding_work_mem(기본값 64MB)을 넘어서게 된다. 결국 초과 분량은 로컬 디스크의 임시 파일 공간으로 쏟아지며 지연 수치가 폭증한다.
- 대처 방안:
- 가장 근본적인 해결책은 애플리케이션 단의 배치 로직을 쪼개어 하나의 큰 트랜잭션이 아닌 만 건 단위의 작은 트랜잭션 단위로 끊어서 분할 커밋하도록 코드를 수정하는 것이다.
- 데이터베이스 서버 자체의 여유 메모리 용량이 풍부하다면 logical_decoding_work_mem 파라미터 값을 증액하여 메모리 안에서 최대한 모든 재정렬 처리를 끝내도록 유도할 수 있다.

- PostgreSQL의 논리적 디코딩 구조는 스키마 변경 사항만을 전용으로 추적하는 별도의 제어 스트림 메시지를 발행하지 않는다. 대신 변환을 진행하는 실시간 시점마다 카탈로그 정보를 직접 조회하는 모델을 취한다.
- 일반적인 ALTER TABLE ... ADD COLUMN 같은 단순한 컬럼 추가 DDL은 구조가 변경된 직후 들어오는 다음 실시간 행 레코드 메시지에 새 스키마 정보가 자연스럽게 반영되어 카프카 토픽으로 잘 넘어간다.
- 타입 변경 및 컬럼 삭제: 하위 호환성을 단번에 깨뜨리는 성격의 스키마 변경은 카프카 컨슈머 애플리케이션이나 다운스트림 싱크 대상의 포맷을 먼저 검토하고 반영해야 에러를 막을 수 있다.
- 새로운 테이블 추가 시: pgoutput 플러그인을 사용하는 환경이라면 새 테이블을 원본 데이터베이스의 발행 객체에 포함시켜 주어야 한다. Debezium 설정 중 publication.autocreate.mode = filtered 옵션을 적극 활용하면 자동으로 새로운 대상 테이블을 감지해 바인딩해 주며, 수동 제어 환경이라면 관리자가 직접 ALTER PUBLICATION ... ADD TABLE 테이블명; 명령을 내려주어야 카프카 스트림이 시작된다.

- 원인: 대상 테이블의 누적 데이터 덩어리가 거대한 경우, 커넥터 초기 기동 시 수행하는 SELECT * 형태의 정적 풀 스캔 트랜잭션은 완료되기까지 수 시간 이상 소요될 수 있다. 이 스냅샷 조회가 완전히 끝을 맺기 전까지는 실시간 변경 트랜잭션을 수집하는 스트리밍 단계로 진입하지 못하므로, 복제 작업이 완전히 멈춰버린 것처럼 보인다.
- 대처 방안:
- Debezium이 제공하는 증분 스냅샷 기술을 사용해야 한다. 신호 전달용 시그널링 테이블에 명령 데이터를 삽입하여 트리거하는 방식으로, 테이블의 영역을 청크 단위로 쪼개어 실시간 스트리밍 처리와 정적 스냅샷 백필 처리를 병행해 내는 방식이다.
- 과거의 데이터 히스토리를 굳이 카프카 스트림으로 처음부터 전부 재현해 낼 필요가 없는 환경이라면, 과감하게 snapshot.mode = never 설정을 적용해 스냅샷 단계를 완전히 생략할 수 있다. 이 경우 커넥터는 구동된 바로 그 시점의 실시간 변경 데이터(LSN)부터 스트리밍을 시작하게 된다.
11. 핵심 포인트 (Q&A 정리)
Q1. PostgreSQL의 WAL이 무엇이고 왜 필수적으로 존재해야 하나요?
A. 데이터 변경을 데이터 파일에 직접 쓰는 대신 별도의 추가 전용 로그(WAL)에 먼저 기록하는 메커니즘이다. 트랜잭션 원자성과 지속성을 보장하며, 데이터 파일을 매 트랜잭션마다 디스크에 동기화하면 성능이 대폭 저하되므로 WAL 한 곳만 동기화하고 데이터 파일 동기화는 체크포인트 시점으로 미룬다. 시스템 중단 시 마지막 체크포인트부터 WAL 기록을 다시 재생(재실행)하는 방식으로 데이터를 유실 없이 일관되게 복구한다.
Q2. LSN(Log Sequence Number)이란 구체적으로 무엇인가요?
A. WAL 전체 스트림 내에서 개별 레코드가 가지는 절대적인 64비트 바이트 위치 오프셋 값이다. 시계열 순서로 단조 증가하므로 데이터의 선후 관계를 판별하는 기준이 되며, 두 LSN의 차이값을 연산하면 정확한 바이트 크기가 나오므로 Debezium 등 복제 시스템의 지연 규모를 측정할 때 핵심 지표로 활용된다.
Q3. 체크포인트는 무엇이며, bgwriter 프로세스와는 어떻게 다른가요?
A. 체크포인트는 메모리 상의 더티 페이지들을 실제 물리 데이터 파일로 동기화하는 작업이며, 전용 checkpointer 프로세스가 수행한다. 이와 달리 bgwriter는 평소에 메모리의 더티 페이지들을 디스크 캐시 레이어까지만 미리 내려보내 두어 백엔드 프로세스들이 작업할 여유 메모리 공간을 항시 확보해 주는 별개의 백그라운드 흐름이다.
Q4. 스트리밍 복제와 논리적 복제의 핵심 차이는 무엇인가요?
A. 스트리밍 복제는 원본 데이터베이스의 바이너리 물리 페이지 변경 데이터 블록을 통째로 전송하는 물리 복제 방식으로, 동일한 메저 버전과 OS 아키텍처 사이의 고가용성 미러링 용도로 사용된다. 반면 논리적 복제는 WAL 스트림 내용을 행 단위 변경 메시지로 변환하여 전송하므로 다른 버전 간, 혹은 이기종 데이터베이스 간 결합이 가능하여 대규모 CDC 아키텍처나 데이터 파이프라인의 핵심 토대로 활용된다.
Q5. Debezium은 원본 PostgreSQL의 데이터를 기술적으로 어떤 방식으로 가져오나요?
A. 데이터베이스의 복제 레벨 설정을 wal_level = logical 상태로 상향하고 전용의 논리적 복제 슬롯을 생성한다. 이후 기본 탑재 출력 플러그인인 pgoutput을 통해 실시간으로 변환되어 나오는 인서트/업데이트/딜리트 트랜잭션 메시지를 스트리밍 프로토콜로 수신하여 카프카의 각 테이블 전용 토픽으로 발행한다.
Q6. 복제 슬롯이 들고 있는 restart_lsn과 confirmed_flush_lsn 정보는 어떤 관계인가요?
A. confirmed_flush_lsn은 수신 측인 Debezium 커넥터가 "여기까지 정상적으로 처리했다"고 응답을 보내온 확정 좌표다. restart_lsn은 데이터베이스 엔진이 해당 복제 슬롯을 위해 디스크 영역에서 삭제하지 않고 보존해야 하는 가장 오래된 WAL의 한계선 좌표다. 현재 실행 중인 트랜잭션의 디코딩 추적을 위해 언제나 restart_lsn ≤ confirmed_flush_lsn 관계를 유지하게 된다.
Q7. REPLICA IDENTITY 속성이 CDC 운영에서 왜 중요한가요?
A. 업데이트나 딜리트 트랜잭션 처리 시 변환되는 WAL 로그 안에 '변경되기 직전의 원래 행 데이터'를 어디까지 포함해 줄 것인가를 통제하는 속성이기 때문이다. 기본값인 DEFAULT 환경에서는 오직 기본키 정보만 전송하므로 컨슈머가 일반 필드의 옛날 값을 읽지 못해 테이블을 FULL 모드로 변경해야 하는 경우가 많다. 만약 기본키가 없는 테이블을 이 고려 없이 발행 목록에 추가하면 원본 실서비스 데이터베이스의 UPDATE/DELETE 쿼리 자체가 거부되는 심각한 부작용을 유발한다.
Q8. Debezium 운영 중 pg_wal 디렉터리가 가득 찼다면 무엇부터 의심하나요?
A. 소속 태스크들이 다운되었거나 심각한 병목에 갇혀 활성 상태가 꺼진 채 과거의 LSN 지점에 머물러 있는 복제 슬롯이 있는지 1순위로 조회해야 한다. 슬롯이 과거 시점을 계속 붙잡고 있으면 체크포인터가 돌아도 오래된 WAL 세그먼트 파일들이 절대로 삭제되지 않는다. 즉시 커넥터를 재가동해 진척률을 당기거나, 복구가 어렵다면 슬롯을 청소해야 데이터베이스 다운을 막을 수 있다.
Q9. 원본에 대형 배치성 트랜잭션이 흘러 들어오면 왜 Debezium 파이프라인 지연 수치가 폭발하나요?
A. 논리적 디코딩 구조는 트랜잭션이 완전히 커밋을 완료하기 전까지 모든 하위 행 레코드 이벤트를 메모리 상의 재정렬 버퍼 공간에 임시로 모아둔다. 이때 배치 작업의 규모가 지정된 logical_decoding_work_mem 임계 용량을 넘어서면 초과 데이터가 로컬 디스크의 임시 파일 공간으로 떨어지기 때문에, 입출력 병목으로 인해 전체적인 디코딩 메시지 가공 처리 속도가 저하된다.
Q10. Full Page Writes가 무엇이고 왜 이 설정을 기본적으로 켜두어야 하나요?
A. 운영체제가 파일 시스템에 데이터를 쓰는 기본 단위 크기와 PostgreSQL 데이터 페이지 블록 단위 크기의 불일치로 인해, 디스크 동기화 도중 정전이 발생하면 페이지 블록의 일부만 손상되어 쓰이는 찢어진 페이지 현상이 발생할 수 있습니다. 이를 극복하기 위해 체크포인트가 완료된 이후 각각의 데이터 페이지가 최초로 변경되는 시점에 해당 페이지의 전체 원본 이미지를 WAL 레코드에 통째로 기록하는 기술이다. 이 덕분에 크래시 복구 시 깨진 페이지 위에 WAL의 전체 이미지를 덮어씌워 복구가 가능해진다. 단, 이로 인해 체크포인트 직후 일시적으로 WAL 파일 생성량이 급증할 수 있다.
12. 참고 자료
https://www.postgresql.org/docs/17/wal.html
https://www.postgresql.org/docs/17/logicaldecoding.html
https://www.postgresql.org/docs/17/logical-replication.html
https://www.postgresql.org/docs/17/view-pg-replication-slots.html
https://debezium.io/documentation/reference/stable/connectors/postgresql.html
https://debezium.io/documentation/faq/
Frequently Asked Questions
Debezium is an open source distributed platform for change data capture. Start it up, point it at your databases, and your apps can start responding to all of the inserts, updates, and deletes that other apps commit to your databases. Debezium is durable a
debezium.io
Debezium connector for PostgreSQL :: Debezium Documentation
Tombstone events When a row is deleted, the delete event value still works with log compaction, because Kafka can remove all earlier messages that have that same key. However, for Kafka to remove all messages that have that same key, the message value must
debezium.io
52.19. pg_replication_slots
52.19. pg_replication_slots # The pg_replication_slots view provides a listing of all replication slots that currently exist on the database cluster, along …
www.postgresql.org
Chapter 29. Logical Replication
Chapter 29. Logical Replication Table of Contents 29.1. Publication 29.2. Subscription 29.2.1. Replication Slot Management 29.2.2. Examples: Set Up Logical Replication 29.2.3. …
www.postgresql.org
Chapter 47. Logical Decoding
Chapter 47. Logical Decoding Table of Contents 47.1. Logical Decoding Examples 47.2. Logical Decoding Concepts 47.2.1. Logical Decoding 47.2.2. Replication Slots 47.2.3. …
www.postgresql.org
Chapter 28. Reliability and the Write-Ahead Log
Chapter 28. Reliability and the Write-Ahead Log Table of Contents 28.1. Reliability 28.2. Data Checksums 28.2.1. Off-line Enabling of Checksums 28.3. Write-Ahead …
www.postgresql.org
'Dev' 카테고리의 다른 글
| 코빗은 왜 체결 엔진에 Rust를 선택했을까? (feat. 추리) (1) | 2026.04.22 |
|---|---|
| Kafka 간 맞추기 (1) | 2026.04.20 |
| 샤갈!! 저 Temporal Workflow 엔진 도입했어요!! (0) | 2026.04.12 |
| 와 CDC(Change Data Capture)! Debezium 로그 기반 아시는구나! (0) | 2026.04.08 |
| Kafka 간보기 (1) | 2026.04.07 |