📑 목차
- 왜 서버리스와 MSA를 함께 고려해야 할까요?
- 서버리스 아키텍처의 핵심 이점과 오해
- 비용 효율성: 실제 사용량 기반 과금의 힘
- 운영 부담 감소: 개발에만 집중할 수 있는 환경
- MSA 패턴과 서버리스의 시너지 효과
- 확장성 높은 서버리스 MSA 설계 패턴
- 비동기 통신과 이벤트 기반 아키텍처
- API Gateway와 Lambda를 활용한 RESTful API
- 데이터베이스 전략: Polyglot Persistence
- 서버리스 MSA 도입 시 고려할 점과 실제 겪은 어려움
- 콜드 스타트와 성능 최적화
- 분산 시스템의 복잡성 관리
- 비용 관리의 함정
- 전통적인 아키텍처 vs 서버리스 MSA: 실제 적용 시 비교
- 결론 및 앞으로의 전망
Image by Stufforge on Pixabay
왜 서버리스와 MSA를 함께 고려해야 할까요?
개발자라면 누구나 한 번쯤 서비스의 확장성 문제로 밤샘 고민을 해본 경험이 있을 겁니다. 초기에는 작은 규모로 시작했지만, 사용자 트래픽이 예상치 못하게 급증하거나 새로운 기능을 빠르게 추가해야 할 때, 기존 모놀리식 아키텍처는 발목을 잡는 경우가 많았습니다. 저 역시 수년 전, 갑작스러운 마케팅 성공으로 하루아침에 트래픽이 10배 이상 폭증했던 서비스에서 서버 증설과 코드 배포를 동시에 진행하며 진땀을 흘렸던 기억이 생생합니다. 이러한 경험 속에서 어떻게 하면 더 유연하고 확장성 높은 애플리케이션을 설계할 수 있을까에 대한 고민이 깊어졌습니다.
이러한 고민의 해답 중 하나로 서버리스 아키텍처와 마이크로서비스 아키텍처(MSA)가 떠올랐습니다. 각각의 개념은 이미 익숙할 수 있지만, 이 둘을 함께 조합했을 때 얻을 수 있는 시너지는 상상 이상입니다. MSA의 독립적인 서비스 단위가 서버리스의 함수 단위와 만나면서, 개발과 운영의 효율성을 극대화할 수 있었습니다. 직접 이 전략을 적용해 보니, 단순히 트래픽을 감당하는 수준을 넘어 개발팀의 생산성을 높이고, 비용을 절감하는 등 다양한 이점을 체감할 수 있었습니다.
서버리스 아키텍처의 핵심 이점과 오해
서버리스는 이름 때문에 서버가 없다고 오해하기 쉽지만, 사실은 서버 관리를 클라우드 제공업체에 맡기고 개발자는 코드 작성에만 집중할 수 있게 하는 패러다임입니다. 실제로 서버리스를 도입하면서 가장 크게 체감한 이점은 바로 운영 부담 감소와 비용 효율성이었습니다.
비용 효율성: 실제 사용량 기반 과금의 힘
기존에는 예측 불가능한 트래픽에 대비해 항상 일정 수준 이상의 서버 자원을 미리 확보해 두어야 했습니다. 이는 곧 유휴 자원 낭비로 이어지는 경우가 많았죠. 하지만 서버리스는 실제 사용량에 따라 과금됩니다. 함수가 실행된 시간, 메모리 사용량, 호출 횟수 등 정교한 지표로 비용이 책정되기 때문에, 트래픽이 적은 시간대에는 거의 비용이 발생하지 않았습니다.
예를 들어, 저희 팀에서 운영하는 특정 배치 작업은 하루에 단 몇 분만 실행되는데, 기존 VM 방식이었다면 24시간 VM을 켜두어야 했습니다. 이를 서버리스 함수로 전환하니, 월 수십만 원에 달했던 비용이 몇천 원 수준으로 대폭 절감되는 것을 직접 확인했습니다. 특히, 예측 불가능한 트래픽 스파이크가 발생했을 때도, 자동으로 확장되며 추가 자원에 대한 비용만 지불하면 되니, 예산 관리 측면에서도 큰 도움이 되었습니다.
운영 부담 감소: 개발에만 집중할 수 있는 환경
서버리스 환경에서는 서버 패치, 운영체제 업데이트, 용량 증설과 같은 인프라 관리 작업에 신경 쓸 필요가 없습니다. 클라우드 제공업체가 모든 것을 알아서 처리해 주기 때문입니다. 이는 개발팀이 인프라 관리에 쏟던 시간을 핵심 비즈니스 로직 개발에 온전히 집중할 수 있게 해주는 결정적인 요소였습니다.
실제로 서버리스 전환 후, 한 달에 약 20시간 이상 소요되던 서버 관리 및 모니터링 시간이 5시간 미만으로 줄어들었습니다. 이 절감된 시간을 신규 기능 개발이나 기존 시스템 개선에 투입할 수 있었고, 이는 곧 서비스 출시 주기를 단축하고 제품의 완성도를 높이는 결과로 이어졌습니다.
MSA 패턴과 서버리스의 시너지 효과
MSA(마이크로서비스 아키텍처)는 하나의 큰 애플리케이션을 작은 독립적인 서비스들로 분리하여 개발하는 방식입니다. 각 서비스는 자체 데이터베이스를 가질 수 있고, 독립적으로 배포 및 확장될 수 있습니다. 이러한 MSA의 특성은 서버리스 함수의 특성과 완벽하게 맞아떨어집니다.
저희 팀에서는 기존 모놀리식 서비스를 MSA로 전환하면서, 각 마이크로서비스를 AWS Lambda 함수로 구현했습니다. 이렇게 하니, 각 서비스는 자체적인 배포 파이프라인을 가지게 되었고, 특정 서비스에 문제가 발생하더라도 전체 시스템에 미치는 영향을 최소화할 수 있었습니다. 예를 들어, 사용자 인증 서비스에 문제가 생겨도 결제 서비스나 상품 조회 서비스는 정상적으로 작동하는 것을 확인했습니다.
특히, Lambda 함수는 특정 이벤트에 의해 트리거되므로, MSA의 이벤트 기반 통신과도 자연스럽게 결합됩니다. 이는 서비스 간의 결합도를 낮추고 유연성을 높이는 데 결정적인 역할을 했습니다.
# 예시: 사용자 정보를 처리하는 간단한 Lambda 함수 (Python)
import json
def lambda_handler(event, context):
try:
user_data = json.loads(event['body'])
user_id = user_data.get('userId')
user_name = user_data.get('userName')
if not user_id or not user_name:
return {
'statusCode': 400,
'body': json.dumps({'message': 'userId and userName are required'})
}
# 여기에 사용자 정보를 저장하거나 업데이트하는 로직 추가
print(f"Processing user: {user_id}, Name: {user_name}")
return {
'statusCode': 200,
'body': json.dumps({'message': f'User {user_id} processed successfully'})
}
except Exception as e:
print(f"Error processing request: {e}")
return {
'statusCode': 500,
'body': json.dumps({'message': 'Internal server error'})
}
위 코드처럼, 각 비즈니스 로직을 작은 Lambda 함수로 캡슐화하면, 필요한 경우 해당 함수만 업데이트하고 배포할 수 있습니다. 이는 개발 속도를 비약적으로 향상시켰고, 여러 개발자가 동시에 다른 마이크로서비스를 개발하는 데 아무런 충돌 없이 협업할 수 있게 해주었습니다.
Image by Nikiko on Pixabay
확장성 높은 서버리스 MSA 설계 패턴
서버리스와 MSA를 결합하여 확장성 높은 애플리케이션을 설계하기 위한 몇 가지 핵심 패턴이 있습니다. 실제로 이 패턴들을 적용하면서 시스템의 안정성과 유연성을 크게 향상시킬 수 있었습니다.
비동기 통신과 이벤트 기반 아키텍처
MSA에서 서비스 간의 통신은 매우 중요합니다. 특히 비동기 통신은 서비스 간의 결합도를 낮추고, 한 서비스의 장애가 다른 서비스로 전파되는 것을 방지하는 데 필수적입니다. 저희는 AWS SQS (Simple Queue Service)와 SNS (Simple Notification Service), 그리고 EventBridge를 적극적으로 활용했습니다.
예를 들어, 사용자 주문 처리 시스템을 설계할 때, 다음과 같은 흐름을 가집니다:
- 사용자가 주문하면
OrderServiceLambda 함수가 주문 정보를 DB에 저장하고, 주문 성공 이벤트를 SNS 토픽에 발행합니다. PaymentServiceLambda 함수는 해당 SNS 토픽을 구독하여 결제 처리를 진행하고, 결제 완료 이벤트를 다시 발행합니다.ShippingServiceLambda 함수는 결제 완료 이벤트를 받아 배송을 시작하고,NotificationServiceLambda 함수는 사용자에게 알림을 보냅니다.
이러한 이벤트 기반 아키텍처는 각 서비스가 독립적으로 확장될 수 있게 하며, 한 단계에서 문제가 발생해도 전체 흐름이 멈추지 않고 재시도 로직을 통해 안정적으로 복구될 수 있도록 설계할 수 있었습니다. 실제로 트래픽이 급증하여 결제 서비스에 일시적인 부하가 걸려도, SQS 큐에 메시지가 쌓여있다가 서비스가 정상화되면 순차적으로 처리되는 것을 확인하며 안정성을 확보했습니다.
# 예시: SQS 메시지 전송 (pseudo-code)
import boto3
import json
sqs = boto3.client('sqs')
queue_url = 'YOUR_SQS_QUEUE_URL'
def send_order_message(order_details):
response = sqs.send_message(
QueueUrl=queue_url,
MessageBody=json.dumps(order_details),
DelaySeconds=0
)
print(f"Message sent: {response['MessageId']}")
# 예시: SQS 메시지 수신 Lambda 함수 (pseudo-code)
def lambda_handler(event, context):
for record in event['Records']:
message_body = record['body']
order_info = json.loads(message_body)
print(f"Processing order: {order_info}")
# 여기에 실제 주문 처리 로직 구현
API Gateway와 Lambda를 활용한 RESTful API
대부분의 웹 애플리케이션은 RESTful API를 통해 클라이언트와 통신합니다. 서버리스 환경에서는 API Gateway가 이 역할을 완벽하게 수행합니다. API Gateway는 들어오는 HTTP 요청을 받아 적절한 Lambda 함수로 라우팅해주는 프록시 역할을 합니다.
실제로 저희는 수십 개의 API 엔드포인트를 API Gateway를 통해 관리하고 있습니다. API Gateway는 자체적으로 인증 및 인가, 요청 스로틀링, 캐싱 등의 기능을 제공하여 백엔드 개발 부담을 크게 줄여주었습니다. 특히, API Gateway의 Custom Authorizer 기능을 활용하여 JWT(JSON Web Token) 기반 인증을 쉽게 구현할 수 있었고, 이는 보안 강화에 큰 도움이 되었습니다.
API Gateway와 Lambda를 함께 사용하면, 특정 API 엔드포인트에 대한 트래픽이 급증해도 Lambda가 자동으로 확장되어 요청을 처리하므로, 무한대에 가까운 확장성을 경험할 수 있습니다.
데이터베이스 전략: Polyglot Persistence
MSA에서는 각 마이크로서비스가 독립적인 데이터 저장소를 가질 수 있습니다. 이를 Polyglot Persistence라고 부르는데, 서비스의 특성에 맞춰 가장 적합한 데이터베이스를 선택할 수 있다는 장점이 있습니다.
저희 프로젝트에서는 다음과 같은 방식으로 데이터베이스를 활용했습니다:
- 사용자 프로필, 주문 내역 등 관계형 데이터가 중요한 서비스에는 Amazon Aurora Serverless를 사용했습니다. 필요에 따라 자동으로 확장 및 축소되어 비용 효율적이면서도 관계형 데이터의 강점을 살릴 수 있었습니다.
- 실시간으로 빠르게 읽고 써야 하는 로그 데이터, 캐싱, 세션 정보 등에는 Amazon DynamoDB와 같은 NoSQL 데이터베이스를 활용했습니다. DynamoDB의 온디맨드 용량 모드를 통해 예측 불가능한 트래픽에도 안정적으로 대응할 수 있었습니다.
- 검색 기능이 필요한 서비스에는 Amazon OpenSearch Service를 연동하여 강력한 검색 기능을 제공했습니다.
이처럼 각 서비스의 요구사항에 맞춰 최적의 데이터베이스를 선택함으로써, 시스템 전체의 성능과 확장성을 극대화할 수 있었습니다. 이는 모놀리식 아키텍처에서 하나의 거대한 데이터베이스에 모든 것을 담아야 했던 제약에서 벗어나, 데이터 접근 방식의 유연성을 확보할 수 있게 해주었습니다.
서버리스 MSA 도입 시 고려할 점과 실제 겪은 어려움
서버리스 MSA는 많은 이점을 제공하지만, 만능은 아닙니다. 실제로 도입 과정에서 여러 가지 어려움을 겪었고, 이를 해결하기 위한 노력이 필요했습니다.
콜드 스타트와 성능 최적화
콜드 스타트(Cold Start)는 서버리스 함수의 고질적인 문제 중 하나입니다. 함수가 오랫동안 호출되지 않으면 클라우드 제공업체가 해당 인스턴스를 회수하는데, 다시 호출될 때 새로운 인스턴스를 프로비저닝하는 데 시간이 걸립니다. 이로 인해 첫 요청에 대한 응답 시간이 길어지는 현상이 발생합니다.
저희 팀에서는 초기 도입 시 사용자 요청이 많은 API에서 콜드 스타트로 인해 응답 시간이 2~3초가량 지연되는 문제를 겪었습니다. 이를 해결하기 위해 몇 가지 방법을 적용했습니다:
- 메모리 최적화: Lambda 함수의 메모리를 충분히 할당하여 CPU 성능을 높이고, 의존성 로딩 시간을 단축했습니다. 실제로 128MB에서 512MB로 메모리를 늘렸을 때, 콜드 스타트 시간이 절반 가까이 줄어드는 것을 확인했습니다.
- Provisioned Concurrency (프로비저닝된 동시성): 특정 Lambda 함수에 대해 미리 일정 수의 인스턴스를 준비해 두는 기능을 사용했습니다. 이는 콜드 스타트 문제를 거의 완벽하게 해결해 주었지만, 유휴 상태에서도 비용이 발생하므로 신중하게 적용해야 합니다.
- 의존성 번들링 최적화: 불필요한 라이브러리를 제거하고, Lambda 레이어를 활용하여 공통 의존성을 분리함으로써 패키지 크기를 줄였습니다.
이러한 노력 덕분에 대부분의 API는 콜드 스타트 문제를 체감하기 어려운 수준으로 개선할 수 있었습니다.
분산 시스템의 복잡성 관리
MSA는 각 서비스가 독립적이므로, 전체 시스템을 이해하고 디버깅하는 것이 모놀리식보다 복잡해질 수 있습니다. 한 요청이 여러 Lambda 함수와 데이터베이스를 거쳐 처리될 때, 어디서 문제가 발생했는지 추적하기가 쉽지 않습니다.
이러한 문제를 해결하기 위해 모니터링과 로깅에 많은 투자를 했습니다. AWS CloudWatch를 통해 모든 Lambda 함수의 로그를 중앙 집중화하고, AWS X-Ray를 사용하여 분산 트레이싱을 구현했습니다. X-Ray를 통해 요청의 시작부터 끝까지 모든 서비스 호출 경로와 각 단계에서의 소요 시간을 시각적으로 확인할 수 있었고, 이는 문제 발생 시 원인을 빠르게 파악하는 데 결정적인 도움이 되었습니다.
또한, 각 Lambda 함수에 상관관계 ID(Correlation ID)를 부여하여, 요청이 여러 서비스를 거치더라도 동일한 ID로 로그를 묶어 볼 수 있도록 구현했습니다. 이는 복잡한 분산 환경에서 로그를 분석하는 시간을 크게 단축시켜주었습니다.
비용 관리의 함정
서버리스는 비용 효율적이지만, 잘못 관리하면 예상치 못한 비용이 발생할 수 있습니다. 저희 팀에서도 초기에는 몇 가지 실수를 통해 불필요한 비용을 지불했던 경험이 있습니다.
- 무한 루프: S3 버킷에 파일이 업로드되면 Lambda가 트리거되고, 그 Lambda가 다시 S3에 파일을 저장하는 과정에서 무한 루프가 발생하여 수십만 번의 함수 호출이 발생했습니다. 즉시 감지하여 중단했지만, 짧은 시간 내에 상당한 비용이 발생할 뻔했습니다.
- 잘못된 트리거 설정: 개발 중 테스트 목적으로 설정했던 트리거가 프로덕션 환경으로 배포되면서 불필요한 함수가 계속 실행되는 경우도 있었습니다.
이러한 경험을 통해 비용 모니터링 및 알림 설정의 중요성을 깨달았습니다. CloudWatch Alarms를 사용하여 예상 비용 임계치를 설정하고, 이를 초과하면 즉시 알림을 받도록 구성했습니다. 또한, 각 Lambda 함수의 메모리 및 타임아웃 설정을 최적화하고, 불필요한 로깅을 줄이는 등 지속적인 비용 최적화 작업을 수행했습니다.
Image by msandersmusic on Pixabay
전통적인 아키텍처 vs 서버리스 MSA: 실제 적용 시 비교
직접 모놀리식, 컨테이너 기반 MSA, 그리고 서버리스 기반 MSA를 모두 경험해 본 결과, 각각의 아키텍처는 장단점이 명확했습니다. 아래 표는 저희 팀의 경험을 바탕으로 주요 지표들을 비교한 것입니다.
| 구분 | 모놀리식 아키텍처 | 컨테이너 기반 MSA | 서버리스 기반 MSA |
|---|---|---|---|
| 초기 개발 속도 | 빠름 (단일 코드베이스) | 보통 (인프라 설정 필요) | 보통 (각 서비스 분리, 클라우드 설정) |
| 운영 복잡성 | 낮음 (단일 배포) | 높음 (컨테이너 오케스트레이션) | 낮음 (서버 관리 없음) |
| 확장성 | 어려움 (전체 스케일업) | 높음 (서비스별 스케일아웃) | 매우 높음 (자동 스케일링) |
| 비용 예측 | 쉬움 (고정 자원) | 보통 (사용량 기반) | 어려움 (호출 횟수 기반, 최적화 필요) |
| 기술 스택 유연성 | 낮음 (단일 스택) | 높음 (다양한 언어/프레임워크) | 매우 높음 (각 함수별 자유로운 선택) |
이 표에서 볼 수 있듯이, 서버리스 기반 MSA는 특히 운영 복잡성과 확장성 측면에서 압도적인 강점을 보였습니다. 비용 예측이 다소 어렵다는 단점은 있지만, 이는 모니터링과 최적화 노력을 통해 충분히 관리 가능한 수준이었습니다.
결론 및 앞으로의 전망
직접 서버리스 아키텍처와 MSA를 결합하여 애플리케이션을 설계하고 운영해 본 결과, 이는 높은 확장성, 운영 효율성, 그리고 개발 생산성 향상이라는 세 마리 토끼를 모두 잡을 수 있는 강력한 전략이라는 것을 확신하게 되었습니다. 물론, 콜드 스타트나 분산 시스템 관리의 복잡성 같은 도전 과제들도 있었지만, 클라우드 제공업체의 발전하는 서비스와 적절한 설계 패턴을 통해 충분히 극복할 수 있었습니다.
이러한 아키텍처는 특히 다음과 같은 조직이나 상황에 적합하다고 생각합니다:
- 예측 불가능한 트래픽 변동이 심한 서비스
- 빠르게 신규 기능을 개발하고 배포해야 하는 스타트업 또는 애자일 조직
- 인프라 운영보다는 핵심 비즈니스 로직 개발에 집중하고 싶은 개발팀
클라우드 기술은 계속 발전하고 있으며, 서버리스는 그 중심에서 중요한 역할을 할 것입니다. 앞으로도 더 많은 기업들이 서버리스 MSA를 통해 혁신적인 서비스를 만들어나갈 것이라고 생각합니다. 저의 경험이 여러분의 아키텍처 설계 고민에 작은 도움이 되었기를 바랍니다.
여러분은 어떤 확장성 전략을 사용하고 계신가요? 서버리스 MSA를 도입하면서 겪었던 특별한 경험이나 팁이 있다면 댓글로 공유해 주세요!