마이크로서비스 도입 이렇게 한다
마이크로서비스 이렇게 한다
책 소개
샘 뉴먼의 두 번째 마이크로서비스 책인데, 첫 책은 마이크로서비스의 개념, 필요성과 적용 패턴 등이 소개되었을 것 같고 몇 년 후 나온 이 책은 좀 더 실용적인 부분에 집중하여 소개하고 있을 것 같다. 역자는 박재호 님, 케누님과 유튜브 채널을 운영하고 계시는 그분 맞다.
마이크로서비스는 '마이크로' '서비스' 라고 쓰지 않고 '마이크로서비스' 라고 쓴다. 이 부분도 주의할 부분.
소프트웨어 엔지니어링에 관련된 용어들이 많이 그렇지만 해당 단어의 의미를 일반적인 의미로 해석하면 안 된다. 흔하게 사용되는 용어더라도 이들은 고유 명사로 이해하고 원래 의미를 파악해야 한다. 이게 번역의 어려움이기도 한데 'Prodcudual Programming' 을 절차적 프로그래밍이라고 이해하면 안 된다는 것이다. 프로시줠 프로그래밍의 의미를 알아야 한다.
1장: 더도 덜도 아닌 마이크로서비스
정의
마이크로서비스는 비즈니스 도메인 중심으로 모델링 된 독립적인 서비스를 배포하는 것에 대한 주제를 다룬다. 즉, 배포에 대한 이야기 이다. 마이크로가 중요한 단어이긴 하지만 얼마나 마이크로 하냐는 그다음 문제인 것이다. 배포가 핵심이다.
그러면 배포 단위가 주요 주제일 텐데 하나로 배포하면 그게 모노리스; 이어서 미루어보면 모델링에 대한 내용이 중요하다는 느낌을 준다.
상호 간의 통신은 네트워크로 통신한다.
서비스 경계는 마이크로서비스의 가장 핫한 주제 중 하나; 보고 들은 경험적으로 보면 완전 케이스 바이 케이스.
독립 배포 가능성
독립 배포가 가능하려면 서비스 간 결합도가 낮아야 한다. 아니 없어야 한다. 결합도가 없어야 한다는 건 다른 서비스를 변경하지 않고 특정 서비스를 변경할 수 있어야 한다. 변경이란 것은 배포한다는 것을 말 함.
경계를 지정하는 것에 대한 고민이 벌써 깊어진다. 데이터베이스에 대한 공유는 당연한 주제라 책 후반부에 따로 언급하고 있을 것이다.
비즈니스 도메인 모델링
책에서는 CD를 판매하는 대규모 다국적 기업에 대한 예를 들고 있다. UI 개발과 백엔드 개발, 그리고 데이터베이스 운영 팀이 상호 협력한다. 전형적인 3 tier 구성이다.
그리고 콘웨이의 법칙을 들어 이 구성을 설명하고 있다.
대안으로 나온 아키텍처는 도메인 속성이 추가되었다. 기존 요구사항이 고객을 중심으로 펼쳐진다. 좋다. 그렇지만 이 구성은 좀 더 넓은 개발자의 역량을 요구한다.
데이터 소유권
공유 자원이 없을 수는 없을 것이다. 단지 어떻게 공유하고 숨길 것인가에 대한 경계가 필요하다. 모놀리스 데이터베이스를 분리하는 과정은 4장에서 따로 다룬다.
아무튼, 이렇게 인터페이스를 잘 정의하는 게 중요하고도 어렵다.
장점
독립 배포가 가능한 곳에서 오는 모든 것들이 장점이다. 하지만 공짜 점심은 없다는 점이다. 트레이드-오프를 위해 어떤 목표를 가지고 있는지 스스로 파악해야 한다.
단점
지금은 웹 서비스가 대세지만 예전, 지금도 일부 레거시 시스템들은 커다란 단일 컴퓨터에 서비스를 배포하고 서버를 운영한다. 그러면서 분산 시스템이 연구되고 도입되었는데 분산 시스템은 네트워킹을 바탕으로 설계되어 있다. 지금은 아주 빠른 네트워크 시스템이 구성되고 있지만 여전히 내부 시스템의 속도와는 비교할 수 없는 병목이 된다. 이는 트랜잭션과 안정성을 포기하게 만들고 더 많은 고민을 하게 한다.
하지만 현시점에서도 모놀리스 시스템도 멀티 티어이고 이들은 네트워크를 통해 연결되어 있다. 마이크로서비스의 단점이라고 보기 힘들다.
기타 주제
UI 문제, 마이크로서비스를 구축하기 위한 기술들, 마이크로서비스 구성의 규모 등도 주요한 주제이다.
소유권 문제
IT와 비즈니스 오너의 간극 등으로 작은 배포, 작은 PO 형태가 이상적이다.
모놀리스 검토
마이크로서비스 이전을 하기 위해 모놀리스 형태도 이해해야 한다. 단일 프로세스(프로그램/프로세스 그 프로세스이다), 분산 모놀리스, 외부 블랙박스 시스템이 모놀리스 시스템으로 간주된다.
단일 프로세스 모놀리스
단일 코드로 구성되고 단일 프로세스로 묶어 배포된다. 서비스 규모에 따라 인스턴스는 여러 벌 뜨지만 코드는 하나이다.
분산 모놀리스
여러 서비스로 구성되어 있지만 함께 배포되어야 하는 시스템이다. 결합도가 높은 아키텍처 때문에 아직 남아 있다.
블랙박스 시스템
일부 마이그레이션을 통해 분리되긴 했지만 더 이상 유지보수가 되지 않고 화석처럼 남아 사용되고 있다.
모놀리스의 문제
소유권과 경계가 복잡하고 배포 경합이 있어 서비스 운영/배포에 병목이 생긴다.
모놀리스의 장점
당연히 장점도 많다.
결합도와 응집력
응집도가 높고 결합도가 낮을 때 안정적이다. 코드와 배포 관점에서 둘은 반대로 움직이는 경향이 강하다.
응집력
함께 바뀌고 함께 머무는 코드
결합도
결합도가 높을수록 함께 변경해야 하는 내용도 많아진다. 다양한 케이스와 해법이 필요.
우선 자주 변경되는 것 - 코드나 데이터 - 과 정적인 코드와 분리한다. 추천 서비스에서 보인 예처럼 추천 서비스가 주문 서비스에 강하게 결합될 필요는 없다. API를 통해 접근하고 사용한다. 또는 적당한 공개 데이터베이스(주로 View로 구성)를 통해 제공된다.
이런 패턴은 3,4장에서 더 자세히 다룬다.
네트워크를 통한 호출이 일어나기 때문에 동기/비동기 문제가 있다. 이를 시간적 결합도가 존재한다고 말한다.
여러 모듈로 구성된 단일 프로세스의 경우 한 모듈이 수정되면 전체 모듈이 다시 배포되어야 한다. 이를 배포 결합도라고 한다. 배포는 늘 위험하다. 이 위험을 줄이는 방법은 변경할 필요가 있는 사항만 배포하는 것이다. 말은 쉽다.
배포 결합도를 줄이기 위해 Erlang 같은 솔루션을 고려해 볼 수도 있다.
마지막으로 배송 서비스 같은 도메인에서 자주 볼 수 있는 주문과 재고 관련 정보를 처리하는 과정에 발생하는 도메인 결합도를 볼 수 있다. 주문과 재고에는 고객의 신용 카드 정보가 필요 없다.
도메인 결합도를 줄이려는 경우 이벤트 주도 시스템이나 pub/sub 패턴 같은 게 도움이 된다. 도메인 주도 설계 과정에서 자세히 알아 본다.
도메인 주도 설계
도메인 주도 설계는 마이크로서비스 모델링에 적당한 해결을 제시해 준다. 에릭 에반스의 도메인 주도 설계 내용 중 마이크로서비스 아키텍처에 관한 사항만 알아보자.
집계aggregation
집계 대상이 되는 데이터는 상태를 가진다. 시각에 따라 다른 상태일 수 있다. 따라서 라이프사이클이 있다. 상태 머신으로 표현된다. 외부 시스템으로 인해 상태가 변경될 수 있는데 이를 통제하는 것은 복잡한 일이다. 또, 하나의 집계는 다른 집계 상태와 연관이 된다면 더 복잡해진다.
경계 컨텍스트
어디까지 숨기고 어디를 공개할 것인다. 명시적인 책임관계를 기준으로 잡으면 되고, 역시 케바케.
마이크로서비스 서비스 경계 매핑
어그리게이션 단위와 바운디드 컨텍스트 모두 서비스 단위가 될 수 있다.
2장: 마이그레이션 계획하기
마이크로서비스의 목표 이해
마이크로서비스는 목표가 아니다. 아키텍처 선택은 의도적 결정이어야 하고 기존 아키텍처로 이룰 수 없는 무언가를 달성하기 위한 전략이어야 한다.
왜 마이크로서비스를 선택하는가?
팀의 자율성
제대로 동작한다는 가정 하에 작은 규모로 구성하고 코드 소유권을 잘 배분하는 것도 좋은 방법이다. 중요한 건, 자율성이 높아지면 남이 도와주길 기다리지 않고 티켓 발행 같은 일을 처리할 필요가 없어진다는 점이다.
출시 시간 단축
요구사항을 반영하고 배포하는데 필요한 태스크를 병렬 수행하거나 자동화하는 구간을 늘리는 방법이 필요하다. 이런 작업들을 반복하다보면 마이크로서비스가 필요한 시점이 온다.
고-부하를 다루기
SaaS 제품을 제공하는 많은 인프라 회사들이 마이크로서비스 아키텍처를 구성해 운영하는 이유이다.
견고하게
멀티 테넌트는 장애 포인트가 더 많다. 리사일런스 엔지니어링도 주요 과제이다. 넷플릭스의 카오스 엔지니어링 같은 시도도 좋다.
마이크로서비스가 아니어도 로드 밸런싱이나 멀티 리전 인스턴스 배포 등으로 견고성을 향상할 수 있다.
개발자 수를 늘이기
맨먼스와 무관하게 마이크로서비스 구축/운영은 더 많은 개발자를 필요로 한다. 다만 마이크로서비스 아키텍처는 개발자 수에 비례해 배포 속도도 개선된다. 물론 팀 간 서비스 소유권이 제대로 조정되고 작업이 분할되어야 한다.
신기술 수용
마이크로서비스 아키텍처는 모놀리스에 비해 신기술을 안정적으로 시도할 수 있는 유용성을 제공한다.
마이크로서비스가 나쁜 선택이 되는 경우
불분명한 도메인
경계 컨텍스트를 잘못 잡은 경우 마이크로서비스의 도메인이 불분명해진다. SnapCI 의 사례에서도 기존 코드 기반을 마이크로서비스로 분해하는 방식이 여러 면에서 추천된다.
그리고 도메인 파악이 미진하면 시스템 분해 보다 도메인 모델링을 먼저 수행해 봐야 한다.
스타트업
하지마라, 지금 하지마라. 가 적용되는 상황이다. 넷플릭스, 에어비엔비 등 많은 회사들이 사업이 무르익은 후 마이크로서비스 아키텍처를 도입했다.
분명한 경계를 중심으로 분할하고 나머지는 모놀리스로 개발하는 게 좋다.
고객 설치형/관리형 소프트웨어
그냥 도커 파일 하나로 끝나는게 좋다.
좋은 이유를 찾지 못함
명확한 목표나 비전이 없으면 할 필요 없다.
균형 조정
팀의 자율성을 높이기 위해 새로운 프로그래밍 언어를 사용하기 위해 마이크로서비스를 도입하는 것은 우선순위가 혼동되고 있다는 것이다. 보통 기존 모놀리스를 수평으로 확장하는 것이 더 낫다.
공감대 형성
작업자 뿐 아니라 관여자들도 모두 이 여정에 동의해야 한다.
조직 변화 구현
코터의 8단계 조직 변화를 위한 과정
위기감 조성 → 혁신 추진체 구성 → 비전과 전략 수립 → 변화 비전 전달 → 광범위한 조치를 위한 직원의 자율권 강화 → 단기적인 성과 창출 → 이익 통합과 더 많은 변화 추구 → 혁신 문화의 정립 → 위기감 조성(반복)
점진적 마이그레이션
작은 부분부터 메인 비즈니스에서 먼 곳부터 시작하자.
운영환경
운영환경에 온전히 정착하기까지 완성된 것이 아니다.
변화에 드는 비용
실수에 따른 추가 비용을 효과적으로 완화하기 위해 점진적 변화를 선택한다. 즉, 애자일해야 하는 것이다.
가역적 결정과 비가역적 결정
비가역적 결정은 정말 심사숙고해야 한다. 아마존 베조스가 1종 의사결정이라 부르는 이 비가역적 선택은 신중하게 천천히 이루어져야 한다. 반면 2종 의사결정은 판단력이 뛰어난 개인이나 소그룹이 신속하게 내리고 수행하는 게 좋다.
호스팅 회사 변경, 공개된 API 변경 등은 되돌리기 힘들다. 프로그래밍 언어 변경, 데이터베이스 시스템 변경 등은 상대적으로 해 볼 만 하다. 새로운 오픈소스 라이브러리 선택 등은 되돌리기 쉬운 편이다.
실험해 볼만한 곳
적당한 곳에서 실패를 예상하고 시도해 보는 것을 추천
목표 설정
도메인 주도 설계가 수반되어야 한다
도메인 주도 설계
도메인 모델을 정의하면서 서비스 분해의 우선순위를 정한다. 화이트보드에 그림을 그려보면서 경계 컨텍스트 확인해 보자.
작업 범위 파악
전체 시스템의 상세한 도메인 모델을 파악하려 하지 말고 분해를 시작할 위치에 대해 적당한 결정을 내릴 정도의 정보를 바탕으로 진행해 보자. 너무 강박에 쫓기지 말자.
이벤트 스토밍
기술 전문가와 비전문가가 함께 참여해 도메인 모델을 정의하는 협업 방식이다.
현실적으로 비전문가가 도메인 모델을 온전히 이해하고 있다고 기대하기는 어렵다. 모델에 대한 공통된 이해가 필요하다는 점만 기억하자.
우선순위 지정을 위한 도메인 모델 사용
종속성이 적은 도메인 모델이 좋은 출발점이다. 문제는 모놀리스 시스템의 코드가 도메인 별로 구분되지 않은 경우도 있다는 것인데… 마이그레이션에 드는 작업 시간 뿐 아니라 난이도를 판별할 수 있는 시각이 필요하다.
결합된 모델
분해가 쉽고 분해로 인해 얻는 이익이 높은 두 축으로 후보들을 구분하여 추출 대상을 결정한다. 모든 소프트웨어 개발이 그렇듯 막상 해 보니 어려운 것도 있고 생각보다 쉬운 것도 있다.
팀 재구성하기
변화하는 구조
지금은 데브옵스의 시대를 지나고 있다.
만병통치약은 없다
다른 조직의 설계도를 무작정 복사해 사용하는 것은 위험하다. 스포티파이의 스쿼드, 챕터, 길드 개념이 늘어났지만 깊은 고민 없이 따라 하고 있다. 답보다 질문을 베껴야 한다.
변화 일으키기
배포와 관련된 활동과 책임을 명시적으로 나열하는 일부터 시작하자. 이 활동을 기존 조직 구조에 분담시킨다. 이렇게 두면, 클라이언트와 백엔드 그리고 운영 조직이 구분된다. 이어서 팀을 통합하는 과정이 일어난다.
전문 기술 변경하기
스스로의 기술을 평가하고 원하는 요구사항을 만족시키기 위해 어떤 지원이 필요한지 확인해야 한다. 물론 모두 최고 수준까지 도달해야 하는 것은 아니다. 스스로 필요한 목표를 달성하기 위해 멘토링을 제공하기 위한 것이다. 이 내용은 비공개로 유지해야 한다. 상위 매니저는 팀 구성과 운영을 위해 기술 지도를 그리고 내부 교육과 투자를 정당화할 수 있어야 한다. 부족한 부분은 외부에서 제공받는 것도 고려할 수 있다.
전환 상태 확인
마이크로서비스 아키텍처로 마이그레이션이 잘 이루어지고 있는지 검증해야 한다.
정기 점검 사항
진로 변경이나 점검을 위한 회고가 필요하다.
정량적인 측정
출시 시기를 단축하는 목적이라면 배포 주기, 배포 횟수, 실패율 등의 지표를 활용할 수 있다.
정성적인 측정
개발하는 과정에 참여한 사람들의 피드백을 받아보자. 업무를 즐긴다면 좋다. 그렇지 않다면 조치가 필요할 수 있다.
매몰 비용
문제가 존재한다는 것을 무시하지 말아야 한다. 언제든지 변화가 가능해, 언제든지 실패할 수 있어 를 열어두어야 한다.
각 단계를 작게 만들자. 방향을 바꾸기 더 쉬워진다.
새로운 방식을 받아들이자
매번 산 넘어 산을 마주하게 될 것이다.
3장: 모놀리스 분할
모놀리스를 그대로 두는 옵션
모놀리스를 쉽게 뜯어내기 어려운 경우가 늘 있다. 코드가 없는 경우도 있고, 너무 많은 변경 비용이 발생하게 되어 엄두를 못 내는 경우도 있다.
잘라내기, 복사하기 또는 재구현
복사하기가 좋은 선택지가 되는 경우가 많다. 마이그레이션이 완료되면 제거해도 된다.
모놀리스 리팩토링
코드를 봉합하는 기법이 있다. 변경 코드 주위를 봉합하고 변경이 이루어진 후 교체한다. 경계를 안전하게 다루는 기법이 '레거시 코드 활용 전략 - 마이클 페더스'에서 소개되고 있다.
모듈식 모놀리스로 일단 변경하기만 해도 마이크로서비스 마이그레이션의 목적 일부를 달성하기도 한다. 언제나 모두 점진적 재작성을 수행할 수 있는 상황은 아니다. 분리된 모듈의 기능을 새롭게 구현하는 경우도 많다. 하지만 새로 작성하는게 몇 개월씩 걸린다면 추천하지 않는다.
마이그레이션 패턴
기존 시스템을 마이그레이션 하면서 사용할 수 있는 교살자 무화과 패턴을 소개한다.
교살자 무화과 애플리케이션
무화과나무는 숙주 나무의 위쪽 가지에 씨를 뿌려 숙주 나무를 감싼다. 숙주는 무화과나무의 지지대가 되다 썩어 죽고 스스로 생존하는 무화과 나무가 남게 된다.
작동 원리
기존 모놀리스로 호출되는 요청을 새로 작성한 마이크로서비스로 리디렉션 하는 기법이다.
분리된 마이크로서비스와 기존 모놀리스를 병행 실행 같은 패턴을 사용해 의도대로 동작하고 있는지 확신할 수 있다.
적용 대상
기존 시스템을 건드리거나 변경하지 않고 새 아키텍처로 대응할 수 있다. 특정 기능을 재 구현하는 대신 모놀리스에서 API 만 먼저 분리하는 과정도 좋은 옵션이다. HTTP redirect 는 이런 기능을 위해 명시적으로 디자인된 인터페이스이다. RPC 유형의 프로토콜보다 좋은 점이다.
리버스 프락시 적용
우선 모놀리스와 통신하는 클라이언트 사이에 프락시를 추가한다. 추가된 네트워크 Hop에 대한 영향도가 있는지 파악해 두어야 한다. 모니터링도 필요하다.
새로 구성하는 마이크로서비스는 처음에는 501을 반환한다. 점진적 기능이 구현되고 점진적 릴리즈가 이루어진다. 프락시를 통해 새로 준비된 기능으로 마이그레이션을 완료하는 시나리오이다.
기능 토클 옵션을 두고 리디렉션을 하거나 말거나 할 수 있다. 이보다 카나리아 릴리즈 패턴이나 완전 병행 실행 패턴을 통해 점진적 개선을 두는 것도 좋다.
데이터
영속성 데이터에 대해서는 4장에 자세히 살펴본다. 지금 단계에서는 모놀리스와 마이크로서비스가 하나의 데이터베이스를 공유한다.
프락시 옵션
보통 추천되는 솔루션은 Nginx 이다. 각 요청의 매개변수를 제대로 지원하기 위해 프락시 옵션을 점검해야 한다.
프로토콜 변경
현재 SOAP 기반 HTTP 인터페이스를 사용하지만 새 마이크로서비스는 gRPC를 지원할 수 있다. 이 방법은 별로 추천되지 않는다. 프락시 자체이 복잡성이 증가하기 때문이다.
배포 프로세스가 느려지게 만들지 말자. 파이프는 멍청하게 엔드포인트는 똑똑하게 유지해야 한다. 차라리 새 마이크로서비스에 새 프로토콜을 지원하는 편이 좋다. 그 이상을 원하면 서비스 메시를 도입할 수 있다.
FTP 프로토콜을 마이그레이션 사례
고객 관점에서 큰 변화가 없도록 유지하기 위한 방법이 필요했다. 기존 모놀리스의 FTP 를 가로채고 새로 만들어진 REST HTTP 서비스로 대체했다. 추후 고객에게 REST API 를 제공하여 병행 실행을 완성한다.
메시지 가로채기 사례
큐에 담긴 이벤트 메시지 일부를 가로채는 작업도 유용하다. 콘텐트 기반 라우터를 도입하는 것인데 이는 똑똑한 파이프가 된다. 유용하긴 하지만 복잡해진다.
그 외 다양한 프로토콜 대응
모놀리스의 인바운드 호출을 가로채는 방법으로 대응할 수 있다.
다른 예
마이크로서비스로 이전하는 것이 아니더라도 교살자 무화과 패턴은 자주 사용되었다.
기능을 마이그레이션 하는 동안 동작 방식 변경하기
마이그레이션 하면서 기능이 추가/변경되는 경우는 어떻게 될까? 숨겨진 버그가 나타나면 문제가 커진다. 묘책은 없지만 마이그레이션이 완료될 때까지 변경을 허용하지 않는 편이 수월하다. 그래서 마이그레이션에 사용되는 시간이 오래 걸리지 않아야 한다. 변경사항을 복원하는 능력도 고려해야 한다.
UI 컴포지션 패턴
온라인 가디언에서 기존 CMS로 부터 새로운 UI를 출시하기로 했다. 전환 과정은 특정분야를 목표로 진행하였다. 심지어 세부 분야에서도 마이그레이션을 더 작은 단위로 쪼개기 위해 노력했다.
페이지 단위
이전 페이지 링크가 새 페이지로 redirect 되면서 새로운 UI/UX 를 제공했다.
위젯 단위
Edge Side Include 는 아파치 웹 서버에서 제공하는 기능으로 특정 영역을 새 서비스의 콘텐츠로 채웠다. 요즘은 브라우저 기술이 훌륭하기 때문에 서버 위젯을 사용할 필요는 없다.
모놀리스 콘텐츠 오케스트레이션 서비스를 위젯 단위로 분리해 마이그레이션 할 수 있다.
모바일 애플리케이션은 하나의 모놀리스이다. 마찬가지로 컴포넌트 단위 UI 도 모두 경계 콘텍스트에 적용할 수 있기 때문에 모바일 앱 배포 없이 UI 를 변경할 수 있는 기술을 통해 서버 코드 변경 만으로 제어된다.
마이크로 프론트엔드
단일 페이지 애플리케이션의 시대이다. 위젯 기반 컴포지션의 형태가 될 수밖에 없다.
웹 컴포넌트의 적용이 더딘 것이 원인인지 마이크로 프론트엔드라는 이름으로 그리고 아일랜드 아키텍처라는 이름으로 컴포넌트 지향 소프트웨어가 자리잡고 있다.
적용 대상
사용하고 있는 SPA 기술에 따라 다르다. UI 컴포지션 기법은 뒤에서 다룬다. 서버 위젯은 더더욱 사용되지 않고 있는 추세 같다.
추상화에 의한 분기 패턴
기존 코드 베이스를 변경하는 과정은 쉽지 않은 상황으로 가득하다. 추상화 분기 패턴으로 기존 코드를 변경하면서 구현된 코드가 안전하게 동일 버전의 코드에서 공존하고 배포 중단도 최소화할 수 있는 방법을 제공한다.
작동 원리
대체할 기능을 위한 추상화를 만들고 이 인터페이스를 사용하도록 기존 클라이언트를 변경한다. 기능을 대체하기 위해 마이크로서비스를 구현한다. 새로운 구현을 사용하도록 추상화를 전환하자. 기존 구현을 제거한다. 마지막으로 추상화도 제거할 수 있다.
기존 구현을 제거할 때 잠시 병행 사용을 위해 제공되던 기능 플래그 구현도 제거하는 편이 좋다.
대체 메커니즘을 위한 분기 자동화
기존 기능을 완벽히 마이그레이션 하기 위해 추상화 인터페이스 아래 검증 스위치를 둔다. 1차 구현을 먼저 호출하고 실패하는 경우 2차 구현을 호출하도록 준비한다. 1차 구현은 새 마이크로서비스를 사용하는 것이고 2차 구현은 기존 모놀리스의 기능을 호출하는 것이다.
복잡성이 추가되는 건 사실이다. 병행 실행 패턴에서 좀 더 자세히 알아본다.
적용 대상
기존 코드베이스를 변경하는데 시간은 걸리지만 기존 코드를 작업하는 동료를 방해하지 않으려는 상황에 유리하다. 피처 브랜치를 오래 관리하는 것보다 추상화에 의한 분기가 좋다.
병행 실행 패턴
교살자 무화과 패턴과 추상화에 의한 분기 패턴은 동일한 기능에 대한 기존 구현과 새로운 구현이 동시에 공존할 수 있게 해 준다. 단, 이 기능은 둘 중 하나를 사용하는 패턴으로 기존 구현으로 쉽게 돌아가는 것에 목적이 있다.
병행 실행 패턴은 두 구현 중 하나를 사용하는 대신 둘을 모두 호출해서 사용한다. 동일한 결과를 반환하는지 확인할 수 있다. 사용되는 결과는 둘 중 하나를 사용하는데 신뢰할 수 있을 시점까지 기존 구현을 기준으로 간주한다. 결과를 반환하지 않고 저장하는 경우는 저장된 데이터를 비교해 사용한다.
신용파생 상품 가격 비교의 사례
시스템 이전 전과 후 상태에 대한 검증이 반드시 필요한 부분이다 병행 실행 패턴이 아니면 검증하기 어려웠을 것이다. 기존 가격 정책 시스템은 새 가격 정책 시스템과 함께 사용되는 동안 배치 작업을 통해 계산 결과를 비교하는 기간을 가졌다. 한 달 후 새 시스템으로 전환을 완료하고 기존 시스템을 폐기했다.
홈게이트 목록의 사례
항공기 같은 제어 시스템의 경우도 자동 항법 장치가 제공하는 제어와 조종사가 수동으로 제어하는 기능을 모두 내장하고 있다. 병행 실행은 의외로 많은 곳에서 제공되고 있는 패턴이다.
검증
함수 호출이 아니라 네트워크 호출로 변경되는 경우 레이턴시 문제나 네트워크 장애 등으로 기능이 실패되는 경우가 있다. 이에 대한 보장도 필요하다.
스파이
테스트에 사용하는 스파이를 적용하는 것도 좋다. 이벤트를 받는 서비스를 마이그레이션 하는 경우 두 번 이상의 이벤트를 받게 될텐데 새로 구현되는 서비스는 스파이를 통해 이벤트를 받고 기록을 남겨 검증에 사용할 수 있다.
깃헙 사이언티스트
깃헙이 만들어 제공하는 Ruby 구현체 말고 다양한 패키지가 공개되어 있다.
어둠의 출시와 카나리아 릴리즈
카나리아 릴리즈는 사용자 중 일부만 새 기능을 안내하고 대부분 기존 구현을 사용하게 하는 패턴이다. 반면 병행 실행은 두 기능을 모두 호출한다. 구글 크롬 브라우저 등이 사용하고 있다.
반면 어둠의 출시는 새 기능을 배포하긴 하지만 외부에 노출되지 않기 때문에 사용자는 아직 사용할 수 없다. 마이크로소프트 윈도우즈의 새로운 버전 배포에 자주 사용하고 있다.
적용 대상
이 구현은 변경되는 기능이 매우 크리티컬 한 경우 자주 사용된다. 구현 과정의 장/단점을 꼼꼼히 따질 필요가 있다.
협업자 데코레이터 패턴
디자인 패턴의 데코레이터 패턴에 착안된 방법이다.
멤버십 프로그램 사례
기존 주문 기능에 맴버쉽 포인트를 추가하는 대신 프락시를 통해 호출을 가로채고 고객에게 제공할 포인트를 결정해 제공한다. 주문은 기존 주문 프로세스를 사용한다.
교살자 무화과 패턴의 프락시보다 똑똑한 프락시를 구현해야 한다. 이 프락시는 자체적인 마이크로서비스가 될 정도가 되기도 한다.
적용 대상
협업자 데코레이터 패턴은 단순할수록 좋다. 모놀리스에 사용되는 요청과 응답 정보에 프락시가 필요한 정보가 있는 상황에 적당하다. 새 기능 추가는 신중하게 고려해야 한다.
변경 데이터 캡처 패턴
모놀리스로 인바운드 호출을 가로채는 대신 데이터 저장소에 변경된 데이터를 사용하는 패턴이다.
멤버십 카드 발급 사례
협업자 데코레이터를 사용해 요청과 응답에 수정을 가하는 대신 모놀리스 데이터베이스의 변경 정보에 이벤트를 생성해 추가 기능을 구현한다.
변경 데이터 캡처 구현
데이터베이스의 트리거를 사용할 수 있다. 트리거는 다른 프로시저처럼 데이터베이스 시스템 내에 설치/작성되어야 한다는 단점이 있다. 오라클 같은 데이터베이스 시스템의 경우 외부 코드를 호출하는 기능이 잘 정의되어 있기도 하다.
보통 트랜잭션 로그를 사용한다. Mysql의 경우 binlog라고 불리는 트랜잭션 로그가 있다.
또는, 별도의 프로그램을 개발하여 diff를 파악하고 처리하자.
적용 대상
코드베이스 변경이 불가능한 경우, 데이터를 복제할 필요가 있는 경우 유용한 패턴이다.
4장: 데이터베이스 분해
마이크로서비스 아키텍처 이관의 가장 큰 난제는 데이터를 어떻게 분리해야 하는 것이다.
공유 데이터베이스 패턴
결합도는 도메인 결합도, 시간적 결합도, 구현의 결합도로 구분할 수 있는데 데이터베이스 공유는 구현 결합도에 속하며 가장 큰 비중을 차지한다.
공유 데이터베이스를 기반으로 여러 서비스로 분리하는 어려움은 뷰를 통해 완화할 수 있지만 완벽한 해법은 아니다.
마이크로서비스는 하나의 상태 머신이 되어야 한다. 상태를 변경하는 주체가 자신 말고 다른 곳에서 이루어진다면 올바른 마이크로서비스가 아니다.
패턴 다루기
각 마이크로서비스 스스로 데이터를 소유할 수 있도록 데이터베이스를 분리하는 방식이 항상 선호된다.
적용 대상
읽기 전용 정적 참조 데이터를 고려하는 경우는 공유 데이터베이스라도 괜찮다. 외부에 공개되는 것을 전제로 한 데이터베이스도 이 패턴을 적용해도 좋다.
그렇지만 안된다
작업 시간이 오래 걸리거나 시스템의 민감한 부분을 변경해야 하는 경우 스키마를 나누는 것은 정말 위험하다. 다른 방법을 알아보자.
데이터베이스 뷰 패턴
데이터베이스 뷰를 통해 결합도 관련 문제를 완화할 수 있다. 접근해서는 안 되는 정보를 은닉할 수 있는 점도 좋다.
공개된 계약으로서 데이터베이스
3장 사례에서 본 대로 공개된 데이터베이스는 쉽게 바꿀 수 없다. 스키마를 변경 불가능한 경우가 존재한다.
표현할 뷰 제공
읽기만 제공하는 클라이언트를 위해 원본 스키마를 대체하는 전용 스키마 뷰를 호스팅 하면 원본 스키마를 변경할 수 있다.
한계
모든 데이터베이스 시스템이 뷰를 제공하지 않는다. 뷰 엔진이 메인 데이터베이스 안에 있어야 하는 제약이 있는 경우도 있다. 즉, 물리적인 배포 결합도가 여전히 남아 있다.
소유권
뷰의 소유권이 애매한 것도 문제다.
적용 대상
기존 모놀리스 스키마를 분해하는 것이 실용적이지 못할 때 뷰를 고려해 보면 좋다.
데이터베이스 래핑 서비스 패턴
다루기 너무 복잡한 경우 혼란을 숨기는 편이 좋다. 데이터베이스에 접근하는 서비스를 감싸 데이터베이스 종속성을 서비스 종속성으로 이동시키는 패턴이다.
적용 대상
스키마 분리가 힘든 경우 좋은 방법이다. 관리 가능한 인터페이스가 구성되면 스텁을 추가하는 것처럼 테스트하기에도 용이해진다.
점진적 개선의 목표에 잘 어울리는 패턴이다.
서비스 as 데이터베이스 인터페이스 패턴
읽기 전용의 새로운 데이터베이스를 추가한다. 이 데이터베이스는 기존 데이터베이스의 변경사항을 가져와 반영된다. 기존 데이터베이스에 접근을 옮기는 것이다. 새 데이터가 지연되는 문제와 신선하지 않은, stale 상태의 데이터를 보고 있어야 하는 경우도 있다.
매핑 엔진 구현
트랜잭션 로그를 사용하는 변경 데이터 캡처 시스템이 좋은 옵션이다.
뷰와 비교
DaaS 패턴은 데이터베이스 뷰 패턴보다 훨씬 정교하다. 데이터베이스 시스템의 종류에 관계없이 구성할 수 있다.
적용 대상
공개된 데이터베이스는 읽기 전용이다. 여기에서 더 발전된다면 데이터 웨어하우스 구축이 필요한 수준이 된다.
소유권 양도
모놀리스에서 서비스를 분리할 때 어떤 데이터는 서비스와 함께 이동하지만 어떤 데이터는 원래 위치에 있어야 한다.
집계를 외부에 공개하는 모놀리스 패턴
모놀리스에 외부로 공개되는 인터페이스를 추가하고 이를 마이크로서비스가 사용하도록 한다. 외부에 API를 공개하는 과정에 바운디드 컨텍스트를 발견할 수 있을 것이다. 아직 자신만의 데이터베이스를 소유하지 않더라도 이들은 새로운 마이크로서비스로 분해된다.
뷰를 제공하는 것보다 유연하게 대응할 수 있다.
데이터 소유권 변경 패턴
모놀리스와 새 마이크로서비스가 같은 데이터베이스를 공유하는 과정이 지나면 모놀리스는 새로운 마이크로서비스로 종단점을 옮기는 작업을 수행한다.
데이터 소유권이 모놀리스에서 마이크로서비스로 옮겨진 것이다. 모놀리스에 스키마를 직접 조회해야 한다면 뷰를 제공하는 것도 괜찮다.
데이터 동기화
교살자 무화과 패턴 등은 새로운 서비스와 기존 서비스로 스위칭할 수 있는 기능이 제공된다. 하지만 데이터베이스가 분리된 상태라면 어떨까? 공유 데이터베이스를 유지해야 하는 기간이 필요하다.
코드와 데이터를 동시에 마이그레이션하는 빅뱅 전환은 가능하면 피하도록 하자. 스키마 변경이 있었다면 어떻게 할까?
애플리케이션에서 데이터 동기화
덴마크 시민 의료 기록 통합 뷰를 저장하는 프로젝트의 예에서 데이터베이스 이관 경험을 보자.
대량 데이터 동기화
특정 기간의 스냅샵을 기준으로 대량 동기화한다. 변경사항을 반영하는 캡처 프로세스를 추가하고 데이터베이스를 동기화 시킨다.
이전 스키마를 읽고 쓰기
새 애플리케이션에서 두 데이터베이스를 동기화한다. 기존 스키마는 읽고 쓰기를 진행하며 새 데이터베이스에는 쓰기만 한다.
새 스키마에서 읽고 쓰기
기존 데이터베이스는 쓰기만 하고 새 시스템에서 읽기와 쓰기를 수행한다.
아직 기존 데이터베이스에도 데이터가 동기화되기 때문에 기존 모놀리스로 변경도 여전히 가능하다.
유용한 사례
애플리케이션 코드를 분할하기 전에 스키마를 분할하려는 경우 매우 유용하다.
적용 대상
데이터를 동기화하는 과정이 복잡히지지만 모놀리스와 마이크로서비스 양쪽에서 데이터에 접근하는 경우 유용한 패턴이다.
예광탄 기록 패턴
스키마 전체를 이관하거나 동기화하는 것 대신 두 종류 이상의 진실의 원천을 사용하는 것을 전체하고 필요한 데이터 집합을 새로운 진실의 원천으로 사용하는 패턴이다.
데이터 동기화
우선 한쪽으로만 쓰기가 일어나고 작성된 데이터는 다른 곳으로 동기화시키는 방법이 있다. 또는 양쪽에 모두 쓰기가 수행된다. 아니면 아무 쪽에나 쓰고 동기화 시키는 방법도 있다.
모든 경우에 지연이 발생하지만 일관성을 얻을 수 있다. 각자의 사정에 적당한지 파악해야 한다.
스퀘어 주문 처리 사례
새로운 서비스를 만들고 백그라운드 워커 프로세스도 데이터베이스에 직접 접근하는 대신 공개된 API 만 사용하도록 변경했다.
데이터 동기화는 각 서비스에서 필요한 데이터만 동기화하였고 지연이 존재한다고 가정하였다.
업스트림 컨슈머가 이벤트 기반의 시스템을 사용한다면 기존 시스템과 새로 만들어진 시스템에서 이벤트를 수신하여 각 데이터를 처리하고 동기화하는 과정을 추가할 수 있다. 이렇게 구성이 완료되면 컨슈머 서비스 마이그레이션은 상대적으로 수월하다.
적용 대상
동기화 구현은 대부분 마이그레이션에 필요한 작업이다. 이벤트 기반 시스템을 사용 중이거나 데이터 캡처 파이프라인이 있다면 좀 더 쉽게 구현 할 수 있다.
두 데이터베이스 시스템 간의 데이터 불일치가 어느 정도까지 허용되는지가 중요한 결정 사항이 될 것이다.
데이터베이스 분리
우선 논리적 분리가 가능하다면 물리적 분리에 도전해야 한다. 잠재적 단일 장애 지점을 회피해야 한다. 물론 스케일 아웃의 이점도 있다.
데이터베이스 먼저, 코드 먼저 분할할까?
데이터베이스 먼저 분할
스키마를 먼저 분할하면 성능과 트랜잭션 무결성 문제를 일찍 발견하여 처리할 수 있다.
하지만 여전히 모놀리스 코드에 의존하므로 공유 데이터베이스로 인한 고충이 늘어난다.
ORM 도구를 사용하는 경우 단일 저장소 계층보다 경계 컨텍스트를 따라 저장소를 분해하는 방법이 추천된다.
코드가 여전히 모놀리스에 남아 있는 경우 이 분리된 데이터베이스를 유지하는 것은 큰 의미가 없다.
코드 먼저 분할
서비스를 먼저 분리하면 얻는 단기적인 개선이 있다. 이후 데이터베이스를 분리하는 방법이 많이 사용되고 있다.
서비스가 분리되면 서비스가 요구하는 데이터가 무엇인지 명확하게 알 수 있다. 이후 데이터베이스까지 분리해가는 역량이 필요하다.
코드를 분할하면서 데이터 접근 계층으로 모놀리스를 사용할 수 있다.
하지만 멀티 스키마를 대응하는 편이 좋다. FK 관련된 문제는 4장 후반부에 다시 논의한다.
함께 분할
아무래도 큰 변화가 수반되기 때문에 스키마나 애플리케이션 먼저 분할하는 방식을 강력히 권장한다.
그러면 무엇을 먼저 분할하나?
케바케. 성능이나 데이터 일관성에 영향이 우려되면 스키마를 먼저 분할. 그런 경우가 아니라면 코드를 분할하여 코드가 데이터 소유에 미치는 형향을 이해하는데 도움이 되는 방향으로 결정한다.
스키마 분리 사례
저수준 데이터 분해 패턴을 살펴보자.
테이블 분할
경계 컨텍스트를 포함하는 하나의 테이블을 마이크로서비스의 소유권에 따라 분리하는 방법이다.
이 과정에 모놀리스의 여러 기능이 하나의 데이터에 수정을 가할 수 있다면 분리되는 마이크로서비스가 데이터를 소유하고 외부 기능에 대해 API를 제공하는 것이 좋다.
적용 대상
경계 컨텍스트에 따라 테이블을 분할해야 한다. 소유권에 대한 문제도 함께 적용할 수 있다.
외래 키 관계를 코드로 이동
외래 키를 사용해 데이터 정합성과 빠른 Join 속도를 도모할 수 있다. 새로운 마이크로서비스에서 이를 처리하려면 어떻게 대응해야 할까?
조인 이동
대기 시간이 걸리는 것을 감수하고 코드로 기능을 이동해야 한다. 캐싱을 적극적으로 사용할 수 있다면 그나마 좋은 해법이다.
데이터 일관성
외래 키를 사용하는 이유 중 하나인 데이터 무결성을 위해 데이터를 삭제하기 전 반드시 확인 과정을 거쳐야 한다.
그리고 삭제가 정말 필요한지 확인하여 삭제 대신 레코드의 상태를 변경하는 것도 가능한 옵션이다.
적용 대상
외래 키 관계 해결에서 중요한 것은 하나로 만들고 싶은 두 가지를 나누지 말아야 한다는 것이다.
공유되는 정적 데이터
정적으로 참조되는 데이터는 그냥 공유하는 것도 좋다. 각 마이크로서비스가 하나의 참조 데이터 스키마를 공유하는 것이다.
그렇지 않다면 마이크로서비스마다 중복하여 참조되는 데이터를 관리할 수도 있다. 물론 코드로 정적 데이터를 관리하는 기법도 있다. 적어도 빌드 시점에 최신화 하는 과정이 필요할 것이다.
또 다른 방법으로 정적 데이터를 제공하는 데이터 서비스를 구성한다. Spring Cloud Config 서버가 그런 역할을 한다.
트랜잭션
마지막 난제이다.
ACID 트랜잭션
대부분의 트랜잭션 문제는 여기에 있다. 다행히 모든 데이터베이스가 ACID를 만족하는 것이 아니라는 점을 활용하자.
원자성이 부족한 상황
트랜잭션 경계로 분리된 마이크로서비스는 원자성이 떨어진다. 지금 단계에서는 어쩔 수 없다.
이중 커밋을 살펴보자.
2단계 커밋
각 커밋은 투표와 커밋 단계로 구분하여 동작하도록 개선한다. 로컬 Lock 이 필요하다.
분산 트랜잭션까지 가지 말자
그냥 데이터베이스를 분리하지 않는 방법도 있다.
아니면 Saga 패턴을 알아보자.
사가 패턴
2단계 커밋과 달리 설계상 상태의 여러 변경 사항을 조정할 수 있지만 자원을 잠글 필요가 없는 알고리즘이다. 이렇게 하려면 비즈니스 프로세스가 명시적으로 모델링되어 있어야 한다.
Long Lived 트랜잭션에 대한 해결 방법으로 사가 패턴은 등장하였다.
사가 실패 모드
사가의 핵심은 역방향 복구와 정방향 복구에 있다.
주문에 대한 롤백은 보상 트랜잭션을 통해 이루어진다. 보상 트랜잭션은 데이터베이스 롤백과 동일하게 수행될 수 없다. 그래서 시맨틱 롤백이라고 하기도 한다. 예를 들면 주문이 완료되어 발송된 이메일을 취소할 수 없다. 대신 주문이 취소되었다는 이메일을 발송해야 하는 것이다.
트랜잭션 단계를 정렬하여 롤백이 수월하도록 개선하는 방법도 있다. 고객에게 포인트 제공을 주문이 완료된 시점이 아니라 상품을 포장하고 발송한 후에 이루어지게 하는 것이 좋은 예제이다.
사가 패턴 구현
오케스트레이션 사가와 연출된 사가 패턴이 있다.
오케스트레이션 사가는 중앙에 관리자가 있다. 오케스트레이터이다. 각 트랜젝션에 대해 조정하며 협업 서비스와 동기화된다. 도메인 결합도가 생길 수밖에 없다.
대신 오케스트레이터의 역할을 간결하게 하고 각 서비스에서 오케스트레이션의 요구를 수용하도록 개발하는 방법이 좋다.
이에 반해 연출된 사가 패턴은 비-선점형 멀티 태스크와 유사하게 동작한다. 각 서비스가 상호 간 협업을 위해 이벤트를 발송하고 이벤트에 반응하도록 구성한다.
이 둘을 적절히 혼용해 사용할 수도 있다.
사가와 분산 트랜잭션
왠만하면 분산 트랜잭션은 피하자.
5장: 마이크로서비스 도입 과정에서 직면하는 문제와 해법
마이크로서비스를 도입하면서 나오는 문제는 샘 뉴먼의 첫 책에 충분히 소개하고 있다. 이를 해결하기 위한 정보를 추가한다.
서비스가 늘어날수록 고충도 커진다
마이크로서비스 아키텍처에서 문제가 발생하는 시점은 복잡 다단하다.
규모에 맞는 소유권
소쿠리 아키텍처가 되기 전에 코드 변경에 대한 소유권을 통제하는 시점이 필요하다.
기존 호환을 깨뜨리자
가능하면 그러지 말고 적어도 컨슈머에 마이그레이션 할 시간을 제공하자.
리포팅
모놀리스 데이터베이스의 가장 큰 장점은 리포팅이 쉽다는 것이다. 별도의 리포팅 데이터베이스를 관리하고 운영하자.
모니터링과 트러블슈팅
로그 집계 시스템이 필요하게 될 것이다. 가능하면 초기에 구성하자.
운영 환경에서 테스트하기 위해 가짜 클라이언트를 등록해 살펴보는 방법도 있다.
로컬 개발 환경
서비스를 몇 개 띄워야 하는가? 서비스를 Mock으로 대체하여 구성할 수도 있다.
너무 많은 것들을 실행해야 한다
높은 수준의 자동화 도구와 쿠버네티스 같은 마이크로서비스 관리 도구를 사용할 필요가 있다. 오픈쉬프트나 관리형 쿠버네티스를 사용하는 것도 좋다.
E2E 테스트
자동화 테스트 범위를 제한하는 것도 좋다.
전역 최적화 대 지역 최적화
가역적 결정과 비가역적 결정을 고민하여 대응하자.
견고성과 회복탄력성
실패하는 시나리오를 알고 있는 것이 중요하다. 서킷 브레이커 등으로 배압 문제를 해결하자.
외톨이 서비스
아무도 모르게 돌아가는 서버가 있는데 이 서버가 뭔 일을 하는지 아무도 몰라 끄지 못하는 상황이다.
마이크로서비스가 오래되면 이런 일도 생긴다. 담당자는 퇴사한 지 오래고 코드도 없다.
마이크로서비스는 모두를 위한 아키텍처는 아니다. 이 책을 통해 마이크로서비스가 자신에게 적합한지 제대로 감을 잡을 수 있길 바랄 뿐이다.