TechFeedTechFeed
Backend

스타트업이 마이크로서비스를 해체하고 모놀리스로 돌아온 이유 — 2년간의 회고

엔지니어 8명 B2B SaaS 스타트업이 처음부터 마이크로서비스로 설계했다가 2년 뒤 모놀리스로 전환한 실전 사례. 서비스 10개에서 발생한 분산 트랜잭션 문제, 에러 추적 한계, 배포 복잡도를 분석하고 strangler fig 패턴으로 재통합한 단계별 접근을 정리한다.

2024년 초, 시리즈 A를 앞둔 B2B SaaS 스타트업 한 곳이 주요 엔지니어링 의사결정을 내렸다. "우리는 처음부터 마이크로서비스로 간다." CTO는 확신이 있었다. 서비스가 커지면 결국 분리해야 하고, 처음부터 제대로 설계하면 나중에 비용이 줄어든다고 했다.

결론부터 말하면, 이 팀은 2년 뒤 마이크로서비스를 해체하고 모놀리스로 돌아왔다. 이 글은 그 팀이 어떤 선택을 했고, 무엇이 잘못됐으며, 어떻게 되돌아왔는지를 정리한 회고다.

※ 이 글은 다수 스타트업 엔지니어링 블로그 사례와 공개 포스트모텀을 종합한 복합 사례입니다. 특정 기업을 지칭하지 않습니다.

처음부터 마이크로서비스로 설계한 이유

이 팀은 Node.js + PostgreSQL 스택으로 B2B 분석 SaaS를 개발했다. 팀 규모는 엔지니어 8명이었고, 투자자로부터 "확장성 있는 아키텍처"를 요구받고 있었다.

마이크로서비스를 선택한 배경은 세 가지였다:

  • 기술적 이유: 데이터 수집·처리·시각화·인증이 서로 다른 스케일 요구사항을 가질 것이라는 예상
  • 조직적 이유: 팀을 기능 단위로 나누어 독립 배포가 가능하게 한다는 계획
  • 투자 설득 이유: "마이크로서비스 아키텍처"가 기술 실사(DD)에서 좋게 보인다는 판단

이들은 총 5개 서비스로 시작했다. auth-service, ingestion-service, pipeline-service, dashboard-service, notification-service. 각 서비스는 독립 레포지토리와 독립 데이터베이스를 가졌다.

초기 3개월은 순조로웠다. 각 서비스가 독립적으로 배포되고, CI/CD 파이프라인도 서비스별로 분리돼 있었다. 팀은 이 구조가 맞다고 확신했다.

문제는 4개월째부터 시작됐다. 제품 요구사항이 구체화되면서 서비스 간 연동이 폭발적으로 늘었다. 고객이 대시보드를 열면 auth-servicedashboard-servicepipeline-serviceingestion-service 순서로 4번의 네트워크 호출이 연쇄됐다. 단일 API 요청이 사실은 서비스 간 내부 호출 7번을 의미했다.

가장 불편했던 점은 에러 추적이었다. 사용자가 "대시보드가 안 열려요"라고 신고하면, 어느 서비스에서 문제가 생겼는지 4개 서비스의 로그를 각각 열어 타임스탬프로 맞춰봐야 했다. 분산 추적(distributed tracing) 시스템이 없었기 때문이다.

마이크로서비스 아키텍처 — 서비스 간 의존성이 늘어나는 과정
처음 5개였던 서비스가 10개로 늘어나자 의존성 그래프가 폭발했다

서비스가 10개가 됐을 때 생긴 일들

출시 후 6개월이 지나면서 팀은 서비스를 5개에서 10개로 늘렸다. billing-service, export-service, webhook-service, audit-service, report-service가 추가됐다. 추가할 때마다 합리적인 이유가 있었다. 하지만 총량이 쌓이자 문제가 구조적으로 드러났다.

분산 트랜잭션 문제가 현실이 됐다. "사용자가 구독을 취소하면 billing-service, notification-service, audit-service에 동시에 반영돼야 한다"는 요구사항이 생겼을 때, 팀은 어떻게 처리할지 이틀 동안 논쟁했다. 결국 saga 패턴을 도입하기로 했고, 이를 구현하는 데 3주가 걸렸다. 이전 모놀리스 환경이었다면 하나의 트랜잭션으로 해결됐을 문제였다.

로컬 개발 환경도 점점 악몽이 됐다. 신규 입사자가 개발 환경을 구성하는 데 평균 이틀이 걸렸다. docker-compose 파일은 200줄이 넘었고, 서비스마다 다른 환경변수 파일이 있었다. 한 서비스를 수정하면 의존하는 다른 서비스를 재시작해야 하는 경우도 있었다.

팀의 신규 멤버가 남긴 온보딩 피드백은 직접적이었다: "서비스 하나 수정하려면 어떤 서비스를 같이 올려야 하는지 모르겠어요. 문서가 있긴 한데 두 달 전 기준이에요."

10개 서비스 로컬 구동 시 docker-compose.yml (일부)
version: '3.8' services: auth-service: build: ./services/auth ports: ['3001:3001'] depends_on: [postgres-auth, redis] env_file: ./services/auth/.env.local ingestion-service: build: ./services/ingestion ports: ['3002:3002'] depends_on: [postgres-ingestion, kafka] env_file: ./services/ingestion/.env.local pipeline-service: build: ./services/pipeline ports: ['3003:3003'] depends_on: [postgres-pipeline, ingestion-service, redis] env_file: ./services/pipeline/.env.local # ... 7개 서비스 더 ... postgres-auth: image: postgres:15 environment: POSTGRES_DB: auth_db POSTGRES_PASSWORD: local_secret postgres-ingestion: image: postgres:15 environment: POSTGRES_DB: ingestion_db POSTGRES_PASSWORD: local_secret kafka: image: confluentinc/cp-kafka:7.5.0 depends_on: [zookeeper] # ... zookeeper: image: confluentinc/cp-zookeeper:7.5.0 # ... redis: image: redis:7-alpine

롤백을 결정한 실제 인시던트

결정적인 계기는 2025년 3월에 발생한 인시던트였다. 금요일 오후 4시, 주요 엔터프라이즈 고객이 "내보내기(export) 기능이 작동하지 않는다"고 신고했다. 팀은 즉시 export-service 로그를 확인했다. 오류는 없었다.

다음으로 pipeline-service를 확인했다. 이상 없었다. ingestion-service도 정상이었다. 한 시간 반 동안 서비스를 하나씩 점검한 뒤에야 원인을 찾았다. billing-service에서 구독 갱신 처리 중 데이터베이스 커넥션 풀이 잠깐 포화 상태가 됐고, 그 순간 export-service가 사용자 권한 확인을 위해 billing-service에 호출을 보냈다가 타임아웃이 발생했다. 그런데 타임아웃 에러가 export-service 로그에 남지 않고 무음으로 실패 처리됐던 것이다.

원인 파악 시간: 1시간 47분. 동일한 구조의 문제였다면 모놀리스에서는 단일 스택 트레이스로 즉시 확인 가능했을 것이다.

모놀리스로 돌아가야 한다는 신호들
• 서비스 간 동기 HTTP 호출이 3단계 이상 중첩될 때
• 분산 추적 없이 에러 원인 파악에 1시간 이상 걸릴 때
• 로컬 개발 환경 구성에 반나절 이상 소요될 때
• 단순 기능 추가에 2개 이상 레포지토리 수정이 필요할 때
• 서비스 간 계약(contract) 변경이 동시 배포를 요구할 때
분산 서비스 환경에서의 에러 추적 어려움 — 여러 서비스 로그를 수동으로 맞춰보는 상황
분산 추적 없이 10개 서비스 로그를 수동으로 맞추는 작업은 긴급 대응 시간을 크게 늘린다

모놀리스로 재통합하는 단계별 접근

팀은 "빅뱅 마이그레이션"을 피하기로 했다. 모든 서비스를 한 번에 합치면 너무 큰 위험을 감수해야 했다. 대신 strangler fig 패턴을 응용해 서비스를 하나씩 모놀리스로 흡수했다.

1단계 — 모놀리스 베이스 생성: 새 레포지토리를 만들고 auth-service를 첫 번째 모듈로 이식했다. 라우팅은 API 게이트웨이를 유지하며 점진적으로 모놀리스로 전환했다.

2단계 — 의존성 낮은 서비스부터 통합: 다른 서비스에 의존받지 않는 notification-serviceaudit-service를 먼저 통합했다. 각 서비스 통합에 평균 1.5주가 걸렸다.

3단계 — 데이터베이스 통합: 각 서비스의 독립 DB를 단일 PostgreSQL 인스턴스의 스키마로 통합했다. 외래 키 제약이 복구됐고, 크로스 서비스 JOIN이 가능해졌다. Saga 패턴으로 구현했던 분산 트랜잭션 코드 3,200줄이 삭제됐다.

4단계 — 모듈형 구조 유지: 서비스를 합치되 내부 모듈 경계는 명확하게 유지했다. 각 도메인은 독립 디렉토리와 인터페이스를 가졌고, 모듈 간 직접 DB 접근을 금지했다.

모듈형 모놀리스 디렉토리 구조 (재통합 후)
src/ ├── modules/ │ ├── auth/ │ │ ├── auth.controller.ts │ │ ├── auth.service.ts │ │ ├── auth.repository.ts ← DB 접근은 여기서만 │ │ └── auth.types.ts │ ├── billing/ │ │ ├── billing.controller.ts │ │ ├── billing.service.ts │ │ ├── billing.repository.ts │ │ └── billing.types.ts │ ├── pipeline/ │ │ └── ... │ └── export/ │ └── ... ├── shared/ │ ├── database/ │ │ └── prisma.client.ts ← 단일 DB 연결 │ ├── events/ │ │ └── event-bus.ts ← 모듈 간 이벤트 (인프로세스) │ └── middleware/ │ └── auth.middleware.ts └── app.module.ts

마이그레이션 중 팀이 꼽은 실수는 크게 두 가지였다.

첫째, DB 통합을 너무 빨리 시도했다. 처음에는 코드 통합보다 DB 통합을 먼저 시작했다가, 서비스별 스키마 충돌로 2주를 낭비했다. 순서가 중요하다 — 코드를 먼저 같은 프로세스로 올린 뒤, 데이터는 마지막에 합쳐야 한다.

둘째, 이벤트 기반 통신을 인프로세스 이벤트로 교체하는 시점을 놓쳤다. 일부 팀원이 Kafka를 그대로 유지하자고 주장해 모놀리스 안에서도 Kafka를 통해 같은 프로세스 내부 서비스가 통신하는 기이한 구조가 3개월간 유지됐다. 결국 인프로세스 이벤트 버스로 교체했고, 이 단계에서 메시지 브로커 운영 비용 월 $380이 절감됐다.

롤백 6개월 후 — 측정 가능한 변화

이 팀은 모놀리스로 완전 전환한 시점을 기준으로 6개월간 주요 지표를 추적했다. 결과는 예상보다 명확했다.

마이크로서비스에서 모놀리스 전환 후 지표 변화 — 배포 시간, MTTR, 인프라 비용 개선
6개월 추적 결과: 배포 시간 68% 단축, MTTR 79% 단축, 인프라 비용 61% 절감

마이크로서비스가 맞는 팀과 모놀리스가 맞는 팀

이 팀의 CTO는 회고 글에서 자신들의 실수를 이렇게 정리했다: "우리는 마이크로서비스가 좋은 아키텍처라는 걸 알았지만, 우리 팀에게 좋은 아키텍처인지는 몰랐다."

마이크로서비스가 실제로 가치를 내는 조건은 구체적이다:

  • 팀 규모: 서비스당 전담팀("two-pizza rule")이 존재할 정도의 규모. 서비스 10개에 엔지니어 8명이면 팀당 담당 서비스가 1개가 넘는다.
  • 독립 스케일링 필요: 서비스마다 트래픽 패턴이 확실히 다를 때. 모든 서비스가 같은 리소스를 소비한다면 분리 효과가 없다.
  • 독립 배포 실익: 하루에 서비스별로 수십 번씩 배포가 일어날 때. 주 2회 배포 조직이라면 마이크로서비스의 배포 독립성은 거의 의미 없다.
  • 기술 스택 다양성: 각 서비스에 다른 언어/런타임이 필요한 경우. 모두 Node.js + PostgreSQL이라면 분리의 기술적 이유가 약하다.
아키텍처 선택 자가진단 체크리스트

모놀리스가 더 나은 경우:
□ 엔지니어 10명 이하
□ 제품-시장 적합성(PMF) 탐색 단계
□ 서비스 간 데이터가 자주 함께 조회됨
□ 하나의 팀이 전체 백엔드를 담당
□ 주 배포 횟수가 개 단위

마이크로서비스가 더 나은 경우:
□ 팀별 독립 배포 주기가 매일 발생
□ 서비스별 스케일링 요구사항이 10배 이상 차이
□ 서비스마다 다른 언어/런타임 필요
□ 조직이 콘웨이 법칙에 맞게 팀이 나뉘어 있음
□ 특정 서비스의 장애가 전체 다운타임이 되면 안 되는 경우
마이크로서비스모놀리스아키텍처 설계스타트업 아키텍처분산 트랜잭션Saga 패턴모듈형 모놀리스strangler figNestJS백엔드 설계

관련 포스트

마이크로서비스 vs 모놀리스 — 현실적 선택 기준2026-03-11NestJS 실전 튜토리얼 — 모듈 설계부터 JWT 인증, Swagger 문서화까지2026-04-04PostgreSQL 커넥션 풀 고갈로 서비스가 멈춘 새벽 3시 — PgBouncer 도입과 연결 관리 재설계 실전 기록2026-04-21pgvector vs Pinecone vs Qdrant — 벡터 데이터베이스 실전 비교 20262026-04-17