RDBMS Implementation & Storage Engines
RDBMS Implementation 및 저장소 Engines의 정의, 범위, 선행 지식, 학습 주제, 참고 근거를 정리한 CS&E 학습 노드입니다.
Article
M
Me
hyunyoun's Blog
data-information-managementdatainformation-managementrelational-systemsrdbms-implementationstorage-enginesdatabasesdatabase-internals10 min read
1. Overview
RDBMS 구현과 스토리지 엔진(RDBMS Implementation & Storage Engines)은 SQL 파서 밑단에서 운영체제 파일 시스템(디스크)과 직접 맞닿아 데이터의 물리적 저장, 버퍼 캐싱, 장애 복구를 책임지는 데이터베이스의 기계실, 스토리지 엔진(Storage Engine)의 아키텍처를 해부합니다.
학습자는 데이터를 디스크에 저장하는 최소 단위인 **페이지/블록(Page/Block, 8KB/16KB)**의 구조와 테이블스페이스(Tablespace)의 물리적 배치를 뜯어봅니다. 나아가 극악의 디스크 I/O 속도를 상쇄하기 위해 메모리에 데이터와 인덱스 페이지를 캐싱하는 **버퍼 풀(Buffer Pool)**의 LRU(Least Recently Used) 교체 알고리즘을 해부합니다. 마지막으로 MySQL의 세계적 지배 엔진인 InnoDB의 아키텍처(Clustered Index, Change Buffer, Doublewrite Buffer)와 PostgreSQL의 **Append-Only 아키텍처(Vacuum)**를 심층 비교하여 대용량 트래픽에 맞는 물리적 DB 튜닝 역량을 확보합니다.
2. Scope & Boundaries
In-Scope
- 물리적 저장 구조 (Physical Storage): 페이지/블록(Page/Block), 레코드(Row/Tuple) 포맷, 테이블스페이스, 파일 시스템 매핑.
- 메모리 아키텍처 (Memory Architecture): 버퍼 풀(Buffer Pool, Shared Buffer), 디스크 I/O 최적화, Dirty Page, Checkpoint, 캐시 교체 정책(LRU, Clock-Sweep).
- 특화 구조 (Engine Specifics):
- MySQL InnoDB: Clustered Index (인덱스 겸 데이터), Change Buffer(인서트 최적화), Doublewrite Buffer, Undo Tablespace.
- PostgreSQL: 힙 테이블(Heap Table) 구조, Append-Only (MVCC 튜플 방식), VACUUM 기법.
- 장애 복구 엔진 (Recovery System): WAL(Write-Ahead Log), Redo Log 순환 구조, LSN(Log Sequence Number), ARIES 복구 알고리즘.
Out-of-Scope
- NoSQL / 분산 데이터베이스 스토리지: LSM Tree, RocksDB, SSTable 구조 → 06-02-01 Key-Value Store Physics 영역으로 위임.
- 운영체제 파일 시스템 내부: ext4, XFS의 Inode, 저널링 상세 로직 → 03-03-03 VFS & Filesystem 영역.
Boundaries
- In-Place Update (InnoDB) vs Append-Only Update (PostgreSQL): MySQL InnoDB는 레코드를 수정할 때 원래 페이지 위치(In-Place)를 직접 변경하며 이전 데이터는 별도 Undo 로그로 뺍니다(공간 재활용 유리, 조각화 적음). 반면 PostgreSQL은 기존 레코드를 지우지 않고 새 버전을 맨 끝에 추가(Append-Only)합니다. 롤백이나 스냅샷 읽기에 우아하지만, 옛날 버전 쓰레기 데이터(Dead Tuples)가 테이블에 무한히 쌓여 테이블 비대화(Bloat)를 유발하므로 주기적으로
VACUUM데몬이 쓰레기를 청소해 주어야 하는 치명적 유지보수 트레이드오프가 존재합니다.
3. Counterexample
- Buffer Pool 튜닝 실패로 인한 디스크 Thrashing (I/O Thrashing): 서버에 RAM이 64GB나 있는데, RDBMS 설정(MySQL
innodb_buffer_pool_size)을 디폴트인 128MB로 방치. 테이블 전체 용량이 10GB인 상황에서 수백 건의 조인 쿼리 유입. 필요한 데이터 페이지가 버퍼 풀에 찰 때마다 다른 페이지를 밀어내고(Evict), 다시 필요하면 디스크에서 읽어오는 I/O 스래싱 발생. 디스크 100% 점유율로 서버가 멈춤. 전용 DB 서버라면 가용 메모리의 70~80%를 버퍼 풀에 맵핑(할당)해야 쿼리의 대부분이 메모리에서(Logical Read) 끝나는 고성능을 발휘합니다. - PostgreSQL Vacuum 장애 (Transaction ID Wraparound & Bloat): 트랜잭션이 엄청나게 많이 일어나는 PostgreSQL에서 Autovacuum 데몬 설정을 너무 약하게 둠. 데드 튜플 청소가 생성 속도를 못 따라가 디스크 사용량이 몇 달 만에 10배 폭발. 급기야 내부 Transaction ID 한계(약 20억 개)에 도달하자 DB가 스스로를 보호하기 위해 전체 동작을 정지(Read-Only Lock)해 버리고 치명적인 강제 Vacuum(수십 시간 소요)에 들어가는 최악의 서비스 마비. 엔진별 물리적 스토리지 특징을 모른 채 설계/운영한 대표적 참사입니다.
4. Prerequisites
- 인덱스와 논리 최적화 (Basic): B-Tree 구조와 옵티마이저. (06-01-02 SQL Engineering)
- 트랜잭션과 ACID 로그 (Basic): WAL(Write-Ahead Log) 메커니즘의 선행 지식. (06-01-03 Transaction Mechanics)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 디스크의 블록을 쪼개 쓰다, 페이지 구조와 레이아웃 (Page & Storage Layout)
- Why to Learn: "왜 VARCHAR(255) 컬럼 하나를 늘렸는데 테이블 성능이 뚝 떨어지는지", 레코드들이 디스크의 기본 I/O 단위(16KB 페이지) 안에 물리적으로 어떻게 구겨 넣어지는지를 꿰어 스키마 설계의 한계를 파악하기 위함입니다.
- What to Learn:
- Concepts: 블록/페이지(Block/Page, 보통 8KB/16KB), 슬롯 배열(Slot Array, 포인터 관리), 로우 마이그레이션(Row Migration), 로우 체이닝(Row Chaining), 헤더 풋터(Header/Footer).
- Skills: 페이지 크기 초과 시 발생하는 현상 인지, 데이터 조각화(Fragmentation).
- How to Learn:
- 1단계: 페이지 내부 구조: 16KB 페이지 구조 안에는 수십/수백 개의 레코드(튜플)가 차곡차곡 쌓임. 레코드 길이가 가변적이므로, 페이지 끝(또는 헤더)에 레코드 시작 주소를 가리키는 슬롯(오프셋) 배열을 두어 이진 탐색으로 로우를 빠르게 찾는 역학을 해부합니다.
- 2단계: 로우 체이닝/마이그레이션:
UPDATE로 레코드 크기가 늘어나 원래 페이지에 담지 못하면, RDBMS는 레코드를 새 페이지로 옮기고 원래 자리엔 새 주소(포인터)만 남깁니다. 데이터를 읽을 때 점프(추가 I/O)가 발생하여 디스크 성능을 심각하게 저하시키는 로우 이동 현상을 뜯어봅니다.
- Implement: 파이썬
Page모의 클래스(100 바이트 가상 메모리 크기).insert(record)시 가용 공간(Free Space)이 줄어들고 슬롯 디렉토리에 포인터 저장. 용량 초과하는 문자열update(record_id, long_string)시PageOverflowException을 발생시켜 외부 확장 페이지(체이닝)를 새로 할당하는 DB 엔진 스토리지 로직 파이썬 흉내 내기.
Recommended
Core Topic 02: 메모리가 디스크를 이기는 법, 버퍼 풀 아키텍처 (Buffer Pool & Checkpoint)
- Why to Learn: 초당 수만 건의
UPDATE를 어떻게 기계적 디스크(HDD)나 SSD가 소화하는지, 메인 메모리의 거대한 캐시망(Buffer Pool)과 비동기 백그라운드 쓰기(Flush)의 예술을 장악하기 위함입니다. - What to Learn:
- Concepts: 버퍼 풀(Buffer Pool / Shared Buffer), 클린/더티 페이지(Clean/Dirty Page), LRU(Least Recently Used) 알고리즘 병목 방어(Midpoint Insertion Strategy), 체크포인트(Checkpoint), 플러시(Flush list).
- Skills: 버퍼 풀 적중률(Hit Ratio) 모니터링 분석, 대용량 스캔 쿼리가 캐시를 밀어내는 현상(Buffer Pool Thrashing) 인지.
- How to Learn:
- 1단계: 더티 페이지 지연 쓰기: 쿼리가
UPDATE실행. RDBMS는 디스크에 즉시 쓰지 않고, 버퍼 풀 메모리의 페이지만 갱신(Dirty Page 표시) 후 완료 응답을 줍니다(물론 복구를 위해 WAL 로그는 디스크 Flush 함). 나중에 체크포인트 스레드가 더티 페이지들을 모아 디스크에 한 방에 붓는(Batch Write) 최적화 역학을 해부합니다. - 2단계: LRU 리스트 파괴 방지: 운영 중인 디비에
SELECT * FROM log_table풀스캔 쿼리 1방. 100GB 데이터가 버퍼 풀을 거쳐가면, 기존에 소중하게 캐싱된 핫 데이터(유저 정보 등)가 LRU 교체로 싹 날아가는 대형 사고. 이를 막기 위해 MySQL이 신규 페이지는 LRU 헤드가 아닌 중간(Midpoint, 5/8 지점)에 넣는 방어 설계를 뜯어봅니다.
- 1단계: 더티 페이지 지연 쓰기: 쿼리가
- Implement: 파이썬 LRU Cache 시뮬레이터 클래스.
max_pages=10. 페이지 110 캐싱(Hot). 대량 덤프 스캔 루틴(100200 번호 페이지 무한 읽기) 실행 시 일반 LRU는 110번 페이지를 모두 쫓아냄(Eviction). Midpoint LRU 모사(신규 데이터는 뒤쪽 Old 리스트에만 머물며 일정 시간 내 재접근 없으면 폐기) 구현하여 핫 데이터(110)가 덤프 쿼리로부터 쫓겨나지 않고 생존하는 캐시 방어 로직 데모.
Practical
Core Topic 03: 데이터 자체가 트리가 되다, MySQL InnoDB 핵심 엔진 (InnoDB Architecture)
- Why to Learn: 전 세계에서 가장 널리 쓰이는 오픈소스 DB 스토리지 엔진인 InnoDB의 강력한 아키텍처(클러스터드 인덱스와 유니크한 버퍼 최적화)를 이해해야 MySQL 전용 쿼리 성능 극한 튜닝이 가능해지기 때문입니다.
- What to Learn:
- Concepts: InnoDB 엔진, Clustered Index (PK=Table 자체), Secondary Index (리프 노드에 PK 값을 저장), Change Buffer(Insert Buffer), Doublewrite Buffer(부분 쓰기 방지), Undo Tablespace.
- Skills: PK 크기가 테이블 용량과 인덱스 용량에 미치는 파급 효과 설계(UUID PK 지양).
- How to Learn:
- 1단계: Clustered Index 제국: InnoDB에서 PK는 단순한 인덱스가 아닙니다. B-Tree 인덱스의 리프 노드 자체에 16KB 데이터 튜플(전체 컬럼)이 그대로 담겨 있습니다(테이블 = PK B-Tree 그 자체). 반면 일반 인덱스(Secondary)는 리프 노드에 데이터 주소가 아니라 PK 값을 저장합니다. 따라서 일반 인덱스로 검색하면 인덱스 탐색 1번 + PK 탐색 1번, 총 2번 트리를 타야 하는 역학을 해부합니다.
- 2단계: UUID를 PK로 쓰면 망하는 이유: UUID를 PK로 쓰면 매우 뚱뚱(36바이트)해집니다. 모든 일반 인덱스도 PK를 물고 있으니 일반 인덱스 크기도 기하급수 폭발. 또한 삽입 시 UUID가 랜덤이므로 거대한 PK B-Tree의 여기저기를 뒤집어 엎으며(Page Split 대량 발생) 쓰기 I/O 폭풍을 일으키는 엔진의 태생적 구조를 뜯어봅니다. (순차 증가 Auto Increment 정수 권장).
- Implement: InnoDB 인덱스 구조 시뮬레이터. 메모리에
ClusteredBTree클래스(리프가 전체 dict)와SecondaryBTree클래스(리프가 PK 저장) 구축. 특정name='John'검색 쿼리 실행.SecondaryBTree에서name으로 PK(105) 획득ClusteredBTree에서 PK 105로 전체 데이터 탐색하는 "Two-step Lookup" 플로우 로그 콘솔 출력. PK 사이즈가 커질수록 전체 메모리 오버헤드 증가 수치 시각화.
Advanced
Core Topic 04: 절대 삭제하지 않는 세계, PostgreSQL의 Append-Only와 VACUUM (PostgreSQL Architecture)
- Why to Learn: 엔터프라이즈 환경에서 MySQL을 대체하며 무섭게 부상하는 PostgreSQL 특유의 "수정 불가(Immutable)" 스토리지 철학을 이해하고, 가장 빈번한 장애 원인인 테이블 팽창(Bloat)과 VACUUM 튜닝을 장악하기 위해서입니다.
- What to Learn:
- Concepts: Append-Only(MVCC 튜플 스토리지), 힙 테이블(Heap Table), CTID(물리적 주소), Dead Tuple, VACUUM (Dead Tuple 청소기), HOT(Heap-Only Tuples) Update 최적화, Autovacuum 데몬.
- Skills: 테이블 팽창률(Bloat) 조회, Autovacuum
scale_factor/threshold실무 튜닝 지표.
- How to Learn:
- 1단계: Append-Only 튜플: PostgreSQL은 데이터를 덮어쓰지 않습니다.
UPDATE수행 시, 옛날 행(Tuple)에 만료 시점을 마킹(Dead)하고, 16KB 페이지 어딘가에 새 행을 추가(Insert)합니다. Undo 로그를 별도로 뒤질 필요 없이 동일 페이지에서 스냅샷 튜플을 바로 찾을 수 있어 읽기 복구 롤백 오버헤드가 극히 적은 장점을 해부합니다. - 2단계: 무덤 청소기 VACUUM: 끝없이 쌓이는 Dead Tuple(쓰레기)을 방치하면 테이블이 100배로 팽창(Bloat)해 Full Scan 시 디스크를 다 읽어야 합니다. 백그라운드 Autovacuum 프로세스가 주기적으로 돌면서 이 데드 튜플들의 공간을 가용(Free) 상태로 수거해주는 메커니즘. 잦은 업데이트가 발생하는 시스템에서 이 데몬이 멈추면 발생하는 시한폭탄을 뜯어봅니다.
- 1단계: Append-Only 튜플: PostgreSQL은 데이터를 덮어쓰지 않습니다.
- Implement: 파이썬
PgHeapTable튜플 배열 구조 시뮬레이션. 튜플은(TxID_min, TxID_max, Data)형태.UPDATE시 기존 튜플의TxID_max만료 표기 후 배열 끝에 새 튜플append(). 데이터 업데이트 100번 반복 후, 배열 길이(물리 디스크 용량)가 1개 100개로 팽창하는 Bloat 현상 출력.run_vacuum(current_txid)호출 시 유효 기간 끝난 Dead Tuple 공간을None으로 마킹하여 재활용 풀로 회수하는 가비지 컬렉터 로직 데모.
7. Terminology
8. References
Primary
- [P1] CS2023 - Information Management (IM) - Physical Database Design
- [P5] SFIA - Database Administration (DBAD) - Storage Engineering
Secondary
- [Database Internals] Alex Petrov - A Deep Dive into How Distributed Data Systems Work
- [High Performance MySQL] Baron Schwartz - InnoDB Architecture & Buffer Pool
Industry
- [MySQL 8.0 Reference Manual] - InnoDB Architecture (Buffer Pool, Change Buffer)
- [PostgreSQL Documentation] - Storage Layout & Routine Vacuuming
9. Final Checklist
Primary
- 16KB 페이지 내에 가변 길이 레코드들이 어떻게 적재되고, 슬롯(포인터)으로 탐색되는지 물리적 레이아웃을 설명할 수 있는가?
- 버퍼 풀(Buffer Pool)이 어떻게 디스크 읽기 I/O를 방어하고, 더티 페이지 지연 쓰기로 쓰기 성능을 올리는지 증명할 수 있는가?
Secondary
- 버퍼 풀의 LRU 알고리즘이 대량 스캔 쿼리에 의해 핫 데이터가 밀려나는 현상(Thrashing)을 방어하는 기법을 해부할 수 있는가?
- MySQL InnoDB에서 Primary Key가 Clustered Index 역할을 할 때, UUID를 PK로 쓰면 왜 치명적인 I/O 폭발이 일어나는지 논증할 수 있는가?
Industry
- Secondary 인덱스 탐색이 InnoDB에서 PK 리프 노드를 두 번 거쳐야(Two-step Lookup) 하는 오버헤드를 아키텍처 관점에서 설명할 수 있는가?
- PostgreSQL의 Append-Only MVCC 구조가 어떻게 빠른 롤백을 보장하며, 동시에 왜 주기적인 VACUUM 튜닝이 필수적인지 엔지니어링할 수 있는가?