Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

70장. Database per Service — 서비스별 DB 분리 패턴

이 장에서 말하고자 하는 것

지금까지 우리는 RDS · DynamoDB · ElastiCache 를 보았다.

마이크로서비스 구조에서 마지막 결정이 남아 있다.

“DB를 서비스마다 따로 두는가, 한 DB를 공유하는가?”

이 결정이 MSA의 성패를 가른다.

이 장은 Database per Service 원칙을 정리한다.


1. 한 DB 공유의 문제

[orders 서비스]
[users 서비스]      → 하나의 RDS
[payments 서비스]

처음에는 단순해 보인다.
하지만 곧 다음 문제들이 나타난다.

1. 스키마 변경의 위험

users 서비스의 스키마 변경이 orders 서비스를 깨뜨릴 수 있다.

2. 한 서비스의 부하가 다른 서비스를 마비

payments 의 무거운 쿼리가 orders 응답을 느리게 만든다.

3. 배포 속도가 묶임

같은 DB 마이그레이션을 여러 팀이 함께 조정해야 한다.

4. 기술 선택이 묶임

orders 는 관계형이 자연스러운데
catalog 는 DynamoDB가 맞다 — 한 DB로는 풀 수 없다.


2. Database per Service 원칙

[orders 서비스]   → RDS PostgreSQL (orders 전용)
[users 서비스]    → RDS PostgreSQL (users 전용)
[payments 서비스] → RDS PostgreSQL (payments 전용)
[catalog 서비스]  → DynamoDB
[sessions 서비스] → ElastiCache

각 서비스가 자기 DB를 소유한다.

  • 다른 서비스는 그 DB에 직접 접근 못 한다
  • 데이터는 오직 그 서비스의 API를 통해서만 본다

3. 데이터를 어떻게 공유하는가

DB가 분리되면 자연스럽게 질문이 생긴다.

“주문 화면에 사용자 이름이 필요한데, users DB에 못 들어가면 어떻게?”

세 가지 패턴이 있다.

1. 동기 API 호출

orders → GET users-service/users/u-1 → 사용자 정보
  • 단순함
  • 다른 서비스가 죽으면 영향받음

2. 이벤트 + 로컬 복제

users 서비스 → "사용자 정보 변경" 이벤트 → SNS/EventBridge
orders 서비스가 받아서 자기 DB에 일부 정보 보관
  • 다른 서비스의 가용성에 덜 의존
  • 약간의 데이터 지연 가능

3. 공유 읽기 모델 (CQRS · BFF)

여러 서비스의 데이터를 합쳐서 보여주는 별도 읽기 전용 서비스를 둔다.

대규모 환경에서 쓴다.


4. Saga — 분산 트랜잭션의 현실

한 DB의 트랜잭션 (BEGIN ~ COMMIT) 은
여러 서비스에 걸친 작업을 한 번에 묶을 수 없다.

"주문 생성" 작업:
  1. orders.orders 테이블에 주문 INSERT
  2. payments.payments 테이블에 결제 시도
  3. inventory.products 재고 차감

이 세 단계를 한 트랜잭션에 못 묶는다.

해결 패턴이 Saga 다 — 77장에서 다룬다.


5. 운영적 분리 — DB만 나누는 게 끝이 아니다

진짜 분리는 다음을 다 포함한다.

  • DB 인스턴스/클러스터 분리
  • 백업 · 복구 책임 분리
  • 모니터링 · 알람 분리
  • IAM 정책 분리 (Task Role)
  • 스키마 마이그레이션 도구 (Flyway · Liquibase 등) 별도 운영

“한 RDS에 스키마만 다르게” 는 진정한 Database per Service가 아니다


6. 우리 서비스에서

[ECS "orders"]
   ↓ orders-task-role
[RDS PostgreSQL "orders-db"]   (Multi-AZ)

[ECS "users"]
   ↓ users-task-role
[RDS PostgreSQL "users-db"]    (Multi-AZ)

[ECS "payments"]
   ↓ payments-task-role
[RDS PostgreSQL "payments-db"] (Multi-AZ)

[ECS "catalog"]
   ↓ catalog-task-role
[DynamoDB "products"]

[ECS "sessions"]
   ↓ sessions-task-role
[ElastiCache "sessions-cache"]

각 서비스 Task Role은 자기 DB에만 접근할 수 있다.


7. 어떻게 시작할까

처음부터 모든 서비스에 DB를 따로 둘 필요는 없다.

Phase 1: 모놀리스 (한 DB)
Phase 2: 서비스 경계가 보이기 시작
Phase 3: 분리한 서비스만 별도 DB로
Phase 4: 새 서비스는 처음부터 자기 DB

“DB 분리” 가 곧 서비스 분리의 진짜 완성이다


8. 직접 확인해보기 — CLI

서비스별 IAM Role + DB 접근 정책

aws iam create-role --role-name orders-task-role ...
aws iam put-role-policy \
  --role-name orders-task-role \
  --policy-name orders-db-only \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": ["rds-db:connect"],
      "Resource": "arn:aws:rds-db:ap-northeast-2:...:dbuser:orders-db/orders-app"
    }]
  }'

다른 서비스의 Task Role로는 orders-db에 못 들어간다


9. 코드로는 이렇게 생겼다 — Terraform (스케치)

module "orders" {
  source = "./modules/microservice"

  name           = "orders"
  db_engine      = "postgres"
  db_size        = "db.t4g.small"
  task_role_arn  = aws_iam_role.orders_task.arn
}

module "users" {
  source = "./modules/microservice"

  name           = "users"
  db_engine      = "postgres"
  db_size        = "db.t4g.small"
  task_role_arn  = aws_iam_role.users_task.arn
}

module "catalog" {
  source = "./modules/microservice"

  name          = "catalog"
  db_engine     = "dynamodb"
  task_role_arn = aws_iam_role.catalog_task.arn
}

같은 모듈을 서비스마다 인스턴스화 — 운영의 표준 모양이다.


10. 이렇게 쓰면 망한다 — 안티패턴

안티패턴 1. 다른 서비스의 DB에 직접 SELECT

“잠깐 한 줄만” 이 결국 강한 결합이 된다.

다른 서비스의 데이터는 그 서비스의 API로만

안티패턴 2. 한 RDS에 스키마만 다르게

인스턴스가 한 대인 이상 부하 · 백업 · 마이그레이션이 묶인다.

안티패턴 3. “공통 DB” 를 두고 모두 거기 쓰자

공통 DB는 모든 서비스의 SPOF가 된다.

안티패턴 4. 분산 트랜잭션을 강요한다

서비스 사이에 ACID 트랜잭션을 묶으려 하면 끝없는 복잡도가 된다.

Saga 패턴을 받아들이고 최종 일관성을 설계한다


11. 한 줄로 정리

Database per Service는 각 서비스가 자기 DB를 소유하는 원칙이며,
MSA의 진짜 경계는 데이터 분리에서 완성된다


12. 이 장의 핵심 정리

  1. 한 DB 공유는 스키마 충돌 · 부하 전염 · 배포 묶임을 만든다.
  2. 각 서비스가 자기 DB를 소유한다 — 다른 서비스는 API로만 접근.
  3. 데이터 공유는 동기 호출 · 이벤트 복제 · 별도 읽기 모델 중에 고른다.
  4. 분산 트랜잭션 대신 Saga와 최종 일관성을 받아들인다.
  5. 운영적 분리 (백업 · 알람 · IAM) 까지 함께 가야 진짜 분리다.
  6. 처음부터 완벽히 분리할 필요는 없다 — 서비스 경계가 보이는 곳부터.