아키텍처 스타일
계층형부터 마이크로서비스까지 대표적인 아키텍처 스타일이 무엇을 얻고 무엇을 내주는지 비교한다.
목차 (7)
스타일의 두 갈래: 한 덩어리인가, 네트워크로 나뉘었나
아키텍처 스타일은 시스템의 큰 모양에 붙은 이름이다. "계층형"이라고 말하면 듣는 사람은 계층이 몇 개 있고 위에서 아래로 부른다는 것, 그래서 무엇이 쉽고 무엇이 어려운지까지 한 번에 떠올린다. 스타일은 처음부터 고르는 정답이 아니라 이런 말의 압축이다. 이 노트는 리처즈·포드의 Fundamentals of Software Architecture가 다루는 스타일 목록을 따라가되, 각 스타일의 장단점은 공개 자료와 이 노트의 판단으로 쓴다.
단일 배포(모놀리식) 스타일은 시스템 전체가 하나로 빌드되고 배포된다. 부품 사이 호출은 함수 호출이라 빠르고 실패하지 않으며, 트랜잭션 하나로 여러 테이블을 함께 바꿀 수 있다. 분산 스타일은 여러 배포 단위가 네트워크로 말한다. 부분별로 따로 늘리고 배포할 수 있지만, 그 대가로 네트워크가 가진 모든 문제를 떠안는다. 어느 쪽이 낫다기보다, 어떤 아키텍처 특성이 필요한지에 따라 고른다(아키텍처 특성과 결정 노트).
단일 배포 스타일
- 계층형(layered): 화면, 업무 규칙, 영속성, 데이터베이스처럼 기술 역할로 계층을 나누고 위에서 아래로만 부른다. 가장 익숙하고 시작하기 쉽다. 각 계층이 바로 아래만 부르게 막아 두면(닫힌 계층) 한 계층을 바꿔도 위로 번지지 않는다. 약점은 업무 변경이 모든 계층을 가로지른다는 것과, 많은 요청이 하는 일 없이 계층을 그냥 통과만 하게 되기 쉽다는 것이다.
- 파이프라인(pipeline, pipes and filters): 데이터를 한 방향으로 흘리며 단계마다 한 가지 변환만 한다. 유닉스의
cat log | grep ERROR | sort가 원형이고, ETL과 로그 처리가 흔한 예다. 단계를 끼우고 빼기 쉽지만, 사용자와 주고받는 대화형 기능에는 맞지 않는다. - 마이크로커널(microkernel, 플러그인 아키텍처): 작은 핵심 시스템에 기능을 플러그인으로 붙인다. 코드 편집기와 브라우저의 확장 기능, 보험 상품마다 다른 계산 규칙을 끼우는 업무 시스템이 이 모양이다. 핵심이 플러그인 계약을 얼마나 잘 정했는지가 전부다.
- 모듈러 모놀리스(modular monolith): 배포는 하나지만 안을 도메인 모듈로 나누고, 모듈은 공개된 인터페이스로만 서로 부르며 자기 테이블만 만진다. 쇼피파이가 거대한 레일스 앱을 서비스로 쪼개는 대신 이 길을 고른 이야기가 Deconstructing the Monolith에 있다. 경계가 잘 지켜지면 나중에 모듈 하나를 서비스로 떼어 내기도 쉽다.
단일 배포의 공통 약점은 퀀텀이 하나라는 점이다(모듈성과 결합 노트). 일부만 바빠도 전체를 늘려야 하고, 한 곳의 메모리 누수가 전체를 멈추며, 작은 수정도 전체를 다시 배포한다. 코드가 커질수록 빌드와 테스트도 느려진다.
분산 컴퓨팅의 오류
분산 스타일로 가기 전에 알아 둘 목록이 있다. 1990년대 선 마이크로시스템즈에서 정리된 분산 컴퓨팅의 여덟 가지 오류다. 빌 조이와 데이브 라이언이 먼저 넷을 꼽았고, 피터 도이치가 1994년에 일곱으로 정리했으며, 제임스 고슬링이 마지막 하나를 더했다(위키백과 정리). 처음 분산 시스템을 만드는 사람이 무심코 믿는 것들이다.
예컨대 화면 하나를 그리려고 서비스 열 개를 차례로 부르면, 호출마다 몇 밀리초씩 붙어 함수 호출 때와는 비교할 수 없이 느려진다. 그중 하나라도 응답이 없으면 화면 전체가 멈추지 않도록 시간 제한과 재시도를 걸어야 하고, 재시도하려면 같은 요청을 두 번 받아도 괜찮게(멱등) 만들어야 한다. 아르논 로템갈오즈의 해설 Fallacies of Distributed Computing Explained가 각 오류가 실제로 어떤 사고로 이어지는지 예를 든다. 시간 제한, 재시도, 서킷 브레이커 같은 부품 자체는 설계의 부품 노트에 있다.
서비스 스타일: 서비스 기반, SOA, 마이크로서비스
- 서비스 기반(service-based): 도메인별로 굵게 나눈 서비스 몇 개(보통 열 개 안팎)가 데이터베이스를 함께 쓴다. 분산의 이점(따로 배포, 장애 격리)을 일부 얻으면서 트랜잭션과 데이터는 단일 배포처럼 다룰 수 있어, 모놀리스에서 처음 나눌 때 현실적인 중간 지점이다.
- 오케스트레이션 SOA: 2000년대 기업 환경에서 유행한 서비스 지향 아키텍처다. 재사용을 최우선으로 해서 업무 서비스, 기업 공용 서비스 같은 기술적 층으로 나누고, 그 사이를 엔터프라이즈 서비스 버스(ESB)와 오케스트레이션 엔진이 잇는다. 재사용을 위해 공용 서비스를 모두가 부르다 보니 결합이 심해졌고, 중앙 엔진이 모든 흐름을 쥐어 작은 변경도 여러 팀의 조율이 필요했다. 지금은 주로 피해야 할 교훈으로 이야기된다.
- 마이크로서비스: 업무 경계(도메인 주도 설계의 경계 맥락)마다 작은 서비스를 두고, 서비스마다 자기 데이터를 가지며, 따로 배포한다. 제임스 루이스와 마틴 파울러가 2014년 글 Microservices에서 그 특징을 정리했다. SOA와 반대로 재사용보다 독립을 고른다. 코드를 조금 중복하더라도 서비스끼리 엮이지 않게 한다. 대가는 분산 트랜잭션, 데이터 일관성, 관측, 배포 자동화 같은 운영 부담이다. 패턴 목록은 microservices.io에 잘 모여 있다.
파울러는 MonolithFirst에서 처음부터 마이크로서비스로 시작해 성공한 사례는 드물고, 모놀리스로 시작해 경계를 확인한 뒤 나누는 편이 안전하다고 썼다. 경계를 잘못 그은 마이크로서비스는 고치기가 모놀리스보다 훨씬 어렵기 때문이다. 어떻게 나누고 데이터를 어떻게 쪼개는지는 분해와 분산 데이터 노트에서 다룬다.
이벤트 기반과 공간 기반
이벤트 기반(event-driven) 아키텍처에서는 서비스가 서로를 부르지 않고, "주문됨" 같은 일어난 사실(이벤트)을 내보내고 관심 있는 쪽이 반응한다. 이벤트를 내는 쪽은 누가 듣는지 모르므로 결합이 느슨하고, 반응하는 쪽을 더하기 쉽다. 파울러의 What do you mean by "Event-Driven"?은 이 말이 실제로는 서로 다른 네 패턴(이벤트 알림, 상태를 실은 이벤트, 이벤트 소싱, CQRS)을 가리킨다고 짚는다. 흐름을 어떻게 짜느냐에 따라 두 토폴로지로 나뉜다.
브로커 방식은 확장과 응답성이 좋지만, 전체 흐름이 코드 어디에도 한곳에 적혀 있지 않아 "주문이 왜 배송까지 안 갔지?"를 추적하기 어렵다. 오류가 나도 누가 되돌릴지 정하기 어렵다. 중재자 방식은 흐름과 오류 처리가 한곳에 보이는 대신 중재자가 병목과 결합의 중심이 된다. 큐와 스트림 자체는 배치와 스트림 처리 노트에 있다.
공간 기반(space-based) 아키텍처는 극단적인 트래픽 급증을 노린다. 공연 예매 시작이나 한정 판매처럼 평소의 수백 배가 순간에 몰리면, 아무리 앱 서버를 늘려도 결국 데이터베이스가 병목이 된다. 그래서 데이터베이스를 요청 경로에서 빼 버린다. 처리 장치마다 메모리 데이터 격자를 두고 서로 복제하며, 데이터베이스 쓰기는 큐를 거쳐 나중에 비동기로 한다. 이름은 여러 프로세스가 함께 쓰는 공유 메모리 공간(튜플 공간)이라는 옛 개념에서 왔다.
비교: 이 노트의 판단
아래 표와 그림은 이 노트가 공개 자료와 경험칙으로 매긴 거친 판단이다. 같은 스타일도 구현에 따라 크게 달라지므로 점수가 아니라 대화의 출발점으로 쓴다.
| 스타일 | 배포 단위 | 데이터 | 잘하는 것 | 내주는 것 |
|---|---|---|---|---|
| 계층형 | 하나 | DB 하나 | 단순함, 낮은 비용, 익숙함 | 규모와 변경이 커지면 경직된다 |
| 파이프라인 | 하나 | 단계 사이로 흘림 | 단계별 재사용, 일괄 처리 | 대화형 기능, 부분 확장 |
| 마이크로커널 | 하나 | 핵심 + 플러그인별 | 기능을 끼우고 빼기 | 핵심 계약을 바꾸기 어렵다 |
| 모듈러 모놀리스 | 하나 | 모듈별 스키마 | 도메인 경계, 나중에 나누기 쉬움 | 경계를 지키는 규율이 필요, 확장은 통째로 |
| 서비스 기반 | 몇 개 | 대개 공유 | 분산 이점과 단순함의 균형 | 공유 DB가 결합 지점으로 남는다 |
| 이벤트 기반 | 여럿 | 서비스별 | 응답성, 확장, 반응하는 쪽 추가 | 흐름 추적, 오류 처리, 일관성 |
| 공간 기반 | 여럿 | 메모리 격자 + 비동기 DB | 극단적 급증과 탄력성 | 복잡하고 비싸며 데이터가 어긋날 수 있다 |
| 오케스트레이션 SOA | 여럿 | 대개 공유 | 기업 공용 기능 재사용 | 강한 결합, 중앙 엔진 병목, 느린 변경 |
| 마이크로서비스 | 아주 많음 | 서비스별 | 독립 배포, 부분 확장, 장애 격리 | 운영 부담, 분산 트랜잭션, 네트워크 비용 |
어떻게 고르나
스타일 고르기도 트레이드오프 분석이다. 출발점은 유행이 아니라 이 시스템이 꼭 잘해야 하는 특성 서너 개, 그리고 그 특성이 시스템 전체에 같은지 부분마다 다른지다. 전체가 같은 특성을 가져도 되면 퀀텀 하나로 충분하므로 단일 배포가 가장 싸다. 부분마다 확장 규모, 가용성, 배포 주기가 크게 다르면 그때 퀀텀을 나눌 이유가 생긴다.
기술 외의 조건도 크게 작용한다. 팀이 하나라면 마이크로서비스의 이점(팀별 독립 배포)을 누릴 사람이 없다. 관측 도구, 자동 배포, 장애 대응 체계가 없다면 분산 시스템은 장애를 찾는 데만 며칠이 걸린다. 데이터가 강하게 엮여 있고 즉시 일관성이 꼭 필요하면 분산의 비용이 커진다. 그리고 어떤 스타일을 고르든, 고른 이유와 그때의 불확실성을 ADR로 남겨 두고, 조건이 바뀌면 다시 묻는다.
- Microservices — Martin Fowler
- Microservice Architecture pattern — microservices.io
- MonolithFirst — Martin Fowler
- What do you mean by "Event-Driven"? — Martin Fowler
- Fallacies of distributed computing — Wikipedia
- Fallacies of Distributed Computing Explained — Arnon Rotem-Gal-Oz
- Deconstructing the Monolith — Shopify Engineering
- Fundamentals of Software Architecture — Richards·Ford (책 소개)