한 줄 정의

MySQL은 커넥션마다 프로세스를 fork하는 대신 스레드를 할당하는 구조를 택했고, 이 선택은 메모리 공유 효율성과 커넥션 생성 비용이라는 실리적인 이유에서 비롯되었습니다.

쉽게 말하면

회사에 새 직원이 올 때마다 사무실을 하나씩 새로 짓는 방식(프로세스 기반)과, 같은 사무실에서 책상만 하나 추가하는 방식(스레드 기반)의 차이입니다.

MySQL은 후자를 택했습니다. 같은 공간(메모리)을 공유하니까 소통 비용이 적고, 새 사람이 들어올 때 준비도 빠릅니다. 대신 한 사람이 사고를 치면 같은 사무실에 있는 모든 사람이 영향을 받는다는 리스크가 있습니다.

왜 이렇게 설계했는가?

메모리 공유가 핵심이다

MySQL에서 가장 비싼 자원은 InnoDB 버퍼 풀 입니다. 수 GB에서 수십 GB에 달하는 이 캐시를 모든 커넥션이 자연스럽게 공유할 수 있다는 것이 스레드 기반의 가장 큰 장점입니다.

프로세스 기반이었다면? 각 프로세스가 독립된 주소 공간을 가지므로, 이 거대한 버퍼 풀을 공유하려면 OS의 공유 메모리(shared memory) 세그먼트를 별도로 설정하고 관리해야 합니다. PostgreSQL이 shared_buffers를 위해 실제로 이렇게 하고 있습니다.

커넥션 생성 비용

웹 애플리케이션 환경에서는 짧은 커넥션이 빈번하게 생성/해제됩니다. fork() 시스템 콜로 프로세스를 생성하는 것보다 스레드 생성이 훨씬 가볍습니다.

물론 MySQL도 매번 스레드를 새로 만들지는 않고 스레드 캐시 (thread_cache_size)를 통해 재사용합니다. 프로세스 기반 DB도 커넥션 풀링으로 이 문제를 완화하지만, 근본적인 생성 비용 차이는 여전합니다.

컨텍스트 스위칭

스레드 간 전환은 프로세스 간 전환보다 가볍습니다. 프로세스 전환 시에는 TLB(Translation Lookaside Buffer) 플러시 와 페이지 테이블 교체가 발생하지만, 같은 프로세스 내 스레드 전환에서는 이런 비용이 없습니다.

다만 이것만으로 아키텍처를 결정하지는 않습니다. 현대 OS에서 컨텍스트 스위칭 비용 자체는 크게 줄었기 때문입니다.

IPC 오버헤드 제거

프로세스 기반에서는 프로세스 간 데이터를 주고받으려면 공유 메모리, 소켓, 파이프 같은 IPC(Inter-Process Communication) 메커니즘이 필요합니다. 스레드는 같은 주소 공간을 사용하므로 뮤텍스/세마포어만으로 동기화가 가능합니다.

그러면 프로세스 기반은 왜 쓰는 건가?

스레드 기반이 이렇게 효율적이라면, 왜 모든 DB가 스레드 기반을 택하지 않았을까요?

PostgreSQL — 프로세스 기반의 대표 주자

PostgreSQL은 클라이언트 커넥션마다 fork()백엔드 프로세스 를 생성합니다. 1986년 개발 초기부터 이 모델을 사용해왔는데, 단순히 “옛날 방식이라서”가 아니라 명확한 설계 철학이 있습니다.

안정성과 격리성 우선

하나의 백엔드 프로세스가 크래시해도 해당 커넥션만 끊어지고, 다른 커넥션은 영향을 받지 않습니다. MySQL에서는 하나의 스레드가 메모리를 오염시키면 같은 프로세스 내의 모든 스레드가 영향을 받을 수 있습니다.

OS의 메모리 보호를 그대로 활용

프로세스 간에는 OS가 메모리 보호를 해주므로, 한 프로세스가 다른 프로세스의 메모리를 실수로 덮어쓸 수 없습니다. 디버깅할 때도 큰 이점입니다. 문제가 발생한 프로세스만 분석하면 되니까요.

커넥션이 많아지면 어떻게 하는가?

커넥션 하나당 프로세스 하나이므로, 커넥션이 수백~수천 개로 늘어나면 메모리와 OS 자원이 빠르게 소진됩니다.

이 문제를 해결하기 위해 PostgreSQL 진영에서는 PgBouncer 라는 외부 커넥션 풀러를 사용합니다. DB 앞단에 별도 프로세스로 위치하면서, 애플리케이션의 커넥션 요청을 받아 실제 PostgreSQL 커넥션을 재사용하는 방식으로 중계합니다. 애플리케이션에서 500개의 커넥션이 들어와도 실제 PostgreSQL에는 50개만 유지할 수 있습니다.

Java/Spring의 HikariCP가 애플리케이션 내부 풀링이라면, PgBouncer는 애플리케이션 외부에서 동작하는 풀링입니다. 프로세스 기반 아키텍처를 선택했기 때문에 이런 외부 풀러가 사실상 필수가 되었다는 점에서, 아키텍처 선택의 부수 효과라고 볼 수 있습니다.

Oracle — 둘 다 지원

Oracle은 Dedicated Server 모드 (커넥션당 프로세스)와 Shared Server 모드 (디스패처가 요청을 공유 서버 프로세스에 분배)를 모두 지원합니다. 워크로드 특성에 따라 선택할 수 있습니다.

정리

flowchart LR
    subgraph MySQL["MySQL (스레드 기반)"]
        direction TB
        P1["mysqld 프로세스"]
        T1["스레드 1"] --> P1
        T2["스레드 2"] --> P1
        T3["스레드 3"] --> P1
        BP["버퍼 풀 (공유)"] -.-> P1
    end

    subgraph PostgreSQL["PostgreSQL (프로세스 기반)"]
        direction TB
        PM["Postmaster"]
        B1["Backend 1"]
        B2["Backend 2"]
        B3["Backend 3"]
        PM --> B1
        PM --> B2
        PM --> B3
        SB["shared_buffers (공유 메모리)"] -.-> B1
        SB -.-> B2
        SB -.-> B3
    end
기준스레드 기반 (MySQL)프로세스 기반 (PostgreSQL)
메모리 공유같은 주소 공간, 자연스러운 공유공유 메모리 세그먼트 필요
커넥션 생성 비용낮음 (스레드 생성)높음 (fork())
컨텍스트 스위칭가벼움 (TLB 유지)무거움 (TLB 플러시)
장애 격리약함 (한 스레드 크래시 → 전체 영향)강함 (해당 프로세스만 종료)
디버깅어려움 (멀티스레드 버그)상대적으로 용이
대표적 공유 자원InnoDB 버퍼 풀 (힙 영역)shared_buffers (공유 메모리)

정리

각자의 약점을 다른 계층에서 메우고 있다는 점이 흥미롭습니다.

MySQLPostgreSQL
설계 우선순위경량화, 빠른 커넥션 처리격리성, 장애 전파 방지
약점한 스레드 크래시 → 전체 영향커넥션 증가 → 프로세스 폭증
약점을 보완하는 방법레플리카 구성으로 가용성 확보PgBouncer 등 외부 커넥션 풀러

내 생각

  • MySQL이 스레드 기반이라는 것은 단순한 구현 선택이 아니라, InnoDB 버퍼 풀 중심의 아키텍처와 깊이 맞물린 설계 결정입니다. 버퍼 풀을 자연스럽게 공유할 수 있기 때문에 innodb_buffer_pool_size를 물리 메모리의 70~80%까지 할당하는 공격적인 설정이 가능합니다.

  • 반면 스레드 기반의 약점인 “한 스레드 크래시 = 전체 영향”은 실무에서 MySQL 서버가 갑자기 죽는 상황으로 나타날 수 있습니다. 이 때문에 MySQL에서는 레플리카 구성(복제)이 사실상 필수적입니다.

관련 개념

출처

  • MySQL 공식 문서 — Connection Handling and Threads
  • PostgreSQL 공식 문서 — Server Processes