카테고리 보관물: Infra

이음의-시스템-인프라를-소개합니다

Retrofit2 + RxJava proguard 설정하기

안녕하세요, 이음소시어스에서 안드로이드 앱 개발을 맡고 있는 김범준(준) 입니다.

최근 안드로이드 HTTP Client 라이브러리를 Volley에서 Retrofit2로 옮기는 과정에서 겪었던 ProGuard 관련 문제와 해결과정을 공유하려 합니다.

문제가 발생한 과정은 다음과 같습니다.

1. 새로운 feature에 쓰이는 API 모듈을 Retrofit2를 통해 구현하였습니다.

2. QA 까지 아무 문제가 없어서 구글 플레이에 업데이트 버전을 배포하였습니다.

3. 구글 플레이에서 앱을 다운로드하니 새로 만든 API 모듈에서 에러가 발생했습니다 ㅠㅠ

 

멀쩡하던 API가 배포하니 갑자기 안되다니.. (멘붕)

92_55169f19d5d14_1833

APK 파일을 다운로드해서 테스트하지 않고 Android Studio를 통한 빌드로만 테스트한 것이 문제였습니다.

ProGuard가 APK를 난독화하는 과정에서 문제가 생겼던 것이죠!

정확히 어떤 문제였는지 설명하기에 앞서 먼저 ProGuard와 저희가 사용한 Retrofit2 설정에 대해 간략하게 설명 드리겠습니다.

 

ProGuard 란?

ProGuard는 APK 빌드 과정에서 사용하지 않는 코드를 제거함으로써 크기를 최소화하고, 남은 코드를 난독화를 통해 리버스 엔지니어링을 어렵게하는 툴입니다.

ProGuard의 경우에는 class, method, field의 이름을 줄여 난독화 하게 되는데, 예를 들어 ‘name’이라는 String 타입의 멤버 변수를 ‘a’라고 뜻을 알 수 없는 짧은 단어로 치환하는 것이죠.

이 때, 난독화가 프로그램 작동에 영향을 줄 수 있는 부분들은 ‘-keep class ~’와 같은 방법으로 난독화에서 제외해줍니다.

 

Retrofit2 + RxJava

저희가 Retrofit2를 도입하면서 선택한 구조는 RxJava의 Observable 클래스와  GsonConverter를 이용합니다.

RxJava의 Observable를 통해 어떤 클래스로 response를 받을 것인지 지정해주면, GsonConverter를 통해 response를 JSON으로 파싱하여 지정한 클래스의 각 멤버 변수에 대입해줍니다.

API 콜을 부르면 콜백에 지정한 클래스의 객체를 돌려주니 바로 콜백에서 클래스 메소드를 사용할 수 있습니다. (넘나 편한것..)

Retrofit2 + RxJava의 자세한 설정이 궁금하신 분은 이 링크를 참고해주세요~

 

문제는 Model 클래스의 난독화에 있었다..

위에서 언급한 것처럼 response가 들어오게 되면 GsonConverter가 JSON으로 파싱해주고 Model 클래스의 멤버 변수에 대입해주는데, 이 때 ProGuard가 이 클래스의 멤버 변수를 난독화하면..! 대응 되는 이름의 멤버 변수가 없으니 문제가 생기는 것이죠.

실제로 난독화 된 클래스를 보면 다음과 같습니다. dex2jar 와 JD-GUI를 통해 디컴파일 하였습니다.

멤버 변수가 a, b, c 로 바뀐 것을 확인할 수 있습니다. 그렇다면 문제를 해결하기 위해선 ProGuard가 Model 클래스를 난독화하지 않도록 만들어주면 되겠죠~

 

ProGuard Configuration

Retrofit 홈페이지는 ProGuard를 사용할 시 어떤 설정을 적용해야 하는지 친절하게 써 놓았습니다.

스크린샷 2016-08-05 오후 3.16.38

이것만 적용하고 설정이 끝났다고 생각해서 문제가 생긴 것이죠 ㅠㅠ

keep model

위의 설정은 Retrofit2이 제대로 작동하기 위한 것이고 RxJava를 추가로 사용하기 때문에 따로 설정을 해줘야 합니다.

위과 같이 사용하는 Model 클래스를 난독화에서 제외해주시면 됩니다. 결과는 다음과 같습니다.

원래 멤버 변수 이름대로 있는 것을 확인할 수 있습니다! 앱 또한 정상 작동하였습니다.

Inner 클래스를 사용하는 경우 몇몇 변수가 난독화가 되는 일이 일어나기도 한다고 합니다. 이런 경우에는 다음과 같이 Inner 클래스도 난독화에서 제외하는 코드를 추가해줍니다.

해결!

 

후기

저에겐 안드로이드 앱 배포가 이번이 처음이었는데요, 예상치 못한 오류가 터져서 고생을 했지만 그래도 해결하고 나니 많이 배운 것 같아 뿌듯하네요 ㅎ

두서 없는 글 읽어주셔서 감사합니다~ (ㅡ)(__)

 

IM8(아임에잇) 오토스케일링 적용기

안녕하세요. 이음소시어스 개발팀 서버개발자 육승찬(루)입니다.

오늘은 이음소시어스의 핫한 서비스 “IM8(아임에잇)“의 오토스케일링 적용경험을 공유하려고 합니다. 먼저 오토스케일링에 대한 이야기를 하기 앞서서, 아임에잇이 어떤 서비스인지 간단하게 설명하고 시작하겠습니다.

IM8 (아임에잇) ?

직장인을 위한 프리미엄 소개팅 서비스

im8_1

IM8은 2013년 10월에 런칭되어 “까다로운 신원인증으로 높은 매칭 신뢰도를 주자”를 목표로 열심히 발전해 왔습니다. IM8의 8은 8개의 회원 타입(스마트커리어비주얼유니크글로벌밀리언패밀리액티브)을 의미합니다.

im8_2

현재 아임에잇의 모든 서버는 AWS를 통해서 운용하고 있습니다. 그럼 본격적으로 AWS x IM8 오토스케일링 적용기를 공유해보겠습니다. GOGO~

기존 서버

처음에는 IM8 API 서버를 EC2(t2.medium) 한대로 운영해왔습니다. 이후 트래픽의 증가에 따라 ELB에 EC2(t2.medium) 서버 2대를 붙여 운영하는 구조로  발전했습니다. 하지만 점점 더 트래픽이 증가하면서 피크 시간대에 서버가 터지는 일이 발생하기 시작했습니다. 그래서 현재 서버구조의 문제점을 정리하고 문제점을 개선하기로 결정했습니다.

서버 증설 방법

1. ELB에 서버를 더 붙이자 OR 서버의 스팩을 올리자 !

하루 두 번 푸시가 갈 때 아임에잇에 가장 접속자가 많이 몰립니다. 왜냐하면, 실제 매칭이 되는 시점에 사용자가 들어와서 상대방의 프로필을 확인하는 구조이기 때문입니다. 그래서 일반시간대에는 많은 서버가 필요 없습니다. 이 방법의 장점은 적은 시간 안에 문제를 해결할 수 있다는 점입니다. 하지만 비용적인 측면에서 굉장히 안 좋다고 판단했습니다. 접속자가 많지 않아도 항상 여러 대의 서버가 띄워져 있고 비용을 계속 지불하기 때문입니다.

2. AutoScaling (v)

접속자가 별로 없는 시간에는 최소한의 서버를 유지하고 피크 시간대에 여러 서버를 유지하는 방법입니다. 하루 두 번 피크가 있는 아임에잇에게 아주 좋은 방법이라고 생각합니다. (대부분 서비스에 좋지 않을까하는 생각이.. 😀) 평상시에는 최소한의 서버를 유지하기 때문에 비용적인 측면에서도 굉장히 유리했습니다. 그래서 이번 기회에 아임에잇에 AutoScaling을 적용해보기로 했습니다.

AutoScaling 준비하기

  • 결제서버 분리
  • 통일된 인스턴스 만들기
  • Scaling되는 서버를 항상 최신으로 유지하기
  • Scaling Rule 정하기
  • 서버를 띄우는 시간이 있기 때문에 푸시가 나가기전에 미리 띄우기 (Pre-warm)

1. 결제서버 분리

아임에잇은 모바일과 웹에서 PG서비스를 붙여서 결제할 수 있도록 제공하고 있습니다. 특정 PG는 요청 가능한 서버의 IP를 제한하고 있는 경우가 있어서 결제서버를 분리해야 했습니다. 기존 서버에서 사용하던 IP를 결제서버에 붙이고 내부에서만 통신 가능한 결제서버를 만들었습니다.

  • 기존 서버구조
기존 서버 구조

기존 서버 구조

  • 결제서버를 분리한 구조
새로운 서버 구조

새로운 서버 구조

2. 통일된 인스턴스 만들기

AWS에서 제공하고 있는 오토스케일링 기능은 미리 정의한 규칙에 따라서 인스턴스가 띄워지고 내려가는 형태로 동작합니다. 인스턴스는 AutoScalingGroup에 등록해놓은 AMI를 이용하여 띄우게 됩니다. 원래 사용하고 있던 서버 2대는 완전히 같은 인스턴스가 아니였습니다. 하나를 나중에 띄웠기 때문에 시스템 라이브러리 같은 경우 조금씩 버전이 달랐습니다. 그래서 이번 기회에 통일된 인스턴스를 만들고자 했고, 구조는 아래와 같습니다.

server

  • 시스템 라이브러리 설치 (ex. git)
  • API Rails App 설정
  • Puma 설정
  • Nginx 설정
  • Papertrail 설정 (로그 수집)

3. 서버를 항상 최신으로 유지하기

오토스케일링 되고있는 서버들은 항상 최신의 코드를 유지해야 하고 같은 응답을 줄 수 있어야 합니다. 아임에잇은 Git으로 소스코드를 관리하고 배포에는 Capistrano를 사용합니다. 지금까지는 서버 2대만을 최신으로 유지하면 됐기 때문에 아래의 순서로 배포를 해왔습니다.

  • Git fetching
  • whenever에 등록된 스케줄링 crontab에 등록
  • Puma Reload

오토스케일링을 적용하기 시작하면 서버가 딱 몇 대라고 가정할 수 없습니다. 그래서 모든 서버를 항상 최신으로 유지하기 위한 아래 두 가지 정리해봤습니다.

  • EC2 Instance가 시작되는 시점에 최신으로 만들기

EC2 설정 중에 “User Data“라는 부분이 있습니다. 인스턴스가 띄워지는 시점에 명령어를 실행시킬 수 있도록 할 수 있습니다. 여기서 소스코드를 최신상태로 유지하고 puma를 띄우는 등의 명령을 할 수 있습니다. 이렇게 하면 스케일링 돼서 실행되는 서버도 최신코드를 유지할 수 있게 됩니다. 쉽고 빠르게 모든 서버의 최신상태로 유지할 수 있지만 매 서버가 띄워질 때마다 Ruby 라이브러리 설치(bundle install)하기 때문에 서버가 띄워지고도 최신상태로 유지하는데 시간이 더 걸린다는 단점이 있습니다.

  • 최신의 AMI를 사용하기

이 방법은 미리 최신의 AMI를 항상 유지하고 서버가 띄워지는 부분에 대해서는 신경을 쓰지 않아도 됩니다. 코드를 한 번만 최신으로 유지하면 되고 라이브러리 설치 시간도 매우 줄일 수 있다는 장점이 있습니다. Ruby 라이브러리 중에 ELBAS라는 라이브러리가 있습니다. Capistrano와 연동되어 배포시 실행되고 있는 인스턴스를 최신으로 만들어주고, AMI까지 만들어주는 아주 좋은 녀석입니다… 이 라이브러리를 사용하면 빠르고 쉽게 항상 최신의 AMI를 유지할 수 있습니다.

4. Scaling Rule 정하기

처음에 AutoScalingGroup만 만들면 아무리 기다려도 서버는 스케일링 되지 않습니다… 그래서 어떤 규칙에 의해서 스케일링 할 건지 정해줘야 합니다. 먼저 AutoScalingGroup 설정을 보시면 “Instances“, “Desired“, “Min“, “Max“가 있습니다. Instances는 현재 서버의 개수, Min은 최소 서버 수, Max는 최대 서버 수를 의미합니다. Desired는 스케일링이 돼서 현재 몇 대를 희망하고 있는지를 의미합니다. Desired와 Instances의 수가 같으면 더는 서버를 띄우거나 내릴 필요가 없다는 의미입니다.

아임에잇에서는 서버를 띄우는 규칙 2지와 서버를 내리는 규칙 2가지를 사용하고 있습니다.

  • Add 1 instance (CPUCreditBalance < 9)

10분안에 CPUCreditBalance의 수치가 9 미만이라는 알람 두번이 울리면 인스턴스를 띄워라!

이 규칙은 CPUCreditBalance 수치에 따라서 동작하게 됩니다. 아임에잇 서버는 현재 t2계열의 인스턴스를 사용하고 있습니다. T2계열의 인스턴스에는  CPUCredit라는 개념이 존재합니다. 크레딧이 부족하면 CPU 연산 능력이 매우 떨어지기 때문에 새로운 서버 한 대를 띄우도록 하고 있습니다.

  • Add 2 instances (CPUUtilization >= 60)

2분안에 CPUUtilization의 수치가 60% 이상이라는 알람 두번이 울리면 인스턴스를 띄워라!

평소에는 CPU 사용률이 평온하다가 피크시간이 되면 갑자기 막 올라갑니다. 그래서 CPU사용률이 60% 이상이 되면 인스턴스 2대를 띄우도록 하고 있습니다. (피크시간이 아니면 올라갈 일이 거의 없기 때문)

  • Delete 1 instance (CPUUtilization < 25), Delete 2 instances (CPUUtilization < 15)

1분에 한번씩 CPU 사용률이 낮다는 알람 10번이 울리면 인스턴스를 내려라 !

피크시간이 지나고 나면 급격히 사용률이 떨어지기 때문에 인스턴스를 내리기 시작합니다. 하지만 인스턴스를 내리는 옵션을 상당히 까다롭습니다. 알람이 울리는 10분은 같지만 연속적으로 10번이 울려야 하기 때문입니다. 이유는 인스턴스를 내려도 되는 상황인지 확실시 하고 싶었기 때문입니다. 대게 인스턴스를 띄워서 생기는 문제보다 내리면서 생기게 되는 문제가 많기 때문입니다..

5. Pre-warm

아마 대부분의 프로그래머분들은 장애를 원하지 않을 것 입니다. 이음소시어스 개발팀도 장애가 나지않게 하기 위해서 노력을 하고 있습니다. 아임에잇 서비스의 경우 12:30, 19:00에 푸시를 보내고 있습니다. 실제로 푸시를 보내고 나서 오토스케일링 룰에 따라 서버를 띄우기 시작하면 대처가 좀 늦습니다. 갑자기 들어오는 트래픽을 버티지 못하고 아예 서버 한대가 죽어버리는 일도 있었습니다. 그래서 12:20, 18:50에 서버를 미리 띄워놓고 있습니다.  서버를 미리 띄우는 코드는 Python으로 작성했고 Crontab에서 실행되도록 하고 있습니다.

 

정리

이번 포스팅에서 아임에잇의 오토스케일링 적용 경험을 적어보았습니다. 사실 이번 포스팅을 작성하면서 너무 뻔한 글이 되지 않을까 걱정이 많았습니다. 이미 오토스케일링에 대한 글도 많고 참고할 부분도 많아서 잘 쓸 수 있을지 고민이 되었기 때문입니다. 그래서 더 자세히, 숨김없이 써보려고 노력했습니다.

오토스케일링을 적용하면서 가장 크게 얻은 두 가지는 비용절감과 불안감이 사라졌다는 것입니다. 기존 서버 비용 보다 45%의 비용을 절감할 수 있었습니다. 그리고 평소보다 가입자가 많은 날은 트래픽에 서버가 죽을까 하는 불안감이 있었습니다. 오토스케일링이 만병통치약은 아니지만 룰에 의해서 확장하고 수축하는 구조 덕분에 더 많은 트래픽을 커버할 수 있게 되어 불안감이 해소되었습니다.

마치며..

이번 오토스케일링을 사용하면서 ELBAS라는 라이브러리가 참 좋다고 느꼈습니다. 너무 쉽게 AMI를 최신으로 유지할 수 있는 방법을 제공해줬기 때문인데요. 그래서 이 라이브러리의 핵심적인 부분을 파이썬 코드로 작성해봤습니다 (파이썬을 오래 쓰다보니 재밌는건 파이썬으로 구현해보고 싶은 맘이 생기네요..)

 

 

이음(I-UM) on AWS

안녕하세요. 이음소시어스 개발팀 서버개발자 육승찬(루)입니다.

바야흐로… 2010년 4월 소셜데이팅 서비스 “이음”을 베타 런칭했습니다. 이음은 5년 동안 베이론 서버 제작을 포함해서 많은 변화를 추구해 왔습니다. 앞서서 이음 서비스를 간단하게 설명하고 시작하겠습니다.

이음은 대한민국 대표 소셜데이팅 서비스이며, 현재 회원 110만 명, 일 평균 1,000쌍 이상의 상호 OK가 진행되고 있습니다. 그리고 150만 이상의 누적 앱 다운로드를 기록하고 있습니다.

스크린샷 2015-07-03 오후 2.22.01

이 포스팅에서는 이음이 AWS를 어떻게 사용하고 있는지 설명하려고 합니다. 이음은 2012년 6월부터 AWS를 사용해 왔습니다. 현재 사용하고 있는 서비스를 간략히 나열해보면 아래와 같습니다.

  • EC2
  • ELB
  • RDS
  • S3
  • CloudFront
  • Route53
  • CloudWatch
  • SES
  • SNS

이렇게 나열해보고 나니 생각보다 많이 쓰고 있었네요! 그럼, 각 서비스를 어떻게 사용하고 있는지 설명해 드릴게요.

나와랏.. 스피드웨건 !

나와랏.. 스피드웨건 !

EC2 & EBS & ELB

이음에서는 다수의 EC2 인스턴스를 사용하고 있습니다. API서버, 웹서버, 어드민서버, 통계서버, 개발서버, Redis, SMS 등을 EC2에 올려놓고 사용하기 때문입니다. EC2는 순식간에 가상 서버를 만들 수 있는 장점이 있습니다. 예로 이전의 포스팅했던 깃랩 업데이트 당시에도 테스트하기 위해 가상서버를 만들어서 진행했습니다. AMI로 인스턴스를 만드니 빠르게 테스트 할 수 있었습니다.

대부분의 EC2의 Volume으로 EBS General Purposed SSD(gp2)를 사용하고 있습니다.

API 서버와 웹서버 같은 경우는 ELB를 앞단에 두고 있습니다. API 서버는 Auto-Scaling 기능을 사용하여 자동으로 scale-out 됩니다. 어드민, 통계, Redis 같은 경우는 아직 scale-out 할 필요성이 없어서 순수 EC2만을 사용하여 구동하고 있습니다.

여담으로 내부적으로 T2 타입 인스턴스를 좋아하고 많이 사용합니다. T2 타입 인스터스는 인스턴스 크기를 기반으로 정해진 고정 비율에 따라 지속해서 CPU 크레딧을 받고, 크레딧을 사용하여 기본 CPU 성능을 넘은 “성능 순간 확장”이 가능합니다. 간단한 테스트 서버가 필요할 경우 T2 타입의 인스턴스를 사용하면 좋습니다 ^^

RDS

이음에서는 MYSQL을 사용하고 있습니다. RDS에서 Mysql Engine을 선택하여 Master 1대, Read Replica 1대를 사용하고 있습니다. 그리고 Provisioned IOPS 옵션은 2000으로 사용하고 있습니다.

RDS 인스턴스는 종료하지 않고 인스턴스 타입을 변경할 수 있습니다. (물론 다운타임은 있습니다) 또한, Replica를 생성할 수 있는 기능도 있어서 관리 측면에서는 매우 좋습니다. RDS는 다른 서비스에 비해 상당히 비싼 편입니다. 하지만 관리적인 측면에서 매우 편하기 때문에 사용하고 있습니다.

최근에 RDS 오로라 엔진이 나왔습니다. 오레곤 리전에 인스턴스를 만들어서 사용해보고 있습니다. 이음 개발팀에서는 긍정적으로 보고 있고 빨리 도쿄리전에서 사용할 수 있었으면 하는 바람입니다.

S3

S3는 장점이 매우 많아 활용도가 무궁무진한 서비스입니다. 비용이 매우 저렴하고 오브젝트에 설정할 수 있는 옵션도 많습니다. Bucket에 로깅 기능도 있기 때문에 쉽고 편하게 사용할 수 있습니다. 그래서 이음에서 사용하는 모든 이미지는 S3에 저장하고 static 이미지들 또한 S3에 업로드하여 사용합니다. 또 이벤트 페이지가 필요할 때, 마크업을 작성하여 S3에 올려서 배포하여 사용하고 있습니다.

관리 측면에서 보면 S3는 정말 좋은 서비스입니다. 보통 static 서버를 따로 구성하게 되면 용량이 찼을 때, 증설 작업을 해줘야 합니다. 하지만 S3는 scale-out, 용량 증설에 고민할 필요가 없습니다. 다양한 서비스에 붙여서 사용할 수 있는 아주 매력적인 서비스입니다. (제가 정말 좋아해요..)

CloudFront

CloudFront는 S3에 업로드된 오브젝트를 배포할 때 사용합니다. 한국을 비롯한 세계 각 리전에 엣지가 있고 S3와 연동 쉬워서 매우 편리합니다. 이음에서는 프로필 이미지, 뱃지 이미지를 포함한 대부분 이미지를 signed URL을 사용하여 배포되고 있습니다.

이음은 AWS와 향후 1년간 CDN 서비스로 CloudFront만 사용하겠다는 Exclusivity 계약을 맺고 사용하고 있습니다. 위 계약을 맺게 되면 비용절감을 할 수 있습니다.

Route53

서비스 도메인 i-um.net, i-um.com의 DNS 서비스를 Route53로 옮겨서 사용하고 있습니다.  Route53는 비용도 저렴하고, 도메인 구입 비용도 비싸지 않습니다. 그리고 AWS의 다른 서비스들과 같이 사용하면 매우 편합니다. ELB, S3 웹호스팅, EIP, CloudFront와 연동이 매우 쉬우므로 좋습니다. 도메인 연결을 AWS Console에서 할 수 있기 때문이기도 하고요.

CloudWatch

ELB, EC2, EBS, RDS, SNS, Auto-Scaling Group의 상태를 모니터링합니다.. 이음에서는 CPU, DISK 등 다양한 기준을 두고 해당 조건에 맞으면 이메일, SMS로 알려줍니다. 그리고 특정 조건에 Auto-Scaling 되도록 연동하여 사용하고 있습니다.

CPU 올라간다~

CPU 올라간다~

SES

SES를 사용하면서 크게 문제 있었던 적은 없었으나, 이메일 반환율 때문에 메일이 온 적이 있습니다. 메일 전송시 반환비율이 높아지면 경고 메일이 옵니다. 반환율이 일정 비율을 넘기면 SES 서비스가 블럭 되므로 평소에 잘 관리해야 합니다.

SNS

이음에서 전송하는 모든 푸쉬는 SNS를 통해서 나갑니다. 여러 디바이스에 한 번에 보낼 수 있어서 매우 편리합니다. PUSH뿐만 아니라 CloudWatch에서 설정한 Alarm을 메일 또는 메세지로 보내주기도 합니다. SNS 내에 SMS를 보내주는 기능이 없어서 SMS 서버를 만들었습니다. 그리고 SNS에서 HTTP로 Call 하도록 구성했습니다.

맺음말

마지막으로 이음에서 사용하는 모든 서비스를 그림으로 그려봤습니다.

스크린샷 2015-07-03 오전 11.05.44

현재 개발팀은 RDS 오로라와 람다 서비스에 많은 관심을 기울이고 있습니다. 오로라는 서베이 후 괜찮다 싶으면 시범적으로 사용해볼 예정입니다. 람다의 경우 아직 Javascript, Java밖에 지원하지 않는 점이 아쉽습니다. AWS는 운영하면서 중간중간 변화를 주는 묘미가 있고 틈나는 대로 구성을 변경하고 있습니다. 앞으로도 더 좋은 서비스가 나오기를 바라면서 마칩니다.

 

PS. 항상 도움을 주는 AWS 팀에게 감사드립니다.

고통과 함께한 GITLAB(깃랩) 업데이트

안녕하세요. 이음소시어스 개발팀 서버개발자 육승찬(루)입니다.

저희 개발팀에서는 깃랩을 사용하고 있습니다. 이번에 깃랩 버전을 업데이트하게 되었고, 그 과정에서 겪은 경험을 공유하면 좋을 것 같아 “고통과 함께한 GITLAB(깃랩) 업데이트” 주제를 가지고 포스트를 작성하게 되었습니다.

GITLAB ?

Create, review and deploy code together

GITLAB(깃랩)은 설치형 버전관리 시스템입니다. 오픈소스로 제작되었고 꾸준히 업데이트되고 있습니다. 깃랩은 CE(Community Edition), EE(Enterprise Edtion), GitLab.com(On gitlab server) 세 가지 형태로 존재합니다.  저희 개발팀에서는 CE를 사용하고 있기 때문에 CE기준으로 설명드리겠습니다.

UPDATE ?

저희 개발팀에서는 2년 전 깃랩 6.1을 BITNAMI로 설치하여 사용해오고 있었습니다.  현재 깃랩 최신 버전이 7.9.2라는 것을 보고 업데이트가 필요하다고 생각이 들기도 했고, 개인적으로 써보고 싶어서 팀장님께 깃랩 업데이트를 제안 드렸습니다.

daum_net_20150321_204544

팀장님도 좋다고 하셔서 업데이트하기로 했습니다. (이때 고통이 뒤따를지 생각지도 못했습니다)

최근의 깃랩은 gitlabhqomnibus-gitlab으로 배포되고 있습니다. gitlabhq를 이용하여 설치하면 DB, REDIS 등 여러 소프트웨어를 직접 설치하고 깃랩에 연결하여 사용했었습니다. 하지만 omnibus-gitlab는 필요한 소프트웨어가 포함되어 패키지 형태로 설치됩니다. 또한, omnibus-gitlab는 기타설정이 매우 쉬우므로 관리하기가 좋습니다.

그래서 개발 서버에 설치된 깃랩 6.1을 omnibus-gitlab으로 업데이트하고자 하였습니다.

HOW ?

  • 제가 생각했던 첫 번째 (실패한)방법입니다.

스크린샷 2015-04-15 오후 3.07.04

먼저 깃랩 6.1에서 데이터를 백업하고 omnibus-gitlab을 설치하여 데이터를 복구하는 것이었습니다. 깃랩은 데이터 백업과 복구기능을 지원하기 때문에 다음 명령어로 데이터를 백업할 수 있습니다.

백업이 완료되면 tmp/bakups 안에 [timestamp]_gitlab_backup.tar 형태의 파일이 저장됩니다.  백업이 완료된 후 omnibus-gitlab을 설치합니다. 패키지 형태로 배포되고 있기 때문에 설치가 매우 쉽습니다. 자세한 설치방법은 깃랩 홈페이지에 있습니다.

omnibus-gitlab 설치가 완료되면 /var/opt/gitlab/backups 안에 위에서 백업한 파일을 옮기고 아래 복구 명령어를 실행했습니다.

그러면 아래와 같은 에러가 발생합니다.

그렇습니다.. 깃랩 백업&복구는 버전이 다르면 적용할 수 없습니다.

으아니

아니 이게 무슨소리요…

  • 두 번째 (실패한)방법입니다.

스크린샷 2015-04-15 오후 3.26.57

 

깃랩은 버전별로 업데이트하는 가이드를 제공하고 있습니다.  저는 gitlab 6.x or 7.x to 7.9(해당 링크는 계속해서 업데이트 되기 때문에 링크가 깨질 수 있습니다. 만약 링크에서 404에러가 난다면 여기로 접속해보세요.) 업데이트 가이드를 참고했습니다. 가이드에 나와 있는 방법대로 업데이트를 진행하고 있었는데 문제가 하나 발생했습니다. 현재 서버에 설치되어있는 gitlab-shell 프로젝트가 깃 프로젝트가 아니었습니다. 일단 gitlab-shell 프로젝트 관련 내용을 넘기고 나머지 커맨드를 입력하고 있었는데 데이터베이스 마이그레이션 부분에서 gitlab-shell 관련해서 에러가 났습니다.

daetul_mung

결국 BITNAMI로 설치 된 깃랩을 업데이트하는 것을 포기했습니다.

  • 세 번째 (성공한)방법입니다.

스크린샷 2015-04-15 오후 3.37.45

 

처음 생각했던 것보다 많이 복잡해졌습니다.. 하지만 희망을 가지고 (BITMANI)깃랩 6.1에서 데이터를 백업하고, 임시로 가상 인스턴스를 만들어서 깃랩을 6.1로 설치했습니다. 그리고 앞에서 백업한 파일을 복구하고 버전 업데이트를 진행했습니다. 버전 업데이트는 두 번째 방법에서 참고한 가이드를 똑같이 따라 했습니다. 업데이트는 순조롭게 진행되었고 데이터 백업까지 할수 있었습니다.

그리고 omnibus-gitlab 7.9.2를 설치했고, 깃랩 7.9.2에서 백업한 데이터를 복구하려 했습니다. 하지만 또 다시 에러가 발생했습니다.. 에러 내용은 데이터베이스 관련 에러입니다. 구글에 조금 찾아보니 깃랩을 ommibus-gitlab으로 마이그레이션하는 방법이 있었습니다. 내용을 요약하면 이렇습니다.

omnibus-gitlab에서는 PostgreSQL를 사용합니다. 만약 이전에 MySQL을 사용했다면 백업한 mysql query를 PostgreSQL query로 변경해야 됩니다. 변경 방법은 mysql-postgresql-converter 프로젝트를 이용해서 변경할 수 있습니다. 그래서 백업한 tar를 풀어서 database.sql을 PostgreSQL query로 변경하고 다시 압축한 후에 복구 명령어를 실행하면 복구가 됩니다. 이 방법을 자세하게 설명한 블로그가 여기있습니다. 참고하세요 ^^

CONCLUSION

일단 업데이트를 완료하고 한숨부터 나오더라구요..

유병재_SNL코리아_극한직업_모음_보기qd

왜 사람들이 그냥 GitHub, BitBucket 서비스를 사용하는지 이해가 가기도 했고요. 글을 정리하면서 몇 가지 팁을 적어보겠습니다.

  • 업데이트를 계획하신다면 omnibus-gitlab으로 업데이트하세요. omnibus-gitlab을 적용하려고 하지 않았다면 과정이 단순해질 수도 있었습니다. 하지만 앞으로 사용하고 관리하는 데 있어서 omnibus-gitlat이 더 좋다고 판단하여 업데이트했습니다.
  • 업데이트는 반드시 임시 가상서버에서 진행해보고 실제 서버에 적용하세요. 만약 제가 실 서버에서 바로 적용했다면 개발팀이 힘들었을 것 같습니다.
  • 끝까지 힘을 내요! (슈퍼파월) 
rsz_1iumstellar

우린 답을 찾을 것이다. 늘 그랬듯이..

 

이상형 오디션 on AWS

2013년 11월 말… 새로운 프로젝트 개발을 시작하게 되었습니다. 이름하여 ‘본격대결, 이상형 오디션’ 입니다. 5개월여의 시간이 흘러 4월말 오픈 베타를 시작으로 5월 7일에 정식 런칭을 하게 되었습니다.

idealmate

 

현재 <안드로이드앱>을 스토어에서 만나볼 수 있습니다. 또한 이상형 오디션의 기획 이야기에 관심이 있으시다면 <게임과 네트워킹의 만남, 본격 대결 ‘이상형오디션’ 기획 이야기>를 읽어보시기를 추천합니다.

 

이 포스팅에서는 이상형 오디션이 인프라로 선택하고 사용한 AWS 서비스에 대해서 소개를 해보려고 합니다. 사용한 서비스를 간략히 나열해보면 아래와 같습니다.

  • EC2
  • RDS
  • S3
  • CloudFront
  • Route53
  • SNS
  • SQS

 

EC2

EC2는 AWS의 기본이 되는 서비스이며 가상 서버입니다. 사용자의 요청을 처리하는 API 서버와 서비스 운영, 통계 등을 담당하는 어드민 서버, 아직 AWS의 서비스로는 지원하지 않는 mongoDB 서버를 EC2 상에서 운영하고 있습니다. API 서버의 경우 ElasticLoadBalancer, AutoScaling 연동을 해서 자동으로 scale-out 이 되도록 해두었으나 아직 서비스 규모가 크지 않아서 효과를 보지는 못했네요.(얼른 트래픽이 많아져서 AutoScaling 이 빛을 발하는 모습을 보고 싶습니다.) MongoDB 의 경우 2014년 4월에 추가된 memory optimized instance인 r3 instance 를 사용하여 가격대비 높은 메모리를 쓰고 있으며 I/O 성능을 높이기 위해 Provisioned IOPS EBS volume을 붙여서 mongoDB 의 storage로 사용하고 있습니다. 참고로 r3 instance의 경우 HVM(Hardware Virtualization) AMI 만을 지원하므로 instance 생성시에 주의해야 합니다.

RDS

개발 시작 즈음부터 RDS에서 PostgreSQL을 사용할 수 있게 되었습니다. 이음에서 MySQL을 사용하면서 겪었던 문제들 중 하나가 table에 새로운 column을 추가할때 table에 lock이 걸려서 해당 table에 대한 요청을 처리할 수 없는 것이었습니다. PostgreSQL의 경우 column 추가가 매우 빨라서 기획이 바뀌거나 기능을 추가하는 경우에 좀 더 유연하게 대처할 수 있을 것으로 판단을 해서 선택하게 되었습니다. 또한 PostgreSQL의 안정성을 믿기로 했습니다. PostgreSQL 또한 Provisioned IOPS를 설정해서 안정적인 성능이 나오도록 했습니다. 역시나 아직까지 트래픽이 많지 않아 매우 안정적이며 여유롭습니다.

S3

S3는 다양하게 활용할 수 있는 매우 좋은 서비스입니다. 서비스를 위한 이미지와 사용자가 직접 업로드하는 프로필 사진 모두 S3에 업로드하여 서비스를 하고 있으며 각종 로그도 저장하고 있습니다. 또한 API 서버 배포시에 S3를 저장소로 사용하고 있습니다.

CloudFront

서비스용 이미지와 사용자 프로필의 경우 S3를 origin으로 하여 CloudFront를 통해 서비스를 하고 있습니다. CDN를 따로 계약할 필요없이 트래픽만큼만 비용을 지불하면 되니 정말 최고의 서비스가 아닌가 싶습니다. 참고로 사용자 프로필의 경우는 무분별한 크롤링을 방지하기 위해 signed URL을 사용하고 있습니다.

Route53

서비스용 도메인인 idealmate.kr의 DNS 서비스로 Route53을 사용하고 있습니다. 4월 10일부터 유료화된 DNSever 대신 사용하게 된 것이 그나마 이야깃거리네요. DNSever의 경우 standard DNS가 도메인당 월 천원(부가세 별도)이고 Route53의 경우 0.5달러여서 조금 더 저렴하기도 합니다.

SNS

모바일앱 Push nofitication을 사용하고 있습니다. 이상형 오디션의 경우 현재는 안드로이드앱만 출시된 상태지만 iOS앱도 출시를 위해 심사중입니다.(애플과의 밀당은 그만하고 싶네요ㅠㅠ)  SNS Push notification의 경우 APNS, GCM을 모두 지원하고 월 최초 백만건이 무료이며 추가 백만건당 0.5달러로 매우 저렴합니다. Push nofitication외에도 CloudWatch에서 설정한 alarm을 받는 용도로도 사용하고 있으며 alarm을 email과 http 연동을 통한 SMS로도 받고 있습니다.

SQS

이상형 오디션의 기능 중 랭킹 기능이 있습니다. 랭킹의 경우 사용자의 연승 갱신, 승리 추가, 채팅 요청, 채팅 수락 등의 액션에 따라서 랭킹 포인트가 증가해야 하는데 API 서버가 바로 랭킹 포인트를 갱신하기에는 부담스러운 작업입니다. 그래서 SQS를 사용해서 랭킹 포인트를 비동기로 증가시키도록 했습니다. 물론 SQS에 쌓인 job을 읽어서 처리하는 worker는 별도로 구현을 했습니다.

 

맺음말

이번 프로젝트를 하면서 AWS 서비스를 다양하게 사용해보았습니다. 이음에서는 아직 사용하지 않는 CloudFront, SNS Push notification을 사용하면서 비용도 절약하고 개발도 편하게 할 수 있었습니다. 프로젝트를 진행하는 동안 AWS도 RDS PostgreSQL 지원, EC2 r3 instance 추가, 가격 인하 등의 변화가 있었고 프로젝트에도 많은 도움이 되었습니다. 앞으로도 AWS가 더 발전하기를 바라면서 이만 마치겠습니다.

 

C언어로 API 서버 개발, 생각보다 나쁘지 않아요

지난 이음 API 개선에 관한 포스팅 <아파치 모듈로 개발된 API 서버, 이음 베이론을 소개합니다> 를 기억하시나요? 이 포스팅이 올라온 후 이음 개발팀 블로그에는 독자 여러분들의 뜨거운 관심(?)이 빗발쳤습니다. (그리고 아직까지 이 정도의 뜨거운 관심을 받은 포스트는 아직 나오지 않고 있습니다…) C언어로 API를 개발한다는 이야기에 흥미롭다는 반응도 있었지만 아무래도 걱정어린 시선이 많았습니다.

사실 말하자면 저도 루비, 파이썬을 비롯한 고급 언어들의 팬입니다. 그동안은 PHP나 Java (Spring) 같은 옛날 언어로 개발을 해왔지만, 처음 루비 온 레일즈를 접하고 그 깔끔함에 반해서 아직도 뭔가를 개발할 일이 있으면 최대한 레일즈를 쓰고 있구요. 과거 Spring을 쓰면서 직접 설정해줘야 했던 수많은 잡일들을 떠올리면서 ‘그동안 나는 뭘 한걸까…’ 하는 기분을 느꼈습니다. 그렇게 레일즈와 즐거운 시간을 보내던 중, 담당 프로젝트가 변경되면서 저는 C언어로 API 서버를 개발해야 하는 운명에 처하게 되었습니다.

으아니

이게 무슨 소리요! 내가… 내가 C 개발이라니!

처음에는 너무 막막했습니다. 레일즈를 써보신 분은 알겠지만, 기타 잡다한 부분은 프레임웍이 다 해주기 때문에 로직만을 작성하는 데에 익숙해져 있었거든요. 그런 저에게는 모든 것을 직접 해야만 하는 C언어 개발은 조금 불편하긴 했지만 결론적으로, 써보니까 생각보다 나쁘지 않았습니다. 다른 분들이 생각하시는 것처럼 유지보수 헬게이트도 아니구요. 무엇보다 성능이 모든 것을 용서합니다. 아무튼 자세한 이야기는 차차 해보기로 할게요.

Apache Portable Runtime

C 언어를 서버로 개발한다! 라고 하면 사람들은 포트 여는 것부터 response writing까지 모두 로우 레벨에서 처리해야 할 거라고 생각하지만, 다행히 그 정도까지는 아니었습니다. 이음의 API 서버 개발은 APR (Apache Portable Runtime) 이라는 큰 프레임웍 위에서 돌아가고 있습니다. 엄밀히 말하자면 프레임웍이라기보다는, 웹 서버인 아파치와 이음의 비지니스 로직을 연결해주는 라이브러리라고 할 수 있겠네요. 여튼 APR 덕분에, C 프로그래밍은 생각만큼 괴롭지 않습니다. APR의 홈페이지 메인을 보면 아래와 같이 이야기하고 있습니다.

“The mission of the Apache Portable Runtime (APR) project is to create and maintain software libraries that provide a predictable and consistent interface to underlying platform-specific implementations” – APR 프로젝트의 목적은 플랫폼 의존적인 구현에 대한 예측 가능하고 일관적인 인터페이스를 제공하는 소프트웨어 라이브러리를 만드는 것이다.

즉, 사용하는 하드웨어와 OS에 상관 없이 상위 레벨의 구현에만 신경쓸 수 있도록 만든 라이브러리가 바로 APR이라는 의미입니다. (자바의 “Write once, run everywhere” 의 향기를 느낀 건 저 뿐일까요?) 그래서 APR을 사용하면 네트워크 관련 low level operation (HTTP 헤더를 파싱한다던지, 파라미터를 읽어서 저장한다든지) 에 대해서는 신경쓰지 않아도 됩니다. 프로그래밍에 필요한 기초 자료 구조 (list, hash table) 도 라이브러리에 포함되어 있습니다.

무엇보다 매력적인 것은 APR의 메모리 풀 관련 기능입니다. 이 메모리 풀 라이브러리는 C 언어 개발에서 아주 골치아픈 메모리 할당 / 해제 문제를 해결해줍니다. 구체적으로 이야기하자면, APR을 사용하여 메모리를 할당 (apr_pcalloc 함수를 사용) 받으면, 클라이언트에 response를 보내줄 때 자동으로 풀에 할당된 메모리들을 해제해준다는 것입니다. 멋지지 않나요? 아무튼, APR의 은총 덕분에 상당히 많은 부분들을 라이브러리에 의존하면서 개발할 수 있습니다.

정적 타입의 강점

런타임에 변수의 타입을 결정하는 동적 타이핑 기반의 언어인 루비나 파이썬과는 달리, C는 컴파일 타임에 타입을 체크합니다. 타입이 맞지 않으면 오류를 내면서 컴파일조차 잘 되지 않습니다. 이 부분이 스크립트 언어를 주로 사용하던 프로그래머들에게는 불편할 수 있습니다. 하지만 저는 타입 안전성을 컴파일 타임에 확인할 수 있다는 점은, 프로그램의 안정성에 매우 큰 부분을 차지한다고 생각합니다. (물론 C 언어가 타입 안전성을 완벽히 보장하는 언어인지 묻는다면 물론 아닙니다. 하지만 컴파일 타임에 타입 체크가 불가능한 언어에 비해서는 분명한 강점을 가지고 있다고 생각합니다.) 타입 안전한 언어에서는 컴파일 타임에서 에러가 나지 않으면 로직에 문제가 있지 않은 이상은 잘 작동하죠. 버그는 있을 수 있지만 프로그램이 죽는 일은 없습니다. 또한 성능도 뛰어납니다.

compiling

그만큼 컴파일에 시간이 걸리긴 하죠. (물론 짤방정도는 아님)

Performance, Performance, Performance

APR을 사용하여 개발한 API 서버의 성능은 그야말로 압도적입니다. 기계어로 컴파일 되니까요.

Screenshot at Jan 21 16-36-24

더 이상의 자세한 설명은 생략한다

 

물론, 당연하지만, C 개발이 장점만 있지는 않습니다.

 

 

Reinventing the wheel

회사의 모 님이 저에게 물었습니다. “C 언어로 API 개발하는 거 어떤가요?” 저는 대답했더랬죠. “바퀴를 재발명하고 있는 것 같아요.” 그렇습니다. C 언어에는 그 흔한 ORM조차 없습니다. C 언어 자체가 객체지향적인 특성이 거의 없는 절차 지향적 언어이기 때문에, ORM을 만들어서 쓴다 해도 영 불편하죠. 덕분에 쿼리를 쓰고, 전송하고, 결과를 확인하고, 데이터 구조에 저장하는 등의 일들을 직접 한땀한땀 해야 합니다. 물론 공통되는 부분은 함수로 만들어서 재사용할 수 있지만, 누군가가 만들어 놓은 라이브러리를 가져다 쓰는 것에 비하면 개발 속도가 느려질 수밖에 없습니다. (그만큼 좀 더 시스템 하층 구조에 대해 깊게 알 수 있다는 장점은 있습니다.) 하다 보면 “꼭 이런 걸 일일이 해줘야 하나..”  란 생각이 들 때도 있습니다. 그리고 이건 개개인의 취향이겠지만, 저는 누군가가 만들어 놓은 소스 코드를 제가 다시 만드는 일이 그렇게 재미있지만은 않더라구요.

car and wheel

좀 이런 느낌…

라이브러리 뿐만 아니라 배포나 디버깅, 에러 리포팅, DB조회 등등 전반적인 개발 환경 역시 덜 자동화되어 있기 때문에 만약 자동화시키고 싶다면 직접 제작하는 수밖에 없습니다. 일례로, 레일즈 어플리케이션을 개발할 때는 모델 클래스를 잘 만들어 두면 데이터베이스 조회를 레일즈 콘솔을 통해 거의 다 할 수 있었으나, C 기반 개발을 할 때는 특정 데이터를 조회하거나 업데이트할 때에는 SQL을 직접 작성하는 수밖에 없습니다. 만약 조인을 해야하는 상황이라면 그 번거로움은 배가 되고 쿼리문이 길어질 수록 중간에 실수를 해서 개발 속도가 느려질 가능성은 커집니다. SQL에 익숙하지 않은 개발자라면 더 심하겠죠. 익숙하지 않은 DBMS를 사용한다면 더더욱 그럴테구요.

생산성

“C 언어는 생산성이 안좋다.” 라는 생각을 많이들 가지고 계신 것 같더군요. 제가 생각하기에, 생산성이라는 것은 몇 가지 측면으로 분류할 수 있을 것 같습니다. (물론 이거 말고도 더 있겠죠?)

  1. 같은 길이의 코드로 얼마나 더 많은 일을 할 수 있는 지
  2. 주어진 스펙을 얼마나 빠르게 개발할 수 있는 지
  3. 유지보수가 얼마나 용이한 지

1에서 당연히 C 언어는 루비에 비해 크게 뒤쳐질 수밖에 없습니다. 언어 자체의 특성도 그렇고, 외부 라이브러리의 사용도 쉽지 않으니까요. 하지만 제가 둘 다 경험해보니, 2의 측면에서 C 언어가 결코 루비에 비해 많이 뒤쳐지지는 않는 것 같습니다. 제가 조금 더 C 언어에 숙련된 프로그래머라면 그 격차는 더 줄어들겠죠? 그 이유는 다음과 같습니다.

  1. C에서는 쓸만한 외부 라이브러리가 많지 않습니다. 하지만 외부 라이브러리를 덜 가져다 쓴다는 말은 그만큼 관련 문서들을 찾아보는 데에 시간이 덜 걸린다는 이야기도 됩니다. 따라서 IDE와 웹브라우저 사이에서의 컨텍스트 스위칭에 시간을 덜 쓸 수 있습니다.
  2. 컴파일 언어라는 특성에서 비롯되는 IDE의 도움이 강력합니다. (자동 완성, 타입 체크, 오류 점검 등)
  3. 컴파일이 되기 때문에, 개발에는 시간이 더 걸릴 수 있지만 테스트와 디버깅에는 시간이 덜 걸립니다.

3의 경우에는 현재 상태에서는 말하기 힘든 부분이 있습니다. 왜냐하면 지금까지 C 언어를 통해 개발된 프로젝트들은 (1) 안정화되어서 커다란 스펙 변경이 없는 경우, (2) 신규 프로젝트라서 스펙이 미리 정해져 있는 경우, 둘 중 하나였기 때문입니다. 추후 애자일한 과정을 통해 잦은 스펙 변경이 있어야 할 때에 C 언어가 얼마나 유지보수가 용이한 지는, 조금 더 지켜보고 말씀드려야 할 것 같습니다. 다만 개인적으로는 코드 양이 더 많기 때문에 유지보수가 더 어렵지 않을까 하는 막연한 추측을 하고 있습니다. 아마 프로젝트 규모가 커질 수록 더하겠죠?

 

결론

 APR을 사용하면 C 언어로 모바일 API를 작성하는 일도 그렇게 어려운 일은 아닙니다. 약간 불편하기는 하지만 프로덕션 레벨에서 쓸 수 없을 정도는 아니고, 그만큼 우월한 성능을 얻을 수 있기 때문에 만약 스펙이 자주 바뀌지 않고 C 언어와 APR 기반 개발에 익숙하다면 시도해볼만 한 가치가 있는 것 같습니다.

지난 번 포스팅에서 많은 분들이 “CPU 사용률을 보면 사실상 CPU가 병목은 아닌 것 같다”고 지적해 주셨습니다. 웹 어플리케이션의 속도가 DB 속도에 의존적인 것은 사실입니다. 하지만 DB 병목보다는 어플리케이션 레벨에서의 불필요한 오버헤드 (정확히 말하자면 Hibernate의 eager loading) 역시 큰 속도 저하의 원인이었습니다. 이를 수정하기 위한 노력을 기울였지만 결국 포기하고 주요한 속도 저하의 원인이었던 ORM을 버리고 서버를 다시 개발하기로 결정하게 된 것입니다. 물론 이게 절대적으로 최선의 선택이었다고는 하기는 힘들지만, 목표한 성과를 이뤘다는 점에서 긍정적이라고 생각합니다.

모든 프로그래밍 언어와 프레임웍은 각자 제 용도에 맞는 쓰임새가 있다고 생각합니다. (너무 모범적인 결론이지만) 이음의 API 서버는 현재로서는 큰 설계의 변화나 스펙의 변화가 없기 때문에 유지 보수가 조금 불편하더라도, 퍼포먼스를 끌어낼 수 있는 C 언어를 사용했죠. 그리고 애초 의도했던 바와 부합하는 효과를 얻었습니다. 특히나 모바일 API 쪽은 HTML 페이지를 렌더링하는 웹 서버와 비교하더라도 많은 기능이 필요하지 않습니다. 만약 웹 서버를 개발하는 일이었다면 string 관련 함수들이 불편한 C를 사용하지 않았을 것입니다. 어떤 언어(프레임웍)을 쓰는 지도 중요하지만 지금 사용하고 있는 도구가 목적에 맞는 지도 중요하지 않을까요?

하지만 무엇보다도 개발자에게 그 도구가 얼마나 손에 익은 도구인지도 중요한 것 같습니다. 아무리 좋은 도구가 있더라도 사용할 줄 모르면 무용지물인 것처럼요. 이음에서는 APR 환경에서 오랜 시간동안 개발을 해왔던 개발자 분이 계셨고 그 분이 첫 삽을 뜬 덕분에 다른 분들이 수월하게 개발을 시작할 수 있었던 것 같습니다. 그리고 개발자들이 대부분 컴퓨터 공학 전공 베이스를 가지고 있었던 덕분에 C 언어를 생소하게 느끼지도 않았구요. 이 글을 읽고 APR 도입을 고려하고 계시다면, 이 점을 다시 고려해보세요 🙂

 About the use of language: it is impossible to sharpen a pencil with a blunt axe. It is equally vain to try to do it with ten blunt axes instead ; 프로그래밍 언어의 사용에 관하여: 무딘 도끼를 가지고 연필을 깎는 일은 불가능하다. 무딘 도끼 10개를 가지고 시도한다고 해도 결과는 마찬가지다.  – E. W. Dijkstra

 

TabsSpacesBoth

그래도 한 프로젝트에 여러 언어는 섞어 쓰지마세요. 술 섞어 먹는거랑 똑같습니다. 먹을 때만 행복

 

Screenshot at Jan 21 18-52-44

 

MongoDB를 쓰면서 알게 된 것들

최근 이음의 글로벌 프로젝트 Hey에서는 많은 수의 유저들에 대한 매칭을 원활하게 처리하기 위해 가장 많이 쓰이고 있는 NoSQL 스토리지 중 하나인 MongoDB를 도입했습니다. RDBMS에 익숙했던 개발자들 입장에서는 굉장히 낯선 부분들이 많았지만 여러가지 시행 착오들을 겪으면서 어느 정도 안정화된 상태로 사용할 수 있게 되었습니다. 1년 전쯤에 개발자 커뮤니티에서 화제가 되었던 Things I wish I knew about MongoDB a year ago 라는 글이 있었습니다. 이번 글에서는 이음에서 MongoDB를 쓰면서 겪었던 여러가지 이야기들을 풀어내 봄으로써, MongoDB 엔진 도입을 검토하고 있는 개발자 분들, 더 넓게는 NoSQL 기술에 관심이 있는 개발자 분들과 다양한 의견들을 나눌 수 있었으면 합니다.

 

mongo-db-huge-logo

MongoDB가 뭔가요?

MongoDB는 10gen (최근 MongoDB로 사명이 변경되었습니다.) 에서 만든 document 기반의 NoSQL 스토리지입니다. 엔진은 C++로 작성되었으며, 오픈 소스입니다. 대표적인 특징으로는 다음과 같은 것들이 있습니다.

– Document-Oriented Storage : 모든 데이터가 JSON 형태로 저장되며 schema가 없습니다.
– Full Index Support : RDBMS에 뒤지지 않는 다양한 인덱싱을 제공합니다.
– Replication & High Availability : 데이터 복제를 통해 가용성을 향상시킬 수 있습니다.
– Auto-Sharding : Primary key를 기반으로 여러 서버에 데이터를 나누는 scale-out이 가능합니다.
– Querying : key 기반의 get, put 뿐만이 아니라 다양한 종류의 쿼리들을 제공합니다.
– Fast In-Place Updates : 고성능의 atomic operation을 지원합니다.
– Map/Reduce : 맵리듀스를 지원합니다.
– GridFS : 별도 스토리지 엔진을 통해 파일을 저장할 수 있습니다.

 

RDBMS로는 안되나요?

Hey에서는 모든 회원들을 상대로 하루 3개 이상의 매칭을 제공합니다. 하나의 매칭을 RDBMS에서 하나의 row로 생각하면, 하루에 회원 수 * 3개의 row가 추가되는 것이죠. (게다가 어른의 사정(?) 때문에 실제로는 더 많은 수가 추가되는 경우가 대부분입니다.) 이렇게 1년이 쌓이면 회원 수 * 1000개가 되겠죠? 유저가 10만이면 1억입니다. 이렇게 많은 숫자의 매칭을 관리하기에 RDBMS는 한계가 있다고 판단되어, scale-out이 가능한 MongoDB를 사용하게 된 것입니다. 사실 처음엔 Apache HBase를 사용하려고 헀으나, HBase는 다음과 같은 문제가 있었습니다.

– Rails 어댑터의 부재 : Hey의 API 서버는 Ruby on Rails를 사용하여 개발되었습니다. Rails에서 HBase를 사용하기 위한 gem인 massive_record가 있긴 했지만, 프로덕션에서 사용하기엔 갈 길이 멀어 보였습니다. (HBase 자체의 문제는 아닙니다.)

– Hadoop의 도입 비용 : 개발팀 내에 Hadoop 경험자가 거의 없어서 클러스터 관리가 어려웠고, Hadoop 자체가 기본적으로 많은 (2~3대 이상) 머신들을 필요로 합니다.

– 미약한 Index 기능 : HBase는 거의 key-value 스토리지처럼 작동하기 때문에 다양한 쿼리들을 실행하기 위해서는 데이터를 full scan해야만 하므로, 스토리지 전체의 성능을 저하시킵니다.

이런 이유때문에 Hey에서는 매칭 데이터 및 document형 데이터는 MongoDB에, 기타 관계형 데이터는 MySQL에 저장하도록 구현하였습니다.

서론이 길었네요 🙂 이제부터 본격적으로 시작해 보도록 하겠습니다.

 

1. Indexing이 자유롭습니다.

MongoDB에서는 가장 많이 쓰이는 RDBMS인 MySQL에서 지원하는 대부분의 인덱스를 지원합니다. 지원하는 인덱스의 목록은 대략 다음과 같습니다.

– Single Field Indexes : 가장 기본적인 인덱스 타입
– Compound Indexes : RDBMS에서 많이 쓰이는 복합 인덱스
– Multikey Indexes : Array에 매칭되는 값이 하나라도 있으면 인덱스에 추가하는 멀티키 인덱스
– Geospatial Indexes and Queries : 위치 기반 인덱스와 쿼리
– Text Indexes : String 컨텐츠에도 인덱싱이 가능
– Hashed Index : BTree 인덱스가 아닌 Hash 타입의 인덱스도 사용 가능

이처럼 강력한 인덱스 기능 덕분에 거의 모든 쿼리들을 빠르게 처리할 수 있습니다. 이는 MongoDB를 선택한 가장 큰 이유인 동시에, 다른 NoSQL들에서는 찾아보기 힘든 장점이기도 합니다.

 

2. 많은 데이터를 한꺼번에 insert하면 안됩니다.

Row 단위의 락을 지원하는 대부분의 RDBMS와는 달리 MongoDB에서는 데이터베이스 단위의 락을 사용합니다….. 뭐..뭐라구요?

e0020102_03085040

기존의 RDBMS 사용자들에게는 그야말로 충격과 공포죠.  하지만 여기서 끝이 아닙니다. MongoDB에서는 데이터베이스별로 Read Lock과 Write Lock이 있는데, read lock은 여러 개의 operation에서 공유가 가능하지만 write lock은 하나의 operation만이 사용할 수 있으며 (중간에 read operation도 허용하지 않습니다.) write lock은 항상 read lock보다 우선권을 갖습니다. 즉, read operation과 write operation이 동시에 queue에서 대기하고 있으면 항상 write operation이 실행된다는 것입니다. 따라서 MongoDB에서 대량의 데이터 (수만 이상) 를 한꺼번에 insert하면, DB가 insert의 수행때문에 다른 작업을 수행하지 못하게 됩니다. 장비를 정지합니다. 아..안되잖아?

따라서 지금 Hey에서는 대량의 insert 시에는 항상 수천 개 정도의 작은 단위로 쪼개어서 실행하고 있습니다. 그나마 MongoDB의 write가 메모리에 쓰는 방식이기 때문에 RDBMS에 비해 성능 저하는 덜하고, 긴 operation의 경우 lock을 양보하기도 하지만 (자세한 내용은 공식 문서를 참조) 그래도 찜찜한건 사실입니다.

 

3. 쿼리 문법이 불편합니다.

JSON 기반의 데이터베이스인데다가 NoSQL이기 때문에, 쿼리를 작성할 때 JSON으로 작성해야 합니다. 우선 간단하게 range 쿼리입니다.

아직까진 나쁘지 않죠? 여기에 and 조건을 좀 넣어볼까요?

이제 여기서 여러개의 조건들을 boolean operator로 묶으면…

슬슬 눈에 안들어오기 시작합니다. 하지만 진짜 최종보스는 SQL의 group by와 유사한 aggregation pipeline입니다.

daetul_mung

이런 쿼리를 쓰다가 괄호라도 하나 삐끗하면…

 

멘붕
대략 이런 기분을 느낄 수 있습니다.

그나마 예제라 조금 간단한데, 제대로 format 맞춰서 조금 복잡한 쿼리를 짜기 시작하면 열줄이 넘어가는 경우도 허다합니다. (실제로 Hey 통계 페이지에는 수십 줄짜리 쿼리가 있다는 소문이…) 개발자들은 배워서 쓰고 익숙해지다보면 어느 정도 해결 되는 문제이지만, 서비스 기획자들이 개발자를 통하지 않고 통계를 내보기는 쉽지 않습니다. 이런 쿼리를 실제로 쓸 일이 있냐구요? 네….  있더라구요.

물론 NoSQL이라 조인이 안되는건 보너스입니다. 모든 쿼리는 단일 콜렉션 내에서 해결하셔야 합니다. 

20111216171940

야! 신난다!

 

4. Subdocument가 계속해서 늘어날 땐 별도의 collection으로 만들고 인덱스를 거세요.

Join이 안되는 MongoDB에서 1:N 관계를 표현할 때에는 referencing과 embedding의 두 가지 방법이 있습니다.

예를 들어 여러 명의 여자친구가 있는 어떤 남자를 document로 표현한다고 생각해봅시다. (예제니까요) 그럼 아마 male 콜렉션에는 이런 데이터가 있을 것입니다. 

(1)번의 referencing으로 이 남자를 표현해 보겠습니다.

(2)번의 embedding은 관계가 있는 문서를 상위 document에 포함해버리는 방법입니다. 아래처럼요.

하지만 둘 다 계속해서 girlfriends의 숫자를 늘리는 데에 좋은 방법은 아닙니다. (말이 좀 이상하지만) 그 이유는 다음과 같습니다.

(1) MongoDB의 document는 최대 size가 제한되어 있습니다. Default는 16MB이고 조정이 가능하지만, 계속해서 늘릴 수는 없습니다.

(2) Document의 크기를 늘리는 operation은 새 document를 insert하는 operation에 비해 매우 느립니다. 사실 이는 MongoDB 내부에서 document growth를 다루는 방식의 문제이기 때문에 size가 늘어나는 모든 document에 해당되는 문제입니다.

따라서 1:N의 관계를 표현할땐 다음처럼 전통적인 RDBMS의 방법을 사용하는 것이 좋습니다.

물론 female 콜렉션의 male_id에 인덱스를 걸어두는 게 성능이 좋겠죠? 이렇게 하면 두 콜렉션을 동시에 가져올수는 없고, 두 콜렉션을 별도로 가져와서 어플리케이션 레벨에서 조인을 수행해야하는 슬픔이 있습니다… 역시 세상에 공짜같은건 없다는 진리를 다시 한번 느낄 수 있죠.

 

5. ActiveRecord와의 연동이 깔끔하지 않습니다.

Hey에서는 Ruby on Rails의 RDBMS 기반 ORM인 Activerecord와, MongoDB기반 ORM인 Mongoid를 동시에 사용합니다. 따라서, 당연하게도, ActiveRecord object에서는 mongoid와의 association을 선언할 수 없습니다. 따라서 다음과 같은 임시방편이 필요합니다.

이정도면 그나마 간단한 경우입니다. 만약 ActiveRecord object 하나와 연결된 여러 개의 Mongoid object를 하나의 transaction으로 묶어서 destroy해야 한다면 어떨까요?

그럴 듯 해보이나요? 하지만 위의 코드는 User를 지우다가 exception이 발생하면 Avatar만 지워지고 User만 남아있는 무시무시한 사태가 발생하게 됩니다. 이런 경우를 모두 생각해야 해서, 두 모델을 섞어서 쓰는 것은 그리 간단하지 않습니다. 쿼리도 마찬가지입니다. Avatar를 3개 이상 가진 User를 모두 반환하는 쿼리는 어떻게 작성하면 될까요? 이건 읽는 분들의 상상에 맡기도록 하겠습니다…

Tenacity같은 gem을 사용하면 조금 더 깔끔하게 되지 않을까 하는 생각도 들지만, 사용해보지 않아서 이렇다 할 말씀은 드리기가 힘드네요. 개인적인 판단으로는 redis와 같은 caching 용도를 제외하고 하나의 어플리케이션에서 두 가지 이상의 데이터베이스를 동시에 사용하는 것 자체가 여러 언어를 사용해서 하나의 어플리케이션을 만드는 것처럼 헬게이트를 여는 일인 것 같습니다. 잘 생각해보시고 정말 필요한 경우가 아니라면 다시 한번 고려해보시길 권고드립니다.

 

맺음말

쓰고 나니 MongoDB의 단점만 잔뜩 늘어놓은 것 같은 기분이 드는 건 기분 탓일까요? ^^; 아무래도 RDBMS에 익숙한 대부분의 개발자들에게 NoSQL은 너무 안되는 게 많은 스토리지처럼 보이는게 현실인 것 같습니다. 개인적인 경험에 비추어 보았을 때에는 MongoDB를, 더 나아가서 NoSQL을 도입할 때에는 스토리지 엔진의 특성을 잘 파악하고 꼭 필요한 경우에만 도입하는 것이 정답인 것 같습니다. 제가 생각하는 MongoDB가 적합한 케이스는 다음과 같습니다.

(1) Schema-free 한 데이터 구조가 꼭! 필요한 경우
Schema가 없다는 건 RDBMS에서는 얻기 정말 힘든 장점임에 틀림없습니다.

(2) Collection간의 관계가 그다지 긴밀하지 않을 경우
다른 collection과의 조인이 지옥이라는 건 위에서 충분히 보여드린 바 있죠….

(3) Insert 된 이후에는 document growth가 잘 일어나지 않는 경우 : document growth는 느립니다.

(4) Sharding이 꼭! 필요한 경우
개인적인 경험으로는 row 수가 억단위가 되지 않는 이상은 RDBMS의 인덱스 최적화와 테이블 파티셔닝으로 충분히 커버가 가능하다고 생각합니다. 사실 Hey에서 매칭 데이터를 MongoDB에 저장하기로 한 것도 매칭 수가 억단위를 넘어갈 경우를 생각해서였습니다만……

slamdunk

Hey에서 sharding이 필요할 정도로 row 수가 많아지는 일은 없었습니다…… (아직까지는요)

(5) 다른 데이터베이스와의 연결이 없이 MongoDB만으로 충분한 경우

 

그냥 하루빨리 오픈소스 진영에서 scale-out이 가능한 RDBMS를 내놓았으면 하는게 개인적인 바람입니다.

긴 글을 여기까지 읽어주신 분들께 감사드립니다. 끝으로 NoSQL에 대해 읽어볼만한 몇 가지 글들의 링크를 남기고, 저는 이만 물러가도록 하겠습니다.

 

NoSQL은 생각보다 쓸만하지 않다

Why does Quora use MySQL as the data store instead of NoSQLs such as Cassandra, MongoDB, or CouchDB?

Cassandra vs MongoDB vs CouchDB vs Redis

 

1425563_647984125251871_1119116285_n

Lethe

서버 사이드 기술과 데이터 핸들링 부분에 관심이 많은 초보 개발자입니다.
이것 저것 많이 공부해보고 있는데 공부할 건 많고 시간은 없고 몸뚱아리는 게을러서 슬프네요.

아파치 모듈로 개발된 API 서버, 이음 베이론을 소개합니다.

안녕하세요. 저는 이음소시어스에서 서버사이드 개발을 하고 있는 오영식(다람)입니다. 개발팀 블로그의 첫 글로 우리의 새로운 API 서버 마개조 프로젝트, 이음 베이론을 소개해 드리려고 합니다.

Introduction

2년쯤 전, 카카오톡에서 “겁나빠른황소” 프로젝트를 적용하였습니다. 초반에는 빠르게 작동하던 카카오톡이 사용자가 늘어남에 따라 속도 또한 느려져 전체적으로 시스템을 개편한 것입니다. 이전부터 카카오톡을 사용해 보신 분들은 아시겠지만, 메시지 전송을 할 때 항상 보이던 바람개비가 그 이후에는 구경조차 어렵게 바뀌었습니다.

이와 비슷하게, 이음 또한 사용자가 많이 늘어나면서 수많은 데이터가 쌓여감에 따라 빠릿빠릿하게 작동하던 어플리케이션이 점점 느려져 사용에 불편함을 느낄 정도가 되었습니다. 또한, 기존에 개발되어 있는 소스코드에 계속해서 기능을 추가하다 보니 소스코드의 양이 너무 방대해져 유지보수가 어렵게 되어, 전면적인 소스코드 개편을 고려하게 되었습니다. 그 결과로 만들어진 것이, 이름하여 베이론 프로젝트입니다.

베이론?

먼저 전혀 중요하지 않지만 베이론이라는 이름은 매우 빠른 서버를 만들자는 취지에 따라 구글에서 자동차 제로백 순위로 검색해 나름 있어 보이는 이름으로 지었습니다.

 

1

실제로 소스코드 저장소 위키에도 이렇게 써 있습니다.

 

베이론은 아파치 웹 서버의 모듈로 개발이 되어 있습니다. 물론 C로 개발하고 있고, 이 때문에 기존의 Spring framework를 사용한 서버보다 훨씬 빠르고 더 적은 리소스로 구동이 가능합니다.

맨 처음 개편을 고려할 때 많은 언어들을 고려했습니다. Rails, PHP, Python, Node.js, Java로 다시만들기(!) 등 많은 선택지가 있었는데, 아파치 모듈(C)를 선택한 이유 몇 가지를 추려보면:

  1. 웹 개발 언어가 많은 연산을 필요로 하지는 않고 대부분의 병목이 DB에서 남에도 불구하고, 생각보다 많은 속도 저하가 웹 어플리케이션 내부에도 존재합니다. C는 (잘 짜면)매우 좋은 성능을 보장해줍니다.
  2. 좋은 성능에도 불구하고 메모리 관리와 같이 컨트롤하기 까다로운 문제도 존재하고, 데이터 저장 및 수정이 타 언어에 비해 불편한 것이 사실입니다. 하지만 아파치가 내부적으로 사용하는 라이브러리(APR)이 매우 강력해 C로도 충분히 빠른 시간 안에 좋은 퍼포먼스의 웹 어플리케이션을 개발 할 수 있습니다.
  3. 스프링, 레일즈와 같은 프레임워크는 웹 개발자에게 매우 편리한 강력한 기능들을 많이 제공하고 있습니다. 하지만 프레임워크는 아무리 좋은 성능을 내더라도 프레임워크 없이 잘 짜여있는 어플리케이션보다 느린 것이 사실이고, 처음에 익히는데 시간이 드는 것이 단점입니다. 따라서 프레임워크에 의존하지 않는 방식으로 개발을 하게 되었습니다. 언젠가 스프링 배치의 퍼포먼스와 개발속도에 분노해 아예 바닥부터 짜버린 배치의 사연도 소개하게 되겠지요.
  4. 사실 C로 API를 구현한다는 사실이 매우 생소하긴 하지만, (NHN에서 통합검색 서버를 개발하신 제 옆의 모 개발자님께서 말씀하시길) 수많은 트래픽을 감당하고 있는 네이버 검색 웹 서버 또한 대부분 아파치 모듈로 개발되어 있어, 이는 충분히 사용할만하고 검증되어 있는 방식이라고 할 수 있습니다.

벤치마크

베이론 서버와 기존 Java로 개발된 서버는 많은 부분에서 퍼포먼스 차이를 보여줍니다. 이 글에서는 두 가지 관점에서 비교를 해 보겠습니다. 첫 번째는 사용자가 직접 체감하게 되는 Latency이고, 두 번째는 서버 개발자들이 항상 전전긍긍하며 바라보게 되는 CPU 사용률입니다. 먼저, Latency를 비교해 보도록 하겠습니다.

(1)   Latency

    1. Java 서버
2

Java 서버의 Latency

 

대체로 평소에는 0.1~0.2초대의 응답속도를 보여줍니다. 그렇지만 우리 서비스의 특성상 특정 시간에 매우 많은 트래픽이 발생하는데, 이 시간대에는 0.7초까지 응답속도가 늦어지는 것을 볼 수 있습니다.

      1. 베이론 서버
3

베이론 서버의 Latency

전체적으로 응답속도가 0.1초 아래로 나온다는 점도 훌륭하지만, 주목하셔야 할 점은 서비스 이용자가 몰리는 시간과 그렇지 않은 시간의 차이가 전혀 없다는 사실을 보아주세요. 어떠한 서비스던 그렇겠지만, 우리처럼 특히 특정 시간에 많은 트래픽이 몰리는 서비스의 경우에는 이러한 항상성(?)은 서비스의 큰 안정성을 보장하게 됩니다.

(2)   CPU 사용률

      1. Java 서버
4

Java 서버의 CPU 사용률

기본적으로 차지하는 CPU 사용률도 최소 5%이고, 이용자가 몰리는 시간에는 특히 30~40%대의 CPU 사용률을 가지고 있습니다. 따라서 안정적인 서비스 제공을 위해서는 좋은 CPU를 가진 인스턴스를 이용할 수 밖에 없습니다.

      1. 베이론 서버
5

베이론 서버의 CPU 사용률

굉장히 들쭉날쭉한 것 같아 보이지만, 평소에는 거의 0%대의 CPU 점유를 하고 있다가 사용자가 몰리는 시간에는 무려 3%까지(!!!) 차지하고 있습니다. 사실상 CPU는 거의 놀고 있습니다. Java서버와 비교하면 정말 큰 차이가 드러나고 있습니다.

(3)   기타

위 벤치마크 자료에는 드러나 있지 않지만 더 무서운 사실이 존재합니다. Java서버는 이용자가 몰리는 시간에 자동으로 인스턴스가 4대로 변경되며, CPU사용률에 따라 6대까지 늘어납니다만, 베이론 서버는 단 2대로 운영이 되고 있습니다. 이 사실까지 감안한다면, 베이론 서버의 압승이라고 볼 수 있겠지요.

단점도 있지 않을까요?

음, 사실 저도 베이론 프로젝트를 맨 처음 들었을 때 복잡한 C로 작성한다는 것이 매우 두려웠는데, 막상 진행을 하다 보니 아파치에서 제공하는 라이브러리를 통해 정말 쉽게 작성할 수 있었습니다. 그나마 있는 단점이라고 하면, 조금 생소하다는 점이 있겠네요. 아파치 모듈로 API를 작성하는 경우는 거의 없으니까요. 또, 필요한 기능이 있으면 직접 구현(!)해야 한다는 단점도 존재합니다.

그러나, 여전히 C는 API를 구현함에 있어 정말 좋은 언어입니다. 이 글에서는 단순히 아파치 모듈의 설명과 벤치마킹 자료만 올려 놓았지만, 나중에 추가로 기본적인 아파치 모듈의 작성 방법에 대해 글을 작성할 수 있을 것 같습니다.

기타

C로 개발하면 API는 몰라도 웹은 어렵지 않을까? 하는 생각을 하실 수도 있겠다는 생각이 들었습니다. 우리는 API를 C로 개발한 뒤에, Front는 Ruby 혹은 Python처럼 빠르고 간결하게 작성할 수 있는 언어로 개발한 뒤 API를 호출하여 결과값을 얻어내는 방식을 고려하고 있습니다. 이 부분은 레진코믹스의 경우와 비슷한데요, 위의 링크를 참고하시면 좋을 것 같습니다. 이렇게 작성하면 C의 빠른 속도와 스크립트 언어의 빠른 개발 속도를 모두 취할 수 있다는 장점이 있습니다. 물론, 웹에서도 API를 이용하기 용이하도록 잘 설계했다는 전제조건이 있어야겠지요.

결론

 

 …Write in C

Q: 모바일 api가 느린데 어떻게 하면 좋을까요?
A: C로 짜면 됩니다.

11