Microservices From a Startup Perspective

이 글은 원 저자인 Susanne Kaiser의 허락을 받아 InfoQ에 게제된 Microservices From a Startup Perspective을 번역한 것입니다.

Photo by Simon Abrams on Unsplash

요약

추출하기 쉬운 작은 후보에서부터 시작하여 마이크로서비스 도입의 경험을 쌓는다.

초반부터 빌드와 배포 자동화 및 모니터링에 집중한다.

시스템의 여러 부분에 걸쳐 있는 교차 관심사 문제를 초반에 해결함으로써 생산성 저해를 막는다. 이를테면 마이크로서비스를 도입하면서 기존의 모노리스도 계속 키운다든가 혹은 모든 마이크로서비스에서 동일한 구현을 계속 반복하는 것 등이 그 예이다.

시스템이 계속해서 진화할 수 있도록 이벤트 주도 시스템으로 설계하며 이벤트 스트림을 이용해 데이터 중복의 오버헤드를 줄이고 새로운 마이크로서비스의 진입 장벽을 낮춘다.

마이크로서비스로의 전환 과정은 개발팀만 노력해서 성공할 수 있는 것이 아님을 인지한다. 이는 다양한 환경의 영향을 받는다. 전환 과정의 발목을 잡는 일이 일어날 수 있음에 유의하고 그에 대응하며 조직내의 다른 구성원들도 이에 대해 알도록 한다.

마이크로서비스(microservice)로의 여행을 시작할 때 어떤 점을 고려해야 할지 처음부터 전부 알기는 어려운 일입니다. 특히 작은 팀이라면 더더욱 그렇습니다. 안타깝게도 이에 대해 쉽게 적용할 수 있는 황금률이란 없습니다. 모든 여행이 서로 다르며 모든 조직이 서로 다른 환경에 마주하게 됩니다. 이 글에서 저는 스타트업 관점에서 얻은 몇 가지 교훈과 문제점들을 공유하고자 합니다. 또한 다음 번에 마이크로서비스를 도입한다면 어떻게 다르게 하겠는지에 대해서도 얘기해보겠습니다.

모노리스에서 마이크로서비스까지, 우리의 여행은 어떻게 시작되었나

처음에 우리는 모든 면에서 모노리스(monolith)인 구조로부터 시작했습니다. 팀 구성원 모두가 함께 참여하여 하나의 제품을 만들고 있었으며 이는 하나의 코드베이스에 구현되어 있었고 여기에 하나의 기술스택을 사용했습니다. 한 동안은 이런 구조는 잘 동작했습니다.

시간이 지나자 모든 것이 변했습니다. 팀은 성장하고 우리는 제품에 더 많은 기능을 넣기 시작했습니다. 코드베이스는 점점 커지고 사용자의 수도 증가했습니다. 이건 물론 좋은 일이었습니다. 하지만 …

무언가를 완료하는데에 매우 긴 시간이 걸리기 시작했습니다. 미팅, 논의, 의사결정 등은 예전 보다 더 오래걸렸습니다. 책임 소재도 분명하지 않게 되었습니다. 예를 들어 어떤 버그가 발생했을 때 어떤 사람이 그에 대해 책임감을 느끼기까지 오랜 시간이 걸렸습니다. 프로세스가 늘어지기 시작했고 생산성은 떨어졌습니다.

더 많은 기능을 추가할 수록 우리 제품의 사용성(usability)은 더 복잡해졌습니다. 계속해서 더 많은 기능이 추가되면서 사용성과 사용자 경험(user experience)은 나빠졌습니다. 사용자의 문제를 잘 푸는 대신 사용자들을 더 혼란시키기 시작했습니다.

모노리스 소프트웨어 아키텍쳐 때문에 전체 시스템에 영향을 주지 않고 새로운 기능을 추가하는 것은 어려운 일이었습니다. 새로운 변화를 배포하는 것도 꽤 복잡해졌습니다. 단지 코드 몇 줄을 변경하는 경우에도 전체 제품을 다시 빌드하고 다시 배포해야 했기 때문입니다. 이로 인해 배포에 따르는 위험은 높아졌고 따라서 배포를 덜 자주 하게 되었습니다. 당연히 새로운 기능의 릴리즈도 느려졌습니다.

이제는 무언가 나누고 바꿔야한다는 니즈가 생긴 것입니다.

그래서 제품 전략을 수정하게 되었습니다. 우리는 사용성과 사용자 경험 개선에 집중하였고 JUST SOCIAL 이라는 제품을 여러 개의 별도의 앱으로 분리했습니다. 분리된 각각의 앱이 특정 유스 케이스를 담당하도록 말입니다. 문서 공유, 실시간 의사소통, 작업 관리, 기업 뉴스 및 편집 내용 공유, 프로필 관리 등을 담당하는 개별 앱을 제공하는 식으로 아이디어는 발전했습니다.

그러는 동안 우리는 하나의 팀을 여러 개의 작은 팀으로 나누고 각 팀에 우리 협업앱의 특정 부분을 할당하여 잘 정의된 책임을 갖도록 하였습니다. 우리는 자율적인 팀을 만들어서 시스템의 각 부분에 대해 자신들의 페이스대로, 다른 팀에 영향을 주지 않으면서 독립적으로 일하도록 만들고 싶었습니다.

원래 하나였던 제품을 여러개로 나누어진 협업앱으로 나누고 그에 맞게 팀도 더 작은 팀으로 나눈 다음에 해야 할 논리적 단계는 이렇게 얻어진 자율성과 유연성을 소프트웨어 아키텍쳐에도 반영시키는 것입니다. 바로 마이크로서비스를 도입하는 것입니다.

마이크로서비스를 도입하고자 했던 원래 의도는 시스템의 여러 부분이 팀 전체에 영향을 미치지 않으면서도 자체적으로 잘 동작하도록 하게 하는 것이었습니다. 작게 나누어진 협업앱의 각 부분을 독립적으로 개발, 배포, 스케일링하면서 변경사항을 빠르게 배포하고 싶었습니다.

우리는 마이크로서비스를 도입하기에 좋아 보이는 후보를 선별해내는 것으로 이 여행을 시작했습니다. 좋은 후보를 찾아내기 위해 우리는 좋은 서비스 모델링의 주요 개념에 대해 생각해봤습니다. 이들은 주요 개념으로 서비스간 느슨한 결합(loose coupling) 원칙과 서비스 내의 높은 응집(high cohesion)이라는 원칙을 따르고 있었습니다. 서비스 내 높은 응집은 대개 일관성이 필요한 연관된 행동들에 의해 반영됩니다. 도메인 주도 디자인(Domain Driven Design)에서 관계된 행동은 바운디드 컨텍스트(Bounded Context)에 반영됩니다. 바운디드 컨텍스트란 도메인 모델이 존재하는 의미상 경계(semantic boundaries)를 말하는데 이는 잘 정의된 비즈니스 기능을 담당하는 서비스를 기술합니다.

우리의 경우 우리의 협업 앱을 고수준의 바운디드 컨텍스트로 설정하여 거칠게 정의된 서비스 경계를 반영하도록 했습니다. 이들은 해당 서비스들을 추후 더 세밀한 서비스로 나눌 수 있는 좋은 시작점이 되었습니다.

우리는 JUST DRIVE의 바운디드 컨텍스로부터 시작했습니다. JUST DRIVE는 문서 관리를 책임지는 협업 앱입니다. 각 문서는 작성자(author)가 생성합니다. 작성자 데이터는 프로필 데이터에서 추출되는데 프로필 데이터는 아직 모노리스 쪽에 있는 프로필 관리에 대한 바운디드 컨텍스트가 관리합니다.

우리는 이것을 공존하는 서비스로 만들었으며 처음부터 새로 만들었습니다. 이것은 사실 기존 것과 완전히 동일하지는 않았습니다. 우리는 새로운 UI를 선보였고 더 많은 기능을 추가하기도 했으며 데이터 구조에 큰 변화를 주기도 했습니다. 새로운 서비스의 바운디드 컨텍스트는 도메인 모델과 어플리케이션 서비스로 이루어져 있습니다. 도메인 모델은 비즈니스 로직을 담당하며 애플리케이션 서비스는 유즈케이스 조율, 트랜잭션, REST 엔드포인트 관리, 영구저장소 어댑터 관리 등을 합니다. 새로운 서비스는 문서 상태를 배타적으로 소유합니다. 즉, 이 서비스는 문서를 읽고 쓸 수 있는 유일한 서비스입니다.

앞서 말한 것처럼 각 문서는 작성자가 생성하고 작성자 데이터는 모노리스에서 관리하는 프로필 데이터에서 나옵니다.

이렇게 해놓고 보니 새로운 서비스와 기존의 모노리스가 어떻게 서로 상호작용해야 할지에 대한 의문이 듭니다.

문서를 표시할 때 마다 프로필 서비스에서 작성자 데이터를 요청하지 않기 위해 우리는 필요한 작성자 데이터를 새로운 서비스에 로컬 카피로 갖고 있기로 했습니다. 데이터에 대한 오너십이 깨지지 않는 이상 데이터가 중복되는 것은 괜찮습니다. 둘러싸고 있는 컨텍스트가 프로필 상태를 독점적으로 소유하고 있는 한 말입니다.

시간이 지나면서 로컬카피와 원본 데이터가 달라지기 시작했기 때문에 기존의 모노리스는 새로운 서비스에게 프로파일이 업데이트될 때 마다 그 내용을 알려줄 필요가 생겼습니다. 모노리스는 프로파일이 변경될 때 마다 새로운 서비스가 구독하고 있는 ProfileUpdatedEvent 이벤트를 퍼블리시합니다. 새로운 서비스는 이 이벤트를 소비하여 자신의 로컬 카피를 적절히 업데이트합니다.

이러한 이벤트주도 서비스간 상호작용(event-driven service interaction)은 서비스 사이의 결합을 줄여줍니다(decoupling). 왜냐하면 우리는 더 이상 모노리스에 컨텍스트를 넘나드는(cross-context) 쿼리를 직접 보내지 않아도 되기 때문입니다. 새로운 서비스는 자신의 로컬 카피 데이터를 이용해 원하는 뭐든 할 수 있고 네트워크를 통하지 않고도 작성자 데이터를 이용할 수 있으므로 조인 쿼리와 같은 것도 더 효과적으로 수행할 수 있으므로 전체적인 자율성이 증가했습니다.

우리는 공존하는 서비스를 처음부터 새로 만드는 것으로 시작해서 이제는 데이터 복제를 목적으로 이벤트 주도 서비스 상호작용을 도입하게 되었습니다.

우리가 마주쳤던 문제들과 해결 과정

공존하는 서비스를 처음부터 만드는 것은 일반적으로 모노리스를 분해하는 좋은 전략입니다. 특히 무언가로부터 벗어나고 싶을 때 좋은데, 예를 들면 이미 유효하지 않게된 비즈니스 로직이나 오래된 기술 스택 같은 것을 사용하고 싶지 않을 때 그렇습니다. 하지만 우리의 경우 처음으로 기존 서비스를 분해할 때 너무 많은 것을 하려고 했습니다. 위에서 설명한 것처럼 처음부터 공존 서비스를 만든 것 뿐만 아니라 새로운 UI도 도입하고 새로운 기능도 추가했으며 데이터 구조에도 큰 변화를 주었습니다. 초반부터 너무 많은 것을 어깨에 짊어진 셈이 되어서 가시적인 성과가 나오기까지는 많은 시간이 걸렸습니다. 특히 처음에는 마이크로서비스에 대한 경험과 자신감을 얻기 위해 빠르게 결과를 얻는 것이 매우 중요합니다.

두 번째 서비스를 만들 때 우리는 다른 접근방식을 취했습니다. 채팅 앱의 고수준 바운디드 컨텍스트에 집중했는데 기존의 코드를 한 단계식 추출하는 방식으로 점진적 탑다운(incremental top-down) 분해 전략을 따랐습니다. 먼저 별도의 웹앱으로 UI를 분리해냈고 모노리스 쪽에 REST-API를 추가하여 분리된 웹앱이 사용할 수 있도록 했습니다. 이 단계를 거친 다음부터는 새로운 웹앱을 독립적으로 개발 및 배포할 수 있게 되었고 따라서 UI를 더욱 빨리 개발할 수 있게 되었습니다.

UI를 추출한 다음에 우리는 조금 더 전진해서 비지니스 로직을 분리할 수 있었습니다. 비즈니스 로직을 정리하는 것은 코드를 크게 변경해야 하는 일이었습니다. 의존성에 따라 모노리스가 사용할 임시 REST API를 제공해야 하기도 했습니다. 하지만 이 단계에서도 우리는 동일한 데이터 스토리지를 사용하고 있었습니다.

결합되지 않은 단독 서비스가 되기 위해 우리는 마지막으로 데이터 스토리지를 분리해내어 새로운 서비스가 채팅 상태를 독점적으로 소유할 수 있도록 할 필요가 있습니다.

각각의 채팅에는 참석자(participant)가 있습니다. 채팅 참석자 데이터는 모노리스 내의 프로필 데이터에서 추출됩니다. 이전의 DRIVE 예시에서 본 것 처럼 우리는 채팅 참석자 데이터에 대한 로컬 카피를 가지고 있으며 ProfileUpdatedEvent 이벤트를 구독하여 모노리스의 오리지널데이터와 로컬 카피가 싱크 되도록 하고 있습니다.

이 시점부터 우리는 모노리스에서 다음 바운디드컨텍스트를 잘라낼 수 있었습니다. 크게 크게 구분되어 있는 서비스를 좀 더 세밀하게 나눌 수 있게 된 것입니다.

우리에게 주어진 또 다른 과제는 사용자 인증 처리였습니다.

거의 모든 서비스에서 우리는 인증을 어떻게 처리해야 하는가에 대한 질문에 부닥쳤습니다. 좀 더 맥락을 설명하자면, 사용자 인증은 매우 세밀하게 나누어져 있어서 도메인 오브젝트 수준까지 내려가야 합니다. 각각의 협업 앱은 자신의 도메인 오브젝트에서 인정을 통제합니다. 예를 들어 문서의 사용자 인증은 해당 앱의 상위 폴더에 있는 인증 설정에 의해 통제되는 식입니다.

반면에 사용자 인증은 세부적으로 나뉘어져있을 뿐만 아니라 서비스들 간에 상호 의존적입니다. 어떤 도메인 오브젝트의 사용자 인증 케이스는 다른 서비스 내에 있는 부모 도메인 오브젝트의 인증 정보에 의존적이기도 합니다. 예를 들어 컨텐트 페이지에 첨부된 문서의 읽기나 추가 권한은 이 페이지의 권한 설정에 의존적이며 이는 문서 자체와는 다른 서비스에 존재합니다.

이런 식의 복잡한 요구사항 때문에 분산된 사용자 인증 문제를 해결하는 것은 큰 골칫거리였고 초기에는 해결책을 내놓을 수 없었습니다. 그 결과로써 나타난 것은 상당한 생산성 저하였습니다. 결과적으로 이와 관련된 부분에 새로운 서비스를 추가하게 되었는데 사용자 인증 문제가 이미 해결된 곳에 추가하게 되었고 그것은 … 기존의 모노리스 시스템이었습니다. 모노리스의 덩치를 줄이는 대신 오히려 키우고 있던 것입니다. 또 다른 부작용은 모든 서비스 마다 사용자 인증을 반복해서 구현하기 시작했다는 점입니다. 매우 초창기에는 이것이 합리적인 선택처럼 보였습니다. 왜냐하면 우리의 초창기 가정은 사용자 인증은 도메인 모델이 있는 곳과 동일한 바운디드 컨텍스트에 속한다는 것이었기 때문입니다. 하지만 이는 서비스 사이의 의존성은 간과한 것입니다. 결과적으로 우리는 데이터를 여기 저기에 복사해야 했고 충돌의 위험을 안게 되었습니다.

요약하자면 결국엔 인증 처리를 중앙화된 마이크로서비스로 모두 합치는 것으로 문제를 해결했습니다.

중앙화된 서비스는 분산된 모노리스(distributed monolith)를 결과적으로 도입하는 리스크를 가져옵니다. 시스템의 한 부분을 변경하면 동시에 다른 부분도 모두 변경해야 하며 이는 분산된 모노리스의 강력한 신호입니다. 우리 사례를 예로 들자면 인증을 필요로하는 새로운 협업 앱을 추가했을 때 우리는 동시에 중앙화된 인증 서비스도 수정해야 했습니다. 이는 모노리스와 마이크로서비스의 단점만 모두 안고 가는 것입니다. 서비스가 강하게 결합되어 있고 게다가 느리고 불안정한 네트워크를 통해 통신하고 있습니다.

대신에 우리는 이 중앙화된 인증 서비스가 소유하는 공통의 계약을 제공하였고 모든 하위 서비스에서 이를 지키도록 했습니다. 우리의 경우 인증과 관련된 행동을 서비스가 공통된 계약으로 번역하도록 하여 인증을 하는 서비스에서 추가적인 번역이 필요 없도록 했습니다. 이 번역은 각각의 하위 서비스에서 진행되며 중앙화된 인증 서비스에서 일어나지 않습니다. 이 공통의 계약 덕분에 우리는 중앙화된 인증 서비스를 수정하거나 재배포하는 일 없이 새로운 서비스를 도입할 수 있게 되었습니다. 여기에 꼭 필요한 것은 이 공통 계약이 안정성(stable)을 유지하는 것 혹은 최소한 예전 버전과 호환되도록 하는 것입니다. 그렇지 않으면 모든 문제를 하위 서비스 문제를 하위 서비스에 떠넘기는 꼴이 되고 우리는 하위 서비스들을 계속해서 업데이트해야 합니다.

새롭게 얻은 교훈

특히 처음에는 빠른 결과를 얻고 마이크로서비스 경험을 쌓기 위해 추출하기 쉬운 작은 서비스부터 시작하는 것이 좋습니다. 만약 덩어리가 큰 서비스를 다루게 된다면 몇 가지 단계를 나누어서 분해 작업을 진행하는 것이 더 관리하기 쉬운 방법이었습니다. 예를 들어, 관리가 가능한 한 단계씩 점진적 탑다운 분해를 하는 것입니다.

여러 분야에 걸친 문제를 초반부터 해결하는 것이 비생산적인 결과를 방지하는데 필수적입니다. 모노리스를 줄이는 것이 아니라 더 키우게 된다거나 모든 서비스에서 같은 문제 해결을 위한 구현을 반복한지 않도록 조심해야 합니다.

여러 서비스에 관여하는 중앙화된 서비스를 만들 때는 분산된 모노리스를 만들어내는 결과를 낳지 않도록 조심해야 합니다. 우리의 경우 공통의 안정된 계약을 통해 분산된 모노리스를 회피할 수 있었습니다.

시스템이 쉽게 진화할 수 있도록 디자인하려면 이벤트 주도의 서비스간 상호작용이 서비스 사이의 높은 결합을 끊어내는 열쇠입니다. 이벤트는 알림이나 데이터 중복의 목적으로 혹은 이벤트를 이벤트 스토어에 장기간 유지하는 방식으로 주된 데이터소스로도 사용할 수 있습니다.

이벤트를 순전히 알림의 목적으로 사용할 때는 대개 추가적인 데이터를 다른 컨텍스트로부터 원격 크로스컨텍스트 쿼리로 가져오게 됩니다. 예를 들어 REST요청 같은 것이 그것입니다. 원격 쿼리가 주는 단순함을 데이터셋을 로컬에서 관리해야 하는 것에서 오는 관리의 부담 보다 더 좋을 수도 있습니다. 특히 데이터셋이 커지는 경우에는 그렇습니다. 하지만 원격 쿼리는 서비스 간에 많은 결합을 추가하게 되고 런타임에서 서비스들을 서로 묶어 버리게 됩니다.

데이터를 내재화하면 다른 컨텍스트로 원격 쿼리를 보내는 일을 피할 수 있습니다. 바로 관련된 크로스컨텍스트 데이터에 대해 로컬 카피를 저장하는 것입니다. 위의 JUST DRIVE 예에서 본 것처럼 문서를 표시할 때 마다 프로필 서비스에서 관련 저자 정보를 요청하지 않기위해 우리는 저자 정보를 복제하여 문서 마이크로서비스 내에 로컬 카피로 저장해 두었습니다. 이렇게 중복된 데이터는 원본 데이터와 싱크를 맞출 필요가 있습니다. 원본 데이터가 변경되는 즉시 로컬 카피도 업데이트해야 하는 것입니다. 수정된 데이터에 대한 알림을 받기 위해 관련 서비스들은 변경된 데이터를 포함하는 이벤트를 구독하고 있다가 자신의 로컬 카피를 그에 맞게 업데이트합니다. 이 예시에서 이벤트는 데이터 복제를 위한 용도로 사용되었으며 이를 통해 원격 쿼리의 필요성은 사라지고 서비스 간 결합도는 낮아집니다. 또한 자율성도 높아지는데 왜냐하면 이 서비스는 이제 이렇게 갖게된 로컬 카피를 이용해 원하는 무엇이든할 수 있기 때문입니다.

이벤트 주도 서비스 통신에서 우리는 아파치 카프카(Apache Kafka)를 초기부터 도입했습니다. 카프카는 분산(distributed), 내장애(fault-tolerant), 스케일가능한(scalable) 커밋 로그 서비스입니다. 처음에우리는 주로 알림 및 데이터 복제 용으로 아파치 카프카를 사용했습니다. 최근에는 아파치 카프카 스트림을 도입해 하나의 진실의 근원(shared source of truth)으로 사용하고자 했습니다. 이를 통해 데이터 복제 오버헤드를 없애고 서비스들의 확장성(pluggability)도 높이고 새로운 서비스의 진입 장벽도 낮출 수 있습니다.

스트림이란 바운드되지 않고 순서가 없는 연속되고 구조화된 데이터 기록의 흐름입니다. 하나의 데이터 레코드는 키-값의 쌍으로 구성됩니다.

아파치 카프카 스트리밍을 이용하여 서비스를 시작하면 카프카 토픽 스트림에 적재되는데 이는 서비스 스코프의 관점에서 처리가 가능합니다. 토픽(topic)이란 어떤 서비스가 어떤 이벤트를 발생시키고 구독할 수 있을지 나누어 놓은 논리적 카테고리입니다. 각각의 스트림은 상태 저장소에 버퍼링 되며 상태 저장소는 경량의 임베디드 디스크 데이터베이스입니다. 적재된 스트림은 코드내에서 사용될 수 있으며 카프카 브로커에서는 실행되지 않고 마이크로서비스의 프로세스 상에서 실행됩니다. 스트림을 사용하면 필요할 때 언제라도 데이터를 사용할 수 있으며 이를 통해 성능과 자율성을 증가시킬 수 있습니다.

아파치 카프카에는 Streams API가 탑재되어 있습니다. 스트림은 DSL(Domain Specific Language)을 이용해 서로 합쳐지거나 필터링할 수도 있고 구루핑하거나 종합할 수도 있습니다. 또한 스트림 내의 메세지에는 map, transform, peek과 같은 함수와 비슷한 작업을 한 번에 수행할 수도 있습니다.

스트림 처리를 구현할 때는 보통 스트림과 데이터베이스가 모두 필요합니다. 카프카의 Streams API는 스트림과 테이블에 대한 추상화를 통해 이러한 기능을 제공합니다. 사실 스트림과 테이블 사이에는 밀접한 연관이 있는데 이를 스트림테이블 이중성(stream-table duality)라고 부릅니다. 스트림은 테이블의 변화에 대한 기록으로 볼 수 있습니다. 스트림에 있는 각각의 데이터는 테이블의 상태 변화를 기록한 것이기 때문입니다. 또한 테이블은 스트림의 각 키의 최신 값에 대한 특정 시간의 스냅샷으로 생각할 수 있습니다.

문서에 작성자 정보를 함께 표시하고자 할 때 카프카 스트림을 이용하여 아래와 같이 구현할 수 있었습니다. 문서 서비스가 문서 토픽으로부터 KStream을 생성하고 프로필 토필에서 오는 저자 관련 프로필 데이터를 이용해 문서에 저자 정보를 추가합니다. 이를 위해 문서 서비스는 프로필 토픽으로부터 KTable을 생성합니다. 이제 우리는 스트림과 테이블 사이에 조인(join)을 걸 수 있으며 그 결과를 새로운 상태 저장소로서 저장할 수 있습니다. 이는 외부에서도 접근할 수 있고 결국 머티리얼라이즈드뷰(Materialized View) 처럼 작동합니다. 프로필이나 문서가 업데이트 될 때마다 이와 관련된 머티리얼라이즈드뷰도 업데이트 됩니다.

다른 이벤트 기반 접근방식과 다르게 아파치 카프카 스트림은 로컬 카피를 유지할 필요가 없습니다. 따라서 데이터 중복이나 싱크의 오버헤드가 없어집니다. 아파치 카프카는 데이터가 필요로하는 곳으로 데이터를 보내고 여러분의 서비스와 동일한 프로레스에서 동작합니다. 이는 확장성을 증가시키므로 새로운 서비스를 이에 적용하면 별도의 데이터 스토어를 설정할 필요 없이 스트림을 바로 사용할 수 있습니다. 오버헤드는 줄고 성능과 자율성은 증가하며 새로운 서비스의 진입 장벽은 낮아집니다.

이러한 전환 과정은 따로 떨어져 진행되는 것이 아니라 다양한 환경의 영향을 받습니다. 팀의 크기, 구조, 익숙한 기술 등이 영향을 미칠 수 있습니다. 특히 데브옵스(DevOps) 경험이 없는 작은 조직이라면 이러한 전환 과정은 느릴 수밖에 없습니다.

만약 기존의 시스템을 계속해서 유지보수해야 한다면 이 또한 전환 과정에 영향을 미칩니다. 당연히 유지보수에 들이는 시간만큼 전환 과정은 느려집니다. 그리고 구동환경 또한 영향을 미칩니다. 현재 온프레미스를 사용 중입니까 아니면 클라우드를 사용합니까? API-Gateway와 같은 관리형 서비스를 사용할 수 있습니까 아니면 이런 것들을 스스로 만들어야 합니까?

만약 여러분의 전략이 짧은 시간에 새로운 기능을 추가하는 것이라면 새로운 요구사항을 어디에 구현할지 결정하는데 어려움을 격을지도 모릅니다. 새로운 서비스에 구현하자니 시간이 많이 걸릴 듯 하고 그렇다고 기존의 모노리스에 구현하자니 모노리스가 줄어드는 것이 아니라 다시 커지는 위험이 있습니다.

다음에 마이크로서비스를 도입한다면 무엇을 다르게 할까

우선 저는 조직의 전략이 마이크로서비스의 목적인 제품 개발속도의 최적화와 변경 사항을 독립적이고 빠르게 배포하는 것에 잘 맞춰저 있는지 확인해보겠습니다. 만약 여러분의 조직이 긴 릴리즈 사이클이나 모든 것을 한 번에 배포하는데 중점을 둔다면 마이크로서비스는 아마도 최선의 선택이 아닐 것입니다. 왜냐하면 그런 조직에서는 마이크로서비스의 장점을 최대한 누릴 수 없기 때문입니다.

마이크로서비스로의 여행을 떠나기로 결정했다면 경영진을 포함한 모두가 이를 위해 노력하는 것이 필요합니다. 그리고 모든 사람들이 이 여행이 복잡하고 시간이 많이 걸린다는 점을 아는 것이 필요합니다. 특히 많은 경험이 없는 초반에는 더더욱 그렇습니다.

제품과 잘 정렬되어 있고(product-aligned) 여러 부서에 걸쳐 있는(cross-functional) 자주적인 팀은 마이크로서비스와 잘 어올립니다. 하지만 데브옵스 문화로 이동하는 것은 매우 초창기부터 잘 생각해봐야 하는 문제입니다. 모든 팀이 지속적인 이터레이션(iteration)에 대해 준비되어 있어야 하며 자신들이 책임져야 하는 서비스를 개발, 배포, 운영, 모너터링을 할 수 있어야 합니다.

모노리스를 여러개의 독립적인 서비스로 분해해내는 것은 단지 전체 과정의 일부일 뿐입니다. 이를 운영하는 것은 또 다른 과제입니다. 더 많은 서비스를 가지게 될 수록 빌드와 배포 과정을 자동화하는 것은 필수적입니다.

만약 이 모든 일을 다시 해야 한다면 저는 추출하기 쉬운 작은 후보에서부터 시작할 것입니다. 그리고 이 첫 서비스에서부터 서비스의 분해뿐만 아니라 빌드 및 배포 자동화, 모니터링에도 집중할 것입니다. 이렇게 만든 서비스는 앞으로 만들 모든 서비스의 기초가 됩니다. 이 기초를 만들기 위해 각 팀에서 선발된 사람들로 모인 임시의 태스크포스를 만드는 것이 도움이 될 것입니다.

각각의 마이크로서비스는 처음부터 자기 자신의 CI/CD 파이프라인을 가져야 합니다. 또 한 가지 고려할 수 있는 것은 각각의 마이크로서비스를 컨테이너화하여 경량의 캡슐화된 런타임 환경을 갖도록 하는 것입니다. 이는 특히 서비스를 클라우드 환경에서 실행할 때 유용합니다.

또한 로그 취합(log aggregation)을 비롯한 모니터링도 초기 단계부터 신경써야 하는 부분입니다. 서버 자체에 대한 모니터링 뿐만 아니라 요청지연 시간이나 산출량, 에러 비율등과 같은 서비스 지표도 서비스의 건강과 가용성을 확인하기 위해 필요합니다. 시간형식(ISO8601)이나 표준시(UTC)와 같은 로그 출력물에 대한 표준화 및 구조화 그리고 연관 아이디 및 로그 취합에 대해 요청 콘텍스트를 도입하는 일 등을 통해 진단 및 포렌직 과정을 수립할 수 있습니다.

많은 것들이 초반에 미리 수행되어야 하는데 이는 시간이 많이 드는 일이기도 하고 조직 전체가 이러한 부담에 대해 이해하는 것이 필요하기도 합니다. 마이크로서비스는 제품 개발에 최대한의 속도를 내기 위한 투자이지 비용을 줄이기 위한 것은 아닙니다.

시장에서의 경쟁력을 유지하기 위해 제품 개발 속도 및 지속적인 개선은 경쟁자로부터 우리를 차별화하기 위한 주된 요소입니다. 마이크로서비스는 제품 개발 속도를 높여 주고 지속적인 개선에 도움을 주지만 경영진을 포함한 모든 사람들이 이를 위해 노력해야 합니다.

저자에 대해

Susanne Kaiser는 독일 함부르크에서 일하는 기술 컨설턴트이며 스타트업에서 CTO로 일하면서 SaaS 제품을 모노리스에서 마이크로서비스로 전환한 경험을 갖고 있다. 컴퓨터 공학을 공부했으며 15년 이상 소프트웨어 개발 및 아키텍처와 관련하여 일을 하며 다양한 국제 컨퍼런스에서 발표하기도 했다.




SOURCE

LEAVE A REPLY

Please enter your comment!
Please enter your name here