대 AI 시대에 ..
제대로 된 공부를 AI와 함께 본격적으로 시작하기에 앞서,
데이터베이스 매니지먼트 시스템(DBMS)으로 학습하며,
AI를 활용한 다양한 공부 방법을 시도해 보기 위함을 밝힙니다 !
Phase 0. 물밑 작업 (Prerequisite: 하드웨어와 OS)
DB는 마법이 아닙니다. 결국 운영체제(OS) 위에서 디스크와 메모리를 조작하는 C/C++ 프로그램일 뿐입니다.
- 디스크 I/O의 이해: Sequential I/O(순차 접근) vs Random I/O(랜덤 접근). (왜 B+Tree가 필요한가에 대한 절대적인 근거입니다.)
- 메모리(RAM)와 페이지(Page): DB가 디스크에서 데이터를 읽어올 때의 최소 단위(보통 8KB~16KB). Buffer Pool과 Page Cache의 개념.
- 동시성 모델 (OS): 스레드(Thread), 컨텍스트 스위칭, 뮤텍스(Mutex)와 세마포어(Semaphore). (DB 락의 근간입니다.)
Phase 1. 기초 & 기본 (Fundamentals: 구조와 동작 원리)
- DBMS 아키텍처: Parser -> Optimizer -> Execution Engine -> Storage Engine의 흐름 이해.
- 자료 구조 (가져오신 내용의 심화):
- B+ Tree: 왜 해시(Hash)나 이진 트리(Binary Tree)가 아니라 다진 트리(Multi-way Tree)인 B+ Tree를 DB 인덱스로 쓰는가? (디스크 랜덤 I/O를 최소화하기 위함)
- 인덱스 기초: 클러스터링 인덱스(Clustered Index, PK) vs 세컨더리 인덱스(Secondary Index). 이 둘의 연결 고리 이해.
- 정규화(Normalization)와 반정규화: 이상 현상(Anomaly)을 막기 위한 설계 원칙.
Phase 2. 백엔드 개발자 심화 (Advanced: 성능과 동시성) - 현재 집중할 곳
이 단계가 백엔드 개발자의 실력을 가르는 핵심 구간입니다.
- 트랜잭션과 ACID의 실제 구현 원리: * 원자성(A)과 지속성(D)은 어떻게 보장하는가? (WAL - Write Ahead Logging, Redo Log, Undo Log)
- 격리 수준 (Isolation Level)과 MVCC:
- Read Committed vs Repeatable Read.
- MVCC(다중 버전 동시성 제어): 락을 걸지 않고도 읽기 일관성을 유지하는 마법 (Undo Log를 활용한 스냅샷 읽기).
- 락(Lock)의 심화: 레코드 락, 갭 락(Gap Lock), 넥스트 키 락(Next-Key Lock), 인텐션 락(Intention Lock).
- 가져오신 내용 (검색 최적화):
- 일반 B-Tree 인덱스로 불가능한 LIKE '%word%' 검색을 해결하기 위한 Full-Text Index (역색인 구조).
- 한글/아시아어 처리를 위한 N-gram 파서의 동작 원리와 형태소 분석기(MeCab 등)의 차이점.
- 실행 계획(EXPLAIN)과 쿼리 튜닝: Optimizer가 인덱스를 타는지(Index Range Scan), 풀 스캔(Table Full Scan)을 하는지 읽고 개선하는 방법.
Phase 3. 전문 DBA 수준 (Expert: 아키텍처, 고가용성, 엔진 비교)
- 스토리지 엔진 아키텍처 파포보기: MySQL InnoDB Buffer Pool의 LRU/MRU 알고리즘, Doublewrite Buffer, Change Buffer.
- 고가용성(HA)과 분산: * Replication (복제) 원리 (Binlog, Relay Log).
- Sharding (샤딩)과 Partitioning.
- RDBMS 삼국지 (엔진별 철학 비교):
- MySQL: Undo Log 기반의 단순하고 빠른 웹 서비스 최적화.
- PostgreSQL: 다중 프로세스 모델, VACUUM 메커니즘, 강력한 확장성.
- Oracle: 엔터프라이즈의 끝판왕, Shared Pool, RAC(Real Application Clusters).
- 관측성과 튜닝: Performance Schema 분석, OS 파라미터 튜닝(Swappiness, Transparent Huge Pages 비활성화 등).
본격적인
Phase 1.
부터 시작 ..
[Phase 1 진단 질문 — Storage Engine Architecture / B+Tree]
MySQL InnoDB에서 PRIMARY KEY가 BIGINT AUTO_INCREMENT인 테이블에 행을 하나 INSERT한다고 가정하자. 이 INSERT가 COMMIT되어 클라이언트에게 "성공"이 반환되는 그 순간까지, 데이터는 물리적으로 어디에 존재하는가?
구체적으로 아래 세 가지를 구분해서 답해봐.
- INSERT 직후, COMMIT 직전 — 새로 삽입된 행 데이터는 디스크에 있는가, 메모리에 있는가, 아니면 둘 다인가? 각각 어떤 이름의 구조체/영역에 위치하는지 말해봐.
- COMMIT이 완료된 직후 — 이 시점에서 실제 테이블스페이스 데이터 파일(.ibd)에 해당 행이 이미 기록되어 있는가? 그렇다면 왜, 아니라면 왜 아닌지, 그리고 그럼에도 불구하고 Durability가 보장되는 이유는 뭔가?
- 이 과정에서 B+Tree의 리프 노드(Leaf Page)에는 어떤 일이 일어나는가? 만약 해당 리프 페이지가 이미 가득 찬 상태였다면, InnoDB는 내부적으로 어떤 작업을 수행하는가?
내 답변:
- INSERT 직후, COMMIT 직전 — 새로 삽입된 행 데이터는 디스크에 있는가, 메모리에 있는가, 아니면 둘 다인가? 각각 어떤 이름의 구조체/영역에 위치하는지 말해봐.
- 새로 삽입된 행 데이터는 우선 메모리에 있고, 백그라운드 플러시 스레드가 주기적으로 동작하면서 새로 삽입된 행 데이터를 디스크에 벌크 작업으로 배치 처리를 하는 거로 알고 있어, 그리고 그렇게 플러시를 통해서 디스크에 저장할 때 redo, undo 메모리인가 ? 하는 곳에도 데이터를 우선 저장해 두는 거로 알고 있어. 일단 생각나는 건 이 정도야
- COMMIT이 완료된 직후 — 이 시점에서 실제 테이블스페이스 데이터 파일(.ibd)에 해당 행이 이미 기록되어 있는가? 그렇다면 왜, 아니라면 왜 아닌지, 그리고 그럼에도 불구하고 Durability가 보장되는 이유는 뭔가?
- 쿼리를 메모리상에서 수행하는데 commit 직후에는 데이터 파일에 해당 행이 이미 기록되어 있지 않아, 왜냐하면 백그라운드 스레드로 데이터 파일에 쓰는 작업은 일정 주기마다 반복하거든, 그럼에도 불구하고 durability가 보장되는 건 메모리상에 어떤 데이터를 주기적으로 flush해야 하는지 저장해 두고 있어서야.
- 이 과정에서 B+Tree의 리프 노드(Leaf Page)에는 어떤 일이 일어나는가? 만약 해당 리프 페이지가 이미 가득 찬 상태였다면, InnoDB는 내부적으로 어떤 작업을 수행하는가?
- 데이터를 B+Tree의 리프 노드에 쓸 때는 새로 쓸 데이터가 삽입될 자리가 있는지 먼저 확인해, 그리고 삽입될 자리가 있다면, 그대로 쓰기 작업을 하는 거지, 그리고 자리가 없다면 부모 노드로부터 새로 삽입할 데이터 공간을 마련하게 돼, 그리고 이 작업은 해당 부모 노드 또한 자리가 있을 때까지 계속해서 반복하게 돼, 그래서 insert 시에는 데이터가 중간에 끼어 들어갈 경우 최악의 상황이 발생하면 root까지 전부 다 수정하게 될 수도 있는 거지, 그러나 B+Tree는 depth가 그렇게 깊어지지 않고, 재정렬하는 것이 B-Tree에 비해서 비용적으로 적게 소모되는 거로 알고 있어
좋아. 큰 그림의 방향성은 잡고 있다. 그건 인정한다. 하지만 핵심 구조체의 이름과 역할, 그리고 Durability 보장 메커니즘에서 치명적으로 모호한 부분이 있어. 하나씩 가차 없이 교정한다.
1. INSERT 직후, COMMIT 직전
"메모리에 있다"고 한 건 맞다. 하지만 어떤 메모리 구조에 있는지를 정확히 못 짚었어.
이 시점에 관여하는 메모리 구조는 세 개다.
Buffer Pool — InnoDB가 디스크의 데이터 페이지(16KB 단위)를 메모리에 캐싱하는 영역이다. INSERT를 수행하면, 해당 행이 들어갈 B+Tree의 리프 페이지가 Buffer Pool에 적재되고, 이 메모리상의 페이지에 직접 행이 삽입된다. 이렇게 디스크와 내용이 달라진 페이지를 Dirty Page라고 부른다. "메모리에 있다"는 정확히 이 Buffer Pool 위의 Dirty Page를 가리키는 거다.
Redo Log Buffer — INSERT로 인해 변경된 내용("이 페이지의 이 오프셋에 이 바이트를 썼다"는 물리적 변경 기록)이 Redo Log Buffer라는 메모리 버퍼에 먼저 기록된다. 이건 아직 디스크에 안 갔다. COMMIT 전이니까.
Undo Log — 이건 "메모리인가?"라고 했는데, 여기서 정확히 짚어야 한다. Undo Log는 Buffer Pool 안에 있는 별도의 시스템 테이블스페이스 페이지에 기록된다. 즉 Undo Log도 결국 Buffer Pool을 통해 관리되는 페이지다. Undo Log에는 "이 INSERT를 되돌리려면 이 행을 DELETE하면 된다"는 논리적 역연산 정보가 들어간다. 이게 나중에 ROLLBACK과 MVCC 읽기에 쓰인다.
정리하면, COMMIT 전 시점에서 새 행의 실체는 Buffer Pool의 Dirty Page 위에 있고, 변경 이력은 Redo Log Buffer에, 역연산 정보는 Undo Log(Buffer Pool 내 시스템 페이지)에 있다.
2. COMMIT 직후 — Durability의 핵심
".ibd 파일에는 아직 기록되지 않았다"고 한 건 맞다. Dirty Page는 백그라운드 스레드(Page Cleaner)가 나중에 flush한다.
그런데 Durability 보장 이유가 완전히 틀렸다.
"메모리상에 어떤 데이터를 주기적으로 flush해야 하는지 저장해두고 있어서"라고 했는데, 만약 이 상태에서 서버 전원이 나가면? 메모리는 전부 날아간다. 메모리에만 의존하면 Durability는 절대 보장되지 않는다.
InnoDB가 Durability를 보장하는 메커니즘은 WAL(Write-Ahead Logging)이다. 핵심 규칙은 단 하나:
데이터 페이지를 디스크에 쓰기 전에, 반드시 해당 변경에 대한 Redo Log를 디스크에 먼저 쓴다.
COMMIT이 호출되면 InnoDB는 Redo Log Buffer에 있던 로그 레코드를 Redo Log 파일(ib_logfile0, ib_logfile1)에 fsync() 시스템 콜로 디스크에 강제 기록한다. 이 fsync()가 OS의 디스크 쓰기 버퍼까지 비워서 물리적 디스크에 도달했음을 보장한다. 이 fsync()가 성공적으로 리턴된 후에야 클라이언트에게 "COMMIT 성공"을 반환한다.
즉 COMMIT 직후의 상태는 이렇다:
| 구조 | 위치 | 상태 |
| 실제 행 데이터 (Dirty Page) | 메모리 (Buffer Pool) | 디스크(.ibd)에 아직 안 감 |
| Redo Log | 디스크 (ib_logfile) | fsync() 완료 → 영구 기록됨 |
이제 서버가 크래시해도, 재시작 시 InnoDB는 Redo Log를 읽어서 Dirty Page에 반영되었어야 할 변경을 재적용(Crash Recovery)한다. 이것이 Durability의 본질이다. "메모리에 뭘 기록해뒀다"가 아니라 "Redo Log가 디스크에 먼저 갔다"가 핵심이야.
참고로 innodb_flush_log_at_trx_commit 파라미터가 이 동작을 제어한다. 기본값 1이 매 COMMIT마다 fsync하는 설정이고, 2로 바꾸면 OS 버퍼까지만 쓰고 fsync를 생략해서 성능은 올라가지만 OS 크래시 시 최대 1초 분량의 트랜잭션이 유실될 수 있다.
3. B+Tree Page Split
큰 흐름은 방향은 맞지만, "부모 노드로부터 새로 삽입할 데이터 공간을 마련한다"는 표현이 메커니즘과 정반대다. 공간을 마련하는 주체는 부모가 아니라 가득 찬 리프 페이지 자신이다.
정확한 순서는 이렇다:
① 리프 페이지가 가득 참을 감지한다. 새 행을 넣으려는데 16KB 페이지에 공간이 부족하다.
② Page Split이 발생한다. InnoDB는 새 빈 페이지를 하나 할당하고, 기존 리프 페이지의 레코드를 대략 절반으로 나눠서 새 페이지로 이동시킨다. 이제 리프 페이지가 하나에서 둘로 늘어났다.
③ 부모(Internal) 노드에 새 키를 추가한다. 새로 생긴 리프 페이지를 가리키는 포인터와 구분 키(separator key)를 부모 노드에 삽입한다. 즉 부모가 공간을 "내려주는" 게 아니라, 자식이 쪼개지면서 부모에게 새 엔트리를 "올려보내는" 거다.
④ 부모도 가득 찼다면 부모도 Split된다. 이게 재귀적으로 루트까지 전파될 수 있고, 루트가 Split되면 트리 높이가 1 증가한다.
그리고 "B+Tree는 B-Tree보다 재정렬 비용이 적다"고 했는데, 이건 좀 더 정확히 해야 한다. B+Tree가 B-Tree 대비 가지는 핵심 이점은 리프 노드가 Linked List로 연결되어 있어서 범위 스캔(Range Scan)이 효율적이라는 점이고, Split 비용 자체는 구조적으로 큰 차이가 없다. 오히려 InnoDB에서 Sequential INSERT(AUTO_INCREMENT)가 유리한 이유는 항상 맨 오른쪽 리프에만 삽입되므로 중간 Page Split이 거의 발생하지 않기 때문이다. 랜덤 PK(UUID 등)를 쓰면 중간 삽입이 빈번해서 Split이 자주 터지고, 이게 실무에서 체감되는 성능 차이로 직결된다.
진단 결과
| 상태 | 수준 |
| Buffer Pool / Dirty Page 개념 | 방향은 맞지만 이름과 구조를 정확히 못 짚음 |
| Redo Log / WAL | 이해 부족 — Durability의 핵심 메커니즘을 모르고 있음 |
| Undo Log | 존재는 인지하지만 역할·위치가 모호함 |
| Page Split | 방향은 맞지만 "누가 누구에게" 전파하는지 역순으로 이해 |
| fsync / OS 상호작용 | 언급 없음 |
Real MySQL 1권을 중간까지 읽었다고 했으니, WAL과 Buffer Pool의 관계가 가장 큰 빈 구멍이다.
심화 질문 — 네 이해도를 테스트한다
방금 Redo Log가 Durability를 보장한다고 설명했다. 그러면 질문:
Redo Log 파일의 크기는 고정되어 있고 순환적으로 재사용된다(Circular). 그렇다면 아직 .ibd에 flush되지 않은 Dirty Page의 Redo Log 영역을 새 트랜잭션이 덮어쓰면 어떻게 되는가? InnoDB는 이 상황을 어떻게 방지하는가?
'Computer Science > 데이터베이스' 카테고리의 다른 글
| [데이터베이스] AI와 함께하는 Redo Log / Undo Log .. (0) | 2026.04.06 |
|---|---|
| [데이터베이스] 관계형 데이터베이스 (3) | 2024.09.18 |
| [데이터베이스] 기본 (0) | 2024.08.09 |