학습 차원에서 틈틈이 해외 전문가들이 블로그나 미디어 그리고 책에서 쓴 글을 정리하고 있습니다. 이번 포스팅도 그중 하나고요. 거칠고 오역된 부분이 있을 수 있습니다. 제대로 번역되지 않은 부분은 확인주시면 반영토록 하겠습니다. 이번 글은 알렉산더 카인즈가 더뉴스택에 마이크로서비스와 모노리식 아키텍처에 대해 비교한 글을 정리한 것입니다.
소위 모노리식 아키텍처에 대한 대응으로 선보인 마이크로서비스 아키텍처는 비즈니스 프로세스를 여러 독립적인 서비스들로 분리한다.
비행기 티겟 예약 맥락을 예로 들면 모노리식 접근은, 티켓을 예약하는 프로세스를 가진 단일 소프트웨어를 개발하는 것을 포함하고 있다. 티켓 예약은 많은 개별 프로세스들을 포함한다. 아마도 비행기 티겟을 예약하는 고객 신용카드로 비용을 청구하고 티켓이 성공적으로 예약되면 고객들에게 확신을 보내준다.
마이크로서비스 아키텍처에서 개별 프로세스들은 독립적인 서비스들로 분리된다. 위의 예에서, 서비스들은 티켓 예약, 카드 결제, 확인 등이 될 수 있다. 이제 독립적인 서비스들은 사전에 정의된 인터페이스들을 통해 서로 커뮤니케이션한다.
마이크로 서비스 아키텍처가 주류가 됐다고 말하는 것은 과소 평가일 것이다. 내가 보는 뉴스피드에서 나는 모노리식 아키텍처에 대한 것들을 더 이상 보지 못하고 있다. 마이크로서비스에서 모노리식으로 돌아갔다는 기사만 보고 있다. 이번 기사에서 우리는 마이크로서비스와 모노리식을 서로 맞붙게 할 것이다.
두 소프트웨어 아키텍처 스타일들이 링으로 들어간다. 하나가 승리자로 남을 것이다.
라운드1: 레이턴시
마이크로서비스들이 돌아가는 것에 대한 근본적인 물리학의 법칙들이 있다. 한 마이크로서비스가 또 다른 서비스를 네트워크에서 호출할 때, 바이트(bytes)가 네트워크상에서 보내진다. 이것은 바이트를 전자 신호나 파장화된 빛(pulsed light)으로 바꾸고 다시 이들 신호를 바이트로 바꾸는 것도 포함하고 있다. 이 연결에 따르면 마이크로서비스 호출 시 레이턴시는 최소 24밀리세컨드다. 실제 처리가 100밀리세컨드가 걸린다고 가정하면, 그때 전체 처리 시간은, 아래처럼 보일 것이다.
이상적으로 모든 호출 실행은 동시에 일어날 수 있고 서로에게 의존하지 않는다. 이것은, 팬아웃(fan-out) 패턴으로 불린다. 아래는 점점 호출이 동시에 실행되면서 어떻게 전체 시간이 줄어드는지 보여주는 다이어그램이다.
보다 많은 호출을 동시에 실행하는 것은, 전체 실행 시간이 내려간다는 것을 의미한다. 모든 호출을 병렬로 실행하는 것은 가장 긴 호출이 끝난 후에 이 서비스가 소비자에게 돌아간다는 것을 의미한다. 한 모노리스는 네트워크 레이턴시가 없다. 모든 호출이 로컬에 있기 때문이다. 완벽하게 병렬화된 세계에서 조차 모노리스는 여전히 빠를 것이다. 이 문제를 해결하기 위한 방법은 팬아웃을 사용해 호출 체인(call chain) 길이를 줄이는 것이다. 그리고 데이터를 가능한 로컬에 두는 것이다. 그러나 결국 마이크로서비스들은 레이턴시에 관해 물리학을 뒤집을 수는 없다. 이것은 모노리스에 분명한 승리다.
라운드2: 복잡성
복잡성을 고려할 때 많은 요인들이 있다. 개발 복잡성과 소프트웨어를 돌리는 것의 복잡성이다. 개발 복잡성 측면에서 마이크로서비스 기반 소프트웨어를 개발할 때 코드 베이스 크기는 빠르게 늘어날 수 있다. 다양한 프레임워크와 심지어 다양한 언어까지 사용한 복수의 소스코드가 포함된다.
마이크로서비스들은 서로 독립적일 필요가 있기 때문에, 종종 코드 중복이 있을 수 있다. 또한 다양한 서비스들은 다양한 버전의 라이브러리들을 사용할 수 있다. 릴리즈 일정이 동기화돼 있지 않기 때문이다.
가동 및 모니터링 측면에서 영향을 받는 많은 서비스들은 매우 밀접하게 관련돼 있다.
모노리스는 단지, 그 자체로 대화한다. 이것은 하나의 잠재적인 파트너를 프로세싱 플로우에 갖고 있다는 것을 의미한다. 마이크로 서비스 아키텍처에서 단일 호출은 복수 서비스들에 영향을 미칠 수 있다. 이들 서비스는 다양한 서버들에 있을 수 있다. 심지어 지리적으로도 다양한 위치에 있을 수 있다.
모노리스에서, 로깅(logging)은 단일 로그 파일을 보는 것 만큼 간단하다. 그러나 마이크로서비스에서 이슈를 기록하는 것은, 복수 로그 파일을 체크하는 것을 포함할 수 있다. 이것은 관련돼 있는 모든 로그 아웃풋을 찾아야할 필요 뿐만 아니라, 그것들을 올바른 순서로 둬야 한다는 것을 의미한다. 마이크로서비스는 고유한 아이디 또는 스팬(Span)을 각각의 호출을 위해 사용한다. 이것은, 엘라스틱서치같은 툴들이 모든 관련 로그 아웃풋을 서비스들에 걸쳐 찾을 수 있게 해준다. 재거(Jaeger)같은 툴들은 복수의 마이크로서비스들에 거쳐 호출을 추적하고 정리할 수 있다.
한 쿠버네티스 클러스터에서 마이크로서비스들을 돌릴 때, 복잡성은 더욱 증가한다. 쿠버네티스는 오토 스케일링 같은 역량을 가능하게 하지만 관리하기 쉬운 시스템은 아니다. 모노리스를 배치하려면 간단한 복사 작업(copy operation)이면 충분할 수 있다. 모노리스를 시작하고 멈추려면 종종 간단한 명령이면 충분하다. 반면 쿠버네티스는 그렇게 하기 어렵다(faint of heart).
트랜잭션들은 마이크로서비스 아키텍처들을 돌릴 때 모노리스와 비교해 복잡성을 추가할 수 있다. 서비스 국경(service borders)들에 걸쳐 데이터가 동기화된다고 보장하기 어렵다. 예를 들어 나쁘게 구현된 호출 재시도(call retry)는 결제를 두 번 실행할 수 있다. 마이크로서비스 아키텍처는 센트럴 코디네이터(central coordinator) 같은 기술을 사용해 이것을 관리할 수 있다.
그러나 모노리식 아키텍처에서 트랜잭션들은 다루기 쉽다. 개발자들에 조차도 투명하다. 복잡성 측면에서 또 다른 승리가 모노리스에 돌아간다.
라운드3: 신뢰성
모노리스에서 모든 호출들은 로컬이다. 네트워크 실패 가능성이 없다. 이것을 마이크로서비스들과 비교해보자. 한 마이크로서비스가 네트워크에서 또 다른 서비스를 99.9% 신뢰성으로 호출한다고 가정해보자. 이것은 1000번 호출 중 하나가 네트워크 문제들로 인해 실패할 것이라는 것을 의미한다. 이제 이 서비스가 또 다른 서비스를 호출한다면 우리는 신뢰성 99.8%를 얻는다.10개 호출 깊이를 가진 한 호출 체인이면 신뢰성은 99% 신뢰성까지 내려간다. 이것은 100개 호출중 한번은 실패한다는 것을 의미한다.
마이크로서비스 아키텍처를 디자인할 때, 네트워크가 어떤 지점에서 깨질 것이라고 가정하는 것이 중요하다. 마이크로서비스들은 이 같은 문제를 해결하기 위한 일부 솔루션들을 제공한다. 오픈소스 스프링 클라우드는 투명한 로드밸런싱과, 자바용 중단 관리를 지원한다. 이스티오(Istio) 같은 서비스 메시(Service meshes)들은 이것을 복수 언어들을 위해 이룰 수 있다. 네트워크 문제들은 마이크로서비스 아키텍처들에서 보다 자주 일어난다. 그러나 이들 문제를 해결할 수 있도록 디자인된 것으로 보인다. 이걸 기반으로 네트워크 실패가 정기적으로 일어난 후 마이크로 서비스들은 이걸 다루기 위한 능력을 증명했다. 이것은 이들 문제와 관련해 숨겨진 버그가 없다는 것을 보장한다.
한 서비스가 마이크로서비스 클러스터에서 중단될 때, 클러스터 매니저는 단지 대체재를 가져올 것이다. 이것은 마이크로서비스 아키텍처를 보다 회복 탄력성이 있도록 만든다. 넷플릭스는 임의로 가상 머신과 컨테이너들을 종료시키는 카오스 몽키( Chaos Monkey)로 불리는 툴을 만들었다.
이 방법은 중단을 프로덕션 환경에서 다룰 수 있다는 것을 보장한다. 모노리스는 물론 한 클러스터에서 규모있게 운영될 수 있다. 그러나 그들의 크기 때문에 문제는 모노리스에 보다 크게 충격을 가할 수 있다. 되살릴 수 있다는 것을 보장하기 위해 의의로 모노리스를 다시 시작하는 것을 상상하는 것은 어렵다. 반면 가장 신뢰할 수 있는 소프트웨어는 모노리스다. 산업 컨트롤러들과 항공 비행 통제가 그 예다. 매우 신뢰할 수 있는 모노리스를 개발하는 것은 분명히 가능하다. 그러나 규모가 커질 때 그리고 클라우드에선 힘들어진다.
결국 마이크로서비스가 승리자다.
라운드4: 자원 사용
한 마이크로서비스 호출이 같은 알고리즘으로 같은 일을 한다면 이것은 항상 모노리스보다 많은 자원을 사용할 것이다. 도커와 가상 머신은 오버헤드를 추가한다. 또 다른 벤치마크는 도커 컨테이너를 사용해 돌일 때 연결의 수가 8% 줄어든다는 것을 발견했다. 이미지 오케스트레이션 또한 자원을 소비할 것이다. 로그 수집과 모니터링 때문일 것이다.
그러나 마이크로서비스들은 우리가 자원을 영리하게 사용하도록 해준다. 클러스터 매니저는 자원을 필요한 만큼 할당할 수 있다. 실제 자원 활용은 많이 낮아질 수 있다. 20% 코드에서 80%의 작업을 하는 모노리스를 보자. 우리가 독립적으로 코드의 '핫'(hot)한 부분을 확장한다면, 무슨일이 벌어지는지 보여줄 수 있다. 예를 들어 한 모노리스에서 한 인스턴스가 8GB를 사용한다면 2개 인스턴스는 16GB를 사용한다. 헤비 리프팅(heavy lifting: 수요가 몰리는)을 수행하기 위해 20 %가 병렬로 실행할 수 있다고 생각해 보자.
8GB의 인스턴스 하나와 램 사용량의 20 %가 1.6GB 인 마이크로서비스가 있다. 이것은 두 인스턴스에서 9.6GB 램을 사용하는 것을 의미한다. 아래 다이어그램은 리소스 사용량의 차이를 보여준다.
모노리스는 극단적인 경우들에서 마이크로서비스보다 성능이 뛰어나다. 예를 들면 한 호출은 대용량 데이터를 전송한다. 그러나 대부분의 시나리오에서 자원 사용은 낮다. 이것은 마이크로서비스에 승리다.
라운드5: 확장성
모노리스를 확장할 수 있는 방법들이 있다. 하나는, 복수의 인스턴스를 돌리고 요청을 거기에 맞게 배분하는 것이다. 아니면 복수의 스레드를 돌리거나 비 차단 IO를 사용할 수 있다.
마이크로서비스 아키텍처에서도 이들 3가지 모두 사실이다. 그러나, 자원 사용에서 살펴본 것처럼 마이크로서비스들은 적은 자원으로 할 수 있다. 이것은 리소스당 보다 많은 연결이 다뤄질 수 있다는 것을 의미한다. 자원에 쓰이는 비용 측면에서 마이크로서비스는 보다 많은 스루풋을 전달한다. 보다 정교한 확장도 가능하다. 한 모노리스가 모든 자원을 사용한다면 보다 많은 연결을 다루는 방법은, 두번째 인스턴스를 가져오는 것이다. 한 싱글 마이크로서비스가 모든 자원을 사용한다면 단지 이 서비스는 보다 많은 인스턴스를 필요로 할 것이다.
마이크로서비스는 덜 자원 집중적이다. 이 때문에 이것은 자원을 절감한다. 확장은 쉽고 정교하다. 이것은 단지 필요한 양의 자원이 사용되고 있다는 것을 의미한다. 관리자들은 AWS나 다른 클라우드 공급 업체들을 필요한 만큼 온라인이나 오프라인에 가져올 수 있다.
예를 들면, 한 모노리스가 최대 아마존 웹서비스 인스턴스에서 지금 돌아가고 있다고 가정해보자. m5.24xlarge 인스턴스는 엄청 큰 96 CPU와 384 GB 램을 제공한다. 이 순간 비용 또한 엄청나다. 24/7로 돌아가는 인스턴스용으로 한 달에 2119.19달러다. 같은 돈으로 8개 버추얼 CPU와 16GB 랩을 가진 24/7로 돌아가는 12개 c5.2xlarge 인스턴스를 살 수 있다.
그러나 대부분의 워크로드는 24시간 1주일 내내 풀로 자원 사용을 요구하지 않는다. 대신에 특정 기간에 자원 사용 피크가 있다. 나머지 시간에는 낮다. 아래는 이들 12개 소형 인스턴스가 24/7 대신에 특정 시간에 돌아간다면 비용이 얼마인지를 보여준다.
시간당 부과되는 것보다 전용 자원이 저렴하기 때문에 싱글 인스턴스가 저렴해지는 지점이 있다. 이 사례에서 크로스 오버 지점은 520시간이다. 대략 이 인스턴스의 70%가 실행 중인 경우다.
12개 온디맨스 마이크로서비스 컨테이너 가격을 한 전용 대규모 모노로식 컨테이너와비교해보라. 마이크로서비스 아키텍처는 보다 세분화돼 있다. 개별 서비스를 확장하는 것 또한 세분화된다. 정교한 확장과 보다 나은 자원 사용은 마이크로서비스에 분명한 승리다.
라운드6: 스루풋
한가지 더 성능 지표를 살펴보자. 절대적인 스루풋이다. 우리는 이미 네트워크 레이턴시와 병렬화 간 관계에 대해 살펴봤다. 스루풋에서도 같은 관계가 적용된다. 네트워크들에 걸쳐 동시에 돌아갈 수 없는 워크로드들에서 모노리스는 나은 성능을 제공할 수 있다. 데이터는 서비스들 사이로 보내질 필요가 있다. 그리고 인프라는 특정 오버헤드를 유발한다. 워크로드가 복수 인스턴스들로 확장될 수 없다면 모노리스는 높은 스루풋을 제공할 수 있다. 대단히 로컬화된 워크로드와 컨테이너로 인해 오버헤드가 없기 때문에 컨테이너 오케스트레이션 또는 서비스 메시는, 모노리스에 득점이다.
라운드7: 타임 투 마켓
사람들이 마이크로서비스 아키텍처를 선택하는 것으로 언급하는 이유들 중 하나는 타임투마켓이다. 이것은 어떤 기능을 위한 비즈니스적인 의사 결정을 하는 것에서부터 이 기능이 공개적으로 이용될 수 있을 때까지의 시간이다. 그들의 크기와 의존성으로 인해 모노리스는 일반적으로 배치가 어렵다. 반면 마이크로서비스를 배치하는 것은 종종 또는 계속해서 쉽다.
한가지 이유는 변화의 영향이 대단히 로컬 화 된다는 것이다. 마이크로서비스는 단지 구현이 아니라 인터페이스를 노출해야 한다. 이것은 개발자들이 서비스들에 의존하는 수정 없이 구현을 바꿀 수 있다는 것을 의미한다. 이 인터페이스는 명쾌하고 버전화될 수 있기 때문에 어떠한 변화도 분명하게 정의된 영향을 갖는다. 모노리스 입장에서 객체 지향 프로그래밍은 구현체 인터페이스의 분리를 가능케 한다. 그러나 이것은 대단히 규율이 갖춰진 팀을 필요로 한다.
나아가 마이크로서비스는 테스트하기 쉽다. 마이크로서비스는 단지 제한된 기능 세트만 커버하기 때문에, 의존성의 양 또한 적다. 이것은 마이크로서비스들의 끊김 없는 롤아웃(rollouts)을 가능하게 한다.
마이크로서비스들은, 클러스터 노드들 일부에 제공될 수 있다. 사용자는 그때 새로운 버전으로 성공적으로 옮길 수 있다. 다른 마이그레이션 전략은 구형 또는 새로운 버전을 동시에 돌리는 것도 포함한다. 이것은 올드 버전으로 빠른 롤백(rollback)을 가능하게 한다. 거기에 문제가 있다면 정교한 마이크로서비스 아키텍처는 빠르고 보다 완성하게 롤아웃을 가능하게 한다. 이것은 프로덕션 배치에 대한 아이디어 시간을 줄여준다.
승리는 마이크로서비스에 돌아간다.
라운드8: 커뮤니케이션
마이크로서비스는 종종 개발팀 규모로 규정된다. 예를 들어 아마존은 투피자 룰(two-pizza rule)을 갖고 있다. 한 개발팀은 피자 두 개로 채울 수 있다. 여기에 담긴 아이디어는 커뮤니케이션은 팀에 제약을 받는다는 것이다. 팀들은 단지 서비스 인터페이스를 통해 커뮤니케이션할 필요가 있다.
마이크로서비스 아이디어가 나오기 오래전에, 프레드 브룩스는 맨먼스 미신(The Mythical Man Month)이라는 책을 썼다. 이 책의 핵심 메시지들 중 하나는 커뮤니케이션 채널들의 수는 팀 멤버들의 수를 증가시킨다는 것이다. 두 사람으로 이뤄진 팀은 하나의 커뮤니케이션 채널만 있다. 4명이면 6개 채널로 늘어난다. 사람1은 사람2, 3, 4와 대화한다. 사람2는 사람3, 4와 대화한다. 그리고 사람3은 사람4와 대화한다. 커뮤니케이션 채널을 위한 이 공식은 n(n − 1) / 2 다.
20명 개발자를 가진 팀은 190개 커뮤니케이션 채널을 갖는다. 이들 개발자들을 2 피자 팀으로 흐트러뜨리는 것은 커뮤니케이션 채널 수를 상당히 줄여준다.
20명으로 된 개발팀을 사례를 들고 이 팀을 4개 마이크로서비스 팀으로 4명씩 분리한다면 우리는 팀당 10개 커뮤니케이션 채널을 갖게 된다. 4개 팀 간 커뮤니케이션 채널 수는 단지 6개다. 전체 커뮤니케이션 채널 수는 46개다. 이것은 20명으로 된 팀의 4분의 1이다. 아래 플롯은 한 대규모 팀과 개별 마이크로서비스 팀 간 커뮤니케이션 채널 수를 보여준다.
대략 10명으로 된 개발팀에서 마이크로서비스 모델은 전통적인 모델에 비해 분명한 이점을 보여준다. 50명 규모 개발팀에서 커뮤니케이션 채널 수는 거의 10배 많다. 이 크기의 팀에서 일하는 모든 사람들은 많은 시간이 커뮤니케이션에 쓰이고 있다는 것을 입증하고 있다. 50명의 개발자를 가진 한 스타트업 미팅은 비효율성 훈련일 수 있다. 10명 이상인 어떤 개발 프로젝트도 그것을 작은 팀으로 쪼개면 잘 지원될 수 있다. 마이크로서비스는 여기에 이상적이다. 서비스 국경들과 함께 잘 정의된 인터페이스를 갖고 있기 때문이다.
마이크로서비스를 위한 또 다른 분명한 승리다.
누가 승자인가?
결과는 모노리스가 승리 2개, 마이크로서비스는 3개다. 이 차트를 볼 때 명심하라. 이것은 상대적이다. 예를 들어 마이크로서비스는 단지 팀 크기가 10명 이상일 때 승리한다. 5명 개발자를 가진 작은 스타트업에선 모노리스에 의해 더 잘 지원받을 수 있다.
모노리스는 덜 움직이는 부품들을 갖고 있으면 관리하기 쉽다. 전담 소규모 팀은 데일리 릴리즈 스케줄을 돌릴 수 있다. 확장은 단지 특정 사용량 이상일 때만 관련이 있다. 제품이 초당 몇 번의 히트만 갖는다면 모노리스가 완벽하게 충분할 수 있다. 호출이, 대용량 데이터를 네트워크상에서 움직인다면 성능 충격을 상당할 수 있고 다른 이점들보다 클 수 있다.
마이크로서비스는 모든 개발 문제들에 대해 만병통치약이 아니다. 아래 마이크로서비스 아키텍처가 잘 맞을 수 있는 몇 가지 표시들이 있다.
24/7 신뢰성이 요구된다(24/7 reliability required)
몇몇 요청을 넘어서는 확장(Scale to beyond a few requests)
피크와 일반 로드에 상당한 차이(Peak and normal load are significantly different)
10명 이상의 개발자(More than 10 developers)
비즈니스 도메인이 보다 작은 도메인들로 나눠질 수 있다(The business domain can be divided into smaller domains)
수명이 짧은 운영(Shorter lived operations)
운영은 REST 호출이나 queue 이벤트로 표현될 수 있다. (Operations can be expressed as REST calls or queue events)
엄격한 크로스 서비스 거래 요구사항이 없다(No strict cross-service transactional requirements)
결국 마이크로서비스는 많은 엔터프라이즈 소프트웨어 프로젝트들에서 강력한 선택이 될 것이다.