MySQL과 PostgreSQL은 모두 유명한 오픈소스 데이터베이스입니다.

그런데 서로 내부의 아키텍처는 다릅니다. 왜 일까요?

MySQL:      스레드 기반 (Thread-based)
PostgreSQL: 프로세스 기반 (Process-based)

이것을 이해하기 위해서는 프로세스와 스레드의 개념이 필요합니다.

A. 프로세스와 스레드의 기본

Concept

Process

  • 독립적인 프로그램 실행 환경

특징

  • 각 프로세스는 완전히 독립된 메모리 공간을 가짐
  • 다른 프로세스의 메모리에 접근할 수 없음 (OS가 막음)
  • 안전하지만, 생성 비용이 비쌈
┌─────────────────────┐
│ Process A (PID 100) │
├─────────────────────┤
│ Code (실행 코드)    │
│ Data (전역변수)     │
│ Heap (동적 할당)    │
│ Stack (함수 호출)   │
├─────────────────────┤
│ Page Table          │  ← 자신만의 메모리 매핑
│ PCB                 │  ← 자신의 제어 정보
└─────────────────────┘

┌─────────────────────┐
│ Process B (PID 101) │
├─────────────────────┤
│ Code (다른 코드)    │
│ Data (다른 데이터)  │
│ Heap                │
│ Stack               │
├─────────────────────┤
│ Page Table          │  ← 완전히 다른 매핑
│ PCB                 │
└─────────────────────┘

Thread

  • 같은 프로세스 내의 여러 “실행 흐름”

특징:

  • 같은 프로세스 내의 스레드들은 Code, Data, Heap을 공유
  • 각 스레드는 자신의 Stack과 TCB만 독립적으로 가짐
  • 빠르지만, 메모리 공유로 인한 복잡성
┌────────────────────────────────────┐
│ Process A (PID 100)                │
├────────────────────────────────────┤
│ Code (실행 코드) - 공유             │
│ Data (전역변수) - 공유              │
│ Heap (동적 할당) - 공유             │
├────────────────────────────────────┤
│ Thread 1          │ Thread 2       │
│ ┌─────────────┐   │ ┌─────────────┐│
│ │Stack 1      │   │ │Stack 2      ││
│ │TCB 1        │   │ │TCB 2        ││
│ │(독립적)     │   │ │(독립적)     ││
│ └─────────────┘   │ └─────────────┘│
└────────────────────────────────────┘

생성 비용 비교 : Process vs Thread

Process 생성 절차

1. PCB (Process Control Block) 생성
   └─ 프로세스의 정보를 담을 구조체 생성

2. 독립된 메모리 공간 할당
   ├─ Code 영역: 프로그램 코드 로드
   ├─ Data 영역: 초기화된 전역변수 준비
   ├─ Heap: 동적 할당을 위한 준비
   └─ Stack: 함수 호출을 위한 준비

3. Page Table 구축
   └─ 가상 주소 ↔ 물리 주소 매핑 만들기
      (매우 복잡! 모든 메모리 영역의 매핑이 필요)

4. 다양한 커널 자료구조 초기화
   ├─ 파일 디스크립터 테이블
   ├─ 신호 처리 정보
   └─ 기타 OS 자원들

총 비용:

  • 메모리: 프로세스 1개 = 5~20 MB
  • 시간: 생성에 수십~수백 ms 소요
  • 예시: 프로세스 1000개 = 5~20 GB 메모리, 수초의 초기화 시간

Thread 생성 절차

1. TCB (Thread Control Block) 생성
   └─ 스레드의 정보를 담을 작은 구조체 생성

2. 독립된 Stack만 할당
   └─ 대략 1~2 MB 정도만 필요
   └─ Code, Data, Heap은 이미 있으니 "포인터"로 공유

3. 레지스터 상태 초기화
   └─ 프로그램 카운터, 스택 포인터 등만 설정

4. OS 자료구조는 기존 프로세스 것 재사용

총 비용:

  • 메모리: 스레드 1개 = 약 1 MB (Stack만)
  • 시간: 생성에 마이크로초 단위
  • 예시: 스레드 1000개 = 1 GB 메모리, 밀리초 단위 초기화

결론

  • 스레드가 프로세스보다 100~1000배 빠르고 가볍다!

Context Switching

가정

  • single core CPU를 사용한다고 가정.

상황

  • 여러 작업을 동시에 수행해야 함.

명제

  • CPU는 한 번에 한 개의 프로세스/스레드만 실행할 수 있음.

이를 위해서는 CPU 가 쉴 틈 없이 많은 작업을 하게 만들어서 다양한 일을 동시에 처리하는 것처럼 사용자가 느끼게 해야함. Context Switching

Time 0-100ms: 프로세스 A 실행
Time 100-200ms: 프로세스 B 실행
Time 200-300ms: 프로세스 A 실행
...

그러나Context Switching 비용 역시 ProcessThread 에서 차이가 존재한다.

프로세스 간 Context Switching

  • TLB 재구축: ~1000 CPU 사이클
  • 캐시 재구축: ~10마이크로초 ~ 1밀리초
  • 총 비용: 1~10 ms 정도 (매우 느림!)
프로세스 A 실행 중...

[Context Switch 발생]

 
OS의 할 일:
1. 프로세스 A의 CPU 레지스터 값 저장
2. 프로세스 B의 CPU 레지스터 값 복원
 
3. ⚠️ MMU(Memory Management Unit) 설정 변경
   ├─ Page Table을 A의 것에서 B의 것으로 전환
   └─ 가상 주소 → 물리 주소 매핑 완전히 바뀜
 
4. ⚠️ TLB (Translation Lookaside Buffer) 비우기
   └─ CPU의 주소 변환 캐시를 완전히 초기화
   └─ 이유: A의 매핑이 캐시되어 있는데 B가 필요함
 
5. ⚠️ CPU 캐시 오염
   └─ L1/L2 캐시에 A의 데이터가 남아있음
   └─ B가 처음부터 새로 캐시를 채워야 함

프로세스 B 실행 시작 (But 초기에는 매우 느림)

스레드 간 Context Switching

  • 레지스터만 교환: ~100 CPU 사이클
  • 총 비용: 약 1 마이크로초 (빠름!)
스레드 A 실행 중...

[Context Switch 발생]

 
OS의 할 일:
1. 스레드 A의 CPU 레지스터 값 저장
2. 스레드 B의 CPU 레지스터 값 복원
 
3. ✓ MMU 설정 변경 안 함!
   └─ 같은 프로세스이므로 Page Table 그대로
 
4. ✓ TLB 비우기 안 함!
   └─ Page Table이 같으므로 기존 매핑 유효
 
5. ✓ CPU 캐시도 대부분 유효
   └─ A와 B가 같은 메모리에 접근할 확률이 높음

스레드 B 실행 시작 (거의 지연 없음)

결론

  • 스레드 전환이 프로세스 전환보다 약 1000배 빠르다!

B. 왜 MySQL은 스레드를 선택했을까?

MySQL 를 웹서비스와 함께 사용하는 상황을 가정해보자. 그리고 이 서비스는 나름 인기를 얻고 있어서 동시에 1000명의 사용자가 접속할 정도라면 아래와 같은 구조가 된다.

웹 서버 + MySQL 구조:

Client 1 ─┐
Client 2 ─┤
Client 3 ─┼─→ MySQL 서버
...      │
Client 1000─┘

동시에 1000명의 클라이언트가 MySQL에 접근

이제 Process 기반이었을 경우와, Thread 기반을 비교해서 살펴보자. Connection Pool 같은 기술은 사용하지 않는 경우를 가정했다.

Process

Client 1 ──→ Process 1 (5 MB)
Client 2 ──→ Process 2 (5 MB)
Client 3 ──→ Process 3 (5 MB)
...
Client 1000 ──→ Process 1000 (5 MB)
  • 메모리 사용량 多
    • 하나의 Process 가 사용하는 메모리가 5MB 라고 하면, 1000명의 사용자를 위해 5GB 의 메모리를 사용.
  • Context Switching 비용 多
    • Context Switching에 많은 cpu 자원이 낭비됨.
├─ 프로세스 1 → 프로세스 2: 1~10 ms 지연
├─ 프로세스 2 → 프로세스 3: 1~10 ms 지연

Thread

  • 비교적 적은 메모리 사용량
    • 하나의 Thread 가 사용하는 메모리가 프로세스의 20% 정도인 1MB 라고 하자. 그 경우, 총 1GB
      • Thread 가 더 적은 메모리를 사용하는 이유를 공감하지 못한다면, 위를 다시 보자.
  • 비교적 적은 Context Switching 시간
  • 추가 이점
    • Buffer Pool (캐시) 공유: 모든 스레드가 함께 사용
    • 데이터 접근 속도 극도로 빠름
Client 1 ──→ Thread 1 (1 MB)
Client 2 ──→ Thread 2 (1 MB)
Client 3 ──→ Thread 3 (1 MB)
...
Client 1000 ──→ Thread 1000 (1 MB)

Context Switching도 빠름:
├─ Thread 1 → Thread 2: ~1 마이크로초
├─ Thread 2 → Thread 3: ~1 마이크로초
└─ 거의 무시할 수 있는 수준!

결론

  • Thread 기반 구조를 사용할 경우, 메모리 절감 + 속도 향상를 통해 더 많은 동시 사용자 처리가 가능하여 MySQLThread 기반 구조를 채택

C. 스레드 기반의 문제

그러나 Thread 를 사용하기 때문에 Process 기반에서는 발생하지 않을 문제도 보유하게 됨.

  • 메모리 공유 로 인한 데이터 정합성 문제
  • 하나의 스레드의 오류의 전파

C-1: 메모리 공유로 인한 데이터 오염

  • 상황
    • 여러 Thread 가 같은 변수에 접근해서 값을 1 만큼 올리는 동작을 하는 것을 상상해보자.
Thread 1        Thread 2
  ↓              ↓
  └──→ [공유 메모리 변수] ←──┘
       count = 0

Thread 1에서: count = count + 1
Thread 2에서: count = count + 1

문제 상황 Race Condition

기대값: count = 2
실제값: count = 1 (또는 다른 값!) ❌

발생 원인

  • 실행 순서의 보장 X.
Time 1: Thread 1: count 읽음 (0)
Time 2: Thread 2: count 읽음 (0)  ← 아직 Thread 1의 변경이 반영 안 됨
Time 3: Thread 1: count + 1 = 1 쓰기
Time 4: Thread 2: count + 1 = 1 쓰기  ← 결과: 1

C-2: 하나의 스레드 오류가 전체를 죽임

  • 하나의 thread 오류가 서비스 전체에 영향을 끼칠 수 있음
모든 클라이언트 ──→ MySQL 서버 (하나의 프로세스) ──→ Thread 1, 2, 3...

Thread 2가 메모리 오류 (Segmentation Fault) 발생!
    ↓
프로세스 전체가 죽음 ❌
    ↓
모든 스레드가 중단 ❌
    ↓
모든 클라이언트 연결 끊김 😱

반면 Process 기반에서는 이러한 문제가 발생하지 않음.

프로세스 기반 (PostgreSQL):
Client 1 ──→ Process 1
Client 2 ──→ Process 2  ← 이 프로세스가 오류 발생!
Client 3 ──→ Process 3

Process 2가 죽어도:
├─ Process 1은 계속 실행 ✓
├─ Process 3도 계속 실행 ✓
└─ Client 2만 연결 끊김

C-3. MySQL에서 Thread의 문제를 해결한 방법

해결책 1: MVCC (Multi-Version Concurrency Control)

아이디어: 데이터를 “버전 관리”처럼 처리

원본 데이터: name = "Alice", age = 25

Thread 1: UPDATE로 age를 30으로 변경하려고 함

MVCC가 하는 일:
├─ 원본 보존: Version 0 (age = 25) - 계속 유지
├─ 새 버전 생성: Version 1 (age = 30) - Thread 1이 수정 중
└─ Undo Log: 이전 버전 기록

동시에 다른 스레드들:
├─ Thread 2: SELECT name, age  → Version 0 읽음 (age = 25)
├─ Thread 3: SELECT name, age  → Version 0 읽음 (age = 25)
└─ Thread 1: UPDATE 완료        → Version 1 완성 (age = 30)

결과: 잠금 없이도 안전한 읽기 가능!

효과: Race Condition 방지, 동시성 증가


해결책 2: Fine-Grained Locking (세분화된 잠금)

아이디어: 전체 데이터베이스를 잠그지 말고, 필요한 부분만 잠금

❌ 과거 방식 (느림):
Buffer Pool 전체를 하나의 Lock으로 관리
    ↓
Thread 1이 Lock 획득 → 페이지 접근
Thread 2가 대기... → 다른 페이지에 접근하고 싶어도 못함
Thread 3도 대기...
결과: 많은 대기 시간

✅ 현대 MySQL:
Buffer Pool을 여러 Instance로 분할
├─ Instance 1 [Lock 1] → Thread 1이 사용
├─ Instance 2 [Lock 2] → Thread 2가 동시에 사용 ✓
├─ Instance 3 [Lock 3] → Thread 3도 동시에 사용 ✓
└─ Instance 4 [Lock 4] → Thread 4도 동시에 사용 ✓

결과: 대기 시간 극감!

효과: 스레드가 많아도 전체가 느려지지 않음


해결책 3: Signal Handling (신호 처리)

아이디어: 스레드 오류를 감지하고 격리

Thread 2에서 메모리 오류 발생!
    ↓
MySQL의 Signal Handler가 감지
    ↓
Thread 2가 가진 Lock/Mutex를 강제로 해제
    ↓
Thread 2만 종료 (다른 스레드는 계속 실행) ✓
    ↓
(하지만 완벽한 해결은 아님 - 메모리가 이미 오염되었을 수 있음)

효과: 일부 상황에서는 전체 시스템 붕괴 방지


7️⃣ PostgreSQL은 왜 프로세스를 고수했을까?

안정성의 철학

PostgreSQL의 생각:

"우리는 금융, 의료, 정부 시스템처럼 
데이터 손실이 절대 안 되는 분야도 사용할 것이다.

따라서 스레드의 복잡성보다 
프로세스의 강력한 격리가 더 중요하다."

각 클라이언트 ──→ 독립 프로세스 (물리적 격리)
    ↓
한 프로세스의 오류가 다른 프로세스에 영향 없음
    ↓
데이터 무결성 보장!

PostgreSQL이 감수하는 비용

대신에...

1. 메모리 사용량이 많음
   └─ Client 1000명 = 5~20 GB 메모리 필요

2. Context Switching 오버헤드 많음
   └─ 1000개 프로세스 간 전환 = 시간 낭비

3. 하지만 안정성은 최고
   └─ 한 클라이언트의 오류가 다른 클라이언트에 영향 없음

8️⃣ MySQL이 PostgreSQL에게 지는 상황

시나리오 A: 복잡한 분석 쿼리 (OLAP)

쿼리: 100억 건의 데이터를 분석하고 집계

MySQL의 한계:
├─ 하나의 쿼리는 기본적으로 1개 스레드만 사용
├─ 32 코어 CPU가 있어도 1개 코어만 사용
└─ 처리 시간: 100초

PostgreSQL의 병렬 처리:
├─ 하나의 쿼리를 여러 프로세스로 분산
├─ 32 코어 CPU를 거의 모두 활용
└─ 처리 시간: 5초 (20배 빠름!)

결론: OLAP은 PostgreSQL이 훨씬 유리

시나리오 B: 장시간 실행 쿼리

상황: 읽기 쿼리 A가 1시간 이상 실행 중

MySQL (MVCC 문제):
├─ 쿼리 A가 오래된 버전을 참조하고 있음
├─ 그 동안 UPDATE가 1000만 건 발생
├─ Undo Log 크기: 점점 증가 (메모리 낭비)
├─ 디스크 I/O 폭증 (성능 저하)
└─ Purge Thread 과부하 (전체 시스템 느려짐)

PostgreSQL:
├─ VACUUM 작업이 명시적이고 예측 가능
├─ 필요할 때만 Lock을 잠시 획득하고 정리
└─ 성능이 더 예측 가능

9️⃣ 정리: 언제 뭘 써야 할까?

MySQL 사용하기 좋은 상황

OLTP (짧은 쿼리 폭주)

예: 웹 서비스 (SNS, 전자상거래)
- 각 쿼리 실행 시간: 100ms 미만
- 동시 접속자: 수천~수만
- 빠른 응답이 가장 중요

→ MySQL 최적

메모리가 제한적인 경우

예: 클라우드 환경 (작은 VM)
- 사용 가능 메모리: 4GB
- 동시 사용자: 수백~수천

→ 프로세스 기반은 메모리 초과
→ MySQL의 스레드 기반이 필수

PostgreSQL 사용하기 좋은 상황

OLAP (복잡한 분석)

예: 데이터 웨어하우스
- 쿼리 실행 시간: 수분~수시간
- 병렬 처리가 중요
- 데이터 정합성 중요

→ PostgreSQL 최적

안정성이 생명인 경우

예: 금융, 의료, 정부 시스템
- 데이터 손실 절대 불가
- 버그로 인한 시스템 다운 절대 불가
- 강력한 격리가 필수

→ PostgreSQL의 프로세스 기반 격리가 필수

데이터가 매우 큰 경우

예: 테라바이트 단위 데이터 분석
- 메모리 사용량이 중요하지 않음
- 정확한 데이터 처리가 가장 중요

→ PostgreSQL

🔟 결론: 트레이드오프 (Trade-off)

MySQL은 우월한 것이 아니라 "최적화된 것"일 뿐입니다.

MySQL의 선택:
"성능을 극대화하되, 
 안정성의 마지노선을 소프트웨어(MVCC, Fine-grained Lock)로 관리한다"

PostgreSQL의 선택:
"성능은 조금 포기하되,
 OS의 프로세스 격리로 절대적인 안정성을 보장한다"

둘 다 합리적인 선택입니다. 
상황에 따라 최적의 도구가 다를 뿐입니다.

1️⃣1️⃣ 학부 시험 대비 핵심 정리

Q1: MySQL이 스레드를 선택한 이유는?

:

1. 메모리 효율: 스레드는 프로세스보다 메모리 사용 5배 적음
2. Context Switching: 스레드 전환이 프로세스 전환보다 1000배 빠름
3. 캐시 효율: 같은 주소 공간을 사용하므로 캐시 히트율 높음
4. 많은 동시 연결 처리 가능

Q2: PostgreSQL이 프로세스를 고수한 이유는?

:

1. 격리: 한 클라이언트 오류가 다른 클라이언트에 영향 없음
2. 안정성: OS 레벨에서 메모리 보호
3. 병렬 처리: 하나의 쿼리를 여러 프로세스로 분산 가능
4. 금융/의료 같은 안전성 중요 분야에 적합

Q3: MySQL의 위험성은?

:

한 스레드의 오류 → 전체 프로세스 죽음 → 모든 클라이언트 연결 끊김

해결책:
1. MVCC: 버전 관리로 Race Condition 방지
2. Fine-grained Locking: 세분화된 잠금으로 경합 감소
3. Signal Handling: 오류 감지 및 격리 시도
(하지만 완벽하지는 않음)

Q4: Context Switching이 왜 느릴까?

:

프로세스 전환:
- Page Table 변경 필요 (가상 주소 매핑이 완전히 다름)
- TLB (주소 변환 캐시) 초기화 필요
- CPU 캐시도 초기화됨
- 총 1~10ms 소요

스레드 전환:
- Page Table 변경 불필요 (같은 주소 공간)
- TLB 유지 가능
- CPU 캐시도 대부분 유효
- 총 1마이크로초 소요

→ 약 1000배의 차이!

1️⃣2️⃣ 추가 학습: 실무 관점

MySQL을 쓸 때 조심할 점

1. 장시간 실행 쿼리 금지
   └─ Purge Lag 발생 → 전체 시스템 느려짐

2. 한 번에 너무 많은 UPDATE 금지
   └─ 메모리 폭증 → Undo Log 비대화

3. 복잡한 분석은 피하기
   └─ 병렬 처리 불가 → 시간 초과

4. 연결 풀 사용 필수
   └─ 스레드 생성 오버헤드 줄이기

PostgreSQL을 쓸 때 장점

1. 복잡한 분석도 안심
   └─ 병렬 처리가 자동으로 최적화

2. 메모리 오염 걱정 없음
   └─ 프로세스가 물리적으로 격리됨

3. 데이터 무결성 최고
   └─ 많은 금융권에서 선택

마지막: 기억해야 할 것

“기술에 정답은 없습니다. 상황에 따라 트레이드오프를 이해하고 최적의 선택을 하는 것이 엔지니어의 역량입니다.”

MySQL과 PostgreSQL의 차이는:

  • 어느 것이 우월한가의 문제가 아니라
  • 어떤 상황에 최적화되어 있는가의 문제입니다.