마이크로서비스로 어떻게 이전할까

https://www.infoq.com/articles/migrating-monoliths-to-microservices-with-decomposition/

회사에서 어떤 분이 마이크로서비스 관련 글을 공유해서 읽어봤다. “마이크로서비스 아키텍처 구축(Building microservices)”의 저자 샘 뉴먼(Sam Newman)이 쓴 글이라 믿을만하다고 했다. 그래서 읽어봤는데, 내가 고민하던 점이 조금 풀린 것 같았다.

마이크로서비스란?

마이크로서비스는 서비스가 너무 커지니까 작게 쪼개는 것이다. 그럼 서비스간에 통신을 해야 하니까 느리지 않을까? 어차피 서버들끼리 내부 네트워크에서 통신하니까 그렇게 느리지 않다. 데이터를 묶어서 보내고, 받아서 다시 풀고 하는 것까지 해도 10ms 정도면 된다. 3단계를 거쳐도 30ms다. 쓸만하다.

하지만 마이크로서비스가 항상 정답은 아니다. 새 기능이 필요할때마다 마이크로서비스를 하나씩 만들면, 나중에 수백개가 넘어가기도 한다. 그 중에 내가 필요한게 어딨는지 찾기 어렵다. 에러가 생겨도 어디가 문제인지 찾기 어렵다. 서비스 콜이 꼬리에 꼬리를 물고 순환하면 더 문제다.

그러니까 적당히 쪼개는게 좋은데, 그 적당히가 참 어렵다. 아니면 그냥 쪼개지말고, 내부적으로 모듈화하는 것도 방법이다. 사실 이런 모듈화는 정말 옛날부터 하던 거다.

모듈화란?

차세대 프로젝트를 할 때였다. 개발하다보니 자바 프로젝트가 너무 커져서, 개발자 PC에서 메모리 부족으로 실행되지 않았다. 그래서 일부 모듈을 비활성화하니까 실행됐다. 어차피 각자 일부분만 코딩하니까 그렇게 해도 충분했다.

샘 뉴먼은 이걸 modular monolith라고 했다. 프로젝트가 너무 크지 않으면 이렇게 하는 것도 좋다. 굳이 마이크로서비스로 만들 필요가 없다. 물론 위의 경우처럼 프로젝트가 너무 커지면 쪼개야겠지만, 이렇게 미리 나눠놓으면 나중에 쪼개기도 쉽다.

도메인 주도 개발이란?

그럼 어떻게 쪼갤까? 도메인별로 쪼갠다. 도메인 주도 개발(Domain-Driven Development, DDD)이다. 회사의 사업부서에서 비즈니스를 설계하고, 개발부서가 소프트웨어로 구현한다면, 소프트웨어를 개발부서의 입장이 아니라 사업부서의 입장에서 짠다.

DDD 책들이 참 읽기가 힘든데, 너무 추상적이라 그런 것 같다. 회사의 내부 비즈니스를 너무 구체적으로 적으면 안된다. 회사의 영업 비밀이 노출될 수도 있고, 너무 한 회사의 도메인 지식에만 국한될 수도 있다. 그래서 추상화를 하다보니 이해하기 어려운 것 같다. 회사에서 실제로 일하면서 업무를 이해해야, 그 추상적인 부분을 구체화할 수 있다.

DDD는 기술적이라기보다 업무적이다. 사업 관점에서 코드를 짜야 비즈니스가 잘 이해되고, 비즈니스가 바뀔 때 따라 바꾸기도 좋다. 그것때문에 코드가 길어지고 느려지고 복잡해지더라도 충분히 감수할 만 하다.

Spring boot 프로젝트의 경우 패키지를 controller, service, dao, model 이렇게 나누던거를, 비즈니스 도메인 별로 product, vendor, promotion 이렇게 나누고 그 안에 각각 controller, service, dao, model을 넣어도 된다. 그러다보면 Product를 product에서도 쓰고 vendor에서도 쓸 수 있는데, 의존성을 끊어내기 위해 VendorProduct 같은 식으로 따로 만든다. 의존성을 낮추기 위해 중복을 허용한다.

일을 단계적으로 해야 하나?

그럼 마이크로서비스로 이전하기로 할 경우, 마이크로서비스로 바로 가는게 좋을까? 기존 서비스를 먼저 정비하고 이전하는게 좋다. 아니 그럼 한 번 할 일을 두 번 하는 게 아닌가? 맞긴 하지만 그게 더 일이 적다.

왜냐하면 버그가 생기기 때문이다. 사람이 하는 일이 그렇다. 큰 덩어리를 한꺼번에 바짝 몰아서 하면 효율적이지만, 그만큼 버그도 많이 생길 것이다. 특히 폭포수 방식으로 빅뱅 출시를 하면 정말 빅뱅처럼 한 번에 버그가 팡 터질 수 있다. 그러니까 찔끔찔끔 조금씩 나눠서 하는게 낫다.

그럼 얼마나 잘게 쪼개는게 좋은가? 가장 작은 단위까지 쪼개는게 좋다. 하나 고치고 배포하고, 하나 고치고 배포하는게 제일 좋다. 한 번에 할 일을 두 번으로 나누고, 네 번으로 나누고, 열 번으로 나누고… 계속 단계를 나눠서 하루에 여러 번이라도 배포하는게 낫다. 그래야 버그가 생겨도 작게 조금씩 생길테니, 찾아서 고치기가 쉽다.

기능들을 잘게 나눠서, 하나씩 하나씩 점진적으로 옮겨나간다. 기존 서비스도 띄워놓고, 새로운 서비스도 띄운다. 그리고 달리는 열차의 바퀴를 갈아끼우는 것처럼, 하나씩 하나씩 바꿔나간다. 그러다가 다 바꾸면 기존 서비스를 종료하면 된다.

하나 고쳐서 배포하고, 실제 환경에서 잘 돌아가는지 지켜보고, 그 다음 걸 배포하고, 모니터링하고… 이걸 반복한다. 체크아웃처럼 쪼개기 어려운 부분은 옛날 것과 새로운 것을 병렬로 둘 다 실행한다. 그래서 옛날 것과 새로운 것이 일치하는지 확인해서, 100% 일치할때까지 새 서비스를 수정해서 배포한다. 이것도 불안하면 일부 사용자들만 새 서비스를 쓰게 한다. 새 서비스를 전체 사용자의 10%, 20%…. 100%가 쓸 때까지 비율을 늘려간다.

데이터베이스도 나눠야 하나?

그리고 데이터도 같이 모듈화해야 한다. 데이터베이스도 monolith면 안된다. 코드와 데이터가 같은 모듈이어야 한다. 이것도 한 번에 떼어내기가 어려우면 점진적으로 떼어낼 수 있다.

그럼 테이블끼리 조인하는 관계를 어떻게 떼어낼것인가? 이 글에서는 생략했지만, 이것도 정말 어려운 문제다. 결론부터 말하면, 메모리던 캐쉬던 테이블이던 어딘가에 데이터를 가져와서 조인해야 한다. 한 모듈에 없는 데이터는 결국 다른 모듈에서 가져와야 한다. 그걸 데이터베이스가 아니라 어플리케이션에서 논리적으로 조인해야 한다. 어플리케이션 레벨에서 Nested Loop Join, Hash Join 같은 걸 해야 한다.

당장 급해서 단계적으로 못하겠다면 어떡하나?

일단은 monolith로 돌려야 한다. 도메인 별로 찢기가 어렵다면, 일단 기능 별로 찢어본다. 그래서 각 기능을 껏다 켰다 하는 feature flag를 만들 수 있다. 그래서 A, B, C, D, 이렇게 4개의 기능이 있다고 하면, feature flag를 true, false로 조정해서 일부 기능만 돌아가도록 제한할 수 있다.

그래서 monolith 서비스를 4개 띄우는데, 첫번째 것은 A 기능만 켜고, 두번째 것은 B 기능만 켜고, 세번째 것은 C 기능만 켜고, 네번째 것은 D 기능만 켠다. 그러면 서비스를 나눈 것과 비슷하다. 리소스를 4배로 소비하겠지만, 일단 중간 단계로 거쳐가는 수준으로는 괜찮다.

여기서 A 기능만 많이 쓴다면, A의 인스턴스 갯수를 늘린다. B 기능을 적게 쓰면 B의 인스턴스 갯수를 줄인다. 이러면 monolith 전체의 인스턴스를 늘리고 줄이는 것보다 유연하게 대응이 가능하다.

Loading

Published
Categorized as xacdo

By xacdo

Kyungwoo Hyun

Leave a comment

Your email address will not be published. Required fields are marked *