🚀 RDBMS 내부 동작, 왜 알아야 할까요?
백엔드 개발을 시작하고 처음 몇 년간은 비즈니스 로직을 구현하고, ORM 을 사용해 CRUD 처리에만 집중하셨을 겁니다. "쿼리만 잘 짜면 되는 거 아냐?"라고 생각할 수 있지만, 트래픽이 몰리는 대용량 서비스 환경이나 치명적인 장애 상황을 맞닥뜨리면 이야기가 달라집니다.
- "갑자기 DB 서버가 죽었다 살아났는데 데이터는 안전한가?"
- "왜 Replication(복제) 지연이 이렇게 심하게 발생할까?"
- "단순한 UPDATE 쿼리 하나인데 왜 이렇게 오랫동안 락(Lock)이 걸릴까?"
이런 문제들을 근본적으로 해결하고 성능을 최적화하기 위해서는 쿼리 튜닝을 넘어, 데이터베이스가 메모리와 디스크 사이에서 데이터를 어떻게 다루는지, 그리고 장애에 대비해 어떤 로그를 남기는지 그 '내부 아키텍처와 스토리지 엔진의 동작 방식'을 반드시 이해해야 합니다.
🤦♂️ RDBMS 내부를 모를 때 하는 실수들
본격적인 설명에 앞서, 실무에서 아키텍처와 로그의 역할을 정확히 이해하지 못해 겪는 대표적인 시행착오 몇 가지를 살펴보겠습니다.
- "메모리는 거들 뿐, SSD니까 알아서 빠르겠지?" (Buffer Pool 설정 간과)
- 데이터베이스 성능의 핵심은 디스크 I/O를 최소화하는 것입니다.
- 서버 스펙만 믿고 MySQL의 innodb_buffer_pool_size를 기본값으로 방치하면 메모리를 충분히 할당하여 최대한 많은 데이터와 인덱스를 메모리에 캐싱해야 하는데, 이를 놓쳐 불필요한 디스크 읽기가 발생하며 성능이 저하되는 경우가 많습니다.
- "트랜잭션은 길어도 상관없지 않나?" (Undo Log 비대화 문제)
- 비즈니스 로직(예: 외부 API 호출)을 트랜잭션 묶음 안에 깊숙이 포함시키는 실수를 종종 합니다.
- 트랜잭션이 길어지면, 해당 트랜잭션이 시작된 시점의 데이터를 유지하기 위해 Undo Log(언두 로그)가 끊임없이 쌓이게 됩니다. 결국 스토리지 공간이 고갈되거나 시스템 전체의 성능이 뚝 떨어지는 원인이 됩니다.
- "동기화 설정은 무조건 강력하게!" (Redo/Binlog Sync 성능 저하)
- 데이터 유실을 막기 위해 디스크 동기화 옵션(innodb_flush_log_at_trx_commit=1, sync_binlog=1)을 가장 보수적으로 설정합니다.
- 데이터 무결성이 우선인 곳에서는 필수지만, 일반적인 로그성 데이터를 적재할 때도 이렇게 설정하면 트랜잭션마다 디스크 I/O가 발생해 병목을 유발합니다.
🧠 RDBMS 아키텍처와 스토리지 엔진
이제 본격적으로 RDBMS 아키텍처의 큰 그림부터 메모리와 디스크의 상호작용, 그리고 데이터 복구를 책임지는 로그들까지 파헤쳐 봅시다.
MySQL과 Oracle은 RDBMS계의 양대 산맥이지만, 아키텍처 설계 철학에서 큰 차이를 보입니다.
1. MySQL의 플러그인(Pluggable) 스토리지 엔진 아키텍처
MySQL은 크게 'MySQL 엔진(Server Layer)'과 '스토리지 엔진(Storage Engine Layer)'으로 분리되어 있습니다.
- MySQL 엔진: 클라이언트 커넥션 관리, SQL 파싱, 옵티마이저(실행 계획 수립) 등 데이터베이스의 두뇌 역할을 합니다.
- 스토리지 엔진: 실제 데이터를 디스크에 저장하고 읽어오는 손발 역할을 합니다. 대표적으로 InnoDB가 있으며, 개발자가 필요에 따라 스토리지 엔진을 교체할 수 있는 것이 특징입니다. 트랜잭션, 락(Lock), 외래키 등은 모두 이 InnoDB 스토리지 엔진 단에서 처리됩니다.
2. Oracle의 모놀리식(Monolithic) 아키텍처
Oracle은 MySQL처럼 엔진이 분리되어 있지 않고, 하나의 거대한 통일된 아키텍처를 가집니다.
- Instance (메모리 + 프로세스): 메모리 영역인 SGA(System Global Area)와 이를 관리하는 백그라운드 프로세스(DBWn, LGWR 등)로 구성됩니다. SGA 안에는 데이터 캐시, 딕셔너리 캐시, 리두 로그 버퍼 등이 통합되어 있습니다.
- Database (디스크): 실제 데이터 파일, 컨트롤 파일, 리두 로그 파일 등이 저장되는 물리적 공간입니다.
이러한 구조적 차이 때문에 MySQL은 InnoDB의 특성을 잘 이해하는 것이 핵심이고, Oracle은 SGA 메모리 튜닝과 백그라운드 프로세스의 동작을 이해하는 것이 중요합니다.
💾 메모리와 디스크의 상호작용 (Buffer & Cache)
데이터베이스가 수백만 건의 데이터를 순식간에 처리하는 비결은, 디스크(SSD/HDD)가 아닌 '메모리(RAM)' 위에서 모든 연산을 수행하려고 하기 때문입니다.
🫀 InnoDB Buffer Pool vs Oracle DB Buffer Cache
- MySQL (InnoDB Buffer Pool): 디스크의 데이터 파일과 인덱스 정보를 메모리에 캐싱해 두는 거대한 공간입니다.
- Oracle (Database Buffer Cache): SGA 내부에 존재하며 역할은 동일합니다. 사용자가 요청한 데이터 블록을 메모리에 올려두고 재사용성을 극대화합니다.
데이터를 읽을 때 RDBMS는 무조건 이 메모리 공간부터 찾습니다. 캐시 히트(Cache Hit)가 발생하면 디스크 I/O 없이 즉시 데이터를 반환합니다.
더 중요한 것은 쓰기 작업(INSERT/UPDATE/DELETE)입니다. 데이터를 변경할 때 RDBMS는 디스크를 직접 수정하지 않습니다.
일단 메모리에 올라와 있는 데이터 블록만 수정합니다. 이렇게 메모리에서는 변경되었지만 아직 물리적 디스크에는 반영되지 않은 상태의 블록을 더티 페이지(Dirty Page) 혹은 더티 블록(Dirty Block)이라고 부릅니다.
💿 디스크로의 반영: Checkpoint와 백그라운드 스레드/프로세스
그렇다면 이 더티 블록들은 언제 디스크에 기록될까요?
- MySQL: 백그라운드에 있는 Page Cleaner Thread가 주기적으로 더티 페이지들을 모아서 디스크로 Flush 합니다.
- Oracle: DBWn (Database Writer)라는 전담 백그라운드 프로세스가 이 역할을 수행합니다. 버퍼 캐시 공간이 부족해지거나 체크포인트가 발생할 때 더티 블록을 데이터 파일에 기록합니다.
이처럼 "여기까지의 변경 사항은 디스크에 안전하게 기록되었다"라고 마커를 찍는 행위를 Checkpoint(체크포인트)라고 합니다.
메모리에서 모아서 한 번에 디스크에 쓰기 때문에 디스크 I/O 부하를 획기적으로 줄일 수 있습니다.
📜 복구와 무결성을 위한 로그들 (Undo, Redo, Binlog)
메모리에서 작업을 처리하면 성능은 좋지만, 치명적인 단점이 있습니다. "디스크에 쓰기 전에 서버 전원이 나가면 어떡하지?" 바로 이 문제를 해결하기 위해 RDBMS는 철저한 로깅 메커니즘을 갖추고 있습니다.
🔙 Undo 영역 (과거를 기억하라)
- 위치: MySQL은 Undo Log, Oracle은 Undo Tablespace라는 디스크 영역에 존재합니다.
- 역할: 데이터가 변경되기 전의 '원본(과거) 데이터'를 보관합니다.
- 핵심 기능 1 - Rollback (롤백): 트랜잭션 도중 에러가 나거나 사용자가 ROLLBACK을 선언하면, 이 Undo 영역에서 원본 데이터를 가져와 복구합니다.
- 핵심 기능 2 - MVCC (다중 버전 동시성 제어): A 사용자가 특정 데이터를 업데이트하고 아직 커밋하지 않았습니다. 이때 B 사용자가 그 데이터를 조회하면 RDBMS는 락을 걸어 B를 무한정 대기시킬까요? 아닙니다. Undo 영역에 있는 '수정되기 전의 데이터(스냅샷)'를 읽어서 반환합니다. 이 메커니즘 덕분에 읽기 작업과 쓰기 작업이 서로 락을 걸지 않고 동시에 높은 성능을 낼 수 있습니다.
🔄 Redo 영역 (미래를 보장하라 - WAL 아키텍처)
- 위치: MySQL은 Redo Log, Oracle은 Redo Log Buffer(메모리)와 Redo Log File(디스크)로 존재합니다.
- 역할: 데이터베이스에 발생한 '모든 변경 사항(물리적 변화)'을 시간 순서대로 꼼꼼히 기록합니다.
- 동작 방식 (WAL - Write Ahead Logging): RDBMS 핵심 개념입니다. 데이터를 수정할 때 더티 블록을 데이터 파일에 바로 쓰는 것은 느리고 비효율적입니다. 대신, "A를 B로 바꿨다"라는 텍스트 성격의 기록을 Redo Log 파일에 먼저 순차적으로(Sequential I/O) 빠르게 기록합니다.
- 장애 복구 (Crash Recovery): 만약 더티 블록이 디스크에 내려가기 전에 서버가 죽었다면? 재시작할 때 RDBMS는 Redo Log를 읽어 들입니다. "커밋은 떨어졌는데 데이터 파일엔 못 적었구나!" 하고 판단한 뒤, Redo Log의 내용을 바탕으로 메모리를 재구성하여 데이터를 살려냅니다.
👯 데이터 복제와 시점 복구를 위한 로그 (Binlog / Archive Log)
- 위치: MySQL은 Binlog, Oracle은 Archive Log입니다.
- 역할: Redo Log가 서버가 죽었을 때를 대비한다면, Binlog와 Archive Log는 '데이터베이스 모든 역사의 기록'입니다. Redo Log는 공간이 꽉 차면 덮어쓰기(Circular)를 하지만, 이 로그들은 덮어쓰지 않고 디스크에 영구적으로 보관합니다.
- 활용1: Replication / Data Guard: Master DB의 로그를 Slave(Standby) DB로 실시간 전송하여 똑같은 복제본을 유지할 수 있게 합니다.
- 활용2: PITR (Point-In-Time Recovery): "어제 오후 3시 정각에 실수로 테이블을 날렸습니다!" 이럴 때 일주일 전 풀 백업본을 복원한 뒤, 이 로그 파일들을 어제 오후 2시 59분 59초까지만 순차적으로 재현(Replay)하여 복구가 가능합니다.
🛠️ 실전, 트랜잭션 처리와 복구 메커니즘 흐름
개념을 잡았으니 실제 쿼리가 어떻게 동작하는지 흐름을 따라가 볼까요?
-- Client 실행
START TRANSACTION;
UPDATE users SET points = points + 100 WHERE id = 1; -- 원래 points는 50이었습니다.
COMMIT;
[내부 동작 시나리오]
- Buffer Pool 확인: id=1인 유저 데이터가 Buffer Pool에 없다면 디스크에서 읽어 메모리에 올립니다.
- Undo Log 기록: 변경 전 데이터인 points=50을 Undo Log에 백업합니다. (나중에 롤백할 수도 있으니까요!)
- Buffer Pool 수정 (더티 페이지): 메모리 상의 데이터를 points=150으로 변경합니다.
- Redo Log 버퍼 기록: id=1 유저의 points를 150으로 바꿨다는 내용을 Redo Log 버퍼에 기록합니다.
- COMMIT 실행 (중요!): * 커밋 명령이 떨어지면 가장 먼저 Redo Log 버퍼의 내용을 디스크의 Redo Log 파일에 플러시(Flush) 합니다. (이제 서버가 죽어도 데이터는 안전합니다! - WAL 메커니즘)
- 동시에 해당 변경 내역을 Binlog 파일에도 기록합니다. (MySQL은 이 Redo와 Binlog의 동기화를 위해 내부적으로 2-Phase Commit(2PC)을 수행합니다.)
- Background 작업: 이후 한가할 때(또는 체크포인트 시점에) 백그라운드 스레드가 Buffer Pool의 더티 페이지(points=150)를 실제 디스크 데이터 파일에 기록합니다.
📝 핵심 설정 포인트 (my.cnf)
실무에서는 트레이드오프를 고려해 아래 설정들을 튜닝합니다.
MySQL (my.cnf 혹은 my.ini)
# 메모리의 약 60~80%를 Buffer Pool에 할당 (가장 중요한 성능 튜닝 포인트)
innodb_buffer_pool_size = 16G
# 트랜잭션 커밋 시마다 Redo log를 디스크에 기록할 것인가?
# (1: 가장 안전, 0/2: 성능 향상, 단 1초 분량의 데이터 유실 가능성)
innodb_flush_log_at_trx_commit = 1
# Binlog를 언제 디스크에 동기화할 것인가?
# (1: 트랜잭션마다 동기화, 가장 안전 / 0: OS에 위임, 성능 우수)
sync_binlog = 1
Oracle (init.ora 혹은 spfile)
# SGA(System Global Area) 내부 버퍼 캐시 및 공유 풀 크기를 최적화합니다.
db_cache_size = 8G
shared_pool_size = 2G
# 12c 버전 이상: 트랜잭션 커밋 시 LGWR 프로세스가 디스크에 쓰는 동작을 제어합니다.
# BATCH 작업 등 대량 데이터 처리 시 비동기(nowait, batch) 방식을 활용하면 성능을 크게 높일 수 있습니다.
commit_logging = batch
commit_wait = nowait
💡 마무리: 오늘 내용의 3줄 요약
RDBMS의 아키텍처와 엔진 내부를 이해하는 것은 안정적이고 빠른 백엔드 시스템 구축에 필수입니다.
- 메모리가 핵심이다: RDBMS는 성능을 위해 Buffer Pool(메모리)에서 대부분의 작업을 처리하며, 변경된 더티 페이지는 백그라운드에서 지연 쓰기(Checkpoint)를 통해 디스크에 반영됩니다.
- 안전장치, 로그들: 롤백과 동시성을 위한 Undo Log, 장애 복구(Crash Recovery)를 위한 Redo Log, 복제(Replication)와 시점 복구를 위한 Binlog.
- WAL (Write-Ahead Logging): 데이터 파일에 직접 쓰는 것보다 순차 I/O인 Redo Log에 먼저 쓰는 것이 빠르며, 이를 통해 '성능'과 '데이터 무결성' 을 구현하는것이 RDBMS 엔진의 핵심 철학입니다.
'DB' 카테고리의 다른 글
| DB 트랜잭션 격리 수준(Isolation Level)과 MVCC 메커니즘의 이해 (1) | 2026.03.04 |
|---|---|
| B-Tree부터 커버링 인덱스까지: RDBMS 인덱스(Index) 가이드 (0) | 2026.03.02 |
| RDBMS 기본 개념 (0) | 2026.02.24 |
| 데이터베이스 1,2,3 정규화 개념 (1) | 2025.06.07 |
댓글