POOOLING FOREST
SQLite가 MySQL 가면을 썼습니다: 분산 DB Marmot 탐방기 - SQLite에 MySQL 프로토콜과 Gossip 기반 분산 구조를 얹은 Marmot 탐방기. 리더 없는 분산
Engineering & Tech

SQLite가 MySQL 가면을 썼습니다: 분산 DB Marmot 탐방기

SQLite에 MySQL 프로토콜과 Gossip 기반 분산 구조를 얹은 Marmot 탐방기. 리더 없는 분산 구조와 MySQL 호환성이 주는 강력한 경험을 소개합니다.

김영태

테크리드

안녕하세요. 풀링포레스트 테크리드 김영태입니다.

개발자라면 누구나 마음속에 "작고 소중한 SQLite"를 품고 살 겁니다. 설정도 필요 없고 파일 하나만 있으면 되는 그 간편함은 정말 매력적이죠. 하지만 서비스 규모가 커지고 트래픽이 늘어나기 시작하면, 우리는 결국 SQLite를 떠나보내야 합니다. "동시성 이슈는 어떡하지?", "서버가 여러 대인데 데이터 동기화는?" 같은 고민 앞에서 결국 무거운 RDBMS로 회귀하곤 하니까요.

저 역시 최근 사내에서 마이크로서비스(MSA)의 사이드카 패턴을 설계하다가 비슷한 고민에 빠졌습니다. 각 서비스 인스턴스마다 가벼운 로컬 DB가 필요한데, 이 데이터들이 클러스터 전체에 공유되었으면 좋겠다는 니즈가 있었거든요.

처음엔 rqlite나 LiteFS 같은 유명한 솔루션을 검토했습니다. 훌륭한 도구들이지만, 제 상황에서는 '리더 선출(Leader Election)' 과정이나 특정 프록시를 타야 한다는 점이 묘하게 거슬리더군요. 그러다 우연히 GitHub 탐험 중에 Marmot이라는 프로젝트를 발견했고, 솔직히 좀 충격을 받았습니다.

"SQLite 위에 MySQL 프로토콜을 얹고, 리더 없는(Leaderless) 분산 구조를 만들었다고?"

오늘은 제가 이 녀석을 직접 찍먹(?)해보고 느낀 점들을 여러분과 나누려 합니다.

왜 리더가 없어야 할까?

기존의 분산 SQLite 솔루션들(rqlite 등)은 대부분 Raft 합의 알고리즘을 사용합니다. 즉, 대장(Leader)을 뽑고 모든 쓰기 작업은 대장을 통해서만 이루어지죠. 안정적이지만, 대장 노드에 부하가 집중되거나 대장이 죽었을 때 선거를 다시 하는 찰나의 순간이 발생합니다.

반면 Marmot은 Gossip 프로토콜을 기반으로 합니다. 쉽게 말해, 동네방네 소문을 퍼뜨리는 방식입니다. 노드 A에 데이터가 들어오면, "야, 이거 새로 들어왔대!" 하고 노드 B, C에게 전파합니다. 덕분에 단일 실패 지점(SPOF)이 없습니다. 어떤 노드에 쓰기 요청을 날려도 상관없다는 뜻이죠.

이 구조를 보고 무릎을 탁 쳤습니다. "아, 사이드카 패턴에는 이게 딱이다." 각 인스턴스가 자기 로컬 DB에 쓰듯이 작업하면, 알아서 전파되니까요.

충격과 공포의 MySQL 호환성

하지만 Marmot의 진짜 킬러 기능은 따로 있었습니다. 바로 MySQL Wire Protocol 호환입니다.

보통 SQLite를 쓰려면 전용 드라이버를 쓰거나 파일 시스템에 접근해야 합니다. 그런데 Marmot은 TCP 포트를 열고 MySQL인 척을 합니다. 이게 무슨 말이냐면, 여러분이 쓰던 `mysql` CLI나 DBeaver, IntelliJ Database 도구에서 그냥 MySQL 접속하듯이 연결하면 된다는 겁니다.

실제로 제가 로컬에서 테스트해 본 커맨드입니다.

# Marmot 노드 실행
./marmot-v2

# MySQL 클라이언트로 접속 (???)
mysql -h localhost -P 3306 -u root

이렇게 접속해서 `SELECT * FROM my_table;`을 날리면, 뒤단에서는 SQLite가 돌고 있는데 겉으로는 MySQL 서버가 응답을 줍니다. rqlite를 쓸 때 HTTP API로 쿼리를 날리느라 클라이언트 코드를 덕지덕지 수정했던 기억이 떠오르며, 허탈한 웃음이 나오더군요.

어떻게 일관성을 맞출까?

물론 세상에 공짜는 없습니다. 리더가 없으면 동시에 여러 노드에서 같은 데이터를 수정할 때 충돌이 나겠죠? Marmot은 이 문제를 해결하기 위해 HLC(Hybrid Logical Clock)LWW(Last-Write-Wins) 전략을 씁니다. 쉽게 말해, "가장 마지막에 쓴 놈이 이긴다"는 단순 무식하지만 강력한 규칙을 적용합니다.

또한, 트랜잭션 처리를 위해 2PC(Two-Phase Commit)를 사용하고, 스키마 변경(DDL) 시에는 클러스터 전체에 락(Lock)을 걸어버립니다.

이 부분에서 호불호가 갈릴 수 있습니다. 금융 거래처럼 엄격한 ACID가 보장되어야 하는 곳에는 쓰기 어렵습니다. 하지만 설정 정보 공유, 세션 클러스터링, 혹은 읽기 비율이 압도적으로 높은 서비스라면 충분히 합리적인 선택지입니다.

직접 굴려보며 느낀 점

실무자 입장에서 가장 마음에 들었던 건 DDL의 멱등성(Idempotency) 자동 처리였습니다. 분산 환경에서 `CREATE TABLE`을 날렸을 때, 이미 테이블이 있으면 에러가 터져서 배포 파이프라인이 깨지는 경우가 종종 있습니다. Marmot은 내부적으로 쿼리를 재작성해서 `CREATE TABLE IF NOT EXISTS`처럼 동작하게 만들어줍니다. 운영하는 사람의 마음을 꿰뚫어 본 기능이라 느꼈습니다.

하지만 주의할 점도 분명했습니다.

  1. 쓰기 충돌: LWW 방식이라 동시에 같은 로우를 수정하면 누군가의 데이터는 조용히 덮어씌워집니다. 비즈니스 로직에서 이걸 감안해야 합니다.

  2. 성능: 2PC를 쓰기 때문에, 단일 SQLite보다는 당연히 느립니다. 쓰기 작업이 엄청나게 많은 서비스라면 병목이 올 수 있습니다.

마치며

Marmot은 "SQLite는 로컬용"이라는 편견과 "분산 DB는 관리가 어렵다"는 편견을 동시에 깨주는 재미있는 도구입니다. 특히 MySQL 프로토콜을 지원한다는 점은 기존 레거시 도구들과의 통합 비용을 '0'으로 만들어줍니다.

물론 아직 버전이 낮고 프로덕션 메인 DB로 쓰기엔 검증이 더 필요합니다. 하지만 풀링포레스트 같은 팀에서 내부 어드민 툴이나, 엣지(Edge) 단의 설정 저장소로 활용하기에는 차고 넘치는 스펙이라고 봅니다.

여러분도 "가볍지만 분산은 필요한" 애매한 상황에 처해있다면, 무거운 RDBMS를 띄우기 전에 Marmot이라는 귀여운 설치류를 한번 키워보시는 건 어떨까요? 기술은 결국 적재적소에 쓸 때 가장 빛나는 법이니까요.

다음에도 현업에서 구르며 발견한 재미있는 도구 이야기로 찾아오겠습니다. 감사합니다.

지금 읽으신 내용, 귀사에 적용해보고 싶으신가요?

상황과 목표를 알려주시면 가능한 옵션과 현실적인 도입 경로를 제안해드립니다.