안녕하세요. 번개장터 엔지니어 이명휘입니다. 해당주제의 두번째 포스팅이 늦어져서 죄송합니다. 저도 인간이기에 세상의 속세에 지배되어 글을 쓰는 것이 늦어졌습니다. 이를 반성하며 현재까지 번개장터가 기술적으로 꾸준히 성장한 부분을 자주 써 내려 가도록 하겠습니다. 참고로 기술과 관련된 저의 개인 브런치도 운영하고 있기에 심심하시면 들어오셔서 보시는 것도 좋을 것 같습니다. 개인 블로그
이번 글에서는 번개장터가 새롭게 구현한 푸쉬시스템의 상세 구조를 중심으로 알아보겠습니다. 현재 2018년 10월을 기준으로 새로운 푸쉬시스템이 구현된 지 1년이 다 되어가는 시점입니다. 그만큼 번개장터의 유저 수와 더불어 트래픽도 급격히 많아졌습니다. 다행히 현재까지 시스템이 잘 작동하고 있으며, 제가 이 글을 쓸 수 있는 이유도 현재까지 큰 문제가 발생하지 않았기 때문입니다.
1. 새로운 시스템 아키텍처의 선택
당시 겪고 있던 푸쉬시스템의 장애는 번개장터 신뢰성에 큰 위협을 초래하는 치명적 문제였기 때문에 시스템을 최대한 빠르게 적용해야 했습니다. 번개장터 엔지니어들은 앞서 개발된 시스템의 사례분석과 리서치 과정을 진행했습니다. 이를 통하여 다양한 아키텍처로 구성된 시스템 후보군을 만들게 되었고, 선택 범위는 크게 두가지 범주로 구성하게 되었습니다. 리서치를 통해 도출된 각각의 장점(파란색)과 단점(빨간색)도 함께 서술하였습니다.
- 전체적 아키텍처 측면
- AWS Lambda 함수기반의 SQS(Simple Queue Service)를 활용한 아키텍처[1]
- Lambda 함수를 활용하기 때문에 가격이 저렴
- 수 많은 Lambda Worker가 유동적으로 생성/소멸되기 때문에 대용량 처리에 용이
- 시스템을 독립적(Micro Service)으로 구현가능
- SQS의 메시지 사이즈 제한
- AWS SNS(Simple Notification Service) 활용
- 푸쉬를 위해 개발된 AWS 서비스
- 코드 레벨에서의 관리가 힘든 부분(커스터마이징 불가능)
- 기존 사례 및 레퍼런스의 부족
- AWS Cloud Watch Log 활용
- 번개장터와 합병된 셀잇에서 이미 구현되어 사용(검증)
- 원래 푸쉬를 위한 용도가 아닌 기형적인 형태
- 대용량 푸쉬 시스템으로 활용될 때 비용 측면을 예측하기가 난해
- 개발 언어 측면
- Golang
- 기존 코드를 일부 사용 가능
- 과거 코드 재사용에 따른 부작용
- Python
- 개발자 모두가 익숙하기 때문에 빠른 개발(생산성)에 용이
- 미래 유지보수에 용이
- Node.js
- 푸쉬 시스템 관련 레퍼런스가 풍부
- 능숙하지 않은 언어를 이용하는데에 따른 개발 지연
- 결과적으로 우리 번개장터는 개발의 용이성과 미래에도 단단한 시스템 아키텍처를 구현하기 위해 AWS Lambda 함수기반의 메시징큐를 구현하기로 결정했으며, 개발언어는 Python으로 선정하게 되었습니다.
- 전체적 아키텍처 측면
2. 새로운 시스템 아키텍처의 구현
- 최종적으로 구현된 번개장터의 새로운 푸쉬 시스템 아키텍처는 아래 그림과 같습니다. 클라이언트가 메인 API 서버에 푸쉬 요청을 하게 되면, 메시지 큐인 SQS를 이용하여 Lambda 함수로 구성된 여러 Worker에게 일을 시키는 형태입니다. 그림에서 보이는 요소들은 다음과 같은 역할을 수행하게 됩니다.
Lambda 함수 기반의 푸쉬 시스템 아키텍처
* SQS - 메인 API서버로 부터 오는 메시지가 쌓이는 큐
* CloudWatch Event Rule - SQS와 Lambda Consumer간 트리거
* Lambda Consumer - SQS에 쌓인 메시지를 가져와 Lambda Worker에 전달
* Lambda Worker - 실질적으로 푸쉬를 보내는 구현체 (APNS / GCM 로직)
- Lambda 함수는 배포 및 디버깅에 취약하고 스테이징 환경을 구현하기 까다로운 부분이 있습니다. 물론 현재는 Cloud9(현 AWS)에서 제작한 클라우드 기반의 통합 개발 환경을 Lambda 함수 작성 시 제공해주고 있지만 아직도 불편한 건 마찬가지입니다. 직접 구현해보신 분들은 공감하시는 내용일 거라 생각합니다. 이를 해결하기 위해 번개장터는 이미 널리 알려져 있는 도구인 APEX를 사용했습니다[2]. 아래 그림처럼 APEX는 Lambda 함수의 배포 및 디버깅 기능을 편리하게 제공합니다. 또한, Lambda 함수로 구현하기 어려운 스테이징 및 프로덕션 환경을 손쉽게 관리할 수 있도록 도와주기도 합니다. 비슷한 기능을 하는 Serveless라는 프레임워크도 존재하지만 APEX에 비해 비교적 어려운 사용성을 지니고 있었기에 Apex를 선택했습니다[3]. Lambda 함수의 핸들링 언어는 앞서 말씀드린 바와 같이 Python3.6을 사용했습니다.
Apex의 설정
주기적으로 SQS에 쌓이는 메시지들을 가져와 Lambda Consumer에서 사용하는 형태이기 때문에 트리거가 필요하게 됩니다. 하지만 AWS에서는 Lambda 함수와 SQS 간의 정식적인 트리거는 지원하지 않고 있습니다.(현재는 가능하다고 합니다.) 이 때문에 우리는 CloudWatch Event Rule를 이용했습니다. CloudWatch Event Rule은 Lambda 실행 예약 규칙을 리눅스의 Cron 표현식으로 설정할 수 있도록 지원합니다.
과거 번개장터 푸쉬시스템에서는 APNS와 GCM 라이브러리의 버전 문제가 의심되어 왔었기 때문에 Python에 제공하는 APNS 및 GCM 라이브러리를 정확하게 검증하는 과정을 거쳤습니다.
새로운 푸쉬시스템의 첫 배포는 1%의 특정 유저를 대상으로 테스트를 진행했고, 장애 유무에 따라서 점점 서비스 유저를 늘려가는 과정을 거쳤습니다. 모니터링 도구는 CloudWatch Log를 사용하였고, 푸쉬 상태에 대한 로그들을 남기도록 하였습니다. CloudWatch Log에 쌓이는 수만 개의 로그들을 시각화할 수 있기 때문에 푸쉬 전송의 실패와 성공에 대한 여부를 쉽게 판단해볼 수 있었습니다.
CloudWatch Log를 이용한 시각화
3. 문제 및 해결사항
CloudWatch Event Rule은 Cron 표현식을 활용하기 때문에 최소 배치 작업의 단위는 1분입니다. 따라서, Lambda Consumer도 1분 단위로 작업을 수행하기 때문에 실시간으로 들어오는 메시지를 처리하지 못하게 됩니다. 이런 이유로 단발적으로 소수의 메시지만 처리하고 함수가 내려가는 문제가 발생합니다. 이 부분의 해결 방법은 Lambda Consumer를 1분 동안 생존 및 유지시키면서 실시간으로 큐에 들어오는 메시지를 지속적으로 가져오도록 요청을 보내도록 하였습니다.
푸쉬 메시지 하나당 하나의 Lambda Worker를 생성하게 되면 메시지 저장을 위한 데이터베이스와의 커넥션 수가 늘어나는 문제와 Lambda 함수의 비용 문제를 초래할 수 있습니다. 이를 해결하기 위해서 아래 그림처럼 Lambda Worker 하나당 메시지 100개를 모아서 한 번에 처리하도록 구현했습니다. 또한, Lambda Worker에서 푸쉬 메시지를 100개씩 모아서 처리하기 때문에 처리 속도에 대한 문제점이 발생했습니다. Python의 비동기 모듈인 asyncio를 활용해 비동기적 처리를 진행하려 했으나, multiprocessing을 모듈을 이용한 푸쉬 전송이 월등히 빨랐습니다. Python에서는 threading 모듈과 같이 병렬화를 위한 다양한 모듈이 있지만, GIL과 같은 제약사항을 고려하여 multiprocessing 모듈을 이용했습니다[4].
multiprocessing 모듈과 병렬처리
- 시스템을 개발할 때는 성능과 비용 간에 나타나는 상충 문제를 잘 해결해야 합니다. 신규 푸쉬시스템을 개발할 때도 최고로 합리적인 Lambda 함수 성능을 찾기 위해서 많은 테스트를 진행했습니다. Lambda 함수의 비용이 부과되는 기준은 크게 호출 수와 메모리 사이즈로 나눠볼 수 있는데 최대한 함수의 호출 수를 늘리지 않고 빠르게 처리하는 것이 비용 감축의 관건이었습니다. 기본으로 제공하는 메모리 사이즈(128MB)부터 시작하여 최대 메모리 사이즈까지 다양한 테스트케이스를 만들어보았으며, 가장 최적의 메모리 사이즈를 찾아 적용했습니다.
4. 결과 및 결론
- 결론부터 간단히 말씀드리면 푸쉬는 아주 잘 가고 있습니다. 비용적인 측면에서도 과거 EC2를 메인으로 했을 때 보다 50% 이상의 비용 절감 효과를 보았습니다. 또한, Lambda 함수 기반의 독립적인 시스템을 구축하였기 때문에 장애에 강한 모습을 보여주기도 합니다. 유지 보수 측면 및 비용적인 측면에서 보았을때 과거 푸쉬시스템에 비해 긍정적인 부분이 많아졌다고 저는 확신합니다.
- 우리 번개장터는 대용량 푸쉬시스템외에도 장애에 유연하게 대비하기 위하여 특수한 부분에 Lamda 함수와 API Gateway를 적극 활용하고 있습니다. 앞으로도 번개장터의 수백만 유저에게 원활한 서비스를 제공하기 위하여 저희는 Lambda 함수를 계속해서 제작할 예정입니다.
5. 인용
- [1] https://cloudonaut.io/integrate-sqs-and-lambda-serverless-architecture-for-asynchronous-workloads/
- [2] https://apex.run/
- [3] https://serverless.com/
- [4] https://en.wikipedia.org/wiki/Global_interpreter_lock
글쓴이 - 이명휘 : 번개장터 주식회사 엔지니어(컴퓨터공학 학사 / 건축공학 석사)
머신러닝에 기반한 최적화 및 신재생에너지에 대한 연구를 했습니다.
흥미로운 논문에 대한 리뷰를 취미로 합니다.
Web : developeco.com
ResearchGate : bit.ly/2HF9M9x
Github : /Myeonghwi