베이글 서비스의 아키텍쳐

August 20, 2015

베이글(bagle.io)이란?

베이글은 스터디지피에스社에서 서비스 중인 일종의 그룹앱 서비스이다. 자세한 내용은 베이글 서비스 소개를 참고하길 바란다.

베이글

이 글을 통해 베이글 서비스의 아키텍쳐에 대해서 설명해 보겠다.

미리 밝히지만 베이글의 아키텍쳐는 베스트 프랙티스가 아니다. 유사한 서비스를 만들 때 참고는 할 수 있겠지만 그저 '나는 이렇게 한 번 해보았다'라는 정도의 의미임을 고려해주길 바란다.

베이글의 소프트웨어 스택

먼저 가볍게 베이글의 소프트웨어 스택에 대해 살펴보고 넘어가자.

백엔드 소프트웨어 스택

베이글의 백엔드를 구현하기 위해 사용한 소프트웨어로 다음과 같은 것들이 있다.

백엔드의 언어로 Node.js를, 데이터베이스로 MongoDB를 선택했다. 그 이유로 특별한 것은 없었다. 그냥.. 뜨고 있으니까.. 고민보다는 실행인 거다.

자연스럽게 웹 프레임워크로 Express.js를 선택하고, ODM(Object-Document Mapper)으로 Mongoose를 선택했다. 디팩토(De facto) 표준이라 고민할 것이 없었다.

또, 큐(Queue)와 임시 저장소의 용도로 Redis를 제한적으로 사용했으며, Node.js 기반의 다양한 라이브러리들을 사용했는데, 특히 Promise 구현체인 Bluebird와 유틸리티인 Lodash를 애용했다.

이 기술들에 대해 일년 이상 경험해 본 결과 개인적으로는 "매우 만족"이다. 각각의 기술에 대해서는 언젠가 자세하게 다룰 날이 있을 것이라 생각한다. 지금은 이 정도로 넘어가자.

프런트엔드 소프트웨어 스택

베이글의 프런트엔드를 구현하기 위해 사용한 소프트웨어로 다음과 같은 것들이 있다.

백엔드는 순수 자바스크립트로 개발했지만 더 나중에 개발된 프런트엔드는 CoffeeScript로 개발했다. 더불어 JadeSass를 사용했다. 이러한 전처리기(Preprocessor)들은 처음 도입해보았는데 코드를 보다 간결하게 작성할 수 있게 해주어서 매우 유용했다.

그리고 웹 프레임워크로는 대세 중의 대세인 AngularJS를 선택했다. 또, 웹과 AngularJS를 위한 다양한 자바스크립트 라이브러리들을 사용했는데, 특히 라우팅을 위한 AngularUI Router, REST API 클라이언트인 Restangular, 다국어 처리를 위한 angular-gettext를 감명깊게 사용하였다.

마지막으로 빌드 도구인 GulpGrunt이다. 원래 Grunt를 사용해 왔으나 이번에 Gulp로 갈아탔다. 빌드된 결과물을 AWS S3로 업로드하기 위한 Grunt S3 플러그인 때문에 Grunt도 함께 사용했다.

마찬가지로 각각의 기술에 대한 자세한 설명은 다음에 기회가 있을 것이다. 지금은 이 정도로 넘어가자.

베이글의 아키텍쳐

본격적으로 베이글 서비스의 아키텍쳐에 대해 설명한다.

백엔드 아키텍쳐 요구사항

먼저 베이글 서비스의 백엔드 아키텍쳐의 요구사항부터 살펴보자.

베이글의 백엔드는 REST API를 제공하는 API 서버데이터베이스(MongoDB), 이벤트 처리와 배치 작업을 위한 워커 서버와 이벤트/작업 (Redis)로 구성된다.

REST API 서버는 사용자, 그룹과 같은 도메인 객체들에 대한 CRUD를 제공한다. 일부 API에서는 이벤트와 작업을 생성해 큐에 저장하기도 한다. 그리고 사진/파일 저장과 조회를 위한 API는 속도 향상 및 캐싱을 위해 앞단에 CDN을 설정한다.

또, 비밀번호 재설정 API는 해당 사용자의 이메일에 비밀번호 재설정을 할 수 있는 링크를 포함한 이메일을 전송한다.

워커 서버는 Redis로부터 이벤트와 작업을 읽어서 관련 사용자들에게 푸시 알림을 보내주거나 작업을 수행하고 데이터베이스에 결과를 저장한다.

마지막으로, REST API 서버들과 워커 서버들은, 데이터베이스와 큐도 마찬가지, 향후 사용자 증가를 대비에 자동 혹은 수동으로 스케일이 가능한 형태로 구현한다.

요약하면,

  • REST API 서버를 제공한다.
  • 데이터베이스가 필요하다.
  • 배치 워커 서버를 제공한다.
  • 이벤트/작업 큐가 필요하다.
  • 사진/파일 캐싱을 위한 CDN이 필요하다.
  • 푸시 알림을 보낼 수 있다.
  • 이메일을 보낼 수 있다.
  • 스케일이 가능하다.

Amazon Web Service (AWS) for 백엔드 아키텍쳐

백엔드 개발과 인프라를 동시에 담당해야 했기에.. 아마존 웹 서비스(AWS)가 없었으면 난 절대 이 일을 시간 안에 못했겠지.

AWS는 인프라를 구성하는 다양한 요소들을 세부 서비스로 제공하고 있다. 베이글에서는 다음과 같은 세부 서비스들을 사용했다.

AWS Web Application Hosting Architecture

  • Route 53: 가용성과 확장성이 우수한 DNS 서비스
  • ELB: 부하 분산과 고가용성을 제공하는 서비스
  • EC2: 가상 서버 인스턴스를 제공하는 서비스
  • CloudFront: 정적 콘텐츠를 빠르게 배포하기 위한 CDN 서비스
  • CloudWatch: AWS 내 리소스 상태를 모니터링하는 서비스
  • Auto Scaling: 자동으로 EC2 인스턴스 수를 조절하는 기능을 제공하는 서비스
  • SNS: 모바일 푸시 알림 서비스
  • SES: 이메일 전송 서비스

AWS와 각 세부 서비스들에 대해 자세히 알고 싶으면 AWS 홈페이지도 좋지만, 그보단 이재홍(pyrasis)님의 블로그를 추천한다. 왠만하면 책.

앞서 살펴본 요구사항에서 데이터베이스와 큐에 관한 것을 빼고는 위 서비스들로 모두 커버가 가능하다. 기본적으로 Route 53으로 DNS 설정을 한다. 그리고 EC2 서버에 API 서버와 워커 서버를 실행하고, API 서버들 앞에 부하 분산을 위한 ELB를 설정한다. CloudWatchAuto Scaling을 설정해 API 서버들과 워커 서버들을 각각 스케일할 수 있도록 한다.

또, CloudFront 디스트리뷰션(Distribution)의 오리진(Origin)으로 API의 URL, 정확히는 ELB의 URL을 지정하고 캐싱이 필요한 API는 클라이언트에서 CloudFront 디스트리뷰션의 URL을 사용하도록 한다. 마지막으로 SNS로 푸시 알림을, SES로 이메일 전송을 처리한다.

참고로 베이글에서는 데이터베이스와 큐는 각각 MongoDB와 Redis를 사용했지만, AWS를 제대로 이용하려면 RDB를 제공하는 RDS, NoSQL 데이터베이스인 DynamoDB와 인 메모리 캐시를 제공하는 ElastiCache, 메시지 큐를 제공하는 SQS를 사용할 수도 있다.

뭐.. 베이글에서 그 서비스들을 사용하지 않은 이유 역시 딱히 없다. 설마.. 잘 몰라서? MongoDB 운영은 MongoDB Cloud Manager, 舊 MMS로 하면 꽤 편하고, Redis는 그렇게 많이 사용하지도 않으니까. 하지만 언젠가는 위 서비스들을 사용할 수도 있겠지.

백엔드 아키텍쳐 구조도

아무튼 그래서 베이글의 백엔드 아키텍쳐 구조도는 다음과 같다. 위에서 다 설명했으니 추가 설명은 필요없을 듯 하다.

백엔드 아키텍쳐 구조도

프런트엔드 아키텍쳐 요구사항

이번엔 베이글 서비스의 프런트엔드 아키텍쳐에 대해 설명한다.

베이글의 프런트엔드는 AngularJS를 기반으로 만들어진 웹 어플리케이션이다. 그러므로 빌드한 결과물은 HTML, CSS, JS, 이미지, 웹폰트 등과 같은 정적 파일들만으로 이루어진다.

그리고 해당 결과물을 웹서버로 서비스하기만 하면 된다. 웹서버는 당연히 스케일해야 하고, 정적 파일이니 속도 향상과 캐싱을 위한 CDN이 필수이다.

Amazon Web Service (AWS) for 프런트엔드 아키텍쳐

백엔드와 마찬가지로 프런트엔드도 AWS 상에 구축되어 있다. AWS의 다음과 같은 서비스를 이용한다.

AWS Web Application Hosting Architecture

  • Route 53: 가용성과 확장성이 우수한 DNS 서비스
  • S3: 비용이 저렴하고 안전성과 확장성이 우수한 인터넷 스토리지를 제공하는 서비스
  • CloudFront: 정적 콘텐츠를 빠르게 배포하기 위한 CDN 서비스

AWS와 각 세부 서비스들에 대해 자세히 알고 싶으면 AWS 홈페이지도 좋지만, 그보단 이재홍(pyrasis)님의 블로그를 추천한다. 왠만하면 책.

앞서 살펴본 요구사항을 위 서비스들로 모두 커버가 가능하다. 기본적으로 Route 53으로 DNS 설정을 하고, S3 버킷에 빌드 결과물을 업로드한 후 스태틱 웹사이트 호스팅 기능으로 서비스한다. 마지막으로 CloudFront 디스트리뷰션의 오리진으로 S3 버킷을 지정해 캐싱하면 된다. S3와 CloudFront가 알아서 스케일을 처리하므로 걱정할 것이 없다.

덧붙여 'www.bagle.io'로 접속하면 'bagle.io'로 리다이렉트하기 위해 별도의 CloudFront 디스트리뷰션과 S3 버킷을 마련하고, S3 버킷의 스태틱 웹사이트 호스팅 설정에서 리다이렉트하도록 처리한다.

프런트엔드 아키텍쳐 구조도

그래서 베이글의 프런트엔드 아키텍쳐 구조도는 다음과 같다. 단순해서 구조도를 그릴 정도도 아니네.

프런트엔드 아키텍쳐 구조도

베이글 아키텍쳐의 개선점

백엔드 아키텍쳐 개선점

백엔드 아키텍쳐는 개선할 여지가 무척 많지만 두가지만 언급해본다.

사진/파일 저장소 변경

사진/파일을 현재는 데이터베이스에 저장하고 있다. AWS에는 S3라는 값싸고 훌륭한 스토리지 서비스가 있는데도 불구하고. 누군가는 S3야말로 AWS를 사용하는 진짜 이유라고 하던데.. 이건 무조건 바꿔야 하는 것이다. 사용자가 많아지기 전에 바꾸는 게 더 좋겠지.

더 상위 수준의 AWS 서비스 이용

직접 EC2를 실행하고, ELB, Auto Scaling 등을 설정하는 대신에 CloudFomation이나 OpsWorks를 사용하거나 혹은 최근에 런칭한 ECS를 사용하는 게 어떨까?

배포에 대해서도 자세히 설명할 기회가 있겠지만, 간략히 말해 베이글 백엔드는 ChefDocker를 사용하는데 그렇다면 방금 언급한 보다 더 상위 수준의 서비스를 사용하는 것이 훨씬 편할 것 같다.

...

프런트엔드 아키텍쳐 개선점

프런트엔드 아키텍쳐 역시 개선할 여지가 많지만 한가지만 언급한다.

크롤러 대응

다시 한 번 말하지만 베이글의 프런트엔드는 AngularJS를 기반으로 만들어진 웹 어플리케이션이다. 그러므로 프런트엔드에는 아무런 컨텐츠가 담겨있지 않다. 먼저 페이지가 로드된 후 자바스크립트에서 AJAX 통신을 통해 컨텐츠를 불러온다.

이런 구조가 무작정 좋은 것이라 생각했지만 부작용이 있었다. 그 중에 하나가 페이스북 같은 서비스에 베이글의 특정 페이지를 공유할 때 해당 페이지의 내용을 미리보기할 수 없다는 것이다. 왜냐하면 페이스북 같은 서비스의 크롤러에서는 자바스크립트가 동작하지 않기 때문이다.

이것에 대응하기 위해서는 좀 더 연구가 필요하다. CloudFront에서 User-Agent 헤더값에 따라 다른 페이지를 서비스하는 것은 가능한 것 같지만 캐싱에 비효율적이라 추천하는 것 같지는 않고, 결국엔 공유를 위한 별도의 URL을 제공해야 할 것 같다. (혹시 좋은 방법이 있으면 알려주세요. ^^)

...

마치며

기승전 AWS!

...

이 글을 작성하기 시작한 후 바로 다른 프로젝트를 시작하는 바람에, 게다가 주어진 시간도 많지 않아서 작성이 너무 더뎠다. 아직까지 그 프로젝트가 끝나진 않았지만 미리 예약해둔, 예이~, 여름 휴가 여행을 떠나는 비행기 안에서 작성하고 있다.

별 것 아닌 내용인데도 이렇게 작성하는 것이 오래 걸리다니.. 다시 한 번 글쓰기의 어려움을 절실히 느끼게 되었고, 그냥 내가 게으른 걸지도 ㅠ_ㅠ

내가 스스로 정리하기 위한 목적으로 작성한 글이지만, 그냥 우연히 이 글을 읽는 사람에게 조금이나마 도움이 되었으면 한다.