pile·
아키텍처·부스트브라더스부스트브라더스·

MSA - 원칙, 아키텍처, 그리고 DB 분리

비브로스가 MSA 전환을 진행하며 정리한 이론과 실전을 한 편으로 묶은 글이다. 핵심은 마지막 장으로, 단일 MongoDB에 모여 있던 104개 컬렉션을 무중단으로 도메인별 DB 6개로 분리한 과정을 다룬다. 앞의 여섯 장은 왜 MSA인가에서 출발해 원칙, 서비스 경계, 아키텍처 유형, 통신·resilience 패턴, 데이터 일관성 순으로 그 작업의 바탕이 된 이론을 정리한다.

핵심 포인트
  • 경계가 틀렸을 때의 증상은 뚜렷하다. 항상 같이 배포되는 서비스 쌍, 여러 서비스가 직접 읽고 쓰는 공유 DB, 유스케이스 하나가 서비스 서너 개를 동기로 왕복하는 chatty 통신이다.
  • DB에 저장하고 broker에 발행하는 흔한 코드에는 두 시스템에 걸친 원자성이 없다. broker가 DB 트랜잭션에 참여하지 않기 때문이다.
  • 보상 트랜잭션은 rollback이 아니다. 결제 취소는 결제를 없던 일로 만드는 게 아니라 환불이라는 새 사실을 만드는 semantic reversal이다.
  • DB 분리는 접근 분리, schema 분리, 클러스터 분리 세 단계로 나뉘며 이번에는 앞의 둘을 진행했다.
  • 소스와 타겟이 같은 클러스터라 복제가 복제를 부르는 loopback 위험이 있었고, 오픈소스 MongoShake에는 루프 방지가 빠져 있어 whitelist와 namespace transform으로 직접 막았다.
상세 정리
  • 모놀리스의 한계 넷: 한 줄을 고쳐도 전체를 재배포해 릴리스가 가장 느린 도메인에 묶이고, scale-out이 프로세스 전체 단위이며, 한 도메인의 메모리 누수나 커넥션 풀 고갈이 프로세스 전체를 내리고, 런타임 업그레이드가 전체 코드의 통과를 요구해 계속 미뤄진다.
  • Node.js에서는 부분 확장 불가가 더 직접적이다. 무거운 동기 연산 하나가 이벤트 루프를 잡으면 도메인과 무관하게 모든 요청이 대기한다.
  • 다섯 원칙: 도메인 소유권, database-per-service, 느슨한 결합과 높은 응집, 독립 배포 가능성, 장애 격리. 여러 서비스가 한 DB를 공유하면 스키마가 암묵적 공용 계약이 되고 독립 배포가 무너지는 첫 지점이 대부분 여기다.
  • 경계 기준 셋: bounded context는 같은 "병원"이라는 단어가 접수 context에서는 대기열을 가진 접수처, 검색 context에서는 진료과목을 가진 검색 대상이 되듯 ubiquitous language가 깨지는 지점이다.
  • 데이터 접근 패턴 기준은 모듈별 모델 의존 그래프, 한 요청에서 함께 조회되는 컬렉션 집합(APM 집계), 하나의 트랜잭션으로 묶이는 컬렉션을 찾는 것이다. 이 응집 클러스터를 가로지르는 경계는 그 트랜잭션을 분산 트랜잭션으로 만들겠다는 선언과 같다.
  • 팀 기준으로는 Team Topologies의 상한을 인용한다. 다섯 명이 서비스 마흔 개를 운영한다면 기술 이전에 경계 설정이 잘못됐다는 것이다.
  • 경계 품질 측정: distributed tracing에서 유스케이스별 span 팬아웃을 집계하면 chatty한 경계가, 배포 이력의 상관관계를 보면 숨은 coordinated deploy가, DB 계정 권한 감사로는 경계를 넘는 직접 조회가 드러난다.
  • event-driven 네 패턴 구분: event notification은 바뀌었다는 사실만 담아 결합이 가장 낮지만 조회 트래픽이 소스로 돌아온다. event-carried state transfer는 데이터를 실어 보내 소스가 죽어도 동작하지만 중복과 eventual consistency를 감수한다.
  • event sourcing은 이력 자체가 도메인 가치인 금융 원장이나 예약 상태 전이에 맞지만, 스키마가 자주 바뀌는 초기 도메인에서는 과거 이벤트를 새 스키마로 끌고 가는 비용이 이득을 넘는다. CQRS는 쓰기가 읽기 모델에 반영되기까지의 시차를 도메인이 감당할 수 있을 때 쓴다.
  • sync의 대가는 가용성 결합이다. 가용성 99.9% 서비스 셋이 체인으로 엮이면 전체는 99.7%로 떨어지고 체인이 길수록 곱셈으로 나빠진다.
  • retry storm: 호출 체인이 3단이고 구간마다 최대 3번씩 시도하면 최하단 장애는 27배 요청으로 돌아온다. 방어는 재시도 총량을 정상 트래픽의 10~20%로 제한하는 retry budget, jitter를 섞은 backoff, circuit breaker의 조합이다.
  • 재시도를 붙이기 전에 그 연산이 멱등인지부터 확인해야 한다. 결제 요청의 재시도는 이중 결제다.
  • transactional outbox: 상태 변경과 이벤트를 같은 DB의 같은 로컬 트랜잭션으로 기록하고, 발행은 outbox를 주기적으로 읽는 polling이나 oplog·binlog를 구독하는 CDC 방식의 relay가 맡는다. relay가 재발행하므로 전달 보장은 at-least-once가 되고 소비자 멱등성이 전제 조건이 된다.
  • 멱등성 확보 방법으로 이벤트 고유 키의 유니크 제약, 유효한 상태 전이만 허용(PENDING→PAID는 반영, PAID→PAID는 무시), 처리한 이벤트 키 기록을 제시한다.
  • saga 조율: orchestration은 어디서 왜 멈췄는지가 한 곳에 모여 관측이 쉬운 대신 orchestrator가 결합점이 되고, choreography는 결합이 낮은 대신 전역 상태가 없어 여러 서비스 로그를 이어 붙여야 한다. 복잡한 핵심 트랜잭션은 전자, 알림·통계 같은 단순 파급은 후자가 일반적 배치다.
  • 발송된 알림처럼 되돌릴 수 없는 단계 이후는 보상 대신 성공할 때까지 재시도하는 forward recovery로 전환해야 하고, 그 분기점을 설계 시점에 정해 둔다.
  • 동시성 경합은 바로 분산 락으로 가기 전에 DB 유니크 제약이나 조건부 업데이트로 풀리는지 먼저 본다. Redlock 같은 분산 락은 만료 시간·경합·시계 오차까지 고려할 것이 많아 락 자체가 장애 지점이 될 수 있다.
  • 접근 분리 실측: Express 4 + TSOA + Mongoose 모놀리스는 어느 도메인 코드든 아무 컬렉션에나 붙을 수 있었다. 전수조사에서 드러난 직접 접근의 상당수는 필요해서가 아니라 가장 편한 경로여서 생긴 것이라 치환이 어렵지 않았다.
  • 오래 걸린 두 유형은 소유가 애매한 컬렉션(어느 capability에 속하는지로 가르고 소유하지 않게 된 쪽에는 API 개방)과 도메인을 가로지르던 aggregation join의 대체다. 호출 빈도가 높은 조회는 event-carried state transfer로 로컬 복제본을 유지하되, 복제본은 화면 표시에는 쓸 수 있어도 정산 기준으로는 못 쓴다고 용도를 갈랐다.
  • 이관 범위 축소: 컬렉션이 가장 많은 도메인은 옮기지 않고 원본 DB를 그대로 그 도메인의 DB로 삼아 작업 범위를 줄였고, 이 결정이 loopback 방지의 한 겹 방어도 됐다.
  • 도구 선택: MongoDB Atlas 단일 클러스터 안에서 서비스를 세우지 않고 DB를 옮겨야 한다는 조건으로, 복사 중 변경분을 못 따라잡아 쓰기 점검이 필요한 방식은 제외하고 무중단과 namespace transform이 되는 MongoShake를 골랐다.
  • full sync 동안에도 애플리케이션은 원본에서 정상 운영되지만 소스 클러스터의 IOPS·CPU 부하가 올라 모니터링 대상이 된다. incremental 단계는 원본의 변경을 수 초 안에 타겟에 반영한다.
  • Atlas 제약: 관리형 서비스라 oplog 직접 접근이 제한돼 기본값인 oplog tailing 대신 change stream 구독으로 변경을 받았다.
  • 무중단 성립 조건: incremental이 full sync 시작 시점의 oplog 타임스탬프부터 적용돼야 복사 중 변경이 누락되지 않고, oplog 보존 기간이 유한하므로 full sync가 그보다 오래 걸리면 이어받을 시작점이 사라진다. 실측 full sync 소요 시간의 1.5배 이상을 oplog 윈도우로 확보하는 것을 기준으로 잡았다.
  • loopback 차단: whitelist가 원본 DB의 namespace만 읽는데 MongoShake가 쓰는 대상은 전부 다른 DB라 매칭될 수 없고, 원본에 남긴 도메인은 애초에 자기 자신으로의 복제가 없다. 설정을 바꿀 때마다 oplog 소비량이 계속 증가하는지 관찰하는 loopback 테스트를 다시 돌렸다.
  • 도구 제약 실측: MongoShake는 mongodb+srv 스킴을 지원하지 않고 URI의 쿼리 파라미터를 경로로 취급해 tls=true 같은 옵션이 인코딩 과정에서 소실되므로 TLS는 별도 설정 파일 항목으로만 켜야 한다. timeseries 컬렉션은 full sync가 도큐먼트와 인덱스는 복제해도 옵션 메타데이터를 복제하지 않는 함정이 있다.
  • 전환 순서: replication lag이 0 근처에 안정된 것을 확인한 뒤 도메인 묶음 단위로 참조를 전환했다. 컬렉션이 가장 적은 묶음을 파일럿으로 먼저, 가장 무거운 도메인은 단독으로 맨 뒤에 뒀다. 실측상 한 도메인이 전체 도큐먼트의 70% 이상을 차지했고 단일 컬렉션 하나가 1,300만 건이었다.
  • 전환 구간의 틈: rolling deploy 동안 구 Pod는 원본에, 새 Pod는 새 DB에 쓰는 구간이 생기고(upsert라 충돌은 없음) 원본을 구독하던 CDC connector는 새 DB의 변경을 모른다. 이 이벤트 미발행 구간은 connector 구독 대상을 바꿔야 닫히므로 배포와 전환을 연속 실행해 분 단위로 줄였다.
  • 검증: 원본과 타겟의 문서 수 비교, 무작위 샘플의 필드 단위 대조(count는 맞는데 타입 변환이나 인코딩으로 내용이 다른 경우 포착), 새 DB를 바라보게 한 애플리케이션 테스트. 인덱스가 빠지면 컷오버 직후 쿼리가 컬렉션 스캔으로 떨어지므로 인덱스도 검증 대상이다.
  • 컷오버 후에도 원본 컬렉션을 바로 지우지 않고 한동안 유지해 문제가 나면 참조만 되돌리는 롤백 경로를 남겼다.
왜 읽나단일 DB 위의 모놀리스를 도메인별로 쪼개려는 백엔드 팀이 이론 정리와 무중단 데이터 이관의 실제 파라미터·함정을 한 문서에서 확인할 수 있다.
부스트브라더스
부스트브라더스 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 아키텍처·aws-architectureAWS Architecture·

    일관성이 새로운 지연이다: 데이터 레이어에서의 AI

    AI 에이전트가 데이터베이스 레이어에서 겪는 데이터 정합성 문제를 다룬다. 비동기 복제 지연(replication lag)으로 stale 데이터를 읽은 에이전트가 잘못된 판단을 내리는 "조용한 독"을 AWS 서비스 기반 세 가지 아키텍처 패턴으로 해결하는 방법을 제시한다. "빠르지만 틀린 답이 약간 느리지만 맞는 답보다 비싸다"는 주장 아래, 데이터베이스 아키텍처를 AI 신뢰성의 기반으로 재정의한다.

    요약 이어보기
    #ai-agent#data-consistency#aurora+2
  2. 아키텍처·flexflex·

    사람도 에이전트도, 덜 읽을수록 더 잘 고칩니다

    flex 팀이 코드베이스의 모듈 경계 설계가 AI 에이전트의 코드 탐색 효율에 미치는 영향을 실측한 글이다. 193개 에이전트 작업 로그를 분석하고, 잘 나뉜 모듈과 뭉친 모듈을 비교한 결과 — 도메인이 물리 모듈로 분리된 저장소에서 에이전트가 불필요한 파일을 덜 읽고 목표 코드에 더 집중했다. 사람도 에이전트도 컨텍스트 한도가 있어, 무관 코드를 덜 읽을수록 수리 성공률이 높다는 것이 핵심 결론이다.

    요약 이어보기
    #llm-agent#context-window#modular-architecture+1
  3. 아키텍처·aws-architectureAWS Architecture·

    데이터 거주 요건을 충족하는 재해 복구 전략

    데이터 거주(data residency) 규정을 지키면서 재해 복구(DR)를 구현해야 하는 조직을 위해 AWS가 세 가지 전략 프레임워크를 제시한다. 금융, 의료, 공공 부문처럼 지리적 데이터 보관 의무가 있는 환경에서도 복구 목표(RTO/RPO)를 달성할 수 있는 아키텍처 선택지를 비교한다.

    요약 이어보기
    #multi-cloud#compliance#disaster-recovery+2