슬슬 프로젝트의 윤곽이 잡혀가는 요즘, 프로젝트에 적용한 아키텍처에 대한 경험을 회고하고자 합니다.
특히 마이크로서비스 아키텍처(MSA) 와 API 게이트웨이 에 대해 이야기하겠습니다.
1. Monolithic vs Mircoservice
처음 프로젝트를 시작할 때, 프로젝트의 전체적인 아키텍처를 선택해야만 합니다. 어떤 선택지가 있었을까요..?
모놀리식 아키텍처 - Monolithic Architecture
전통적인 방식으로, 애플리케이션의 모든 기능이 하나의 거대한 서비스 안에 통합된 구조입니다. 개발 초기에는 단순하고 빠르게 개발할 수 있다는 장점이 있지만, 프로젝트의 규모가 커질수록 다음과 같은 단점이 나타납니다.
- 낮은 유연성: 작은 수정 사항이 전체 애플리케이션의 재배포를 요구합니다.
- 기술 스택의 종속성: 전체 서비스가 하나의 기술 스택에 묶여있습니다.
- 확장성의 한계: 특정 기능에 대한 트래픽이 증가해도, 전체 애플리케이션을 확장해야 하므로 비효율적입니다.
따라서 큰 규모의 프로젝트보다는 작은 규모의 프로젝트에 어울리는 아키텍처라고 생각했습니다.
마이크로서비스 아키텍처 - Microservices Architecture, MSA
애플리케이션을 기능별로 잘게 쪼개어, 독립적으로 배포하고 확장할 수 있는 작은 서비스들의 조합으로 만드는 구조입니다. Lostark-Simulator 프로젝트는 다음과 같은 명확한 기능 구분이 있었기에 MSA가 적합하다고 판단했습니다.
- Data Service: 캐릭터 정보, 스킬, 장비 등 게임의 핵심 데이터를 관리합니다.
- Simulator Service: 복잡한 전투 로직을 수행하고 그 결과를 계산합니다.
- API Gateway: 클라이언트의 모든 요청을 받아 적절한 서비스로 라우팅합니다.
- Frontend Service: 사용자가 캐릭터 정보를 보거나, 시뮬레이션을 실행하는 Website 입니다.
2. Lostark-Simulator Architecture
프로젝트의 전체 아키텍처는 다음과 같습니다.
- Frontend (React): 사용자가 캐릭터를 조회하거나 시뮬레이션을 실행하는 Website 입니다.
- API Gateway: 모든 클라이언트(Frontend)의 요청을 받는 단일 진입점입니다.
- Data Service: MongoDB와 같은 데이터베이스와 통신하며, 캐릭터 정보 및 게임 데이터를 CRUD하는 역할을 담당합니다.
- Simulator Service: Redis로부터 필요한 데이터를 받아, 복잡한 시뮬레이션 로직을 수행합니다.
- Redis: 자주 사용하는 Data를 Data Service로부터 매번 받아오지 않고 caching합니다.
3. API-Gateway의 역할
MSA에서 API 게이트웨이는 단순히 요청을 전달하는 프록시 서버 이상의 역할을 합니다.
- 단일 진입점 (Single Point of Entry): 클라이언트는 여러 개의 백엔드 서비스 주소를 알 필요 없이, 오직 API 게이트웨이의 주소만 알면 됩니다. 이로 인해 클라이언트와 백엔드 서비스 간의 결합도가 낮아집니다.
- 라우팅 (Routing):
/api/characters/*
와 같은 요청은 Data Service로,/api/simulator/*
와 같은 요청은 Simulator Service로 보내주는 길목 역할을 합니다. 이를 통해 서비스의 내부 구조를 클라이언트로부터 숨길 수 있습니다. - 인증 및 인가 (Authentication & Authorization): 모든 요청에 대해 공통적으로 필요한 사용자 인증(로그인 여부 확인 등)을 API 게이트웨이에서 중앙 집중적으로 처리할 수 있습니다.
- 로드 밸런싱 및 장애 처리: 특정 서비스에 여러 인스턴스가 실행될 경우, API 게이트웨이가 트래픽을 분산시켜주거나, 특정 서비스에 장애가 발생했을 때 다른 서비스에 영향이 가지 않도록 격리하는 역할을 수행할 수 있습니다.
4. MSA와 API 게이트웨이 도입의 장단점
프로젝트를 진행하며 느꼈던 장점과 단점은 다음과 같습니다.
장점
- 독립적인 개발 및 배포: 각 팀(또는 개인)이 맡은 서비스를 다른 서비스에 대한 걱정 없이 독립적으로 개발하고 배포할 수 있었습니다. 예를 들어, 시뮬레이션 로직의 변경이 데이터 서비스의 재배포를 요구하지 않습니다.
- 높은 확장성: 만약 시뮬레이션 요청이 폭주한다면, 다른 서비스는 그대로 두고 Simulator Service만 수평적으로 확장(서버 증설)하여 부하에 대응할 수 있습니다.
- 기술 스택의 유연성: 각 서비스는 자신에게 가장 적합한 기술 스택을 선택할 수 있습니다. 예를 들어, 데이터 서비스는 Node.js로, 시뮬레이터 서비스는 계산 성능이 더 좋은 Python이나 Go로 구현할 수도 있습니다.
단점
- 설계의 복잡성: 서비스 간의 통신 방법, 데이터의 일관성 유지 등 모놀리식 아키텍처에 비해 고려해야 할 점이 많습니다.
- 테스트의 어려움: 전체 시스템의 통합 테스트를 진행하기가 상대적으로 까다롭습니다.
- 네트워크 통신 비용: 서비스 간의 호출이 모두 네트워크를 통해 이루어지므로, 모놀리식 구조에 비해 응답 시간이 길어질 수 있습니다.
5. MSA 도전 과제 해결 방안
마이크로서비스 아키텍처는 많은 장점을 제공하지만, 동시에 새로운 도전 과제들을 제시합니다. 저희 프로젝트에서 직면했거나 고려했던 주요 도전 과제들과 그 해결 방안은 다음과 같습니다.
1. 설계의 복잡성 (서비스 간 통신 및 데이터 일관성)
- 서비스 간 통신 방법:
- 동기 통신 (Synchronous Communication): REST API (HTTP)나 gRPC와 같은 프로토콜을 사용하여 실시간으로 응답을 주고받습니다. 저희 프로젝트에서는 주로 REST API를 사용하고 있습니다.
- 비동기 통신 (Asynchronous Communication): 메시지 큐(Kafka, RabbitMQ 등)를 활용하여 서비스 간의 결합도를 낮추고, 이벤트 기반 아키텍처를 구축할 수 있습니다. 이는 특정 서비스의 장애가 다른 서비스에 미치는 영향을 최소화하고, 대규모 트래픽 처리에도 유리합니다.
- 데이터 일관성 유지:
- Saga 패턴: 분산된 여러 서비스에 걸쳐 비즈니스 트랜잭션을 관리하는 패턴입니다. 각 서비스는 자신의 로컬 트랜잭션을 완료하고 다음 서비스에 이벤트를 발행하며, 실패 시 보상 트랜잭션을 통해 롤백합니다.
- 이벤트 기반 아키텍처: 각 서비스가 자신의 데이터를 소유하고, 데이터 변경 시 이벤트를 발행하여 다른 서비스가 이를 구독하고 자신의 데이터를 업데이트하는 방식입니다. 이를 통해 서비스 간의 직접적인 데이터 종속성을 줄이고 최종 일관성(Eventual Consistency)을 달성합니다.
- API Gateway의 활용: API 게이트웨이에서 인증, 로깅, 요청 라우팅 등 공통적인 횡단 관심사(Cross-cutting Concerns)를 처리하여 개별 서비스의 복잡성을 줄이고 서비스 개발에 집중할 수 있도록 합니다.
2. 테스트의 어려움 (통합 테스트)
- 단위 테스트 (Unit Test): 각 마이크로서비스는 독립적인 비즈니스 로직을 가지므로, 개별 서비스 내에서 철저한 단위 테스트를 수행하는 것이 중요합니다.
- 통합 테스트 (Integration Test):
- 계약 테스트 (Contract Test): 서비스 간의 API 호출 규약(Contract)을 테스트하여, 한 서비스의 변경이 다른 서비스에 예상치 못한 영향을 주지 않도록 보장합니다. (예: Pact)
- 엔드투엔드 테스트 (End-to-End Test): Docker Compose 등을 활용하여 전체 마이크로서비스 스택을 로컬 환경에 배포하고, 실제 사용자 흐름과 유사하게 테스트를 수행합니다. 이는 시스템의 전반적인 동작을 검증하는 데 필수적입니다.
- 테스트 자동화: CI/CD 파이프라인에 단위, 통합, 엔드투엔드 테스트 단계를 포함하여 코드 변경 시마다 자동으로 테스트를 실행하고 피드백을 받습니다.
- Mocking/Stubbing: 외부 서비스나 데이터베이스 의존성을 Mocking하거나 Stubbing하여 테스트 환경을 단순화하고 테스트 실행 속도를 높입니다.
3. 네트워크 통신 비용 (지연 시간 및 오버헤드)
- 최적화된 통신 프로토콜 사용: REST API는 유연하지만, 대량의 데이터나 고성능이 요구되는 경우 gRPC와 같은 이진 프로토콜을 고려할 수 있습니다. gRPC는 Protocol Buffers를 사용하여 더 효율적인 직렬화와 압축을 제공합니다.
- 캐싱 (Caching): Redis와 같은 인메모리 캐시를 사용하여 자주 접근하는 데이터를 빠르게 제공합니다. 저희 프로젝트의 시뮬레이터 서비스에서 게임 데이터를 캐싱하는 것이 좋은 예시입니다. 이는 데이터베이스나 외부 API 호출 횟수를 줄여 네트워크 지연 시간을 크게 단축시킵니다.
- 데이터 전송량 최소화: 서비스 간 통신 시 필요한 최소한의 데이터만 전송하도록 설계합니다. 불필요한 필드를 제거하고, 데이터 구조를 최적화합니다.
- 서비스 배치 최적화: 마이크로서비스들을 물리적으로 가까운 서버나 동일한 네트워크 존에 배포하여 네트워크 지연 시간을 최소화합니다. 클라우드 환경에서는 가용성 영역(Availability Zone) 내 배치를 고려할 수 있습니다.
5. Conclusion
MSA는 복잡한 초기 설계 단계를 거쳐야 한다는 단점이 있지만, 각각의 서비스가 독립적이기 때문에, 독립적으로 개발할 수 있어 협업에 유용하고, 단순히 서비스를 추가해주면 되기때문에 확장성이 높고, 독립적인 기술 스택으로 개발할 수 있는 장점이 있습니다.
이번 프로젝트는 설계단계에서 세세한 명세를 정해주지 않는다면 개발단계에서 많은 시행단계를 갖게 되는 단점또한 느낄 수 있었습니다.
따라서 설계와 명세가 진짜진짜 중요한 아키텍처이고, 이러한 복잡한 설계와 명세 단계를 거치면 이후 개발과 운영 단계에서 큰 장점을 갖는 아키텍처임을 느꼈습니다.
많은 수의 Service를 거느리기 때문에 Front 단계에서 수많은 Service에 쉽게 접근 가능하도록 API-Gateway service를 만드는게 좋다는 것도 느꼈습니다.
다만 이렇게 Service를 쪼개게 되면, 단일 Service 프로젝트에 비해 네트워크 비용이 증가하기에 gRPC 와 같은 Protocol를 추가 학습해서 성능개선을 할 필요가 있습니다.
'프로젝트' 카테고리의 다른 글
Go vs Rust (0) | 2025.09.03 |
---|---|
CRA vs Vite (0) | 2025.08.19 |
REST, gRPC, Kafka (3) | 2025.08.12 |