- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- 전체 서버 아키텍처
- 스토리지 엔진 깊이 보기
- 복제 (Replication)
- Galera 클러스터 (동기 멀티 마스터)
- 파티셔닝 (Partitioning)
- MariaDB와 MySQL의 관계와 차이
- 운영과 튜닝
- 함정과 흔한 실수
- 마치며
- 참고 자료
들어가며
MariaDB는 MySQL에서 갈라져 나온 오픈소스 관계형 데이터베이스입니다. 처음에는 MySQL과 거의 같은 코드를 공유했지만, 시간이 지나면서 독자적인 스토리지 엔진, 복제 방식, 클러스터링 기술을 발전시켜 왔습니다. 그래서 "MySQL과 호환되는 무언가" 정도로만 알고 있으면, 실제 운영 환경에서 마주치는 여러 선택지와 동작 차이를 제대로 다루기 어렵습니다.
이 글에서는 MariaDB의 아키텍처를 위에서 아래로 따라가 봅니다. 클라이언트 요청이 들어와서 SQL이 파싱되고, 옵티마이저가 실행 계획을 세우고, 실제 데이터를 읽고 쓰는 스토리지 엔진까지 도달하는 전체 흐름을 먼저 그립니다. 그다음 InnoDB, Aria, ColumnStore, MyRocks 같은 스토리지 엔진을 비교하고, 비동기 복제부터 준동기, GTID, 그리고 Galera 동기 멀티 마스터 클러스터까지 데이터를 여러 노드로 퍼뜨리는 방법을 살펴봅니다. 마지막으로 파티셔닝과 운영 시 자주 마주치는 함정을 정리합니다.
버전에 따라 기본값이나 지원 여부가 달라질 수 있으므로, 구체적인 수치나 기능은 항상 사용 중인 버전의 공식 문서를 확인하는 것을 권장합니다. 이 글은 큰 그림과 개념을 잡는 데 초점을 맞춥니다.
전체 서버 아키텍처
MariaDB 서버는 크게 두 층으로 나눠 생각하면 이해가 빠릅니다. 위쪽은 SQL을 해석하고 실행 계획을 세우는 서버 계층(SQL layer) 이고, 아래쪽은 실제 데이터를 디스크에 저장하고 읽어 오는 스토리지 엔진 계층 입니다. 이 둘을 분리한 것이 MySQL 계열의 가장 중요한 설계 특징입니다.
클라이언트 (앱 / mysql CLI / 커넥터)
│
▼
┌───────────────────────────────────────────────────┐
│ Connection Layer │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ Thread Pool │ │ 인증 │ │ 세션/권한 │ │
│ └─────────────┘ └──────────────┘ └───────────┘ │
└───────────────────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ SQL Layer │
│ ┌────────┐ ┌────────┐ ┌──────────┐ ┌───────┐ │
│ │ Parser │──▶│Rewrite │──▶│Optimizer │─▶│ Plan │ │
│ └────────┘ └────────┘ └──────────┘ └───┬───┘ │
│ │ 쿼리 캐시(있는 경우) / 권한 확인 │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Executor │ │
│ └───────────────────────┬──────────────────────┘ │
└──────────────────────────┼─────────────────────────┘
│ Storage Engine API (handler)
▼
┌───────────────────────────────────────────────────┐
│ Storage Engine Layer │
│ ┌────────┐ ┌──────┐ ┌────────────┐ ┌────────┐ │
│ │ InnoDB │ │ Aria │ │ ColumnStore│ │MyRocks │ │
│ └────────┘ └──────┘ └────────────┘ └────────┘ │
└───────────────────────────────────────────────────┘
│
▼
데이터 파일 / 로그 / 인덱스
각 단계가 하는 일을 짚어 보겠습니다.
연결 계층 (Connection Layer)
클라이언트가 접속하면 서버는 우선 연결을 받아들이고 인증을 처리합니다. 사용자 이름, 비밀번호, 접속 호스트, 권한을 확인하고, 세션 단위의 변수와 컨텍스트를 준비합니다.
MariaDB는 연결마다 스레드를 할당하는 전통적인 방식과 스레드 풀(thread pool) 방식을 모두 지원합니다. 연결이 많고 짧은 쿼리가 빈번한 환경에서는 스레드 풀이 컨텍스트 스위칭 비용과 메모리 사용을 줄여 줍니다. 동시에 수천 개의 연결을 다뤄야 하는 워크로드라면 스레드 풀을 검토할 가치가 있습니다.
SQL 계층 (Parser · Optimizer · Executor)
연결이 확립되면 들어온 SQL 문자열은 다음 단계를 거칩니다.
- 파서(Parser): SQL 문자열을 토큰으로 쪼개고 문법을 검사해 내부 구문 트리로 만듭니다. 문법 오류는 여기서 걸러집니다.
- 전처리/리라이트(Preprocess · Rewrite): 뷰를 펼치거나, 일부 서브쿼리를 조인으로 바꾸는 등 옵티마이저가 다루기 좋은 형태로 다듬습니다.
- 옵티마이저(Optimizer): 비용 기반(cost-based)으로 여러 실행 방법 중 가장 저렴할 것으로 추정되는 계획을 고릅니다. 어떤 인덱스를 쓸지, 조인 순서를 어떻게 할지, 임시 테이블이나 정렬이 필요한지를 결정합니다.
- 실행기(Executor): 확정된 계획을 따라 스토리지 엔진 API를 호출하며 실제로 행을 읽고 씁니다.
옵티마이저는 테이블의 통계 정보(행 수, 인덱스의 선택도 등)에 의존합니다. 통계가 오래되었거나 부정확하면 잘못된 계획을 고를 수 있으므로, 운영에서는 통계 갱신이 중요합니다.
스토리지 엔진 계층
실행기는 데이터를 직접 만지지 않습니다. 대신 핸들러(handler) API 라는 일관된 인터페이스를 통해 스토리지 엔진에 "이 조건에 맞는 다음 행을 달라", "이 행을 써라" 같은 요청을 보냅니다. 덕분에 같은 SQL이라도 테이블이 어떤 엔진을 쓰느냐에 따라 트랜잭션 지원 여부, 락 방식, 디스크 레이아웃이 완전히 달라집니다.
이 분리 덕분에 한 데이터베이스 안에서 테이블마다 다른 엔진을 섞어 쓸 수 있습니다. 예를 들어 트랜잭션이 중요한 주문 테이블은 InnoDB로, 대량 집계 분석용 테이블은 ColumnStore로 둘 수 있습니다.
스토리지 엔진 깊이 보기
스토리지 엔진은 MariaDB 아키텍처의 핵심입니다. 각 엔진은 서로 다른 워크로드에 최적화되어 있습니다.
InnoDB
InnoDB는 대부분의 OLTP(온라인 트랜잭션 처리) 워크로드의 기본 선택지입니다. 다음과 같은 특징을 가집니다.
- ACID 트랜잭션: 커밋, 롤백, 격리 수준을 지원합니다.
- 행 단위 락(row-level locking): 동시성이 높은 환경에서 테이블 전체를 잠그지 않고 필요한 행만 잠급니다.
- MVCC(다중 버전 동시성 제어): 읽기와 쓰기가 서로를 덜 막도록, 읽기는 특정 시점의 일관된 스냅샷을 봅니다.
- 클러스터형 인덱스(clustered index): 데이터가 기본 키 순서로 물리적으로 정렬되어 저장됩니다. 그래서 기본 키 설계가 성능에 큰 영향을 줍니다.
- 크래시 복구: 리두 로그(redo log)를 통해 비정상 종료 후에도 일관성을 회복합니다.
InnoDB의 내부 동작을 단순화하면 다음과 같습니다.
쓰기 경로 (단순화)
COMMIT ──▶ 리두 로그 기록(WAL) ──▶ 버퍼 풀의 페이지 수정
│ │
│ (디스크에 먼저) │ (메모리에서 dirty page)
▼ ▼
로그 파일 버퍼 풀(메모리)
│
│ 체크포인트 / 백그라운드 flush
▼
데이터 파일(.ibd)
여기서 핵심 개념은 WAL(Write-Ahead Logging) 입니다. 데이터 페이지를 디스크에 즉시 쓰는 대신, 변경 내용을 먼저 리두 로그에 순차적으로 기록합니다. 순차 쓰기는 랜덤 쓰기보다 훨씬 빠르고, 만약 서버가 갑자기 죽어도 리두 로그를 재생해 복구할 수 있습니다.
버퍼 풀(buffer pool) 은 InnoDB가 데이터와 인덱스 페이지를 캐시해 두는 메모리 영역입니다. 워킹셋이 버퍼 풀에 들어오면 디스크 I/O가 크게 줄어들기 때문에, 버퍼 풀 크기는 InnoDB 성능 튜닝에서 가장 중요한 항목 중 하나입니다.
Aria
Aria는 MyISAM의 후계 격으로 개발된 엔진입니다. 크래시 복구가 가능하다는 점에서 MyISAM보다 안전합니다. MariaDB 내부에서 시스템 테이블이나 임시 테이블 용도로 쓰이기도 합니다. 다만 InnoDB 수준의 완전한 트랜잭션과 행 락은 제공하지 않으므로, 동시 쓰기가 많은 일반 OLTP 테이블에는 InnoDB가 더 적합합니다.
ColumnStore
ColumnStore는 분석(OLAP) 워크로드를 위한 컬럼 지향(columnar) 엔진입니다. 전통적인 행 지향 저장 방식과 달리, 같은 컬럼의 값을 모아서 저장합니다.
행 지향 (InnoDB) 컬럼 지향 (ColumnStore)
[id|name|amount|date] id : 1,2,3,4,...
[ 1|kim | 100 |...] name : kim,lee,park,...
[ 2|lee | 250 |...] amount : 100,250,90,...
[ 3|park| 90 |...] date : ...
한 행을 통째로 읽음 특정 컬럼만 골라 읽음
집계 쿼리(예: 특정 컬럼의 합계나 평균)는 필요한 컬럼만 읽으면 되므로 I/O가 크게 줄고, 같은 컬럼끼리 모여 있어 압축률도 높습니다. 대신 한 건 한 건을 자주 갱신하는 OLTP 패턴에는 맞지 않습니다. 대용량 데이터를 스캔하고 집계하는 분석 시스템에 적합합니다.
MyRocks
MyRocks는 RocksDB 기반의 LSM 트리(Log-Structured Merge-tree) 엔진입니다. B-트리 기반의 InnoDB와는 쓰기 처리 방식이 근본적으로 다릅니다.
- 쓰기 증폭(write amplification)이 낮음: LSM 구조는 쓰기를 순차적으로 모아서 처리하므로, 특정 워크로드에서 디스크에 실제로 기록되는 양이 줄어듭니다.
- 높은 압축률과 공간 효율: 같은 데이터를 더 적은 공간에 저장하는 경향이 있어, 저장 비용이 중요한 대용량 환경에 유리합니다.
- 읽기 시 트레이드오프: LSM 구조 특성상 읽기 경로가 여러 레벨을 거칠 수 있어, 워크로드에 따라 읽기 비용이 달라집니다.
쓰기가 매우 많고 저장 공간을 절약해야 하는 환경에서 InnoDB의 대안으로 검토됩니다.
스토리지 엔진 선택 기준 (비교표)
| 항목 | InnoDB | Aria | ColumnStore | MyRocks |
|---|---|---|---|---|
| 주 용도 | 범용 OLTP | 시스템/임시 테이블 | 분석 OLAP | 쓰기 많은 대용량 |
| 트랜잭션(ACID) | 완전 지원 | 제한적 | 분석 위주 | 지원 |
| 락 단위 | 행 단위 | 테이블 위주 | 분석 위주 | 행 단위 |
| 저장 구조 | B-트리 | B-트리 | 컬럼 지향 | LSM 트리 |
| 압축 | 옵션 | 제한적 | 강함 | 강함 |
| 강점 | 균형, 동시성 | 가벼움 | 집계 스캔 | 쓰기, 공간 효율 |
| 약점 | 공간 사용 | 동시 쓰기 | 단건 갱신 | 읽기 트레이드오프 |
선택의 출발점은 거의 항상 InnoDB입니다. 트랜잭션과 동시성, 안정성의 균형이 좋기 때문입니다. ColumnStore와 MyRocks는 "이 워크로드가 InnoDB의 약점을 정확히 찌른다"는 확신이 있을 때 도입을 검토하는 것이 안전합니다. 지원되는 엔진 목록과 기본 활성화 여부는 버전과 빌드에 따라 다를 수 있으니 공식 문서로 확인하시기 바랍니다.
설치된 엔진을 확인하는 방법은 다음과 같습니다.
-- 사용 가능한 스토리지 엔진과 지원 상태 확인
SHOW ENGINES;
-- 특정 테이블이 어떤 엔진을 쓰는지 확인
SELECT table_name, engine
FROM information_schema.tables
WHERE table_schema = 'mydb';
-- 테이블 생성 시 엔진 지정
CREATE TABLE orders (
id BIGINT NOT NULL AUTO_INCREMENT,
user_id BIGINT NOT NULL,
amount DECIMAL(12,2) NOT NULL,
created DATETIME NOT NULL,
PRIMARY KEY (id),
KEY idx_user (user_id)
) ENGINE=InnoDB;
복제 (Replication)
한 대의 서버만으로는 가용성과 읽기 확장에 한계가 있습니다. 복제는 한 서버(프라이머리/마스터)의 변경 사항을 다른 서버(레플리카/슬레이브)로 전달해 같은 데이터를 유지하는 기술입니다.
MariaDB 복제의 중심에는 바이너리 로그(binary log, binlog) 가 있습니다. 프라이머리는 데이터를 바꾸는 모든 작업을 binlog에 기록하고, 레플리카는 이 로그를 받아 자기 데이터에 다시 적용합니다.
┌────────────── Primary ──────────────┐
│ 트랜잭션 ──▶ 스토리지 엔진 │
│ │ │
│ └──▶ Binary Log (binlog) 기록 │
└──────────────────┬───────────────────┘
│ binlog 이벤트 전송
▼
┌────────────── Replica ───────────────┐
│ IO 스레드 ──▶ Relay Log 저장 │
│ │ │
│ SQL/Worker 스레드 ─▶ 데이터에 적용 │
└──────────────────────────────────────┘
레플리카 쪽 동작을 조금 더 풀어 보면, IO 스레드 가 프라이머리로부터 binlog 이벤트를 받아 로컬의 릴레이 로그(relay log) 에 저장하고, SQL 스레드(또는 병렬 워커들) 가 릴레이 로그를 읽어 실제 데이터에 적용합니다.
비동기 복제 (Asynchronous)
기본 복제 방식은 비동기 입니다. 프라이머리는 트랜잭션을 커밋하면 즉시 클라이언트에 성공을 응답하고, 레플리카가 그 변경을 받아 적용했는지는 기다리지 않습니다.
- 장점: 프라이머리의 쓰기 지연이 레플리카에 영향을 받지 않아 빠릅니다.
- 단점: 프라이머리가 커밋한 직후 죽으면, 아직 레플리카로 넘어가지 못한 트랜잭션이 사라질 수 있습니다(데이터 유실 가능성). 또한 레플리카가 프라이머리보다 뒤처지는 복제 지연(replication lag) 이 생길 수 있습니다.
준동기 복제 (Semi-synchronous)
준동기 복제는 비동기와 동기의 절충안입니다. 프라이머리가 커밋을 클라이언트에 확정하기 전에, 적어도 하나의 레플리카가 해당 이벤트를 받았다는 확인(ack) 을 기다립니다.
여기서 중요한 점은, 레플리카가 이벤트를 적용했다 는 보장이 아니라 수신해서 릴레이 로그에 안전하게 기록했다 는 보장이라는 것입니다. 그래도 프라이머리만 데이터를 들고 있는 상황을 줄여 주므로, 비동기보다 데이터 유실 위험이 작습니다. 대신 레플리카의 ack를 기다리는 만큼 커밋 지연이 늘어납니다.
GTID (Global Transaction ID)
전통적인 복제는 "binlog 파일 이름과 그 안의 위치(offset)"로 어디까지 복제했는지를 추적했습니다. 이 방식은 페일오버(장애 시 다른 서버로 승격) 때 위치를 정확히 맞추기가 까다로웠습니다.
GTID 는 모든 트랜잭션에 클러스터 전역에서 유일한 식별자를 붙이는 방식입니다. 덕분에 레플리카는 "내가 어떤 트랜잭션까지 적용했는가"를 위치가 아니라 ID로 알 수 있고, 프라이머리가 바뀌어도 빠진 트랜잭션을 명확하게 이어받을 수 있습니다. 페일오버와 토폴로지 변경이 훨씬 수월해집니다.
복제 상태를 확인하고 GTID 기반으로 복제를 시작하는 예시는 다음과 같습니다(구문은 버전에 따라 다를 수 있습니다).
-- 레플리카에서 복제 상태 확인
SHOW REPLICA STATUS\G
-- GTID 기반 복제 설정 예시
CHANGE MASTER TO
MASTER_HOST='primary.example.internal',
MASTER_USER='repl',
MASTER_PASSWORD='secret',
MASTER_USE_GTID=slave_pos;
START REPLICA;
Galera 클러스터 (동기 멀티 마스터)
지금까지의 복제는 프라이머리 한 대가 쓰기를 받고 나머지가 따라오는 구조였습니다. Galera 클러스터 는 다릅니다. 모든 노드가 읽기와 쓰기를 모두 받을 수 있는 동기 멀티 마스터 방식입니다.
애플리케이션 / 로드 밸런서
/ | \
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Node A │◀─▶│ Node B │◀─▶│ Node C │
│ (R/W) │ │ (R/W) │ │ (R/W) │
└──────────┘ └──────────┘ └──────────┘
▲ ▲ ▲
└───────── wsrep 그룹 통신 ─────┘
(커밋 시 write-set을 모든 노드에 전파/인증)
Galera의 핵심은 커밋 시점에 일어나는 인증 기반 복제(certification-based replication) 입니다. 흐름을 단순화하면 다음과 같습니다.
Galera 커밋 흐름 (단순화)
1) 클라이언트가 Node A에서 트랜잭션 실행
│
▼
2) 커밋 직전, 변경 내용을 write-set으로 묶음
(바뀐 행과 그 키 정보)
│
▼
3) write-set을 모든 노드에 브로드캐스트
│
▼
4) 각 노드가 동일한 순서로 인증(certification) 수행
- 다른 노드의 동시 트랜잭션과 충돌하는가?
│
├── 충돌 없음 ──▶ 모든 노드가 커밋 (일관 상태)
│
└── 충돌 있음 ──▶ 나중 트랜잭션을 거부(롤백)
→ 애플리케이션에 데드락 유사 에러
여기서 "동기"라는 말의 의미를 정확히 이해하는 것이 중요합니다. Galera는 커밋 시점에 write-set을 모든 노드에 전파하고 인증을 통과시키므로, 커밋이 성공하면 그 트랜잭션은 클러스터 전체에 반영될 것이 보장됩니다. 다만 실제로 각 노드의 디스크에 적용(apply)되는 것은 약간 늦을 수 있어, 흔히 가상 동기(virtually synchronous) 라고 부릅니다.
Galera의 장점과 주의점
- 데이터 일관성: 인증을 통과한 트랜잭션은 모든 노드에 반영되므로, 비동기 복제의 데이터 유실 위험이 거의 없습니다.
- 고가용성: 한 노드가 죽어도 나머지 노드가 읽기와 쓰기를 계속 받습니다.
- 읽기 확장: 모든 노드에서 읽을 수 있습니다.
다만 다음을 주의해야 합니다.
- 쓰기 충돌과 인증 실패: 여러 노드에서 같은 행을 동시에 고치면 인증 단계에서 충돌이 나고, 나중 트랜잭션이 롤백됩니다. 그래서 멀티 마스터로 자유롭게 쓰기를 분산하기보다, 쓰기는 한 노드로 몰아주는 패턴을 흔히 씁니다.
- 커밋 지연: 모든 노드와 통신해야 하므로, 네트워크가 느리거나 노드가 멀리 떨어져 있으면 커밋이 느려집니다. 지리적으로 분산된 환경에서는 신중해야 합니다.
- 쿼럼과 스플릿 브레인: 네트워크가 분리되었을 때 데이터가 갈라지는 것을 막기 위해, 다수파(쿼럼)를 이룬 쪽만 동작합니다. 그래서 노드 수를 홀수(예: 3)로 두는 것이 일반적입니다.
- DDL과 큰 트랜잭션: 스키마 변경이나 매우 큰 트랜잭션은 클러스터 전체에 영향을 줄 수 있어 다루기가 까다롭습니다.
Galera 설정 예시
Galera는 wsrep 관련 설정으로 동작합니다(파라미터 이름과 권장값은 버전에 따라 다를 수 있습니다).
[galera]
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name=my_cluster
wsrep_cluster_address=gcomm://10.0.0.1,10.0.0.2,10.0.0.3
wsrep_node_address=10.0.0.1
wsrep_sst_method=mariabackup
# Galera는 InnoDB와 행 기반 복제 포맷을 전제로 함
default_storage_engine=InnoDB
binlog_format=ROW
innodb_autoinc_lock_mode=2
새 노드가 클러스터에 합류할 때는 기존 노드로부터 데이터를 받아 동기화해야 합니다. 이를 SST(State Snapshot Transfer) 라고 하며, 전체 데이터셋을 통째로 복사합니다. 변경분만 빠르게 따라잡는 IST(Incremental State Transfer) 도 있습니다. 대용량 클러스터에서 SST는 시간과 부하가 크므로 운영 시 고려해야 합니다.
복제 방식 비교표
| 항목 | 비동기 | 준동기 | Galera |
|---|---|---|---|
| 쓰기 노드 | 단일 프라이머리 | 단일 프라이머리 | 모든 노드 가능 |
| 커밋 응답 시점 | 즉시 | 레플리카 수신 ack 후 | 클러스터 인증 통과 후 |
| 데이터 유실 위험 | 있음 | 낮음 | 매우 낮음 |
| 커밋 지연 | 낮음 | 중간 | 네트워크에 의존 |
| 복제 지연 가능성 | 있음 | 있음 | 매우 작음 |
| 대표 용도 | 읽기 확장, 백업 | 유실 민감 OLTP | HA, 일관성 중시 |
파티셔닝 (Partitioning)
테이블이 매우 커지면, 하나의 거대한 테이블을 논리적으로 여러 조각으로 나누는 파티셔닝 이 유용합니다. 애플리케이션에서는 여전히 하나의 테이블처럼 보이지만, 내부적으로는 정의한 규칙에 따라 여러 파티션에 데이터가 나뉘어 저장됩니다.
하나의 논리 테이블 (sales)
┌──────────────────────────────────────────────┐
│ PARTITION BY RANGE (YEAR(sale_date)) │
└──────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ p2023 │ │ p2024 │ │ p2025 │ │ pmax │
│ < 2024 │ │ < 2025 │ │ < 2026 │ │ 나머지 │
└────────┘ └────────┘ └────────┘ └────────┘
WHERE sale_date >= '2025-01-01'
──▶ p2023, p2024 는 스캔 제외 (파티션 프루닝)
파티셔닝의 가장 큰 이점은 파티션 프루닝(partition pruning) 입니다. 쿼리 조건에 파티션 키가 들어가면, 옵티마이저가 관련 없는 파티션을 아예 건너뛰어 스캔 범위를 줄입니다. 또 오래된 데이터를 통째로 비울 때 파티션 단위로 빠르게 제거할 수 있어 대용량 시계열 데이터 관리에 유용합니다.
범위 파티셔닝 예시는 다음과 같습니다.
CREATE TABLE sales (
id BIGINT NOT NULL AUTO_INCREMENT,
sale_date DATE NOT NULL,
amount DECIMAL(12,2) NOT NULL,
PRIMARY KEY (id, sale_date)
)
ENGINE=InnoDB
PARTITION BY RANGE (YEAR(sale_date)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
주의할 점은 파티셔닝이 만능이 아니라는 것입니다. 파티션 키가 쿼리 조건에 들어가지 않으면 모든 파티션을 다 뒤져야 해서 오히려 느려질 수 있습니다. 또 파티션 키는 기본 키를 포함한 모든 고유 키에 포함되어야 한다는 제약이 있어 키 설계에 영향을 줍니다.
MariaDB와 MySQL의 관계와 차이
MariaDB는 MySQL에서 포크되어 시작했기 때문에, 많은 SQL 문법과 클라이언트 프로토콜이 호환됩니다. 그러나 두 프로젝트가 독립적으로 발전하면서 차이가 커지고 있습니다.
| 항목 | MariaDB | MySQL |
|---|---|---|
| 출발 | MySQL에서 포크 | 원조 |
| 라이선스/거버넌스 | 커뮤니티 중심 재단 | 상용 벤더 주도 |
| 스토리지 엔진 다양성 | Aria, ColumnStore 등 다양 | InnoDB 중심 |
| 동기 클러스터 | Galera 기본 통합 | 별도 그룹 복제 솔루션 |
| JSON 처리 | 함수 위주 접근 | 네이티브 타입 강조 |
| 일부 신기능 | 독자 기능 추가 | 독자 기능 추가 |
실무에서 기억해야 할 점은, 버전이 올라갈수록 두 제품의 호환성이 100%가 아니라는 것 입니다. 기본적인 CRUD와 표준 SQL은 대체로 호환되지만, 복제 프로토콜, GTID 형식, JSON 처리, 일부 시스템 변수, 신규 함수 등에서 차이가 있을 수 있습니다. 따라서 한쪽을 다른 쪽으로 옮기는 마이그레이션을 계획한다면, 단순 덤프/복원만으로 끝난다고 가정하지 말고 호환성 검증을 충분히 거쳐야 합니다. 구체적인 차이는 사용 중인 버전끼리 공식 문서로 대조하는 것이 안전합니다.
운영과 튜닝
아키텍처를 이해했다면, 운영에서 자주 손대는 항목을 정리해 두는 것이 좋습니다.
자주 보는 설정 항목
[mysqld]
# InnoDB 버퍼 풀: 워킹셋을 메모리에 담을수록 디스크 I/O 감소
# (전용 DB 서버라면 물리 메모리의 상당 부분을 할당하는 것이 일반적)
innodb_buffer_pool_size = 8G
# 리두 로그 관련: 너무 작으면 체크포인트가 잦아짐
# (권장값과 파라미터명은 버전에 따라 다를 수 있음)
innodb_log_file_size = 1G
# 커밋 시 로그 flush 정책 (내구성과 성능의 트레이드오프)
# 1: 가장 안전(매 커밋 flush), 0/2: 더 빠르지만 유실 위험 증가
innodb_flush_log_at_trx_commit = 1
# 복제 포맷: Galera 및 안정적 복제에는 ROW 권장
binlog_format = ROW
# 연결이 많은 환경이면 스레드 풀 검토
thread_handling = pool-of-threads
각 값의 적정선은 워크로드와 하드웨어, 버전에 따라 다릅니다. 위 값들은 출발점일 뿐이며, 실제 운영에서는 모니터링 지표를 보며 조정해야 합니다.
모니터링 포인트
- 버퍼 풀 히트율: 디스크로 떨어지는 비율이 높다면 버퍼 풀이 작거나 워킹셋이 너무 큰 신호입니다.
- 복제 지연: 레플리카가 프라이머리보다 얼마나 뒤처졌는지 주기적으로 확인합니다.
- 느린 쿼리 로그(slow query log): 실행 계획이 나쁜 쿼리를 찾아내는 출발점입니다.
- 락 대기와 데드락: 동시성 문제를 조기에 발견합니다.
- Galera 상태 변수: 클러스터 크기, 플로 컨트롤 발생 빈도, 인증 실패율 등을 봅니다.
실행 계획은 EXPLAIN으로 확인합니다.
-- 옵티마이저가 고른 실행 계획 확인
EXPLAIN
SELECT u.id, u.name, COUNT(o.id) AS cnt
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.created >= '2026-01-01'
GROUP BY u.id, u.name;
-- 실제 실행 통계까지 보고 싶을 때 (지원 시)
ANALYZE
SELECT * FROM orders WHERE user_id = 42;
함정과 흔한 실수
오래 운영하면서 반복적으로 마주치는 함정들을 모아 두었습니다.
-
버퍼 풀을 너무 작게 잡음: 디스크 I/O가 병목인데 원인을 쿼리 탓으로만 돌리는 경우가 많습니다. 워킹셋과 버퍼 풀 크기의 관계를 먼저 확인하시기 바랍니다.
-
비동기 복제를 동기로 착각: 비동기 복제에서는 프라이머리 장애 시 마지막 트랜잭션 몇 개가 사라질 수 있습니다. "복제가 있으니 유실은 없다"는 가정은 위험합니다. 유실에 민감하다면 준동기나 Galera를 검토하세요.
-
Galera에서 멀티 마스터로 무분별하게 쓰기 분산: 같은 행을 여러 노드에서 동시에 고치면 인증 충돌이 잦아집니다. 보통은 쓰기를 한 노드로 몰고, 나머지를 읽기용으로 씁니다.
-
파티션 프루닝이 안 되는 쿼리: 파티션 키가 WHERE 조건에 없으면 모든 파티션을 스캔합니다. 파티셔닝을 도입했는데 오히려 느려졌다면 프루닝이 동작하는지 EXPLAIN으로 확인하세요.
-
통계 미갱신으로 인한 나쁜 계획: 데이터 분포가 크게 바뀌었는데 통계가 오래되면 옵티마이저가 엉뚱한 인덱스를 고를 수 있습니다.
-
엔진을 워크로드와 맞지 않게 선택: 분석 집계에 InnoDB만 쓰며 느리다고 불평하거나, 단건 갱신이 잦은데 ColumnStore를 쓰는 식의 불일치를 조심하세요.
-
DDL을 무겁게 실행: 큰 테이블의 스키마 변경은 락과 부하를 유발할 수 있습니다. 온라인 DDL 옵션과 그 제약을 사용 버전 문서로 확인하고, Galera에서는 더욱 신중히 다뤄야 합니다.
-
MariaDB와 MySQL 호환성 과신: 버전이 다른 두 제품 간 마이그레이션을 단순 덤프/복원으로 끝낼 수 있다고 가정하지 마세요. 복제·GTID·일부 함수에서 차이가 날 수 있습니다.
마치며
MariaDB의 아키텍처는 "SQL을 해석하는 계층"과 "데이터를 저장하는 스토리지 엔진 계층"의 분리에서 출발합니다. 이 분리 덕분에 같은 SQL로도 InnoDB, Aria, ColumnStore, MyRocks처럼 성격이 전혀 다른 엔진을 워크로드에 맞춰 골라 쓸 수 있습니다.
데이터를 여러 노드로 퍼뜨리는 방법은 비동기 복제에서 준동기, GTID, 그리고 Galera 동기 멀티 마스터까지 일관성과 성능 사이에서 서로 다른 균형점을 제공합니다. 어떤 것이 "정답"인 것이 아니라, 데이터 유실 허용치, 지연 요구, 운영 복잡도에 따라 선택이 달라집니다.
파티셔닝은 대용량 테이블을 다루는 강력한 도구지만, 파티션 키 설계와 프루닝이 핵심이라는 점을 기억해야 합니다. 그리고 MariaDB와 MySQL은 뿌리는 같지만 갈수록 차이가 커지고 있으니, 버전별 동작은 항상 공식 문서로 확인하는 습관이 안전합니다.
마지막으로 강조하자면, 이 글의 구체적인 파라미터, 구문, 기본값은 버전에 따라 달라질 수 있습니다. 큰 그림으로 방향을 잡되, 실제 적용 전에는 사용 중인 버전의 공식 문서를 반드시 확인하시길 권합니다.
참고 자료
- MariaDB Knowledge Base: https://mariadb.com/kb/en/
- Storage Engines: https://mariadb.com/kb/en/storage-engines/
- InnoDB: https://mariadb.com/kb/en/innodb/
- Galera Cluster: https://mariadb.com/kb/en/galera-cluster/
- Replication: https://mariadb.com/kb/en/replication/
- Partitioning Tables: https://mariadb.com/kb/en/partitioning-tables/
- Global Transaction ID: https://mariadb.com/kb/en/gtid/
- Galera Cluster Documentation: https://galeracluster.com/library/documentation/
- MySQL Reference Manual: https://dev.mysql.com/doc/