311. 데이터베이스 퍼 서비스 (Database per Service) 패턴
핵심 인사이트 (3줄 요약)
- 본질: 데이터베이스 퍼 서비스(Database per Service)는 마이크로서비스 아키텍처(MSA)에서 각 마이크로서비스가 자신만의 전용 데이터베이스를 소유하고, 다른 서비스는 이 DB에 절대 직접 접근할 수 없도록 물리적/논리적으로 완벽히 격리(Isolation)하는 아키텍처 원칙이다.
- 가치: 모놀리식 DB의 "단일 테이블 스키마 변경이 전사 시스템을 터뜨리는 연쇄 장애(Ripple Effect)"를 원천 차단하여, 각 개발팀이 타 부서 눈치 볼 필요 없이 독립적으로 데이터를 뜯어고치고 배포할 수 있는 궁극의 조직적 자율성(Autonomy)을 제공한다.
- 융합: 이 패턴을 도입하는 순간 분산 환경에서의 JOIN 불가와 2PC(Two-Phase Commit) 불가능이라는 거대한 딜레마가 발생하므로, 필연적으로 CQRS(명령과 조회의 책임 분리)와 Saga(사가) 패턴 같은 고난도 데이터 동기화 전술들과 한 몸처럼 융합되어야 한다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
-
개념: MSA로 서버를 10개로 쪼갰다면, DB도 10개로 쪼개야 한다는 원칙이다. '주문 DB'는 주문 서비스만 접근할 수 있고, '결제 서버'가 주문 데이터가 필요하면 DB 쿼리를 직접 때리는 게 아니라, 주문 서버의 API를 정중하게 호출해서 가져가야 한다.
-
필요성: 개발자들이 서버 코드를 30개의 마이크로서비스로 쪼갰다(API 분리). 그런데 DB는 여전히 거대한 오라클(Oracle) 1대만 쓰고 있다(공유 DB). 결제팀이 성능을 높이겠다고
ORDERS테이블의status칼럼 이름을pay_status로 바꿨다. 그 순간, 배송팀, 장바구니팀, 고객센터팀 서버가 이 테이블을 공유하고 있었기 때문에 일제히Column Not Found에러를 뿜으며 전사 쇼핑몰이 마비되었다. 코드를 분리해 봐야 데이터를 공유하면 100% 강결합(Coupling) 상태다. 이를 '분산 모놀리스(Distributed Monolith)'라는 최악의 안티패턴이라 부르며, 이를 박살 내기 위해 1서비스 1DB 철학이 필요했다. -
💡 비유: 한 지붕 아래 사는 대가족(공유 DB)에서, 누군가 말도 없이 화장실 구조를 바꾸면 온 가족이 화장실을 못 쓰게 됩니다. 그래서 가족들이 10채의 독립된 원룸(Database per Service)으로 독립했습니다. 이제 각자 자기 집 화장실을 분홍색으로 칠하든 부수든 마음대로 할 수 있습니다. 다른 형제 집에 볼일이 있으면 마음대로 문을 열고 들어가는 게 아니라 초인종(API)을 누르고 부탁해야 합니다.
-
등장 배경 및 발전 과정:
- 모놀리식 공유 DB (Shared Database): 모든 부서가 하나의 RDBMS를 공유했다. DBA(데이터베이스 관리자) 한 명이 모든 스키마 변경을 통제하며 비즈니스 속도를 늦추는 병목이 되었다.
- MSA의 대두 (Conway's Law): 팀을 서비스 단위로 찢으면서 "남의 팀 데이터는 남이 알아서 관리해라"라는 조직 분리 철학이 대두되었다.
- 데이터 중심에서 도메인 중심(DDD)으로의 전환: Bounded Context(제한된 문맥) 내에서 완벽한 데이터 주도권(Ownership)을 갖기 위한 필연적 산물로 Database per Service 패턴이 정립되었다.
-
📢 섹션 요약 비유: 이 패턴은 "내 돈은 내 지갑에, 네 돈은 네 지갑에"라는 철저한 재산 분할입니다. 지갑을 합쳐서 쓰면 편하긴 하지만(Join 등), 나중에 네가 돈을 훔쳐갔네 내가 더 많이 썼네 하며 반드시 가족 간에 피 터지는 싸움(스키마 충돌)이 벌어집니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
1. Database per Service의 3가지 물리적/논리적 분리 형태
DB를 쪼갠다는 것은 회사 예산과 클라우드 인프라 수준에 따라 여러 깊이로 구현할 수 있다.
| 격리 수준 | 구조 설명 | 장점 / 단점 | 독립성 (Isolation) |
|---|---|---|---|
| 1. Private-tables-per-service | DB 인스턴스 1대 안에 여러 서비스의 테이블이 섞여 있음. 대신 논리적 접근 권한(Grant)만 분리하여 A서비스는 B서비스 테이블에 SELECT 불가. | 장점: 비용이 안 듦 (DB 1대) 단점: A서비스 쿼리 폭주 시 B서비스도 같이 느려짐 (Noisy Neighbor) | Low (논리적 분리) |
| 2. Schema-per-service | DB 인스턴스는 1대지만, 내부에 서비스별로 아예 **독립된 Schema(Schema A, Schema B)**를 파서 격리. | 장점: 스키마 단위 백업/복구 쉬움 단점: 여전히 물리적 인프라는 공유됨 | Medium |
| 3. Database-server-per-service | 서비스별로 물리적인 DB 서버(EC2나 RDS)를 따로 띄워버림. | 장점: 완벽한 리소스 격리, 맘대로 DB 엔진 선택(Polyglot) 단점: AWS 인프라 비용 폭발, 관리 포인트 10배 증가 | High (완벽한 격리) |
현대의 이상적인 클라우드 네이티브 MSA는 무조건 3번(Database-server-per-service)을 지향하며, 필요에 따라 주문은 RDBMS(MySQL)에, 상품 추천은 NoSQL(MongoDB)에 담는 **폴리글랏 퍼시스턴스(Polyglot Persistence)**의 자유를 얻는다.
2. 패턴의 양날의 검 (해결해야 할 대가들)
이 멋진 패턴을 도입하는 순간, 과거 모놀리식 환경에서 0.1초 만에 하던 당연한 작업들이 '불가능의 영역'으로 변모한다. 이를 아키텍트는 뼈를 깎는 고통으로 우회(Workaround) 설계해야 한다.
A. 글로벌 트랜잭션 (2PC)의 불가능
- 문제: 결제 서비스(결제 DB)에서 돈을 빼고, 주문 서비스(주문 DB)에서 상태를 '완료'로 바꿔야 한다. 예전엔
BEGIN...COMMIT한 번이면 두 테이블이 동기화됐지만, 지금은 DB가 2대라 하나의 트랜잭션으로 묶을 수 없다. 중간에 네트워크가 끊기면 돈만 빠져나가고 주문은 실패하는 데이터 붕괴가 발생한다. - 해법: 분산 트랜잭션을 포기하고 결과적 일관성(Eventual Consistency)을 보장하는 **Saga 패턴(보상 트랜잭션)**이나 **2PC(Two-Phase Commit)**를 도입해야 한다.
B. 데이터 조인(JOIN)의 불가
-
문제: "최근 한 달간 5만 원 이상 결제한 유저의 주소 목록"을 뽑아달란다. 유저 정보는
User DB, 결제 정보는Payment DB에 있다. 두 서버의 DB가 달라SELECT ... JOIN쿼리가 아예 안 먹힌다. -
해법: API 게이트웨이 수준에서 두 API를 호출해 메모리에서 조립하는 API Composition(BFF) 패턴이나, 두 DB의 변경 이벤트를 Kafka로 쏴서 조회 전용 읽기 DB에 합쳐놓는 CQRS(명령과 조회 책임 분리) 패턴을 도입해야 한다.
-
📢 섹션 요약 비유: 각자 독립해서 살게 된 가족들에게 "내일 다 같이 김치찌개 해 먹자"라고 지시하는 것은 극도로 어렵습니다. 동생은 재료를 샀는데, 형은 늦잠 자서 안 사 오면 찌개가 완성되지 않습니다(트랜잭션 붕괴). 가족 간에 완벽한 무전기(Kafka) 통신과 약속된 룰(Saga)이 있어야만 공동 작업이 가능해집니다.
Ⅲ. 융합 비교 및 다각도 분석
1. 공유 데이터베이스 (Shared Database) 패턴과의 대결
MSA로 간다고 해서 무조건 DB를 찢어야 하는 것은 아니다.
| 패턴 | 장점 (Why use it?) | 단점 (Why avoid it?) |
|---|---|---|
| 공유 DB (Shared DB) | - 거대한 JOIN 쿼리가 그냥 됨. - 완벽한 ACID 트랜잭션 보장. - 레거시 시스템 운영이 익숙함. | - 한 부서의 테이블 스키마 변경 시 전체 팀의 회의/승인 필수 (독재). - DB 락(Lock)에 의한 전면 장애 전파. |
| DB per Service | - 한 팀의 스키마 변경이 남에게 영향 안 줌 (자율성/속도 극대화). - 각 기능에 최적화된 DB 엔진 사용. | - 분산 트랜잭션/CQRS 구현 난이도가 미친 듯이 높음. - 인프라 비용 폭발. |
"완벽한 트랜잭션"이 목숨보다 중요한 금융권 코어 뱅킹은 차라리 코드는 MSA로 쪼개되, DB는 'Shared DB'로 놔두는 기형적인 타협을 하기도 한다. 분산 트랜잭션이 터져 고객 잔고가 꼬이는 것보다는 개발 속도가 조금 느린 게 낫기 때문이다.
과목 융합 관점
-
도메인 주도 설계 (DDD): Database per Service는 본질적으로 DDD의 **'바운디드 컨텍스트(Bounded Context)'**를 물리적 스토리지 레벨까지 강제한 것이다. 같은 '상품(Product)' 데이터라도 배송팀 DB의 Product와 추천팀 DB의 Product의 형태를 완전히 분리시키는 사상이다.
-
데브옵스 (DevOps): 부서 간 얽힌 데이터 의존성을 끊어내어, 릴리즈(Release)를 다른 팀과 날짜를 맞추지 않고 하루에 100번씩 마음대로 각자 배포할 수 있는 '독립적 배포(Independent Deployability)' 파이프라인의 완성 조건이다.
-
📢 섹션 요약 비유: 공유 DB가 모두가 한 칠판(DB)을 나눠 쓰고 있어서 내 글을 쓰려면 남의 허락을 받아야 하는 '학교 교실'이라면, DB per Service는 각자 자기 방에 칠판을 두고 남이 보든 말든 마음대로 썼다 지웠다 할 수 있는 '프라이빗 오피스'입니다.
Ⅳ. 실무 적용 및 기술사적 판단
실무 시나리오
-
시나리오 — 데이터 참조 지옥 (API N+1 문제): 리뷰 서비스(Review DB)가 있다. 리뷰 목록을 그릴 때 작성자의 '닉네임'과 '프로필 사진'이 필요해, 리뷰가 100개 뜰 때마다
유저 서비스(User DB)API를 100번 호출하는 N+1 지옥이 펼쳐졌다. 유저 서버는 폭주해서 죽어버렸고 네트워크 딜레이로 화면은 5초 뒤에 뜬다.- 아키텍트의 해결책: '데이터 중복(Data Duplication)'에 대한 두려움을 버려야 한다. MSA에서 다른 서비스의 API를 매번 부르는 것은 재앙이다. 아키텍트는
Review DB에user_name과user_profile_img칼럼을 아예 복사해서 넣어버려야 한다. 유저가 프로필을 바꿀 때마다Kafka메시지를 쏴서 리뷰 DB의 복사본을 갱신(Eventual Consistency) 시키는 것이, 동기 통신 100번을 하는 것보다 천 배 빠른 아키텍처 다. 정규화(Normalization)의 미덕을 과감히 버려라.
- 아키텍트의 해결책: '데이터 중복(Data Duplication)'에 대한 두려움을 버려야 한다. MSA에서 다른 서비스의 API를 매번 부르는 것은 재앙이다. 아키텍트는
-
시나리오 — 레거시 분리 실패 (스파게티 조인의 덫): 차세대 프로젝트로 레거시를 MSA로 이관 중이다. 회원 테이블과 결제 테이블을 DB per Service로 멋지게 쪼개어 각각 AWS RDS로 분리했다. 그런데 기존 통계팀의 어드민 화면에서 "어제 가입한 회원 중 5만 원 이상 결제한 사람의 이름"을 뽑아내는 통계 JOIN 쿼리가 실행 불가 상태에 빠졌다. 어드민 시스템이 통째로 마비되었다.
- 아키텍트의 해결책: **데이터 통합/조회 계층(CQRS)**에 대한 사후 대책 없이 DB를 무지성으로 찢은 탓이다. DB per Service 환경에서 통계나 분석 같은 복잡한 쿼리를 위해, 아키텍트는 반드시 분리된 10개의 DB에서 발생한 CUD(Create, Update, Delete) 이벤트를 실시간으로 빨아들여 거대한 하나의 **'Data Warehouse'나 '읽기 전용 통합 DB(Read Replica)'**로 스트리밍해 두는 구조(예: Debezium CDC + Kafka)를 뒤에 몰래 받쳐주어야 한다.
도입 체크리스트
- 조직적: 조직이 정말 "자율적"으로 운영될 준비가 되었는가? DB를 찢어 놨는데도, 여전히 주문팀이 배송팀 스키마까지 관리해 주고 있거나 한 명의 사내 천재 DBA가 모든 10개의 DB를 수작업으로 권한 통제하고 있다면 이 패턴은 껍데기에 불과하다.
- 설계적: 데이터가 양쪽 DB에 불일치하게 꼬였을 때(Eventual Consistency 지연 중), 클라이언트에게 어떤 화면을 띄워줄 것인지 UX 팀과 합의했는가? "지금 당장 돈이 안 들어왔더라도, 5분 내로 들어옵니다"라고 고객을 달랠 비즈니스적 룰이 코드보다 먼저 수립되어야 한다.
안티패턴
-
뒷문 개방 (Backdoor Database Access): 결제 팀이 주문 팀 API를 호출하기 너무 귀찮다며, 주문 팀의 DB 주소(IP)와 비밀번호를 몰래 알아내서 자기 코드에 직접 JDBC 커넥션을 맺고
SELECT쿼리를 날려 빼가는 최악의 범죄. 이럴 거면 MSA를 할 이유가 없다. 캡슐화가 붕괴되고 다시 강결합된 진흙탕(Distributed Big Ball of Mud)으로 굴러떨어진다. -
📢 섹션 요약 비유: 이웃(결제팀)이 자기 집 앞마당(API)에서 무를 팔고 있는데, 돈 내기 귀찮다고 밤에 몰래 이웃집 텃밭(DB) 담장을 넘어 무를 뽑아오는 짓은 아키텍처의 도둑질입니다. 무밭의 흙(스키마)이 바뀌면 도둑질을 하던 내 코드도 폭파됩니다.
Ⅴ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | 모놀리식 공유 DB (AS-IS) | 데이터베이스 퍼 서비스 (TO-BE) | 개선 효과 |
|---|---|---|---|
| 정량 | DB 스키마 수정 시 전체 시스템 회귀 테스트 1주일 | 내 서비스 DB 수정 후 자기 테스트만 수행(1시간) | 데이터 스키마 변경 시 리드타임 99% 감축 |
| 정량 | 특정 테이블 락(Lock)에 의한 전사 서비스 다운타임 잦음 | 격리된 DB 사용으로 해당 서비스만 다운 | 데이터 병목 장애 시 영향 범위(Blast Radius) 90% 차단 |
| 정성 | DBA의 일방적 독재 및 개발팀의 스키마 변경 공포 | 각 팀이 독립된 Data Ownership 행사 (폴리글랏) | 애자일/데브옵스 문화의 기술적 완성 및 팀 사기 진작 |
미래 전망
- 데이터 메시(Data Mesh)와 분산 쿼리 엔진: 쪼개진 수십 개의 DB를 관리하는 고통을 덜기 위해, 최근에는 Presto(Trino)나 AWS Athena처럼 여러 개의 분리된 DB(MySQL, Mongo, S3)를 마치 하나의 거대한 DB인 것처럼 가상으로 묶어서 JOIN 쿼리를 때려주는 분산 쿼리 엔진 기술이 눈부시게 발전하여, DB를 찢으면서 발생하는 조인 불가(Join Impossible)의 딜레마를 인프라 레벨에서 구원해 주고 있다.
- 스트리밍 기반의 Eventual Consistency 대중화: 과거엔 "어떻게 데이터가 1초라도 안 맞을 수 있어?"라며 2PC를 억지로 고집하던 엔터프라이즈들이, Kafka 기반의 강력한 실시간 이벤트 스트리밍 툴이 대중화됨에 따라 "약간 늦어도 괜찮아, 결국엔 맞춰질 테니까(Eventual Consistency)"라는 성숙한 분산 데이터 철학을 전면적으로 수용하는 시대로 넘어왔다.
참고 표준
- 마이크로서비스 패턴 (크리스 리처드슨): MSA를 다루는 모든 교과서에서 1장 1절에 명시하는 분산 시스템 데이터 격리의 절대 법칙.
- CAP 정리: 일관성(Consistency)과 가용성(Availability)의 트레이드오프에서, DB per Service 패턴은 일관성을 살짝 포기하고 파티션 내구성(Partition Tolerance)과 가용성을 취하는 분산 시스템 수학 이론의 전형적인 증명이다.
데이터베이스 퍼 서비스(DB per Service) 패턴은 소프트웨어 공학이 치르는 가장 고통스럽지만 가장 영광스러운 독립 전쟁이다. DB를 하나로 묶어두면 개발하기는 달콤하다. 하지만 코드가 100만 줄을 넘어서는 순간, 그 거대한 공유 DB는 모든 혁신을 틀어막는 괴물 같은 중력 덩어리가 된다. 기술사는 "조인을 못해서 불편하다", "트랜잭션이 안 돼서 미치겠다"라고 쏟아지는 개발자들의 불만을 십자가처럼 짊어지고서라도, 반드시 이 데이터의 탯줄을 무자비하게 끊어내어 진정한 의미의 클라우드 네이티브 MSA 세상으로 이끌고 나가야 하는 선구자다.
- 📢 섹션 요약 비유: 알을 깨고 나오는 새의 고통과 같습니다. 하나의 거대한 알(공유 DB) 속에 있으면 안전하고 따뜻하지만 영원히 하늘을 날 수 없습니다. 알껍데기를 깨고 각자의 독립된 몸(DB per Service)을 가질 때, 비로소 차가운 바람(분산 트랜잭션의 어려움)을 맞겠지만 하늘 높이 무한정 스케일 아웃하여 날아갈 수 있는 자유를 얻게 됩니다.
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| 사가 패턴 (Saga Pattern) | DB가 분리되면서 불가능해진 2PC(글로벌 트랜잭션)를 대체하기 위해, 분리된 DB들을 이벤트로 느슨하게 엮어 트랜잭션을 일치시키는 단짝 기술. |
| CQRS (명령과 조회 책임 분리) | DB가 분리되면서 불가능해진 거대한 JOIN 쿼리 딜레마를 해결하기 위해, 조회를 위한 뷰(View) DB를 따로 만들어 엮어주는 아키텍처. |
| 폴리글랏 퍼시스턴스 (Polyglot Persistence) | 1서비스 1DB를 하기 때문에 굳이 전부 똑같은 오라클을 살 필요 없이, 필요에 따라 MySQL, Redis, Neo4j를 입맛대로 골라 쓸 수 있는 혜택. |
| 분산 모놀리스 (Distributed Monolith) | 코드는 MSA로 쪼갰지만 DB는 여전히 1개의 큰 통뼈 DB를 같이 쓰는, 이도 저도 아닌 가장 위험한 안티패턴. |
| 결과적 일관성 (Eventual Consistency) | "지금 당장 1초 뒤엔 두 DB의 데이터가 다르더라도, 언젠가(수 초 내에) 결국 똑같아진다"는 분산 시스템의 타협적 믿음. |
👶 어린이를 위한 3줄 비유 설명
- 3형제가 돈을 한 저금통(공유 DB)에 같이 모으고 있었어요. 그런데 형이 아이스크림 산다고 돈을 확 빼가면, 동생은 장난감을 못 사서 펑펑 울며 싸움이 났어요.
- 그래서 부모님이 **"이제 각자 자기 돼지저금통(각자의 DB)을 따로 가져라!"**라고 찢어주셨어요. 이제 형이 돈을 다 써도 동생 저금통은 완전 안전해요.
- 이렇게 여러 프로그램이 서로 싸우거나 망가뜨리지 않게 각자의 데이터 창고를 완벽하게 따로따로 만들어주는 철학을 **'데이터베이스 퍼 서비스'**라고 부른답니다!