WAL Group Commit이 여러 트랜잭션의 fsync 비용을 줄이는 방식
WAL Group Commit은 왜 커밋 속도를 높일까? fsync 병목의 실제 구조
온라인 주문이 몰리는 순간 수백 개의 트랜잭션이 거의 동시에 커밋을 요청한다고 가정해 보겠습니다. 데이터베이스가 각 요청마다 로그를 디스크에 동기화한다면, 실제 데이터 처리보다 저장장치의 응답을 기다리는 시간이 더 길어질 수 있습니다.
WAL Group Commit은 여러 트랜잭션의 로그를 하나의 동기화 작업에 포함해 이러한 병목을 줄이는 방식입니다. 단순히 쓰기를 모아서 처리하는 기능이 아니라, 데이터의 지속성을 유지하면서 커밋 처리량을 높이기 위한 데이터베이스 내부 최적화입니다.
커밋 응답 전에 WAL을 먼저 기록하는 이유
WAL은 Write-Ahead Logging의 약자로, 변경된 데이터 페이지보다 복구에 필요한 로그를 먼저 기록하는 방식입니다. 장애가 발생해 메모리에 있던 변경 내용이 사라지더라도 디스크에 남은 WAL을 재생하면 커밋된 상태를 복원할 수 있습니다.
일반적인 커밋 과정은 다음과 같습니다.
- 트랜잭션이 데이터를 변경합니다.
- 변경 내용을 설명하는 WAL 레코드가 로그 버퍼에 추가됩니다.
- 커밋 레코드까지 저장장치에 안전하게 동기화합니다.
- 동기화가 확인되면 애플리케이션에 커밋 성공을 응답합니다.
실제 테이블의 데이터 페이지는 나중에 기록될 수 있지만, 복구에 필요한 WAL은 먼저 안전한 저장장치에 도달해야 합니다. 이 순서가 지켜지기 때문에 갑작스러운 프로세스 종료나 시스템 장애 이후에도 데이터베이스가 일관된 상태를 복원할 수 있습니다.
병목은 로그의 크기보다 동기화 횟수에서 발생한다
WAL 레코드 한 건의 크기는 비교적 작을 수 있습니다. 그러나 로그를 운영체제 캐시에서 실제 저장장치로 밀어내는 동기화 작업은 일정한 지연을 발생시킵니다.
트랜잭션 20개가 각각 별도의 동기화를 요청하면 저장장치는 비슷한 작업을 여러 번 수행해야 합니다. 반대로 20개의 커밋 레코드를 하나의 WAL 범위에 포함해 한 번에 동기화하면, 한 차례의 작업으로 여러 트랜잭션의 지속성 조건을 충족할 수 있습니다.
여기서 중요한 점은 Group Commit이 여러 트랜잭션을 하나의 트랜잭션으로 합치는 기능은 아니라는 것입니다. 각 트랜잭션의 격리성과 성공 여부는 그대로 유지되며, 디스크 동기화 시점만 함께 사용하는 것입니다.
여러 커밋이 하나의 flush를 공유하는 과정
동시에 여러 세션이 커밋을 요청하면 먼저 도착한 세션이 WAL 동기화를 수행하는 동안 다른 세션들의 커밋 레코드도 버퍼에 추가될 수 있습니다.
이때 한 번의 동기화로 가장 마지막 커밋 위치까지 WAL이 저장되면, 그 이전 위치에 포함된 트랜잭션들도 모두 안전하게 기록된 것으로 판단할 수 있습니다. 따라서 여러 세션이 같은 동기화 결과를 공유하게 됩니다.
PostgreSQL은 별도의 지연값을 설정하지 않아도 동시 커밋이 겹치는 시점에 Group Commit이 발생할 수 있습니다. commit_delay는 더 많은 커밋을 모으기 위해 의도적인 대기 시간을 추가하지만, 동시 트랜잭션이 부족하거나 값을 지나치게 높이면 응답 지연만 증가할 수 있습니다.
처리량은 증가해도 모든 요청이 빨라지는 것은 아니다
Group Commit의 가장 큰 효과는 초당 처리할 수 있는 트랜잭션 수를 늘리는 데 있습니다. 저장장치 동기화 횟수가 감소하면 동일한 하드웨어에서도 더 많은 커밋 요청을 처리할 수 있습니다.
하지만 트래픽이 적어 동시에 커밋하는 세션이 거의 없다면 묶을 트랜잭션도 없습니다. 이 경우 Group Commit의 이점은 제한적입니다.
또한 그룹을 크게 만들기 위해 지나치게 오래 기다리면 전체 처리량은 늘어도 개별 사용자가 느끼는 응답 시간은 나빠질 수 있습니다. 따라서 평균 응답 시간만 보지 말고 TPS와 함께 상위 95% 또는 99% 지연 시간을 확인해야 합니다.
PostgreSQL과 MySQL은 무엇을 확인해야 할까
PostgreSQL
PostgreSQL에서는 Group Commit이 기본 동작 과정에서 자연스럽게 발생할 수 있으므로 commit_delay를 무조건 높이는 방식은 적절하지 않습니다. 이 설정은 동시 커밋이 존재하고, 실제 병목이 커밋 동기화에서 발생할 때만 효과가 있습니다.
WAL 입출력 시간을 분석하려면 track_wal_io_timing을 활성화한 뒤 WAL 쓰기와 동기화 시간을 확인할 수 있습니다. CPU 사용률은 낮은데 WAL 동기화 시간이 계속 증가한다면 저장장치 지연이나 커밋 빈도를 함께 점검해야 합니다.
MySQL InnoDB
MySQL InnoDB에서는 redo log와 binary log의 지속성 설정을 함께 살펴봐야 합니다. innodb_flush_log_at_trx_commit=1은 커밋마다 로그를 기록하고 동기화하는 기본 설정이며, binary log를 사용하는 환경에서는 sync_binlog도 장애 복구 범위에 영향을 줍니다.
값을 완화하면 성능이 높아질 수 있지만 시스템 장애 시 최근 커밋된 트랜잭션이 유실될 가능성이 생깁니다. 이는 Group Commit을 최적화하는 것과 데이터 지속성 수준을 낮추는 것이 서로 다른 결정이라는 뜻입니다.
설정을 변경하기 전에 확인할 순서
1. 커밋이 실제 병목인지 확인한다
CPU, 디스크 지연, WAL 또는 redo log 동기화 시간, 초당 커밋 수를 함께 측정해야 합니다. 단순히 디스크 사용률이 높다는 이유만으로 Group Commit 관련 설정을 변경하면 다른 병목을 놓칠 수 있습니다.
2. 애플리케이션의 커밋 빈도를 살펴본다
자동 커밋 상태에서 짧은 쿼리를 지나치게 자주 실행하면 동기화 요청이 불필요하게 증가할 수 있습니다. 논리적으로 하나의 작업인 쿼리들은 적절한 트랜잭션 경계 안에 묶는 것이 좋습니다.
다만 트랜잭션을 무조건 크게 만들면 잠금 유지 시간과 롤백 비용이 증가할 수 있으므로, 관련 작업만 하나의 단위로 묶어야 합니다.
3. 부하 테스트로 처리량과 지연을 함께 비교한다
설정 변경 전후의 TPS만 비교해서는 충분하지 않습니다. 평균 응답 시간과 상위 지연 시간, WAL 동기화 횟수, 장애 발생 시 허용 가능한 데이터 유실 범위를 함께 검증해야 합니다.
Group Commit의 핵심은 안전한 동기화의 공유다
WAL Group Commit은 로그 기록을 생략하는 기술이 아닙니다. 여러 트랜잭션이 각각 필요로 하는 디스크 동기화를 하나의 물리적 작업으로 공유하는 방식입니다.
동시 접속자가 많고 짧은 트랜잭션이 반복되는 환경에서는 커밋 처리량을 크게 높일 수 있지만, 트래픽이 적거나 병목이 CPU와 잠금 경합에 있다면 효과는 제한적입니다.
결국 중요한 것은 특정 설정값을 따라 하는 것이 아니라, 커밋 요청이 어디에서 대기하는지 측정하는 것입니다. Group Commit은 데이터 안전성과 처리 성능 사이에서 타협하는 편법이 아니라, 동일한 지속성 보장을 더 효율적으로 달성하기 위한 내부 설계입니다.
댓글 0
첫 댓글을 남겨보세요.