D는 영속성 or 지속성이라 함
'커밋된 변경은 영속적이다.'
근데 어떻게 영속적인지 들어가면 끝이 없음
이글이 말하는건
'Durability는 ACID에서 설정으로 직접 trade-off를 조절할 수 있는 글자다'
postgresql의 synchronous_commit , mysql innodb의 innodb_flush_log_at_trx_commit 같은 설정은 강한 지속성과 처리량 사이의 다이얼임
그리고 그 다이얼을 안전하게 돌리려면
WAL,fsync,group commit, doublewrite buffer 같은 메커니즘을 알아야함
---
지속성의 정의 - 커밋되면 살아남는다
durability : 트랜잭션이 커밋된 후에는 , 그 변경 사항이 시스템 충돌.전원 차단.OS 크래시가 발생해도 비휘발성 저장소에 영속적으로 남는다는 보장
핵심 단어는 '커밋된'임. 커밋되지 않은 트랜잭션은 살아남을 의무가 없음
ACID중에서 A가 오히려 지워줄 의무를 가짐.
사용자 관점에선 "커밋 응답을 받은 그 순간 전원을 빼도, 다시 켰을 때 그 데이터가 있어야 한다."
저장소가 SSD든 HDD든 NVMe든 상관없음. 핵심은 데이터가 살아남아야함
---
지속성은 왜 느릴까?
직관적으로 "DB에 쓰면 디스크에 쓰겠지"라고 생각하기 쉽지만 실제로 디스크에 쓰는건 매우느림. 진짜로 느림
메모리 쓰기는 나노초 단위
SSD쓰기는 마이크로초 단위
HDD쓰기는 밀리초 단위
자릿수가 100~1000배 차이가 남
데이터베이스는 매 트랜잭션마다 다음을 디스크에 써야 한다고 생각해보면
- 데이터 행(row) 자체
-인덱스 (b트리나 hash등)
- 시스템 메타데이터(트랜잭션 로그, 통계 정보 등)
이 모든걸 매번 디스크에 직접 쓰면 처리량이 무너짐.
그래서 db는 2가지 전략으로 이 문제를 풀어왔음
1. WAL(Write-Ahead Log) 변경사항의 델타만 작은 로그에 빠르게 쓰고, 실제 데이터 페이지는 나중에 비동기로 씀
2. 메모리 버퍼 + 주기적 스냅샷 - 모든 쓰기를 메모리에 두고, 백그라운드에서 디스크로 flush
Redis는 두 전략 모두를 제공함.
RDB(스냅샷)와 AOF(WAL).
Redis 7부터는 둘을 결합한 하이브리드 영속성이 기본임
Redis = 스냅샷 계열로 단정하면 부정확함
대부분의 RDBMS는 WAL 방식을 씀
--------
WAL - 델타만 빠르게 쓴다
WAL의 핵심 원리 : 데이터 페이지를 디스크에 쓰기 전에 변경 사항의 로그를 먼저 쓴다. 그 로그가 디스크에 안전하게 쓰여야만 트랜잭션을 커밋된 것으로 본다.
이게 왜 빠를까
'변경 사항의 델타'만 기록하기 때문임
행 ID 7의 balance를 100에서 200으로 변경 <- 같은 작은 레코드.
데이터 페이지 전체(postgresql 8KB , Innodb 16kb) 전체를 쓰는 것보다 훨씬 작음
게다가 WAL은 "순차 쓰기"이고, 디스크는 순차 쓰기에 최적화되어 있음(HDD에서는 헤드 이동이 없고 , SSD에서도 GC 압박이 적음)
만약 DB가 충돌하면, 메모리에 있던 변경 사항(아직 데이터 페이지에 반영 안됨)은 사라짐.
하지만 WAL은 디스크에 있음. 재시작 시 DB는 마지막 체크포인트 이후의 WAL을 읽으며 변경을 재적용(redo)함.(전체 WAL을 처음부터 읽지 않음. 체크포인트 시점 이전의 변경은 이미 데이터 페이지에 반영되어 있으니.)
결과적으로 충돌 직전의 커밋된 상태로 복원.
이게 트랜잭션의 A를 알아보자 에서 내가 설명한 redo phase랑 같은 메커니즘임.
WAL은 durability와 crash recovery의 기반이고
원자성은 그 위에서 redo/undo 기반의 recovery protocol과 함께 구현됨
학계의 표준 알고리즘은 ARIES이고
InnoDB가 ARIES계열에 가까움
PostgreSQL은 다른길을 택했고.
WAL은 무한정 커지면 안되니 세그먼트(고정 크기 파일,PostgreSQL은 16MB)단위로 잘라 씀
체크포인트가 일어나면(데이터 페이지가 디스크에 완전히 flush) 옛 세그먼트는 재활용/삭제 가능함
Redis도 WAL과 비슷한 메커니즘을 가짐.
appendonly yes 설정 시 모든 명령을 AOF에 append함. 충돌 시 AOF 를 재실행해 메모리 상태를 복원.
RDB 스냅샷과 함께 쓸 수 있음.
----------------------------------------
여기서 디테일 포인트가 있음
WAL을 디스크에 쓰는것도 단순하지 않음
운영체제는 거짓말을 함
DB가 write(fd , wal_record , size) 시스템 콜로 WAL을 쓴다고 해보면
OS(윈도우,리눅스)는 보통 이렇게 동작함
1. DB가 write() 호출
2. OS는 데이터를 페이지 캐시(메모리) 에 저장하고, 즉시 "성공"응답
3. 실제 디스크 쓰기는 나중에 OS가 알아서 함
이 단계 2에서 OS는 거짓말을 한거임. DB에는 "성공"이라고 했지만 실제로는 메모리에만 있음.
만약 이 시점에 전원이 나가면? WAL 데이터는 사라지고, DB는 사용자에게 이미 "커밋되었습니다"라고 알린 상태인거임
즉 D를 위반한거
여기서
fsync가 나온거
이 문제를 해결하기 위해 OS는 fsync(fd) 시스템 콜을 제공함
- "이 파일의 모든 변경을 지금 디스크의 비휘발성 매체까지 flush해라".
fsync는 OS 수준에서 디스크 flush를 요청함. 다만 진짜 영속성은 하드웨어 협력이 있어야함
디스크의 휘발성 쓰기 캐시, RAID 컨트롤러 캐시, 펌웨어의 write reorder 등이 끼어들면
fsync가 반환된 후에도 전원 차단 시 데이터 손실될 수 있음.
이 하드웨어 차원은 아래에서 다룸
즉 fsync는 d의 충분조건이 아니라 필요조건에 가까움.
호출하지 않으면 절대 보장될 수 없지만, 호출했다고 해서 보장되는 것도 아님.
DB는 보통 매 커밋마다
1. WAL 레코드를 write()로 OS 캐시에 씀
2. fsync()로 디스크까지 flush.
3. 그 후에 사용자에게 "커밋 성공"응답
이게 ACID-D의 표준 구현 패턴임
------
그럼 fsync가 만능이냐? 그것도 아님
fsync는 비쌈
대략적으로 HDD에서는 한 자릿수~수십 ms(7200rpm 기준 한 회전이 8ms, 회전 지연이 큰 비중)
SATA/NVMe SSD에서는 수십 us ~ 수 ms 수준(장치.커널.전원보호 여부에 따라 편차 큼)
매 커밋마다 fsync를 호출하면 처리량이 떨어지는 건 분명함
특히 짧은 트랜잭션이 많은 OLTP 워크로드에서 결정적인 병목임
이래서 DB들이 fsync를 묶어서(group commit)처리하거나, 아예 끄거나(asynchronous commit)
하드웨어 도움(NVRAM, 배터리 백업 RAID 컨트롤러)을 받는 등 여러 우회를 함
----
앞에서 fsync가 OS레벨 flush를 요청한다고 했는데
사실 디스크 자체에도 캐시가 있음.
일부 디스크는 fsync 명령을 받고도 디스크 내부의 휘발성 쓰기 캐시까지만 도착하면 응답함.
그 캐시는 전원 차단 시 날아감.
이래서 PostgreSQL같은 DB는
wal_sync_method로 더 강력한 동기화방식(open_datasync, fdatasync 등)을 선택할 수 있음.
또한 엔터프라이즈 디스크는 BBU(Batter Backup Unit)로 캐시 보호를 함.
---
이제 설정을 보자.
각 DB는 "얼마나 강하게 fsync를 할 것 인가"를 사용자가 선택하게 해줌.
PostgreSQL의 asynchronous_commit
PostgreSQL의 공식 문서 기준 5단계가 있음
| 값 | 의미 | 데이터 손실 위험 |
| off | WAL fsync 없이 커밋 응답.백그라운드에서 비동기로 flush | 최대 wal_writer_delay * 3 (기본 ~600ms) |
| local | 로컬 WAL fsync 확인 후 응답. 복제는 무관 | 로컬 디스크 보장만 |
| remote_write* | 동기 standby의 OS 캐시 도착 확인 후 응답. | standby OS 크래시 시 손실 가능 |
| on(기본) | 로컬 WAL fsync(standby 있으면 sstandby의 디스크 fsync까지)확인 후 응답. | standby 있을 시 : primary와 모든 동기 standby가 동시 손상되어야 손실 |
| remote_apply* | 동기 standby가 적용까지 완료한 후 응답(standby 쿼리에서도 보임). | 가장 강함, 가장 느림 |
* remote_write/remote_apply/on(복제 의미)는 synchronous_standby_names가 설정되어 있어야 의미가 있음
동기 standby가 설정되지 않은 단일노드에서는
on/local/remote_Write/remote_apply 모두 같은 동작(로컬 fsync까지)임
또한 synchronous_commit = off는 데이터 손실은 가능하지만 데이터 손상은 일으키지 않음.
즉 잃어버린 트랜잭션은 aborted clean 된 것처럼 보이고 DB 상태는 일관성을 유지함.
이게 fsync = off(파라미터 자체를 끄는 더 위험한 설정)와의 결정적 차이
MySQL InnoDB의 innodb_flush_log_at_trx_commit
공식 매뉴얼 기준 3단계
| 값 | 의미 | 데이터 손실 위험 |
| 0 | 매 커밋 시 아무것도 안함. WAL은 1초마다 일괄 flush | 최대 1초 |
| 1(기본, ACID) | 매 커밋마다 WAL을 디스크까지 flush | 거의 없음 |
| 2 | 매 커밋마다 WAL을 OS캐시까지 쓰고 1초마다 fsync. | OS 크래시 시 최대 1초 |
0과 2 모두 1초 손실 가능하지만, 2는 DB크래시 시에는 안전(OS캐시까지는 쓰여있음, OS가 살아있으면 결국 fsync됨)
0은 DB크래시만으로도 손실 가능.
그래서 실무에서는 "조금 빠르게"가 필요하면 2를 선호함
실무에선 언제쓰냐?
금융,결제,주문 -> PG는 on(또는 remote_apply 로 복제까지) /mysql : 1
일반 oltp : 위와 동일
분석/배치 적재, 임시 워크로드 -> PG는 local이나 off/ mysql은 2
로그 수집, IoT 매트릭, 캐시 -> PG는 off / MySQL은 0 또는 2
PostgrSQL은 트랜잭션 단위로 SET LOCAL Synchronous_commit = 'off'로 설정 변경 가능함.
즉 중요한 트랜잭션은 강한 d. 덜 중요한 트랜잭션은 빠른 응답으로 혼합 운영할 수 있음
이게 PostgreSQL의 큰 장점임
Durability는 다이얼이고
PostgreSQL은 5단계 InnoDB는 3단계까지 얼마나 강하게 선택 가능하니
워크로드별로 트랜잭션마다 다르게 설정하는 것도 가능
-----------
매 커밋마다 fsync하면 처리량이 무너진다고 했는데
그럼 강한 영속성을 유지하면서 처리량을 어떻게 올릴까?
위에서도 말했는데
답은 group commit임
원리는
여러 트랜잭션이 거의 동시에 커밋하려 한다고 가정.
각각 fsync하면 N번의 디스크 IO가 필요
하지만 WAL은 어차피 순차적이고 한 번의 fsync는 그 시점까지의 모든 WAL을 함께 flush함
그래서 DB는 이렇게 함
1. 트랜잭션 T1,T2,T3가 거의 동시에 커밋 요청
2. T1이 fsync를 시작하는 동안 T2,T3는 대기
3. T1이 fsync가 끝나면 T2,T3의 WAL도 같이 flush(그들이 쓴 WAL이 T1의 fsync 시점 이전에 OS 캐시에 도착했으므로)
4. 한 번의 fsync로 세 트랜잭션 모두 디스크 보장 확보
결과 : N개 트랜잭션이 1번의 fsync 비용을 공유. 처리량 확보
postgreSQL과 MySQL InnoDB모두 그룹커밋을 자동으로 함.
별도로 키거나 끄는 설정은 없음. 강제로 미세조정할 수 있지만 공식 가이드에서 모두
대부분 효과 없거나 오히려 느려질 수 있다고함.
----
WAL과 별개로 InnoDB는 doublewrite buffer라는 개념이 있음
부분 쓰기(torn Page) 문제인데
InnoDB의 데이터 페이지는 보통 16KB임. 그런데 디스크 OS/디스크의 atomic write 단위는 보통 4KB(또는 512B)
그래서 InnoDB가 16KB 페이지를 쓰는 도중 전원이 나가면
일부만 쓰여진 깨진 페이지(torn page)가 디스크에 남을 수 있음
WAL만으론 이 문제를 해결 못함
WAL은 논리적 변경 기록("이 위치를 X에서 Y로 변경")이기 때문에
페이지 자체가 물리적으로 깨져있으면 그 위에 델타를 적용해봐야 결과가 보장되지 않음.
즉 깨진 페이지 자체를 복구할 수단이 별도로 필요함.
DoubleWrite Buffer의 해결법은
InnoDB는 데이터 페이지를 디스크에 쓰기전 연속된 doublewrite 영역에 먼저 씀.
그 후 실제 위치에 쓰고
(MySQL 8.0.20 이전엔 시스템 테이블스페이스 내, 이후엔 별도의 doublewirte 파일)
크래시 후 복구 시
- 실제 위치의 페이지가 깨졌으면 -> doublewrite buffer의 깨끗한 복사본으로 복원
- doublewrite buffer가 깨졌으면 -> 실제 위치의 페이지를 사용( 실제 쓰기는 시작도 안 한 것)
이름은 double wirte지만 I/O가 2배가 되지는 않음.
한 번의 fsync로 여러 페이지를 묶어 처리함.
실제 성능 영향은 워크로드에 따라 다른데
percona분석 기준 SSD/NVMe + MySQL 8.0.20+ 환경에서는 보통 5~10% 수준이지만
HDD + 쓰기 집중 워크로드에서는 50% 이상의 처리량 손실까지 보고된 사례가 있음
PostgreSQL은 다른 방법(full_page_writes = on,기본값)으로 같은 문제를 품
- 체크포인트 후 첫 변경 시 페이지 전체를 WAL에 기록
----------------------------
WAL이 무한정 쌓이지 않게, 그리고 충돌 후 복구가 너무 오래 걸리지 않게 하려면 체크포인트 가 필요함.
체크포인트의 역할
1. 메모리(버퍼 풀)의 더티 페이지들을 디스크의 실제 위치에 flush
2. 그 시점까지의 WAL은 더 이상 redo에 필요 없으므로 재활용 가능 표시
3. 충돌 후 복구는 이 체크포인트 이후의 WAL만 재실행하면 됨.
트레이드 오프가 있음
잦은 체크포인트 : 복구 빠름, 하지만 더티페이지 flush IO가 많아져 평소 처리량 저하
드문 체크포인트: 평소 IO 적음. 하지만 복구 시간 길어짐
postgreSQL : checkpoint_timeout(기본 5분) , max_wal_size(wAL 누적 크기 임계)
mysql innodb. : innodb_log_file_size로 redo log 크기 조절(체크포인트 빈도와 직결)
이 다이얼은 D와 관계없지만
어차피 WAL이 fsync 되어 있으면 데이터는 안전함
복구 시간과 평소 IO 부담을 조절함
----------------------
정리
1. Durability =/ 디스크 쓰기 : DB가 디스크에 쓴다는 말은 OS캐시까지고 진짜는 fsunc로 os-level flush를 요청한 후에도 하드웨어 캐시까지 안전해야 보장. (BBU, atomic write, 적절한 fs barrier 등)
2. fsync는 비싸다 : 매 커밋마다 fsync하면 처리량이 무너지니 그룹 커밋 필수
3. D는 다이얼이다. : 강도를 조절하자 ACID 4글자 중 설정으로 가장 직접적으로 트레이드 오프를 조절할 수 있는 영역
4. WAL이 durability와 recovery의 기반 : 변경 델타를 작고 빠르게 디스크에 기록하는 메커니즘이 D와 충돌 복구의 핵심.
A는 그 위에서 redo/undo 기반의 recovery protocol과 함께 구현. 대표 알고리즘 ARIES고 innodb가 가까운 계열. postgreSQL은 MVCC로 다른길
5.하드웨어도 거짓말 한다. : OS 캐시 + 디스크 캐시 때문에 진짜 D는 BBU나 atomic write 같은 하드웨어 협력 없이 어려움
6. 부분 쓰기 방어 : WAL만으로는 페이지 단위 atomic write가 보장되지 않으니 doublewrite buffer / full_page_writes 같은 추가 메커니즘ㅇ ㅣ필요함
결국 D는
"데이터가 언제까지 안전한가"가 아니라
"어디까지를 안전하다고 정의할 것인가" 의 문제임
OS캐시인지
로컬 디스크 매체까지인지
동기 standby의 디스크까지인지
standby의 적용까지인지
시스템마다 답이 다르고 그 경계를 어디에 그을 것인지가 곧 워크로드의 trade-off 선택임
나왔다 내 야동 - dc App
다음 예정 글은 뭔가요
오토스케일링 , 복제,동시성 제어, 파티셔닝, 샤딩, 내부 엔진, 커서, 보안,암호, nosql과의 비교 중 하나
arabozi가 아니네..
해당 댓글은 삭제되었습니다.
@ㅇㅇ(112.165) 토먹금 - dc App
개추 - dc App
개추 - dc App
I'm 쌌어요 - dc App
취업 안할께~
나보다 많이 아는것 같은데? - dc App
ㄴㄴ 님도 예전에 다 배운거임. 난 그냥 그걸 머리에서 꺼내준거고
님 취업했죠? 창업이나? 이정도 지식을 가지고 백수라는건 사회적낭비임
쉬었음 로그아웃 청년인데요..
취업시켜줘!!!!!!!!!!!!!!!!!
PostgreSQL 조회 성능 개선하면서 자세히 살펴봤었는데 그 경험이 이해에 빠른 도움이 되네 좋은 정보 개추야
진짜 항상 감사합니다
옵테인 스토리지를 쓰면 빠르다는 무서운 사실
지잡임?
오오
근데 다 읽어봣는데 이글은 왜 정보탭이 아닌것..?
감사합니다... 다음 글도 기대할게요 - dc App
다음글은 10년뒤에 봅시다
cs에 정답은 없고 다 약속임, 이정도면 괜찮겠다.