마이크로 서비스(Micro Service)
마이크로서비스는 애플리케이션을 작은 독립된 서비스들로 나누어 개발하고 운영하는 아키텍처 스타일이다.
각 서비들은 독립적으로 배포되고 실행되며, 고유한 비즈니스 기능을 수행한다. 이들은 보통 REST API, 메시지 브로커 등을 통해 상호작용하며, 서로 다른 기술 스택을 사용할 수도 있다.
마이크로서비스의 주요 장점은 확장성과 독립적인 개발 가능성이다.
각 서비스가 독립적으로 확장 가능하고, 특정 서비스에 문제가 생겨도 다른 서비스에 영향을 최소화할 수 있다.
1. 마이크로 서비스 개발 흐름
바이너리 → 도커 이미지 → 컨테이너 레지스트리에 이미지 푸시 → 헬름 차트 작성 → 쿠버네티스 클러스터 배포
언어나 프레임워크를 이용해서 개발한 소스를 Makefile로 빌드하여 바이너리를 생성한다.
운영환경에서는 바이너리로 실행될 수도 있고, 도커 이미지, 쿠버네티스 파드 등 다양한 런타임 환경에 배포하고 운영할 수 있다.
Dockerfile 등을 사용해서 이미지를 빌드하는데 도커 이미지가 여러 개라면, Docker-compose를 이용해서 도커 런타임에 배포한다.
도커 이미지를 참조해서 헬름 차트를 만들고, 헬름 차트를 쿠버네티스에 배포한다.
2. CQRS
CQRS (Command and Query Responsibility Segregation): 명령과 조회의 책임 분리에 초점을 맞춘 아키텍처
이 패턴은 데이터의 읽기와 쓰기를 별도의 모델로 처리하며, 이를 통해 성능, 확장성, 유지보수성을 개선할 수 있다.
마이크로서비스 아키텍처에서 CQRS는 서비스 간의 책임을 명확히 분리하고, 데이터 흐름 및 비즈니스 로직을 효율적으로 관리하기 위해 자주 사용된다.
마이크로서비스와 CQRS를 결합하면, 각 서비스가 읽기와 쓰기 모델을 독립적으로 구현하며, 이를 통해 성능과 확장성을 극대화할 수 있다.
대다수의 애플리케이션에서 읽기와 쓰기는 많이 다르다.
쓰기는 정규화된 단일 엔트리에 영향을 주는데 반해, 질의는 소스 범위에서 정규화되지 않은 데이터를 조회할 수 있다.
- Command 서비스와 Query 서비스 분리: Command(쓰기)와 Query(읽기)를 각각 독립된 마이크로서비스로 분리한다.
- 데이터 저장소 분리: 각 서비스가 독립된 데이터 저장소를 가질 수 있다.
- 예: Command는 관계형 데이터베이스, Query는 NoSQL 또는 캐싱 시스템 사용, DynamoDB + Redis, MySQL + MongoDB 등
- 데이터 저장소가 분리되면, 각 작업에 최적화된 방식으로 설계할 수 있다.
- 영속적인 트랜잭션 저장은 PostgreSQL을 이용하고, 인덱스 조회 질의에서는 Elastic Search나 Redis를 활용해 읽기 성능을 극대화할 수 있다.
- 이벤트 기반 통신: Command와 Query 간 데이터 동기화를 위해 이벤트를 활용한다.
- Command 서비스는 작업 후 이벤트를 발생시키고, Query 서비스는 이를 구독하여 데이터를 업데이트한다.
- 예: Kafka, RabbitMQ, Pulsar, Redis Streams 같은 메시지 브로커 사용
- 하지만 이러한 접근법은 설계 및 구현의 복잡성을 증가시킬 수 있으며, 데이터 일관성이 일시적으로 깨질 가능성이 있다. 이를 해결하기 위해, 이벤트 소싱(event sourcing)이나 최종 일관성 모델을 활용할 수 있다.
장점
- 확장성
- 독립적인 크기 조정: 디플로이먼트, HPA, VPA를 이용해서 수평 및 수직 확장 가능
- 읽기와 쓰기를 독립적으로 확장할 수 있어 트래픽 증가에 유연하게 대응 가능
- 예: 쓰기는 고성능 DB로 최적화하고, 읽기는 캐싱 시스템으로 확장
- 최적화된 데이터 스키마: 질의 쪽에는 쿼리에 최적화된 스키마를 사용하고, 쓰기 쪽에는 업데이트에 최적화된 스키마를 사용한다.
- 관심사의 분리: 대부분의 복잡한 비즈니스 논리는 쓰기 모델로 이동하고, 읽기 모델은 상대적으로 간단해진다.
- 보안 강화: 데이터에서 올바른 도메인 엔티티만 쓰기를 수행할 수 있는지 쉽게 확인할 수 있다.
- 비즈니스 로직 단순화
- Command는 데이터 변경 로직에 집중하고, Query는 읽기 성능 최적화에만 집중한다.
- 단순한 쿼리: 읽기 데이터베이스에서 구체화된 뷰를 저장하여 쿼리를 실행할 때 애플리케이션에서 복잡한 조인이 발생하는 것을 방지한다.
마이크로서비스 기반으로 개발된 많은 오픈소스 아키텍처로 내부적으로 CQRS를 구현한다.
대용량 데이터를 처리할 때 디스크 IO에서 병목현상이 발생하는 것을 방지하기 위해서 내부적으로 질의와 쓰기 프로세스를 분리하거나 레디스를 캐시로 사용해서 데이터를 관리하고 객체 스토리지에 장기간 데이터를 보관하며 병렬 처리하는 것 등이 실제 사용되는 방식이다.
그라파나 관측 가능성이나 엘라스틱서치도 동일하게 작동한다.
대용량 데이터를 다루고자 하는 경우는 튜닝을 하는 과정에서 읽기와 쓰기 비율을 조정하는 등 거의 무조건 발생한다.