본문 바로가기
DB

RDBMS 아키텍처와 스토리지 엔진

by 정권이 내 2026. 3. 1.

🚀 RDBMS 내부 동작, 왜 알아야 할까요?

백엔드 개발을 시작하고 처음 몇 년간은 비즈니스 로직을 구현하고, ORM 을 사용해 CRUD 처리에만 집중하셨을 겁니다. "쿼리만 잘 짜면 되는 거 아냐?"라고 생각할 수 있지만, 트래픽이 몰리는 대용량 서비스 환경이나 치명적인 장애 상황을 맞닥뜨리면 이야기가 달라집니다.

  • "갑자기 DB 서버가 죽었다 살아났는데 데이터는 안전한가?"
  • "왜 Replication(복제) 지연이 이렇게 심하게 발생할까?"
  • "단순한 UPDATE 쿼리 하나인데 왜 이렇게 오랫동안 락(Lock)이 걸릴까?"

이런 문제들을 근본적으로 해결하고 성능을 최적화하기 위해서는 쿼리 튜닝을 넘어, 데이터베이스가 메모리와 디스크 사이에서 데이터를 어떻게 다루는지, 그리고 장애에 대비해 어떤 로그를 남기는지 그 '내부 아키텍처와 스토리지 엔진의 동작 방식'을 반드시 이해해야 합니다.

🤦‍♂️ RDBMS 내부를 모를 때 하는 실수들

본격적인 설명에 앞서, 실무에서 아키텍처와 로그의 역할을 정확히 이해하지 못해 겪는 대표적인 시행착오 몇 가지를 살펴보겠습니다.

  1. "메모리는 거들 뿐, SSD니까 알아서 빠르겠지?" (Buffer Pool 설정 간과)
    • 데이터베이스 성능의 핵심은 디스크 I/O를 최소화하는 것입니다.
    • 서버 스펙만 믿고 MySQL의 innodb_buffer_pool_size를 기본값으로 방치하면 메모리를 충분히 할당하여 최대한 많은 데이터와 인덱스를 메모리에 캐싱해야 하는데, 이를 놓쳐 불필요한 디스크 읽기가 발생하며 성능이 저하되는 경우가 많습니다.
  2. "트랜잭션은 길어도 상관없지 않나?" (Undo Log 비대화 문제)
    • 비즈니스 로직(예: 외부 API 호출)을 트랜잭션 묶음 안에 깊숙이 포함시키는 실수를 종종 합니다.
    • 트랜잭션이 길어지면, 해당 트랜잭션이 시작된 시점의 데이터를 유지하기 위해 Undo Log(언두 로그)가 끊임없이 쌓이게 됩니다. 결국 스토리지 공간이 고갈되거나 시스템 전체의 성능이 뚝 떨어지는 원인이 됩니다.
  3. "동기화 설정은 무조건 강력하게!" (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;

[내부 동작 시나리오]

  1. Buffer Pool 확인: id=1인 유저 데이터가 Buffer Pool에 없다면 디스크에서 읽어 메모리에 올립니다.
  2. Undo Log 기록: 변경 전 데이터인 points=50을 Undo Log에 백업합니다. (나중에 롤백할 수도 있으니까요!)
  3. Buffer Pool 수정 (더티 페이지): 메모리 상의 데이터를 points=150으로 변경합니다.
  4. Redo Log 버퍼 기록: id=1 유저의 points를 150으로 바꿨다는 내용을 Redo Log 버퍼에 기록합니다.
  5. COMMIT 실행 (중요!): * 커밋 명령이 떨어지면 가장 먼저 Redo Log 버퍼의 내용을 디스크의 Redo Log 파일에 플러시(Flush) 합니다. (이제 서버가 죽어도 데이터는 안전합니다! - WAL 메커니즘)
    • 동시에 해당 변경 내역을 Binlog 파일에도 기록합니다. (MySQL은 이 Redo와 Binlog의 동기화를 위해 내부적으로 2-Phase Commit(2PC)을 수행합니다.)
  6. 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의 아키텍처와 엔진 내부를 이해하는 것은 안정적이고 빠른 백엔드 시스템 구축에 필수입니다.

  1. 메모리가 핵심이다: RDBMS는 성능을 위해 Buffer Pool(메모리)에서 대부분의 작업을 처리하며, 변경된 더티 페이지는 백그라운드에서 지연 쓰기(Checkpoint)를 통해 디스크에 반영됩니다.
  2. 안전장치, 로그들: 롤백과 동시성을 위한 Undo Log, 장애 복구(Crash Recovery)를 위한 Redo Log, 복제(Replication)와 시점 복구를 위한 Binlog.
  3. WAL (Write-Ahead Logging): 데이터 파일에 직접 쓰는 것보다 순차 I/O인 Redo Log에 먼저 쓰는 것이 빠르며, 이를 통해 '성능'과 '데이터 무결성' 을 구현하는것이 RDBMS 엔진의 핵심 철학입니다.
반응형

댓글