비동기 커밋
비동기 커밋 (Asynchronous Commit) — 커밋 보고는 빠르게, 내구성은 조금 느슨하게
트랜잭션이 끝난다고 클라이언트에 알려주는 순간, 저장소까지 실제로 쓰여졌을까요? 보통은 그걸 보장하기 위해 약간의 딜레이가 생기는데, 이 대가를 줄이는 방법이 바로 비동기 커밋이에요. 대신에 "아주 최근에 커밋된 일부 트랜잭션은 서버가 크래시하면 사라질 수 있다"는 리스크를 받아들이는 거죠. 많은 애플리케이션에서는 이게 충분히 합리적인 거래입니다.
출처: 공식문서
비동기 커밋은 무엇이 다른가요
이전 섹션에서 봤듯이 일반적인 커밋은 동기적이에요. 서버는 트랜잭션의 WAL 레코드가 영구 저장소에 flush 될 때까지 기다린 뒤에야 성공을 알려줍니다. 그래야 "커밋됐다고 보고된 트랜잭션"이 바로 다음 순간에 서버가 크래시해도 반드시 보존된다는 약속이 생기죠.
문제는 짧은 트랜잭션일수록 이 대기가 전체 트랜잭션 시간의 큰 부분을 차지한다는 점이에요. 비동기 커밋 모드를 선택하면 서버는 트랜잭션이 논리적으로만 끝나면, 즉 생성된 WAL 레코드가 아직 디스크에 도착하기 전에 곧바로 성공을 반환합니다. 덕분에 작은 트랜잭션들의 처리량(throughput)이 눈에 띄게 올라가요.
대가는 데이터 손실이라는 점을 정확히 이해하기
비동기 커밋이 가져오는 위험은 데이터 손실이지, 데이터 손상이 아니에요. 크래시 시 서버는 마지막으로 flush 된 레코드까지 WAL을 재생( replay )해서 복구하는데, 그 결과 데이터베이스는 항상 스스로 일관된 상태로 돌아옵니다. 다만 아직 디스크에 못 간 트랜잭션들은 그 상태에 반영되지 않을 뿐이에요. 정리하면 "마지막 몇 개 트랜잭션이 유실"될 뿐이고요.
이때 트랜잭션은 커밋 순서대로 재생되기 때문에 불일치가 생길 수 없어요. 예를 들어 트랜잭션 B가 이전 트랜잭션 A의 결과에 의존해 변경을 만들었다면, A의 효과만 사라지고 B의 효과가 남는 일은 불가능합니다.
클라이언트가 그걸 믿고 외부 행동을 하면 안 되는 순간
클라이언트가 "이 트랜잭션은 반드시 기억될 거야"라고 가정하고 외부적인 행동을 한다면 비동기 커밋을 쓰면 안 돼요. 극단적인 예로, 은행이 ATM 현금 지급을 기록하는 트랜잭션에 비동기 커밋을 쓰는 일은 절대 없겠죠. 반면 이벤트 로깅 같은 시나리오는 그렇게 강한 보장이 필요 없어서 아주 잘 어울립니다.
트랜잭션마다 커밋 모드를 고를 수 있어요
여기서 멋진 점은 커밋 모드를 트랜잭션 단위로 선택할 수 있다는 거예요. 즉 동기 커밋 트랜잭션과 비동기 커밋 트랜잭션이 동시에 돌면서, 성능과 내구성 보장 사이를 유연하게 저울질할 수 있죠. 이 모드는 사용자 설정 파라미터인 synchronous_commit으로 조절하며, 어떤 트랜잭션에 적용될지는 그 트랜잭션의 커밋이 시작되는 시점의 값에 따라 정해집니다.
몇몇 유틸리티 명령은 예외인데요, 예를 들어 DROP TABLE은 synchronous_commit 설정과 무관하게 항상 동기적으로 커밋돼요. 서버의 파일 시스템과 데이터베이스의 논리적 상태가 일치해야 하기 때문이죠. 2단계 커밋을 지원하는 명령들, 이를테면 PREPARE TRANSACTION도 항상 동기적입니다.
리스크 창(risk window)은 얼마나 길까요
비동기 커밋과 WAL 쓰기 사이의 위험 구간 동안 서버가 크래시하면 해당 트랜잭션의 변경은 사라집니다. 다행히 이 창의 길이는 제한되어 있어요. 백그라운드 프로세스인 WAL writer가 wal_writer_delay 밀리초마다 작성되지 않은 WAL 레코드를 디스크로 flush 하거든요. 실제 최대 창 길이는 wal_writer_delay의 세 배인데, WAL writer가 바쁜 시간에는 페이지 단위로 통째로 쓰는 것을 선호하도록 설계됐기 때문이에요.
⚠️ 주의: immediate 모드 종료(즉시 종료)는 서버 크래시와 동등하게 취급됩니다. 즉시 종료 시 flush 되지 못한 비동기 커밋은 유실될 수 있어요.
비슷해 보이지만 다른 것들: fsync와 commit_delay
비동기 커밋은 fsync = off 설정과는 다른 동작이에요. fsync는 서버 전체 설정이라 모든 트랜잭션의 동작을 바꾸고, 데이터베이스의 여러 부분에 대한 쓰기 동기화 로직을 통째로 꺼버립니다. 그래서 시스템 크래시(하드웨어·OS 크래시이지 PostgreSQL 자체의 실패가 아닌)가 나면 데이터베이스 상태가 임의로 크게 손상될 수 있어요. 반면 비동기 커밋은 fsync를 끈 경우에 얻을 수 있는 성능 개선의 대부분을, 데이터 손상이라는 위험 없이 제공합니다.
commit_delay도 비동기 커밋과 비슷하게 들리지만, 사실은 동기 커밋 방식이에요. 실제로 commit_delay는 비동기 커밋 중에는 무시됩니다. commit_delay는 트랜잭션 하나가 WAL을 flush 하기 직전에 잠시 지연을 넣어서, 그 flush가 비슷한 시각에 커밋하는 다른 트랜잭션들까지 함께 처리해주길 기대하는 설정이에요. 한 번의 flush 비용을 여러 트랜잭션이 나눠 부담하도록, 단일 flush에 참여할 그룹이 모일 시간 창을 넓혀주는 셈이죠.
더 알아보기 (Learn more)
- WAL 구성 파라미터 (runtime-config-wal) —
synchronous_commit,wal_writer_delay,commit_delay등 자세한 설정값 - WAL 신뢰성 (wal-reliability) — fsync와 데이터 손상의 관계
- 트랜잭션 (transactions) — 커밋과 롤백의 기본 개념