MOM.SOFT

MOMSOFT  /  기술 블로그

Architecture

모놀리스를 쪼갤 때 현실적인 순서

쪼개는 단위는 코드가 아니라 데이터입니다. 소유권을 먼저 나누고, 모듈형 모놀리스에서 한 번 멈춘 뒤, 운영이 준비된 다음에야 프로세스를 분리하십시오.

프로젝트 문의하기 →

결론부터: 쪼개는 순서는 코드가 아니라 데이터입니다

모놀리스 분리를 논의하는 자리에서 가장 먼저 나오는 그림은 대개 네모 상자 여러 개와 그 사이를 잇는 화살표입니다. 주문 서비스, 회원 서비스, 결제 서비스. 그런데 그 그림을 그린 다음 실제로 손을 대 보면, 상자를 떼어내는 일이 아니라 하나의 데이터베이스에서 테이블을 떼어내는 일에서 막힙니다. 조인 한 줄, 외래 키 하나, 트랜잭션 하나가 상자 두 개를 붙들고 있기 때문입니다.

그래서 권하는 순서는 명확합니다. 첫째, 데이터 소유권을 먼저 나눕니다. 둘째, 코드를 모듈 경계로 정리하되 배포는 하나로 유지합니다. 셋째, 운영 기반(로그·추적·배포·테스트)이 준비된 다음에야 프로세스를 분리합니다. 그리고 상당수 팀은 두 번째 단계에서 멈추는 편이 낫습니다. 모듈형 모놀리스는 마이크로서비스로 가는 중간 정거장이 아니라, 그 자체로 충분히 좋은 종착지인 경우가 많습니다.

이 글은 "MSA로 가자"는 글이 아닙니다. 가야 한다면 어떤 순서로 가야 덜 다치는지, 그리고 언제는 가지 말아야 하는지를 판단하기 위한 글입니다.

왜 처음부터 마이크로서비스가 답이 아닌가

분리는 결합을 없애지 않고 형태만 바꿉니다

모놀리스 안의 결합은 눈에 보입니다. 컴파일러가 잡아 주고, IDE가 참조를 찾아 주고, 리팩터링 도구가 한 번에 바꿔 줍니다. 이 결합을 네트워크 너머로 옮기면 결합 자체는 사라지지 않고 컴파일러가 못 보는 결합으로 바뀝니다. 응답 필드 하나를 지우면 이제 배포 후 다른 팀의 운영 시간에 오류로 나타납니다.

여기서 중요한 것은 비용의 성격입니다. 모놀리스의 결합 비용은 개발 시간에 지불되고, 분산 시스템의 결합 비용은 운영 시간에 지불됩니다. 개발 시간의 비용은 리팩터링으로 갚을 수 있지만, 운영 시간의 비용은 장애로 갚습니다.

트랜잭션 경계가 무너집니다

모놀리스에서는 주문 생성과 재고 차감과 포인트 적립이 한 트랜잭션 안에 있습니다. 실패하면 전부 롤백됩니다. 이것을 세 서비스로 나누면 그 보장이 사라집니다. 대안은 사가(saga)나 아웃박스 같은 최종 일관성 패턴인데, 이는 단순히 "패턴을 하나 적용하는 일"이 아닙니다. 보상 트랜잭션을 도메인 규칙으로 정의해야 하고, 중간 상태를 사용자에게 어떻게 보여 줄지 기획과 합의해야 하고, 보상마저 실패했을 때 사람이 개입하는 운영 절차를 만들어야 합니다. 코드 문제가 아니라 조직과 업무 규칙의 문제입니다.

여기서 자주 나오는 오해가 분산 트랜잭션을 쓰면 된다는 생각입니다. 두 단계 커밋은 참여자 전원이 준비 단계에서 자원을 잠근 채 조정자의 결정을 기다리는 구조라, 그 사이에 조정자나 참여자 하나가 죽으면 잠금이 남고 나머지가 함께 멈춥니다. 장애를 격리하려고 서비스를 나눈다는 목적과 정면으로 충돌합니다. 실무의 선택지는 사실상 한 트랜잭션 안에 두거나, 최종 일관성을 업무 규칙으로 받아들이거나 둘 중 하나입니다.

배포 단위는 조직 구조를 따라갈 때만 이득입니다

마이크로서비스의 가장 큰 효용은 기술적 확장성이 아니라 독립 배포입니다. 팀 A가 팀 B의 릴리스 일정을 기다리지 않아도 되는 것. 그런데 팀이 하나라면 이 효용은 없습니다. 서비스가 다섯 개여도 한 팀이 다섯 개를 다 배포해야 한다면, 배포 횟수만 늘고 얻는 자유는 없습니다.

판단 기준을 하나만 남긴다면 이렇게 잡으십시오. 독립적으로 배포 일정을 정할 수 있는 팀의 수를 넘어서는 서비스는, 조직이 아닌 다른 이유를 스스로 증명해야 합니다. 증명이 되는 경우도 분명히 있습니다. 런타임이나 언어가 근본적으로 다른 경우, 자원 사용 패턴이 달라 함께 두면 서로를 밀어내는 경우(대용량 배치와 실시간 API), 장애 반경을 물리적으로 끊어야 하는 경우, 규제나 망 분리 요건으로 격리가 강제되는 경우입니다. 이 중 어디에도 해당하지 않는 초과분은 순수 비용입니다.

분산 시스템의 기본 세금

서비스를 하나 늘릴 때마다 자동으로 따라오는 것들이 있습니다. 배포 파이프라인, 서비스 계정과 권한, 시크릿 관리, 헬스 체크, 타임아웃과 재시도 정책, 서킷 브레이커, 로그 수집 설정, 대시보드, 알람 룰, 온콜 문서, 스키마 마이그레이션 절차. 도메인 가치를 만들지 않으면서 서비스 수에 비례해 늘어나는 항목들입니다. 이 세금을 자동화로 상각할 수 없다면 서비스를 늘리면 안 됩니다.

모듈 경계를 찾는 기준: 데이터 소유권

경계를 찾을 때 흔히 쓰는 방법은 "도메인을 나눈다"입니다. 틀린 말은 아니지만 실행 가능한 기준이 아닙니다. 회의실에서 명사를 나열하면 누구나 다른 그림을 그립니다. 실행 가능한 기준은 하나입니다. 이 테이블에 쓰기(write)를 하는 코드가 어디인가.

1단계: 테이블별 쓰기 주체를 전수 조사합니다

읽기가 아니라 쓰기입니다. 읽기는 나중에 조회 전용 API나 읽기 모델로 풀 수 있지만, 쓰기가 여러 곳에서 일어나는 테이블은 소유자가 없는 테이블이고, 소유자가 없는 테이블은 절대로 깔끔하게 떼어지지 않습니다.

이때 코드에서 save()delete() 호출만 찾으면 절반을 놓칩니다. 실제 쓰기 경로는 보통 이보다 많습니다.

이 경로들을 테이블 단위로 모아 표를 만들면 대체로 세 부류가 나옵니다.

2단계: 조인과 외래 키를 경계 후보선으로 봅니다

모듈 경계를 가로지르는 조인이 많은 곳은 경계가 잘못 그어진 곳입니다. 반대로 조인이 거의 일어나지 않는 테이블 묶음은 이미 사실상 분리되어 있는 것입니다. 회의실에서 그림을 그리기 전에 실제 쿼리를 보십시오. PostgreSQL이라면 pg_stat_statements로 실행 횟수 상위 쿼리를 뽑고, 그 안에서 테이블 쌍이 함께 등장하는 빈도를 세면 됩니다. 대체로 데이터가 그린 그림이 회의실 그림보다 정확합니다.

3단계: 경계를 넘는 참조를 ID로 바꿉니다

물리 분리를 하기 전에, 모놀리스 안에서 먼저 참조 방식을 바꿉니다. 객체 참조를 ID 참조로 낮추는 작업입니다.

// 경계를 넘는 객체 참조 — 조인과 지연 로딩이 경계를 무의미하게 만듭니다
@ManyToOne(fetch = FetchType.LAZY)
private Member member;

// 경계 안에서만 의미 있는 값으로 낮춥니다
private Long memberId;   // 다른 모듈의 식별자. 조인하지 않습니다.

이 변경은 지루하고 코드는 잠깐 더 불편해집니다. 화면에 회원 이름을 찍으려면 조회 호출이 한 번 더 필요하고, 목록 화면에서는 건별 조회가 반복되는 문제가 곧바로 드러납니다. 해법은 정해져 있습니다. ID를 모아 한 번에 조회하는 배치 조회를 모듈의 공개 계약에 넣는 것입니다. 나중에 프로세스를 나눌 때 이것이 없으면 화면 하나에 원격 호출이 수십 번씩 나갑니다.

이 불편함 자체가 경계 비용을 미리 계산해 보는 일입니다. 여기서 "이건 못 하겠는데"라는 말이 나온다면 물리 분리는 더더욱 못 합니다. 같은 작업에 네트워크 지연과 부분 실패가 얹히기 때문입니다.

4단계: 경계를 코드로 강제합니다

사람의 규율에 맡긴 경계는 반드시 무너집니다. 마감 전날 밤에 무너집니다. 그래서 경계는 테스트로 고정해야 합니다. Java 진영이라면 ArchUnit 같은 아키텍처 테스트 도구로, Spring 기반이라면 모듈 경계 검증을 지원하는 Spring Modulith 같은 도구로 잡을 수 있습니다.

// 개념 예시 — 주문 모듈은 회원 모듈의 내부 구현을 직접 참조할 수 없습니다
noClasses().that().resideInAPackage("..order..")
    .should().dependOnClassesThat()
    .resideInAPackage("..member.internal..")
    .check(classes);

핵심은 각 모듈이 api(공개 계약)와 internal(구현)을 나누고, 다른 모듈은 api만 참조하게 하는 것입니다. 이 규칙 하나가 있으면 나중에 물리 분리를 할 때 "무엇을 REST나 메시지로 바꿔야 하는가"의 목록이 이미 만들어져 있는 셈입니다.

쓰기를 나누면 조회가 먼저 깨집니다

소유권 분리에서 가장 자주 무시되다가 가장 늦게 터지는 것이 조회입니다. 여러 테이블을 조인해 정렬하고 페이징하던 목록 화면은 경계가 생기는 순간 한 방에 만들 수 없게 됩니다. 선택지는 셋뿐이고, 고르는 기준은 정렬 키와 필터 조건이 어느 모듈 소유인가입니다.

먼저 떼어낼 후보를 고르는 기준

첫 분리는 기술 검증이자 조직 학습입니다. 그러니 가장 중요한 도메인을 먼저 떼지 마십시오. 주문이나 결제처럼 매출의 심장부를 첫 실험 대상으로 삼는 것은, 처음 배우는 수술을 심장에서 시작하는 것과 같습니다.

기준좋은 첫 후보나쁜 첫 후보
데이터 쓰기 결합자기 테이블만 씀, 경계 넘는 조인 거의 없음핵심 테이블과 한 트랜잭션으로 묶임
일관성 요구최종 일관성으로 충분(알림, 통계, 파일 처리)즉시 일관성 필수(재고 차감, 잔액 변경)
트래픽 특성나머지와 뚜렷이 다름(배치성, 폭발적, 외부 연동 대기)본체와 동일한 패턴
변경 빈도매우 잦거나 매우 드묾(양극단)본체와 같은 주기로 함께 바뀜
장애 격리 가치죽어도 본체가 살아 있으면 되는 기능죽으면 어차피 서비스 전체가 멈추는 기능
롤백 가능성되돌리기 쉬움데이터 이관이 비가역

실제로 첫 분리 후보로 자주 맞아떨어지는 것들은 화려하지 않습니다. 알림 발송, 파일·이미지 처리, 외부 시스템 연동 어댑터, 리포트·통계 집계 정도입니다. 공통점이 있습니다. 비동기로 처리해도 업무가 성립하고, 실패하면 재시도로 회복되며, 본체의 트랜잭션에 참여하지 않습니다. 반대로 인증처럼 "모두가 쓰지만 죽으면 전부 멈추는" 기능은 겉보기에 독립적이어도 첫 후보로는 위험합니다. 얻는 것은 코드 정리뿐이고, 지는 것은 전체 가용성이 한 프로세스의 가용성에 묶이는 위험입니다.

그리고 후보를 고를 때 반드시 함께 물어야 할 질문이 있습니다. "이걸 떼면 되돌릴 수 있습니까?" 스트랭글러 방식으로 라우팅을 새 서비스로 조금씩 옮기되, 문제가 생기면 즉시 옛 경로로 되돌릴 수 있게 설계하십시오. 되돌릴 수 없는 첫 분리는 실험이 아니라 도박입니다.

공통 모듈이 새로운 결합이 되는 함정

분리 작업을 시작하면 거의 예외 없이 common, core, shared 같은 이름의 모듈이 생깁니다. 그리고 이 모듈은 시간이 지나면 쪼개기 전보다 더 나쁜 결합이 됩니다. 이유는 구조적입니다.

공통 모듈은 오직 커지는 방향으로만 진화합니다

새 기능이 두 모듈에서 필요해지면 공통으로 올립니다. 아무도 공통에서 내리지 않습니다. 내리는 일은 위험하고 칭찬받지 못하기 때문입니다. 그래서 공통 모듈은 단조 증가하며, 결국 모든 모듈이 모든 것에 의존하는 상태가 됩니다. 이때는 서비스가 다섯 개로 나뉘어 있어도 공통 라이브러리 버전을 올리려면 다섯 개를 함께 배포해야 하므로, 독립 배포라는 유일한 이득이 사라집니다. 남는 건 분산 시스템의 비용뿐입니다.

징후는 한 가지로 판별됩니다. 공통 모듈을 고쳤을 때 함께 배포해야 하는 서비스가 몇 개인가. 하나를 넘어가기 시작하면 그 공통 모듈은 이미 라이브러리가 아니라 숨은 모놀리스입니다. 라이브러리라면 소비자가 각자 원할 때 버전을 올릴 수 있어야 하고, 그러려면 옛 버전과의 호환이 유지되어야 합니다.

무엇이 공통에 들어가도 되는가

선을 이렇게 그으십시오.

중복을 허용해야 하는 지점

같은 이름의 개념이 두 모듈에 각각 존재하는 것을 견디십시오. 주문 모듈의 "회원"은 배송지와 등급이 필요한 존재이고, 정산 모듈의 "회원"은 사업자 번호와 계좌가 필요한 존재입니다. 이 둘을 한 클래스로 합치면 필드 스무 개짜리 신 클래스가 되고, 어느 모듈을 고쳐도 다른 모듈이 깨집니다. 중복 제거는 미덕이지만, 서로 다른 이유로 변하는 코드를 합치는 것은 중복 제거가 아니라 결합 생성입니다.

공유 데이터베이스는 공통 모듈보다 나쁩니다

"코드는 나눴지만 DB는 하나"로 오래 머무는 구성이 흔합니다. 이 상태에서는 누구도 스키마를 자유롭게 바꿀 수 없고, 어느 서비스가 어떤 테이블을 언제 잠그는지 아무도 모릅니다. 물리 분리를 정말로 하겠다면 스키마 분리(최소한 스키마·계정 단위의 접근 권한 분리)가 선행이지 후행이 아닙니다. 각 모듈의 DB 계정에서 남의 테이블 권한을 회수해 보면, 설계 문서가 아니라 실행 오류가 진짜 경계를 알려 줍니다. 반대로 권한을 회수할 자신이 없다면, 그것은 아직 모듈형 모놀리스에 머물러야 한다는 뜻입니다.

쪼개기 전에 갖춰야 할 것

아래 항목이 갖춰지지 않은 상태에서 서비스를 나누면, 분리의 이득이 나타나기 전에 운영 부담이 먼저 옵니다. 순서대로 점검하십시오.

1. 로그와 추적

모놀리스에서는 스택 트레이스 한 장이면 원인이 보입니다. 분리 후에는 요청 하나가 여러 프로세스를 지나가므로, 상관관계 ID가 없으면 디버깅이 불가능해집니다. 반드시 먼저 갖추십시오.

기준은 이렇습니다. "장애 신고를 받고 몇 분 안에 어느 서비스가 원인인지 말할 수 있는가." 서버별로 로그를 뒤져야 답이 나온다면 아직입니다.

2. 배포

3. 테스트

테스트 없이 쪼개는 것은 안전망 없이 줄을 타는 것입니다. 다만 필요한 테스트의 종류가 바뀝니다.

4. 비동기 처리의 기본기

경계를 넘는 통신 중 상당수는 결국 이벤트가 됩니다. 그렇다면 최소한 이것들은 물리 분리 전에 모놀리스 안에서 연습해 두십시오.

-- 아웃박스: 도메인 변경과 이벤트 기록을 같은 트랜잭션에 넣고,
-- 발행은 별도 프로세스가 재시도하며 담당합니다.
CREATE TABLE outbox_event (
  id           BIGSERIAL PRIMARY KEY,
  aggregate_id VARCHAR(64)  NOT NULL,
  event_type   VARCHAR(100) NOT NULL,
  payload      JSONB        NOT NULL,
  created_at   TIMESTAMPTZ  NOT NULL DEFAULT now(),
  published_at TIMESTAMPTZ
);

아웃박스가 해결하는 문제는 하나입니다. DB 커밋과 메시지 발행을 따로 하면 그 사이에 프로세스가 죽었을 때 데이터는 바뀌었는데 이벤트는 없는 상태가 생기는데, 같은 트랜잭션에 기록해 두면 그 구멍이 없어집니다. 대신 발행이 중복될 수 있습니다.

그래서 멱등 처리가 반드시 따라와야 합니다. 대부분의 메시징 인프라는 "최소 한 번" 전달을 보장하므로 중복 도착은 예외가 아니라 정상 동작입니다. 소비자는 같은 이벤트를 두 번 받아도 결과가 같아야 합니다. 처리한 이벤트 ID를 유니크 제약이 걸린 테이블에 기록하고 중복 삽입이면 건너뛰는 정도의 단순한 방법으로 충분한 경우가 많습니다. 이 습관이 없는 팀이 메시징을 도입하면 중복 발송이나 중복 적립 같은 사고가 반드시 납니다.

물리 분리 당일: 데이터를 어떻게 옮기는가

모듈 경계까지 정리한 팀이 마지막에 가장 많이 다치는 지점이 데이터 이관입니다. 코드 배포는 되돌릴 수 있지만, 이관 중에 갈라진 데이터는 되돌리기 어렵습니다. 순서는 이렇게 잡으십시오.

  1. 권한부터 나눕니다. 새 서비스용 DB 계정을 만들고 자기 소유 테이블 외에는 권한을 주지 않습니다. 아직 같은 DB여도 됩니다. 이 단계에서 터지는 오류가 미처 못 찾은 경계 위반입니다
  2. 복제를 먼저 세웁니다. 원본에서 새 저장소로 단방향 복제를 걸고, 새 서비스는 읽기만 합니다. 쓰기는 아직 옛 경로가 담당합니다
  3. 대사(reconciliation) 배치를 붙입니다. 양쪽 건수와 체크섬을 주기적으로 비교하고, 전환 기간 내내 돌립니다. 불일치를 사람이 우연히 발견하게 두면 안 됩니다
  4. 읽기 트래픽을 조금씩 옮깁니다. 문제가 보이면 라우팅만 되돌립니다. 데이터를 건드리지 않았으므로 되돌리기가 안전합니다
  5. 마지막에 쓰기를 넘깁니다. 여기가 되돌리기 어려워지는 지점이므로 앞 단계가 모두 안정된 뒤에만 합니다
  6. 옛 테이블은 바로 지우지 않습니다. 쓰기 권한만 회수하고 일정 기간 남겨 둡니다. 삭제는 언제든 나중에 할 수 있지만 복구는 그렇지 않습니다

여기서 흔한 실수가 양쪽에 동시에 쓰는(dual write) 방식입니다. 애플리케이션 코드에서 옛 DB와 새 DB에 나란히 쓰면 둘 중 하나만 성공하는 순간 정합성이 깨지고, 어느 쪽이 진실인지 알 수 없게 됩니다. 이것은 하나의 트랜잭션이 아닙니다. 쓰기 원본은 항상 한 곳으로 두고, 다른 쪽은 아웃박스나 변경 데이터 캡처를 통해 단방향으로 따라오게 만드십시오.

실제 순서 체크리스트

  1. 현황을 측정합니다. 테이블별 쓰기 주체 표, 경계를 넘는 조인 빈도, 배포 주기와 실패율, 변경이 잦은 파일 목록. 그림을 그리기 전에 숫자를 봅니다
  2. 패키지를 도메인 기준으로 재배치합니다. controller / service / repository가 최상위인 구조를 order / member / settlement가 최상위이고 그 안에 계층이 들어가는 구조로 바꿉니다. 파일 이동뿐이라 위험이 거의 없고, 경계가 눈에 보이기 시작합니다
  3. 모듈마다 apiinternal을 나눕니다. 다른 모듈이 참조하는 지점을 api로 모읍니다. 이 목록이 곧 미래의 서비스 인터페이스입니다
  4. 아키텍처 테스트로 경계를 잠급니다. 이 시점부터 경계는 더 나빠지지 않습니다
  5. 경계를 넘는 객체 참조를 ID 참조로 낮춥니다. 조인을 조회 호출로 바꾸고, 목록 화면을 위한 배치 조회를 공개 계약에 넣습니다
  6. 경계를 넘는 트랜잭션을 이벤트로 바꿉니다. 아직 같은 프로세스 안이므로 잘못되면 되돌리기 쉽습니다. 여기서 최종 일관성이 업무적으로 수용되는지 검증합니다
  7. 여기서 멈추고 몇 개 분기를 지내 봅니다. 이 상태로 배포 속도와 장애 대응이 충분하다면 더 나아갈 이유가 없습니다
  8. 운영 기반을 점검합니다. 앞 절의 로그·배포·테스트 항목을 실제로 통과하는지 확인합니다
  9. 후보 하나만 물리 분리합니다. 권한과 스키마를 먼저 나누고, 읽기부터 옮기고, 롤백 경로를 남겨 둡니다. 두 개를 동시에 하지 마십시오
  10. 다음 분리 전에 회고합니다. 걸린 시간, 늘어난 운영 부담, 실제로 얻은 이득을 적습니다. 이득이 비용보다 작았다면 다음 분리는 하지 않는 것이 옳은 결정입니다

언제는 하지 말아야 하는가

여기가 이 글에서 가장 중요한 부분입니다. 아래에 해당한다면 분리는 문제를 해결하지 않고 문제에 운영 부담을 더할 뿐입니다.

팀이 하나일 때

독립 배포의 이득을 받을 주체가 없습니다. 한 팀이 관리하는 다섯 개의 서비스는 다섯 배의 배포와 다섯 배의 알람과 다섯 배의 문서를 만들고, 대신 아무것도 돌려주지 않습니다. 앞서 적은 예외(런타임 차이, 자원 경합, 규제 격리)에 해당하는지 먼저 확인하고, 아니라면 모듈 경계까지만 하십시오.

도메인이 아직 흔들릴 때

신규 제품이나 방향이 자주 바뀌는 영역에서는 경계 자체가 매달 바뀝니다. 모놀리스 안에서 경계를 옮기는 것은 리팩터링이지만, 서비스 사이에서 경계를 옮기는 것은 데이터 이관 프로젝트입니다. 경계에 확신이 없으면 물리적으로 굳히지 마십시오. 확신은 도메인을 오래 운영해 봐야 생깁니다.

병목이 애플리케이션이 아니라 데이터베이스일 때

느린 원인이 인덱스 부재, N+1 조회, 커넥션 풀 설정, 잘못된 쿼리라면 서비스를 나눠도 그대로 느립니다. 오히려 같은 DB를 여러 프로세스가 두드리게 되어 연결 수만 늘어납니다. 쿼리 튜닝과 캐시로 해결되는 문제를 아키텍처로 풀려고 하지 마십시오. 순서상 프로파일링이 먼저입니다.

진짜 문제가 코드 품질일 때

"손댈 수 없는 코드"가 고통의 원인이라면 분리는 그 코드를 개선하지 않습니다. 나쁜 코드를 프로세스 경계 너머로 옮길 뿐이고, 이제는 디버깅까지 어려워집니다. 이름 짓기와 책임 분리, 테스트 추가로 해결되는 문제에 배포 파이프라인과 네트워크를 얹는 것은 비용만 늘리는 선택입니다.

테스트도 파이프라인도 없을 때

이 상태에서의 분리는 안전망 없는 대수술입니다. 자동 배포와 경계 테스트를 먼저 만드는 데 쓰는 시간이 분리 자체보다 훨씬 높은 수익을 냅니다. 그리고 그 기반을 갖추고 나면 상당수 팀은 분리가 덜 급하다는 사실을 알게 됩니다.

장애를 받아 줄 사람이 없을 때

분리된 시스템은 밤에 깨어 있는 사람을 전제로 합니다. 온콜 순번도, 대응 절차도, 인시던트 기록도 없는 조직에서 서비스를 늘리면 장애 탐지가 사용자 문의로 대체됩니다. 조직이 감당할 수 있는 운영 표면적을 넘어서면 아키텍처가 아무리 좋아도 결과는 나빠집니다.

동기가 기술 유행이나 채용일 때

"요즘 다 MSA를 한다", "이력서에 쓸 게 필요하다"는 이유로 시작한 분리는 예외 없이 중간에 멈춥니다. 그리고 절반만 쪼개진 시스템은 모놀리스보다도 나쁩니다. 모놀리스의 제약과 분산 시스템의 비용을 동시에 지불하기 때문입니다. 시작 전에 "이 분리로 무엇이 좋아지는가"를 한 문장으로 쓸 수 없다면 시작하지 마십시오.

강한 일관성이 업무 요건일 때

잔액, 재고, 좌석, 한도처럼 순간의 불일치가 곧바로 금전 손실이나 규제 위반이 되는 영역은 트랜잭션 경계 안에 두는 편이 안전합니다. 이런 영역을 굳이 쪼개려면 보상 로직과 정산 배치, 불일치 탐지, 수동 개입 절차까지 만들어야 하고, 그 총비용이 분리로 얻는 이득을 넘는 경우가 대부분입니다. 규모가 큰 시스템일수록 돈이 오가는 핵심 구간은 끝까지 한 트랜잭션 안에 남겨 두는 선택이 드물지 않습니다.

정리

모놀리스 분리에서 실패하는 팀과 성공하는 팀의 차이는 기술 선택이 아니라 순서입니다. 실패하는 팀은 서비스 목록을 먼저 정하고 데이터를 나중에 만납니다. 성공하는 팀은 데이터 소유권을 먼저 정리하고, 코드 경계를 테스트로 잠그고, 운영 기반을 갖춘 뒤, 마지막에 프로세스를 나눕니다.

그리고 성공하는 팀의 상당수는 마지막 단계를 하지 않습니다. 경계가 분명한 모듈형 모놀리스는 배포가 간단하고, 트랜잭션이 안전하고, 디버깅이 쉽고, 필요할 때 언제든 떼어낼 수 있는 상태입니다. 언제든 쪼갤 수 있는 상태를 만드는 것이 목표이고, 실제로 쪼개는 것은 그 상태에서 내리는 별개의 판단입니다. 이 둘을 구분하는 것만으로도 대부분의 불필요한 전환은 걸러집니다.

같은 주제의 다른 글

맘소프트는 19년 넘게 금융 · 엔터프라이즈 · 모바일 시스템을 만들어 왔습니다. 여기 적는 글은 그 과정에서 실제로 부딪히고 해결한 것들입니다.

기술 판단이 필요하신가요

맘소프트는 소프트웨어 개발과 CTO 대행 · 기술 자문을 제공합니다. 편하게 문의해 주세요.

[email protected]