<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Checkpoint on jyukki's Blog</title><link>https://jyukki.com/tags/checkpoint/</link><description>Recent content in Checkpoint on jyukki's Blog</description><generator>Hugo -- 0.147.0</generator><language>ko-kr</language><lastBuildDate>Sun, 19 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jyukki.com/tags/checkpoint/index.xml" rel="self" type="application/rss+xml"/><item><title>백엔드 커리큘럼 심화: PostgreSQL WAL, Checkpoint, Replication Lag를 한 흐름으로 보는 운영 기준</title><link>https://jyukki.com/learning/deep-dive/deep-dive-postgresql-wal-checkpoint-replication-lag/</link><pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate><guid>https://jyukki.com/learning/deep-dive/deep-dive-postgresql-wal-checkpoint-replication-lag/</guid><description>PostgreSQL에서 WAL 생성, 체크포인트, 복제 지연을 따로 보지 않고 하나의 쓰기 경로로 묶어 판단하는 실무 기준을 정리합니다.</description><content:encoded><![CDATA[<p>운영 중인 PostgreSQL이 갑자기 느려질 때 팀이 자주 놓치는 것이 있습니다. 애플리케이션에서는 <code>commit</code>이 느려지고, 모니터링에서는 디스크 쓰기와 복제 지연이 같이 튀는데, 원인을 각각 다른 문제로 나눠 보는 것입니다. 하지만 실제로는 <strong>WAL 생성, checkpoint, replication lag가 하나의 쓰기 파이프라인</strong>으로 연결돼 있는 경우가 많습니다. WAL이 빨리 쌓이면 checkpoint 압력이 커지고, 디스크 flush가 밀리면 commit latency가 올라가고, standby가 그 WAL을 제때 재생하지 못하면 read replica가 뒤처집니다.</p>
<p>그래서 실무에서는 &ldquo;복제가 느리다&rdquo;, &ldquo;체크포인트가 잦다&rdquo;, &ldquo;DB 쓰기가 무겁다&quot;를 따로 보지 않고 한 흐름으로 묶어 판단해야 합니다. 이 글은 <a href="/learning/deep-dive/deep-dive-db-replication-read-write-splitting/">DB 복제 &amp; 읽기/쓰기 분리</a>, <a href="/learning/deep-dive/deep-dive-postgresql-autovacuum-tuning/">PostgreSQL Autovacuum 튜닝</a>, <a href="/learning/deep-dive/deep-dive-backup-dr-strategy/">Backup/DR 전략</a>에서 이어지는 관점으로, WAL에서 replica 적용까지를 운영 숫자 중심으로 정리합니다.</p>
<h2 id="이-글에서-얻는-것">이 글에서 얻는 것</h2>
<ul>
<li>WAL, checkpoint, replication lag가 왜 한 문제처럼 움직이는지 설명할 수 있습니다.</li>
<li>commit latency, WAL 생성량, lag seconds, lag bytes를 어떤 우선순위로 봐야 하는지 기준을 잡을 수 있습니다.</li>
<li>PostgreSQL 튜닝에서 파라미터만 만지기 전에 어떤 워크로드 조건을 먼저 확인해야 하는지 판단할 수 있습니다.</li>
<li>사용자 영향이 생기기 전에 경고를 올리고, 30분 안에 완화하는 운영 체크리스트를 가져갈 수 있습니다.</li>
</ul>
<h2 id="핵심-개념이슈">핵심 개념/이슈</h2>
<h3 id="1-wal은-단순-로그가-아니라-쓰기-경로의-중심이다">1) WAL은 단순 로그가 아니라 쓰기 경로의 중심이다</h3>
<p>PostgreSQL의 모든 변경은 먼저 WAL(Write Ahead Log)에 기록됩니다. 이 구조 덕분에 크래시 복구와 복제가 가능하지만, 반대로 말하면 <strong>WAL 경로가 막히면 쓰기 성능과 복제 안정성이 함께 흔들립니다.</strong> 애플리케이션이 느끼는 <code>commit</code> 지연은 단순히 테이블이 커서 생기는 것이 아니라, 아래 세 요소가 겹친 결과일 수 있습니다.</p>
<ol>
<li>트랜잭션이 너무 큰 batch로 묶여 WAL burst를 만든다.</li>
<li>checkpoint가 너무 자주 돌거나 너무 급하게 flush한다.</li>
<li>standby가 WAL 적용 속도를 따라가지 못해 lag가 누적된다.</li>
</ol>
<p>핵심은 WAL 생성량 자체보다 <strong>생성 속도와 소비 속도의 균형</strong>입니다. 초당 20MB WAL이 꾸준히 나오는 시스템은 버틸 수 있어도, 평소 2MB/s이던 시스템이 배치 5분 동안 120MB/s로 튀면 checkpoint와 replica가 동시에 밀릴 수 있습니다. 그래서 WAL은 평균보다 피크를 보는 편이 맞습니다.</p>
<h3 id="2-checkpoint는-메모리를-디스크로-정리하는-과정이지만-잘못-맞추면-지연-스파이크를-만든다">2) checkpoint는 메모리를 디스크로 정리하는 과정이지만, 잘못 맞추면 지연 스파이크를 만든다</h3>
<p>checkpoint는 dirty buffer를 디스크에 내리고, 복구 시작 지점을 당기는 작업입니다. 문제는 이 과정이 너무 자주 발생하면 쓰기 I/O가 급증하고, 너무 늦으면 crash recovery 시간과 WAL 보관량이 커진다는 점입니다. 결국 checkpoint는 &ldquo;적을수록 좋다&quot;도 아니고 &ldquo;자주 해야 안전하다&quot;도 아닙니다.</p>
<p>실무에서 먼저 볼 기준은 아래 정도가 출발점으로 괜찮습니다.</p>
<ul>
<li><code>checkpoints_req</code>가 <code>checkpoints_timed</code>보다 자주 많아지면, 시간 기반보다 <strong>WAL 용량 한도에 먼저 걸리고 있다</strong>는 뜻입니다.</li>
<li><code>checkpoint_write_time + checkpoint_sync_time</code>가 평소 대비 2배 이상 튀고 같은 시점에 <code>commit p95</code>가 함께 상승하면, checkpoint pressure를 의심할 가치가 큽니다.</li>
<li>대화형 서비스에서 <code>commit p95</code>가 20ms를 넘는 구간이 5분 이상 지속되면 애플리케이션 재시도와 커넥션 정체가 붙기 시작합니다.</li>
</ul>
<p>여기서 중요한 의사결정 순서는 <code>파라미터 조정</code>보다 <code>쓰기 패턴 확인</code>이 먼저라는 점입니다. 10만 건 UPDATE를 한 트랜잭션으로 밀어 넣는 배치라면 <code>max_wal_size</code>만 올려도 해결이 제한적입니다. 이런 경우는 <a href="/learning/deep-dive/deep-dive-online-ddl-expand-contract/">온라인 DDL Expand/Contract</a>나 <a href="/learning/deep-dive/deep-dive-batch-idempotency-reprocessing/">배치 멱등성/재처리 플레이북</a>처럼 작업 단위를 줄이는 쪽이 먼저입니다.</p>
<h3 id="3-replication-lag는-복제-기능-문제가-아니라-wal-소비-속도-부족-신호다">3) replication lag는 &ldquo;복제 기능 문제&quot;가 아니라 WAL 소비 속도 부족 신호다</h3>
<p>standby는 primary가 만든 WAL을 받아서 재생합니다. 따라서 lag는 네트워크만의 문제가 아니라, <strong>standby CPU, 디스크, 긴 replay 지점, vacuum 충돌, 대형 트랜잭션 재생 비용</strong>이 함께 만든 결과입니다. 그래서 lag를 초 단위 하나만 보면 판단이 늦습니다.</p>
<p>함께 봐야 할 핵심 지표는 최소 네 가지입니다.</p>
<ul>
<li><code>lag seconds</code>: 사용자 체감과 운영 경보에 직결</li>
<li><code>lag bytes</code>: 대형 burst를 빠르게 감지하는 데 유리</li>
<li><code>WAL generation rate</code>: primary가 얼마나 빠르게 로그를 만들고 있는지</li>
<li><code>replay/apply throughput</code>: standby가 실제로 얼마나 따라가고 있는지</li>
</ul>
<p>추천 출발 기준은 아래처럼 두 단계로 나누는 편이 현실적입니다.</p>
<ul>
<li><strong>대화형 서비스 경고선</strong>: lag 5초 초과 3분 지속 또는 lag bytes 512MB 초과</li>
<li><strong>즉시 완화선</strong>: lag 30초 초과 또는 lag bytes 2GB 초과, 동시에 read-after-write 오류가 관측됨</li>
<li><strong>분석/배치 전용 replica</strong>는 위 기준을 느슨하게 둘 수 있지만, 그래도 lag 추세가 계속 우상향이면 쓰기 폭증이나 apply 병목을 먼저 의심해야 합니다.</li>
</ul>
<p>이 판단은 <a href="/learning/deep-dive/deep-dive-db-failover-fencing-playbook/">DB Failover &amp; Fencing 플레이북</a>과 같이 봐야 합니다. lag가 큰 replica를 그냥 승격 후보로 두면 RPO 계산이 흐려지고, 장애 전환 때 더 큰 문제를 만들 수 있기 때문입니다.</p>
<h3 id="4-파라미터는-중요하지만-튜닝-우선순위는-항상-워크로드부터다">4) 파라미터는 중요하지만, 튜닝 우선순위는 항상 워크로드부터다</h3>
<p>PostgreSQL 운영에서 흔한 실수는 <code>checkpoint_timeout</code>, <code>max_wal_size</code>, <code>wal_compression</code>, <code>synchronous_commit</code> 같은 옵션을 먼저 건드리는 것입니다. 물론 필요하지만, 우선순위는 아래가 더 안전합니다.</p>
<ol>
<li><strong>트랜잭션 크기</strong>: 한 번에 너무 많이 쓰지 않는가</li>
<li><strong>쓰기 분포</strong>: 특정 분, 특정 테이블, 특정 배치에 몰리지 않는가</li>
<li><strong>standby 스펙</strong>: primary보다 지나치게 약하지 않은가</li>
<li><strong>I/O 여유도</strong>: checkpoint 순간에도 디스크 큐가 버틸 수 있는가</li>
<li><strong>파라미터 조정</strong>: 그 다음에 <code>max_wal_size</code>, <code>checkpoint_completion_target</code> 등을 조정</li>
</ol>
<p>예를 들어 <code>checkpoint_timeout=5min</code>, <code>max_wal_size=1GB</code>로 운영하는데 피크 시간 WAL 생성량이 200MB/min이라면 5분을 채우기 전에 용량 한도에 걸려 request checkpoint가 반복될 가능성이 큽니다. 이때는 <code>max_wal_size</code>를 늘려 <strong>최소 30~60분 피크 WAL을 흡수할 수 있는지</strong>부터 보는 편이 낫습니다. 반대로 WAL은 충분한데 standby apply가 느리면 <code>wal_compression</code>보다 standby 디스크와 CPU, 긴 쿼리 충돌, replica 전용 vacuum 정책을 먼저 점검해야 합니다.</p>
<h2 id="실무-적용">실무 적용</h2>
<h3 id="1-장애-전이-전에-보는-운영-대시보드-5종">1) 장애 전이 전에 보는 운영 대시보드 5종</h3>
<p>운영 화면은 복잡할수록 오히려 늦습니다. 아래 다섯 개를 한 화면에서 같이 봐야 판단이 빨라집니다.</p>
<ol>
<li><code>commit p95 / p99</code></li>
<li><code>WAL bytes per second</code></li>
<li><code>checkpoints_req</code>, <code>checkpoint_write_time</code>, <code>checkpoint_sync_time</code></li>
<li><code>replication lag seconds / bytes</code></li>
<li>디스크 <code>await</code>, queue depth, write IOPS</li>
</ol>
<p>여기서 추천 우선순위는 <strong>사용자 영향 → WAL 생성 압력 → flush 압력 → replica 소비 속도</strong>입니다. replica lag만 보고 있으면 이미 commit이 느려진 뒤일 수 있습니다.</p>
<h3 id="2-실무-기준숫자조건우선순위">2) 실무 기준(숫자·조건·우선순위)</h3>
<p>아래는 시작점으로 쓰기 좋은 기준입니다.</p>
<ul>
<li><code>commit p95 &gt; 20ms</code> 5분 지속: 경고</li>
<li><code>commit p95 &gt; 50ms</code> 또는 timeout 증가: 즉시 원인 분석 시작</li>
<li><code>checkpoints_req / (checkpoints_req + checkpoints_timed) &gt; 0.3</code>: WAL 용량 압박 의심</li>
<li>lag <code>&gt; 5초</code> 3분 지속: read routing 보수적으로 전환 검토</li>
<li>lag <code>&gt; 30초</code> 또는 <code>&gt; 2GB</code>: replica 읽기 차단 또는 primary 폴백 검토</li>
<li>WAL 생성량이 평시 대비 <code>3배 이상</code> 급증: 배치/대량 수정/DDL 여부 우선 확인</li>
</ul>
<p>우선순위는 다음이 안전합니다.</p>
<ol>
<li>사용자 쓰기 실패와 지연 상승 차단</li>
<li>replica stale read 차단</li>
<li>checkpoint 폭주 완화</li>
<li>장기 구조 수정</li>
</ol>
<h3 id="3-기본-설정을-점검할-때의-출발선">3) 기본 설정을 점검할 때의 출발선</h3>
<p>환경마다 다르지만, 아래 정도는 출발선으로 자주 씁니다.</p>
<ul>
<li><code>checkpoint_completion_target</code>: 0.9 근처에서 시작</li>
<li><code>max_wal_size</code>: 피크 30~60분 WAL을 버틸 수 있게 산정</li>
<li><code>checkpoint_timeout</code>: 5~15분 범위에서 워크로드 기준 결정</li>
<li><code>wal_compression</code>: full page write 부담이 큰 환경이면 검토</li>
<li><code>synchronous_commit</code>: 핵심 경로만 엄격히, 나머지는 업무 특성에 따라 분리 검토</li>
</ul>
<p>단, 이 값들을 일괄적으로 올리기보다 <a href="/learning/deep-dive/deep-dive-linux-io-models/">Linux I/O 모델</a>과 스토리지 특성을 먼저 확인해야 합니다. 디스크가 이미 포화된 상태에서 <code>max_wal_size</code>만 키우면 문제를 뒤로 미루는 데 그칠 수 있습니다.</p>
<h3 id="4-30분-완화-런북-예시">4) 30분 완화 런북 예시</h3>
<ul>
<li><strong>0~5분</strong>: commit latency, lag seconds/bytes, 최근 배포·배치 여부 확인</li>
<li><strong>5~10분</strong>: lag 큰 replica 읽기 제외, read-after-write 경로 primary 고정</li>
<li><strong>10~20분</strong>: 문제 배치 중지 또는 chunk 축소, 대형 트랜잭션 분할 검토</li>
<li><strong>20~30분</strong>: checkpoint/WAL 설정 재평가, standby apply 병목과 디스크 여유도 점검</li>
</ul>
<p>핵심은 이 단계에서 완벽한 원인 규명보다 <strong>stale read와 write latency 전이 차단</strong>을 먼저 하는 것입니다.</p>
<h2 id="트레이드오프주의점">트레이드오프/주의점</h2>
<p>첫째, <code>max_wal_size</code>를 크게 잡으면 request checkpoint는 줄어들 수 있지만, 장애 시 복구 시간이 길어지고 디스크 여유가 더 필요합니다.</p>
<p>둘째, checkpoint를 너무 드물게 만들면 평소 성능은 좋아 보여도 장애 복구 시간이 길어질 수 있습니다. 운영 SLO뿐 아니라 RTO도 같이 봐야 합니다.</p>
<p>셋째, lag 기준을 너무 빡빡하게 잡으면 replica를 자주 제외하게 되어 primary 부하가 치솟을 수 있습니다. 반대로 너무 느슨하면 stale read를 오래 허용하게 됩니다.</p>
<p>넷째, standby 스펙을 primary보다 과하게 낮추면 평상시엔 버텨도 피크 burst에서 반드시 밀립니다. 복제는 &ldquo;남는 서버&quot;가 아니라 <strong>같은 쓰기 속도를 따라갈 수 있는 서버</strong>가 맡아야 합니다.</p>
<p>다섯째, autovacuum과 checkpoint를 분리해서 보면 안 됩니다. bloat가 커지고 vacuum이 밀리면 결국 더 많은 I/O와 WAL 압력을 부르게 됩니다. 그래서 <a href="/learning/deep-dive/deep-dive-postgresql-autovacuum-tuning/">PostgreSQL Autovacuum 튜닝</a>과 같이 운영해야 의미가 있습니다.</p>
<h2 id="체크리스트-또는-연습">체크리스트 또는 연습</h2>
<h3 id="체크리스트">체크리스트</h3>
<ul>
<li><input disabled="" type="checkbox"> commit latency, WAL 생성량, checkpoint 시간, replication lag를 한 화면에서 본다.</li>
<li><input disabled="" type="checkbox"> lag seconds뿐 아니라 lag bytes도 경보 기준에 포함한다.</li>
<li><input disabled="" type="checkbox"> <code>checkpoints_req</code> 비중이 높을 때 배치/DDL/대형 트랜잭션을 먼저 확인한다.</li>
<li><input disabled="" type="checkbox"> replica 제외 기준과 primary 폴백 기준이 문서화돼 있다.</li>
<li><input disabled="" type="checkbox"> 피크 시간 WAL 생성량 기준으로 <code>max_wal_size</code>를 산정한다.</li>
<li><input disabled="" type="checkbox"> 장애 시 30분 완화 순서가 온콜 런북에 고정돼 있다.</li>
</ul>
<h3 id="연습-과제">연습 과제</h3>
<ol>
<li>최근 7일 기준으로 시간대별 WAL 생성량을 그려 보고, 평시 대비 3배 이상 튄 구간에 어떤 배치나 대량 수정이 있었는지 연결해 보세요.</li>
<li><code>checkpoints_req</code>와 <code>commit p95</code>를 같은 그래프에 올려, request checkpoint가 실제 사용자 지연과 얼마나 같이 움직이는지 확인해 보세요.</li>
<li>replica 1개를 골라 <code>lag seconds</code>, <code>lag bytes</code>, <code>apply throughput</code>를 함께 수집하고, 어떤 값이 먼저 이상 신호를 주는지 비교해 보세요.</li>
</ol>
<h2 id="관련-글">관련 글</h2>
<ul>
<li><a href="/learning/deep-dive/deep-dive-db-replication-read-write-splitting/">DB 복제 &amp; 읽기/쓰기 분리</a></li>
<li><a href="/learning/deep-dive/deep-dive-postgresql-autovacuum-tuning/">PostgreSQL Autovacuum 튜닝</a></li>
<li><a href="/learning/deep-dive/deep-dive-db-failover-fencing-playbook/">DB Failover &amp; Fencing 플레이북</a></li>
<li><a href="/learning/deep-dive/deep-dive-backup-dr-strategy/">Backup/DR 전략</a></li>
<li><a href="/learning/deep-dive/deep-dive-linux-io-models/">Linux I/O 모델 정리</a></li>
</ul>
]]></content:encoded></item></channel></rss>