ACID에서 Durability는 어떻게 구현되는가: MySQL과 PostgreSQL의 서로 다른 접근 방식

JH.KIM·2025년 12월 27일

이론이 아닌 운영 관점에서 바라보는 Durability

이 글에서 제시하는 수치는 일반적인 운영 환경에서 관측되는 대략적인 범위입니다. 실제 성능은 하드웨어, 워크로드, 설정에 따라 달라집니다.


목차

PART 1. ACID로 DB 동작 이해하기
1. Application을 넘어 DB 레벨로
2. ACID 개념 정리

PART 2. Durability의 실제 의미
3. 메모리, OS, 디스크: 데이터가 저장되는 계층
4. write()와 fsync()의 차이
5. Redo Log / WAL의 역할
6. Group Commit: fsync 비용 분산

PART 3. MySQL (InnoDB)의 Durability 설계
7. InnoDB Redo Log 구조
8. innodb_flush_log_at_trx_commit 옵션
9. 설정별 성능 차이
10. = 2를 선택할 때의 전제 조건

PART 4. PostgreSQL의 Durability 설계
11. WAL 구조
12. 기본 설정에서의 Durability
13. 구조적 Group Commit
14. 고트래픽에서의 Latency 특성

PART 5. 비교와 선택
15. 설계 철학의 차이
16. 트래픽 증가에 따른 성능 특성
17. 서비스 유형별 선택 경향
18. 정리


PART 1. ACID로 DB 동작 이해하기


1. Application을 넘어 DB 레벨로

평소에는 JPA나 ORM 레벨에서 @Transactional을 붙이고 넘어갑니다. 하지만 성능 튜닝이나 장애 대응을 하다 보면 DB 내부 동작을 알아야 할 때가 있습니다.

ACID는 그 출발점으로 좋습니다. 이 글에서는 ACID를 간단히 훑은 뒤, 그중 Durability를 깊이 살펴봅니다.


2. ACID 개념 정리

2.1. Atomicity

트랜잭션 내의 모든 연산이 전부 성공하거나 전부 실패합니다. 단일 DB 내에서는 명확하게 보장됩니다.

분산 환경에서는 Saga 패턴, Outbox 패턴 등으로 해결합니다.

2.2. Consistency

DB가 정의된 제약조건(NOT NULL, UNIQUE, FK 등)을 항상 만족하는 상태를 유지합니다.

분산 시스템의 "eventual consistency"와는 다른 개념입니다.

2.3. Isolation

여러 트랜잭션이 동시에 실행될 때 서로 격리합니다. 완벽한 격리(Serializable)는 동시성을 제한하므로, 대부분 Read Committed나 Repeatable Read를 사용합니다.

MySQL과 PostgreSQL 모두 MVCC를 사용하지만, 구현 방식이 다릅니다.

2.4. Durability

"COMMIT된 트랜잭션은 영구적으로 저장된다"는 속성입니다.

그런데 여기서 질문이 생깁니다.

  • "영구적"이란 무엇인가? 메모리? OS 버퍼? 디스크?
  • DB 프로세스가 죽으면? OS가 죽으면?
  • 이 보장을 위해 얼마나 많은 성능을 희생하는가?

이 질문들에 대한 답이 MySQL과 PostgreSQL에서 다르게 나타납니다. 다음 PART에서 구체적으로 살펴봅니다.


PART 2. Durability의 실제 의미


3. 메모리, OS, 디스크: 데이터가 저장되는 계층

애플리케이션이 DB에 데이터를 쓸 때, 데이터는 여러 계층을 거칩니다.

[Application] → [DB Buffer] → [OS Page Cache] → [Disk Controller Cache] → [Disk]

각 계층마다 crash 시 결과가 다릅니다.

Crash 유형별 데이터 손실 범위

  • DB 프로세스 crash: DB Buffer 손실, OS Page Cache는 생존
  • OS crash (커널 패닉, 서버 재부팅): OS Page Cache까지 손실, 디스크에 기록된 것만 생존
  • 전원 장애: 디스크 컨트롤러 캐시까지 손실 가능 (배터리 백업 있으면 생존)

4. write()와 fsync()의 차이

시스템 콜 수준에서 write()fsync()는 완전히 다른 보장을 제공합니다.

write()

  • 데이터를 OS Page Cache에 복사
  • 디스크에는 아직 없을 수 있음

fsync()

  • OS가 해당 파일의 모든 데이터를 디스크에 기록하도록 강제
  • 이 호출이 반환되면 디스크에 있다고 믿을 수 있음

fsync()는 비용이 큽니다.

저장 장치fsync latency (대략)
7200 RPM HDD5~15ms
15000 RPM HDD3~8ms
SATA SSD0.5~2ms
NVMe SSD0.1~0.5ms
배터리 백업 RAID0.1~0.3ms

5. Redo Log / WAL의 역할

매 트랜잭션마다 전체 데이터 페이지를 fsync하는 것은 비효율적입니다. 대신 로그 선행 기록(Write-Ahead Logging) 방식을 사용합니다.

Write-Ahead Logging 동작 방식

  1. 데이터 변경 전에, 변경 내용을 로그에 먼저 기록
  2. 로그만 fsync하면 COMMIT 완료
  3. 실제 데이터 페이지는 나중에 비동기로 기록
  4. Crash 발생 시 로그를 replay하여 복구

순차 쓰기라서 무작위 쓰기보다 빠릅니다.

MySQL에서는 Redo Log, PostgreSQL에서는 WAL(Write-Ahead Log)이라고 부릅니다.


6. Group Commit: fsync 비용 분산

매 트랜잭션마다 fsync를 하면, fsync latency가 곧 commit latency가 됩니다.

NVMe SSD에서 fsync가 0.2ms라면?
→ 단순 계산으로 초당 최대 5,000 TPS
→ 고트래픽 서비스에서는 병목

Group Commit은 여러 트랜잭션의 로그 기록을 모아서 한 번의 fsync로 처리합니다.

Group Commit 동작 방식

트랜잭션 1 → 로그 기록 (대기)
트랜잭션 2 → 로그 기록 (대기)
트랜잭션 3 → 로그 기록 (대기)

한 번의 fsync

트랜잭션 1, 2, 3 모두 COMMIT 완료

fsync 비용이 여러 트랜잭션에 분산되고, 동시 요청이 많을수록 효율이 좋아집니다.

시나리오fsync/commit예상 TPS (NVMe 기준)
단건 commit1회~3,000~5,000
Group Commit (10건 묶음)0.1회~20,000~40,000
Group Commit (50건 묶음)0.02회~50,000+

PART 3. MySQL (InnoDB)의 Durability 설계


7. InnoDB Redo Log 구조

InnoDB는 Redo Log를 통해 Durability를 구현합니다.

[트랜잭션 변경] → [Redo Log Buffer] → [OS Page Cache] → [Disk]

COMMIT 시점에 Redo Log가 어디까지 기록되어야 하는지가 Durability 수준을 결정합니다.


8. innodb_flush_log_at_trx_commit 옵션

MySQL은 innodb_flush_log_at_trx_commit 파라미터로 이 동작을 제어합니다.

= 1 (기본값, Full Durability)

COMMIT → Log Buffer → OS Page Cache → fsync() → 완료
  • 매 COMMIT마다 fsync 호출
  • DB crash, OS crash 모두 안전
  • 가장 느리지만 가장 안전

= 2 (Relaxed Durability)

COMMIT → Log Buffer → OS Page Cache → 완료
         (fsync는 1초마다 별도로)
  • COMMIT 시 fsync를 호출하지 않음
  • DB crash에서는 안전, OS crash에서는 최대 1초 손실 가능
  • 성능이 크게 향상됨

= 0 (Minimal Durability)

COMMIT → Log Buffer → 완료
         (모든 flush가 1초마다)
  • Log Buffer에만 기록하고 반환
  • DB crash에서도 최대 1초 손실 가능
  • 가장 빠르지만 가장 위험

설정값별 동작 요약

설정COMMIT 시 동작손실 가능 범위
= 1Log Buffer → OS → Disk없음
= 2Log Buffer → OSOS crash 시 ~1초
= 0Log BufferDB crash 시 ~1초

9. 설정별 성능 차이

설정값commit latency (NVMe)예상 TPS보장 수준
= 10.3~1ms3,000~8,000DB + OS crash
= 20.05~0.2ms15,000~40,000DB crash only
= 00.01~0.05ms30,000~60,000없음

10. = 2를 선택할 때의 전제 조건

많은 서비스가 = 2를 선택합니다.

= 2를 선택하는 이유

  1. OS crash는 드물다

    • 커널 패닉, 하드웨어 장애, 전원 문제 정도
    • 잘 관리된 환경에서는 연 1~2회 수준
  2. 1초 손실이 치명적이지 않은 경우가 많다

    • 사용자 활동 로그, 세션 데이터, 캐시성 데이터
  3. 애플리케이션 수준에서 재처리가 가능하다

    • 멱등성, 메시지 큐, Saga 패턴 등

단, 다음 조건을 확인해야 합니다.

= 2 선택 전 체크리스트

  • 비즈니스가 "1초 이내 손실 가능"을 허용하는가?
  • 재처리/복구 메커니즘이 갖춰져 있는가?
  • OS crash 감지를 위한 모니터링이 있는가?
  • 운영팀이 이 설정의 의미를 이해하고 있는가?

결제, 정산, 금융 거래라면 = 1 또는 별도 보장 필요


PART 4. PostgreSQL의 Durability 설계


11. WAL 구조

PostgreSQL도 WAL을 통해 Durability를 구현합니다.

[트랜잭션 변경] → [WAL Buffer] → [WAL 파일 (Disk)]

12. 기본 설정에서의 Durability

주요 관련 설정입니다.

설정기본값설명
synchronous_commitonCOMMIT 시 WAL fsync 여부
fsynconWAL fsync 사용 여부 (끄지 말 것)
wal_sync_methodfdatasyncfsync 방식

synchronous_commit = on은 MySQL의 innodb_flush_log_at_trx_commit = 1과 유사한 보장을 제공합니다.


13. 구조적 Group Commit

MySQL에서 Group Commit은 성능 최적화를 위해 "추가된" 기능입니다. PostgreSQL은 WAL writer 프로세스의 동작 방식 자체가 Group Commit을 자연스럽게 만듭니다.

PostgreSQL의 Group Commit 동작 방식

Backend 1 → WAL Buffer 기록 → "fsync 요청"
Backend 2 → WAL Buffer 기록 → "fsync 요청"
Backend 3 → WAL Buffer 기록 → "fsync 요청"

WAL writer가 모아서 fsync

Backend 1, 2, 3 모두 완료

동시 트랜잭션이 많을수록 하나의 fsync에 더 많이 묶입니다.

동시 연결 수평균 묶음 크기 (대략)
102~5
5010~20
20030~100
500+100~500

14. 고트래픽에서의 Latency 특성

PostgreSQL의 구조적 Group Commit은 흥미로운 특성을 만듭니다.

저트래픽 (동시 연결 수 적음)

  • 묶을 트랜잭션이 적음
  • fsync 비용이 각 트랜잭션에 온전히 부담
  • commit latency 상대적으로 높음 (0.5~2ms)

고트래픽 (동시 연결 수 많음)

  • 많은 트랜잭션이 하나의 fsync에 묶임
  • fsync 비용 분산
  • commit latency가 오히려 낮아지거나 안정됨 (0.2~0.5ms)

반직관적이지만, 부하가 높아지면 latency가 오히려 안정되는 구간이 있습니다.

synchronous_commit = off

PostgreSQL도 Durability를 완화하는 옵션이 있습니다.

SET synchronous_commit = off;

COMMIT 시 fsync를 기다리지 않습니다. WAL writer가 주기적으로(기본 200ms마다) fsync합니다.

MySQL = 2가 1초마다 fsync하는 것과 비교하면, PostgreSQL이 좀 더 촘촘하게 보호합니다. 단, 둘 다 "비정상 종료 시 일부 손실 가능"이라는 점은 같습니다.


PART 5. 비교와 선택


15. 설계 철학의 차이

MySQL (InnoDB)

  • 웹 애플리케이션과 함께 성장
  • "선택 가능한 옵션"에 초점
  • 개발자/운영자가 명시적으로 트레이드오프 선택
  • innodb_flush_log_at_trx_commit의 세 가지 값이 예시

PostgreSQL

  • 학술적, 엔터프라이즈 배경
  • "안전한 기본값"에 초점
  • 구조적으로 안전성 확보, 최적화는 자동으로
  • Group Commit이 설정 없이 동작하는 것이 예시

Durability 설정 비교

측면MySQL (InnoDB)PostgreSQL
기본 DurabilityFull (= 1)Full (sync_commit = on)
완화 옵션= 2, = 0sync_commit = off
손실 가능 범위= 2: ~1초off: ~수백ms
Group Commit있음구조적으로 내장
설정 복잡도명시적 선택 필요기본값으로 충분한 경우 많음

16. 트래픽 증가에 따른 성능 특성

저트래픽

  • MySQL = 1과 PostgreSQL 비슷하게 느림
  • MySQL = 2만 빠름

고트래픽

  • PostgreSQL의 Group Commit 효과 두드러짐
  • MySQL = 1은 여전히 느림
  • PostgreSQL은 full durability 유지하면서 성능 개선

MySQL = 2는 모든 구간에서 가장 빠르지만, OS crash 시 데이터 손실 가능성이 있습니다.


17. 서비스 유형별 선택 경향

서비스 유형흔히 관측되는 조합배경
일반 웹 서비스MySQL = 2 또는 PostgreSQL 기본성능과 안정성의 균형
고트래픽 서비스MySQL = 2 + 재처리 보장최대 TPS 필요
금융/정산MySQL = 1 또는 PostgreSQL 기본손실 불허
로그/분석MySQL = 0 또는 전용 시스템성능 최우선

18. 정리

ACID는 체크리스트라기보다 설계 기준에 가깝다

ACID를 단순히 "지원함 / 지원하지 않음"으로 구분하는 것은
현실의 데이터베이스를 이해하는 데에는 다소 한계가 있습니다.
대부분의 관계형 데이터베이스는 각자의 방식으로 ACID를 충족하고 있으며,
중요한 차이는 어떤 방식으로 이를 구현하고 있는지,
그리고 어떤 선택지를 제공하고 있는지에 있습니다.

Durability는 비용과 선택의 영역이다

흔히 이야기되는 "COMMIT되면 항상 절대적으로 안전하다"는 표현 역시
모든 상황을 그대로 반영한다고 보기는 어렵습니다.
Durability는 하나의 고정된 개념이 아니라 여러 단계로 나뉘며,
각 단계는 서로 다른 성능 비용과 안정성 특성을 가집니다.

  • 매 트랜잭션마다 fsync 수행: 상대적으로 높은 latency, 높은 수준의 안정성
  • OS Page Cache까지 기록: 낮은 latency, DB 프로세스 장애까지는 안전
  • 메모리 버퍼에만 기록: 매우 낮은 latency, 장애 상황에서는 보장이 제한적

DB 선택보다 더 중요한 질문들

  • 선택한 데이터베이스가 어떤 설계 철학을 가지고 있는가?
  • 기본 설정은 어떤 수준의 보장을 전제로 하고 있는가?
  • 성능과 안정성 사이의 트레이드오프를 인지한 상태에서 선택했는가?
  • 예상하지 못한 장애 상황에 대해 대응 전략이 마련되어 있는가?
profile
일하며 겪은 문제를 나눠요

0개의 댓글