[발 번역] 잘가~~ Global Lock, MongoDB 2.0 vs 2.2

해당 글은 http://blog.serverdensity.com/2012/05/23/goodbye-global-lock-mongodb-2-0-vs-2-2/ 발 번역한 것입니다. 예전에 MongoDB의 Global Lock에 대해서 올라온 글(http://blog.pythonisito.com/2011/12/mongodbs-write-lock.html) 을 보면 MongoDB의 Global Lock이 성능에 어떤 영향을 주는 지 알 수 있습니다. 특히 메모리 사이즈 이상의 데이터가 올라갔을 때, 느려지는 원인 중에 이 Global Lock의 영향도 있었습니다.  그리고 2.2에서 다시 개선이 있다고 합니다. 제목만 보면 아시겠죠? 오역에 주의하시길 바랍니다.

아마도 MongoDB 에서 가장 악명 높고 자주 거론되는 문제가 Global Lock 이슈일껍니다.  Write 동작이 일어날때 전체 서버가 락이 된다는 것을 의미합니다. 엄청 안좋게 들리지만, 실제로,  성능에 끼치는 영향은 별로 없습니다. MongoDB 2.0 버전부터 이런 종류의 작업 동안 적절히 락을 포기하도록 개선이 있었기 때문입니다.(MongoDB 2.0 includes significant improvements)

확실히, MongoDB 1.8 에서는 데이터를 저장하기 전에 Memcached를 이용해서 insert를 조절했지만, 2.0으로 업그레이드 한 후에는 직접 MongoDB로 저장하고 이런 부분을 제거했습니다. 이 부분의 향상은 some benchmarks at the end of last year comparing v1.8 to 2.0 에서 잘 설명되어 있습니다.(역자 주: 제가 예전에 올린 발번역 글 중에도 해당 글이 있습니다. https://charsyam.wordpress.com/2012/01/04/%EB%B0%9C-%EB%B2%88%EC%97%AD-mongodb-write-lock/ )

FaultingWrites

그럼에도 불구하고, 2.2 버전의 중요 이슈는 Global Lock의 제거와 데이터베이스 Lock을 collection 수준의 Lock으로 변경하고 더 미래에는 더욱 더 세분화된 동시성  제어 방식으로 변경하는 것입니다.(역자 주: 일반적인 DBMS는 Global Lock(서버락), 데이터베이스 Lock, Table(collection) Lock, Page Lock, Record Lock 수준으로 볼 수 있습니다. 후자일 수록 Lock의 범위가 적습니다.)

2.2 버전에서의 개선 부분은 2 가지 입니다.

  • global reader/writer lock 의 제거 – 첫 시도로 database level lock
  • PageFaultException 구조 – page fault 시에 Lock 양보.

첫째로, database level Lcok으 구현된건 맞지만, 제대로 사용하기 위해서는 어플리케이션에서 몇가지 아키텍처 변경이 필요합니다. 예를 들어, 하나의 큰 collection을 사용하면,  하나의 커다란 database를 사용한느 것이기 때문에, 거의 차이가 없습니다. 하지만,  두번째, 동시성 향상의 경우(Lock 양보 정책) 하나의 collection 에서도 업그레이드만 하면 바로 이득을 볼 수 있을 듯 합니다.

10gen 의 CEO이자 MongoDB의 최초 개발자인 Dwight Merriman는 MongoSF에서 MongoDB의 변화와 구조에 대해서 좋은 얘기들을 많이 했습니다. 다음 .비디오를 보시길 추천합니다.

10gen 에서는 실제 사용과는 관련이 없다는 이유로, 공식 벤치마크는 제공하지 않습니다. 개인적으로 업그레이드 후에 영향받는 쿼리등을 확인하기 위해서는 benchRun 같은 도구를 이용해야 합니다.  버전에 따른 이런 차이점을 확인하는데는 벤치마크가 유용합니다.

Rick Copeland 는 꽤 멋진 벤치마크를 했고 이것은 1.8과 2.0 사이의 향상ㅇ 대해서 보여주고 있습니다. 그래서 1.8, 2.0과 대비해서 2.1를 테스트 하기로 했습니다. 2.1은 2.2 버전을 위한 개발 버전입니다.

Comparing v1.8, v2.0 and v2.1

Rick이 사용했던 Amazon EC2(m1.large)에 같은 AMI를 이용해서 완전히 동일한 코드를 사용했습니다.  같은 데이터베이스를 설치하고 Page Fault Read/Write에 대해서 벤치마크를 수행했습니다. Non Page Fault 상황은 하지 않았는데, 2.2의 주요 변경이 Page Fault 와 Locks 을 어떻게 다루는가였고, Non Page Fault는 큰 차이가 없었습니다. 그건 항상 빠르기 때문입니다.

그리고 MongoDB 가 큰 데이터를 가지고 있다고 가정했습니다. 보통, 모든 데이터와 인덱스가 메모리에 있는 상황이 아니라 워킹셋만 메모리에 존재할 것이기 때문입니다.(역자 주: 모든 데이터가 메모리에 있으면, Non Page Fault 상황이라고 봐도 크게 무방할 듯 합니다.)

MongoDB Faulting Ops/s

위의 그래프에서 확실하게 Rick의 결과를 재현했습니다. 그리고 MongoDB 2.1의 테스트 결과를 보면, 그 차이가 매우 확연합니다.  read 중에 write에 실패하는 수에 상관없이 성능이 확 떨어지는 부분이 없습니다. 이것은 global lock이 확실하게 사라졌기 때문입니다. 아래의 그래프를 봅시다.

MongoDB Faulting Lock

Rick의 도구도 이용했고, 추가로 mongod에서 무슨 일이 일어났는지 확인하기 위해서 mongostat을 이용해서 통계를 모았습니다.  확실히 global lock 에 사용된 시간은 0% 입니다.

Conclusions

많은 데이터베이스를 사용할 때 큰 성능을 주기 위해서 MongoDB 2.2에서 global lock은 사라졌습니다. 그러나 실제로 업그레이드 할만한 영향을 미치는 것을 PageFaultException 개선을 통한 Lock 양보 작업입니다.  이것은 MongoDB가 write 시에 동기화 작업이 일어나기 전에 page fault 를 발견하고 해당 페이지를 건드리기 때문입니다.

첫번째 그래프는 테스트 동안에 급격한 저하없이 일관적인 성능을 유지한다는 것을 보여줍니다. Rick의 코드 하나의 데이터베이스에서 쿼리를 했고, 벤치마크는 두번째 변경인 PageFaultException의 향상으로 부터 실제 향상이 일어난 것을 보여줍니다. 대부분 업그레이드를 할 이유가 여기에 있을 것입니다.  해당 버전에서 여러 데이터베이스를 통한 벤치마크에서는 얼마나 성능 향상을 보여줄지 흥미롭습니다.

[발 번역] 핀터리스트 아키텍처에 대한 설명 추가 – 180만명의 방문자, 10배 성장, 12명의 직원, 410TB의 데이터

해당 글은 http://highscalability.com/blog/2012/5/21/pinterest-architecture-update-18-million-visitors-10x-growth.html 글을 발 번역 한 것입니다. 오역에 주의하세요. 이번에 라쿠텐에서 핀터리스트의 가치도 대략 1조원 정도로 보고 몇백억을 투자했다는데, 여러모로 핀터리스트와 인스타그램은 유사한듯 합니다.

 

해당 글은 지난 포스트 A Short on the Pinterest Stack for Handling 3+ Million Users 와  Pinterest growth driven by Amazon cloud scalability 에 대한 업데이트 버전입니다. ( 역자 주: 간단히 요약하면 핀터리스트의 서버는 아마존 EC2에서 돌고 S3에 410TB의 데이터가 있음, 150대의 웹서버, 90대의 멤캐쉬 서버 35대의 내부 서버, 64대의 m/s DB서버 피크시간에는 시간당 $52, 밤에는 $15 를 사용하고 있으며, 결국 클라우드 서비스가 아니었으면 운영하기 힘들었을 것이다라는 게 핵심입니다.)

 

핀터레스트의 스토리는 인스타그램과 매우 유사합니다.  가파른 성장, 많은 유저, 많은 데이터, 몇명의 직원으로 모든 것이 클라우드 위에서 돌아갑니다.(역자 주: 아마존을 사용하는 것으로 추측 가능합니다. 개인적으로 tumblr도 유사하다는 느낌이 듭니다.)

 

핀터리스트와 인스타그램 둘 다, 과학과 기술 위대한 발전을 만들지는 못했지만,  실리콘 밸리의 혁신이 부족한 상황이라는 의미보다는 일반적인 환경에서 더욱 쉽게 성공할 수 있다는 지표입니다.  사용자 수는 거대하고, 평가액은 매우 높습니다. 우리는 기본적으로 성장을 지탱해줄 수 있는 기초 기술의 혁신을 바랍니다. 혁신은 좀 더 미묘합니다. 만약 제대로 올바른 아이디어를 실행할 수 있다면, 요새는 이러한 성장을 하기가 쉽습니다. 그리고 거기에 익숙해져야 합니다.

 

핀터레스트의 최근 현황은 다음과 같습니다.:

  • S3에 8백만 개의 오브젝트가 410TB 의 유저 데이터를 저장하고 있습니다. 8월에 비하면 10배나 늘어났고, EC2 instance는 3배 더 늘어났습니다.(역자 주: 피크 타임에 한 시간에 %52 달러라고 했으니, 하루에 최대 $1248 씩 사용한다는 뜻입니다. 한달이면 …)
  • 작년 12월 기준으로 12명의 직원이 있습니다. 클라우드를 사용하기 때문에, 작은 규모의 팀에서 이런 급격한 성장을 처리할 수 있었습니다.
  • 사용 경비를 절약하고 있습니다. 대부분의 트래픽이 점심부터 저녁까지에 집중됩니다. 그래서 밤에는 피크 타임의 40% 정도의 인스턴스만 사용하고, 피크 시간에 $52 정도를 사용하고, 부하가 적을때는 한시간에 $15 정도 사용합니다.
  • 150대의 웹서버가 있습니다.(역자 주: ELB를 사용합니다.)
  • DB의 부하를 줄여주는, 90 대의 메모리 캐시 서버가 있습니다.
  • 35 대의 캐시 서버는 내부 목적으로 사용합니다.
  • 70 대의 마스터 DB가 있고 각각 다른 지역에 백업 DB가 있습니다.
  • Python과 Django 를 이용합니다.
  • 한 DB가 50% 이상 차면, 그 때 확장을 쉽게 하고, 충분한 IO 성능을 위해서샤딩을 하게 됩니다.
  • ELB를 로드 밸런서로 이용합니다. ELB API로 쉽게 장비를 추가하거나 제거할 수 있습니다.
  • 역사상 가장 빨리 성장한 사이트 중에 하나입니다. 3월에 180만명의 유저가 사용했는데, 그전 달 보다 50% 늘어난 것입니다. AWS를 이용했기 때문에 매우 적은 IT 자원을 사용했습니다.
  • 클라우드를 사용하기 때문에 아주 쉽고 싼 가격으로 테스트가 가능합니다. 새로운 서비스를 위해서, 장비를 새로 사는것이 아니라, 큰 비용이 들지 않습니다.
  • Hadoop을 이용한  Elastic Map Reduce 를 데이터 분석 목적으로 사용하는데 한달에 수백 달러 밖에 들지 않습니다.

Related Articles

[분산 캐시]Memcached 의 flush_all의 주의 사항을 읽고!!!

주의사항: 제 글에 잘못된 내용이 있어서 이를 수정합니다. 꼭 다음 글을 같이 읽어주시기 바랍니다.

http://geekdani.wordpress.com/2012/05/19/memcached-flush_all-%EB%91%90%EB%B2%88%EC%A7%B8/

(2012/05/19 11:13)

지난번  포스트”Redis와Memcache의flush는왜다를까?”

(https://charsyam.wordpress.com/2012/05/17/%EB%B6%84%EC%82%B0%EC%BA%90%EC%8B%9C-redis-%EC%99%80-memcache%EC%9D%98-flush%EB%8A%94-%EC%99%9C-%EB%8B%A4%EB%A5%BC%EA%B9%8C/)

라는 글을 올린 후, memcached에 공헌도 많이 하시고, 실제로 memcached 커미터 수준인 분께서 다음과 같은 댓글을 올려주셨습니다. 실제로 flush_all 의 사용법에 대해서 주의사항이 있다는 것입니다. 그리고 또 다른 고수님께서도 해당 문제에 대한 퀘스천 마크를 남기셨습니다.(아, 실력 부족이 여실히 들어나네요.  그래도 제 주변에는 고수분들이 많으셔서 다행입니다. 부러우시죠? 이분들이 제 자랑입니다.)

그런데 문제는 아무리 소스를 봐도 알 수가 없다는 것이었습니다.(아, 실력 부족) 다만 코드를 유심히 보면서 고민했던 것은, current_time 이나 oldest_live 가 바뀔 경우에는 해당 가능성이 있다는 것만을 발견했습니다. 그런데 current_time은 보면, clock_hander 에서 값을 현재시간으로 바뀌어주는 코드만 있고, 나머지 부분에서는 바뀌는 부분이 없습니다. 점점 더 미궁으로 빠지는 거죠.

그래서 다시 한번 질문을 올렸습니다. “케이스를 알려주세요” 라구요.

그러자 또 다른 고수 @GeekDani 님께서 다음과 같은 답변을 들어주시고, 감사히 해당 내용에 대해서 블로그 포스팅까지 해주셨습니다.

http://geekdani.wordpress.com/2012/05/19/memcached-flush-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%A0%90/

꼭 읽어보시길 바랍니다.

사실 이것만 봐도 대부분의 분들이 이해하실 수 있으실 것 입니다. 불안하신가요? 다만 다행인 것은 대부분의 사람들은 flush_all 다음에 옵션이 있다는 것을 모릅니다. 그리고 그냥 flush_all 만 했을 경우에는, 제가 말했듯이 계속 oldest_live 가 current_time 보다 항상 작기 때문에 문제가 바생하지 않습니다.

예전에 저도 이와 비슷하게 flush_all 을 여러 번 한적이 있는데, 문제가 없었습니다. 캐시 내용이 거의 몇 테라급인데도 말이지요. 아마도 다른 분들도 exptime을 추가로 주는 부분을 안 쓰실 가능성이 높습니다.

그럼 이 글을 왜 적었느냐? 그냥 넘어가기는 아쉬우니, 실제로 flush_all 에 delay 시간을 적어주면 어떻게 되는 것인가에 대한 코드의 조건을 잠시 살펴보도록 하겠습니다.

먼저 flush_all 은 이해를 하고 있으니, flush_all에 옵션을 주면 어떤 동작이 일어나는지 먼저 알고 있어야 합니다. 일단 정확하게 설명하면, flush_all 뒤의 옵션은 “특정 시간에 데이터를 모두 삭제하라” 입니다. .이거 관련해서 뒤에서 다시 한번 설명하겠습니다.

아래의 코드에서 exptime > 0 의 코드가 우리가 flush_all 에 옵션을 추가로 줄 경우 동작하게 되는 것입니다. 다시 살아나는 것을 보니, 분명히, oldest_live 값이 current_time 보다 커질 것이다라고 볼 수 있을껍니다.


if (exptime > 0)

settings.oldest_live = realtime(exptime) - 1;

else /* exptime == 0 */

settings.oldest_live = current_time - 1;

item_flush_expired();

옵션으로 시간이 지정되어있지 않으면 그냥 oldest_live 가 현재 시간으로 지정되지만, 시간이 지정되어 있으면 realtime 이라는 함수를 통해서 변경됩니다. 이게 왜 필요한가? 라고 물어볼 수 있는데, 여기서 memcached 만의 재미난 특징이 하나 있습니다. 아마 expire time 설정해 보신 분들은 한번씩 실수하게 되는 것인데 일단 코드 먼저 보시죠. Memcached의 119라인을 보시면 됩니다.


#define REALTIME_MAXDELTA 60*60*24*30

 

static rel_time_t realtime(const time_t exptime) {

/* no. of seconds in 30 days - largest possible delta exptime */

 

if (exptime == 0) return 0; /* 0 means never expire */

 

if (exptime > REALTIME_MAXDELTA) {

/* if item expiration is at/before the server started, give it an

expiration time of 1 second after the server started.

(because 0 means don't expire).  without this, we'd

underflow and wrap around to some large value way in the

future, effectively making items expiring in the past

really expiring never */

if (exptime <= process_started)

return (rel_time_t)1;

return (rel_time_t)(exptime - process_started);

} else {

return (rel_time_t)(exptime + current_time);

}

}

REALTIME_MAXDELTA 라는 값이 지정되어 있는데 이 값보다 옵션으로 입력된 값이 크면 exptime – process_started 값을 던져줍니다. REALTIME_MAXDELTA 보다 작으면 현재 시간에 해당 값을 더해서 줍니다. 혹시나 process_started 값이 궁금하신 분들도 있으실텐데 그냥 프로세스 시작 값이 저장되어 있고 실제 current_time의 경우 이미 process_started 값이 빠져 있으니 신경안쓰셔서도 됩니다. 다만, exptime이 REALTIME_MAXDELTA 보다는 큰데 현재시간보다 적으면, 그냥 값을 1로 설정합니다. 이러면 그냥 지워졌다고 보시면 됩니다. 즉 원칙은 REALTIME_MAXDELTA 보다 적은 값은 상대 값이고, 그 이상의 값은 절대 값이라는 겁니다(겨우, 이걸 설명하려고 이렇게나 지면을!!! 퍽퍽퍽)

그런데 여기서 Expire time 관련한 아이템은 딱 두 가지 종류가 있습니다.

1)     Expire time 을 지정하지 않은 아이템

2)     Expire time 을 지정한 아이템( 2012/05/19 11:13 이 부분의 내용이 잘못되었습니다. 위에 지정한 부분을 같이 읽어주세요. 다만 잘못된 생각의 흐름을 남기기 위해서 글을 수정하지 않고 부분부분 표시만 해둡니다. 감사합니다.  )

Expire Time을 지정하지 않은 아이템의 경우는 크게 상관이 없습니다. 그냥 그 시간이 되면 사라지는 겁니다.(lazy delete이긴 하지만) 문제는 2)의 케이스입니다. 먼저 Expire time이 flush_all 에 지정한 옵션 값보다 작다면, 아무 문제가 없습니다. 그런데 초반에 Expire Time을 한 20년 뒤로 잡아두었다면 어떻게 될까요? 사실 이건 flush_all에 delay 시간을 주느냐 안주느냐와는 상관이 없이 문제가 발생합니다. 그래서 사용되는 코드가 아까 슬쩍 지나간 item_flush_expired(); 입니다. Thread.c 의 505라인에 있는데, 단순히 lock을 호출하고 do_item_flush_expired() 을 호출합니다 이런 패턴은 외부에서 호출하는 함수는 Lock을 타고, 내부에서 사용하는 함수는 그냥 호출하게 할 수 있으므로 편리합니다. 이런 패턴을 뭐라고 하는데 까먹었네요. ㅋㅋㅋ

do_item_flush_expired() 는 thread.c 의 548 Line에 있습니다. 소스코드는 다음과 같습니다.


void do_item_flush_expired(void) {

int i;

item *iter, *next;

if (settings.oldest_live == 0)

return;

for (i = 0; i < LARGEST_ID; i++) {

/* The LRU is sorted in decreasing time order, and an item's timestamp

* is never newer than its last access time, so we only need to walk

* back until we hit an item older than the oldest_live time.

* The oldest_live checking will auto-expire the remaining items.

*/

for (iter = heads[i]; iter != NULL; iter = next) {

if (iter->time >= settings.oldest_live) {

next = iter->next;

if ((iter->it_flags & ITEM_SLABBED) == 0) {

do_item_unlink_nolock(iter, hash(ITEM_key(iter), iter->nkey, 0));

}

} else {

/* We've hit the first old item. Continue to the next queue. */

break;

}

}

}

}

(2012/05/19 11:13 코드를 보면 지금까지 사용하던 exptime 이 아니라 time입니다. 이것은 서로 다른 동작입니다. 꼭 상단의 @GeekDani 님 글을 참고하시기 바랍니다.)

코드를 보면 현재 설정된 oldest_live 보다 더 이후의 expire_time을 가지고 있는 것은 미리 삭제해버립니다. 즉 flush_all [딜레이 시간] 이 들어가더라도, 몇몇 아이템은 그 순간 사라지게 됩니다. 여기서 재미난 추측을 하나 해볼 수 있습니다. Expire Time을 굉장히 길게 설정한 아이템이 많다면? Redis 처럼 operation이 블록되는 현상이 벌어질 수 있을 것 같습니다.(뭐, 테스트는 다음에 해보겠습니다.)

그래서 flush_all 을 하게 되면 oldest_live 가 현재 시간으로 설정되었다가, 다시 flush_all 100을 하게 되면, 일부데이터는 바로 사라지지만, 다시 데이터가 복구된 거 같은 효과가 발생합니다. 이 때 oldest_live 값이 current_time 보다 커지기 때문에 get 시에 영향을 안 받는 거죠. 다음 코드 생각나시죠?


if (settings.oldest_live != 0 && settings.oldest_live <= current_time &&

it->time <= settings.oldest_live) {

do_item_unlink(it, hv);

do_item_remove(it);

it = NULL;

if (was_found) {

fprintf(stderr, " -nuked by flush");

}

}

그런데 기억하셔야 할 것은 flush_all [expire time] 이 된 것은 delay가 된 것일 뿐입니다. 즉 해당 시간이 되면 다시 flush_all과 같아집니다. 주의하시기 바랍니다.

결국, 최초에 문제가 발생할 수 있을 만한 부분에서 결국 문제가 발생한 것입니다.

다시 정리하자면.

1)     그냥 flush_all 은 문제 없다. flush_all [expire_time] 을 아는 사람도 거의 없다.

2)     Flush_all 하고 나서 다시 flush_all [expire_time] 하면 해당 시간 이후로 expire_time 이 지정된 아이템과, 이미 get에서 제거된 아이템들을 제외하고는 전부 다시 보이게 된다.

3)     그러나 다시 해당 delay 시간이 지나가면 flush_all이 된다.

4)     그리고 flush_all에 옵션을 줄 수 있는 라이브러리가 별로 없는듯 하다.(php와 python 라이브러에는 그냥 flush_all 만 있네요.. 크게 주의하지 않으셔도 될 듯 합니다.)

5)     당연한 얘기지만, flush_all [expire_time] 주고 나서 그 사이에 set한 데이터들은 모두 사라진다. 까먹지 말자.

 

결론(2012/05/19 11:13 @GeekDani 님의 글을 참고로 수정합니다.)

  • flush_all 후 flush_all [exptime] 하면 exptime까지는 보여집니다. (물론 flush_all [exptime] 전에 GET 했던 것은 살아나는 효과는 일어나지 않습니다.)
  • flush_all [exptime] 후 exptime 안에서 SET은 지나면 사라진다.
  • flush되는 것과 expire되는 것은 처리가 다르다.
  • 기타 등등

[분산캐시] Redis 와 memcache의 flush는 왜 다를까?

Twitter에서 열심히 놀던 중에, 고수님이신 @zerocool_kor 님께서 아래와 같은 트윗을 날리셨습니다.

저는 물론 다 알고 계실분이 왜 이런 질문을 올리실까 하면서 FLUSHDB 라는 명령을 언급했습니다. Redis 에는 FLUSHDB라는 명령이 전체 데이터를 삭제하는 기능을 합니다.

그런데 다음과 같은 내용을 알려주시는 겁니다. 역시나 직접 테스트 다 해보시고 이상한 동작을 문의하신거죠.

그렇습니다. FLUSHDB를 하는 동안 다른 redis의 동작이 대기하는 현상이 있는 것입니다. 사실 이것은 Redis 구조상 아주 당연한 일입니다. 왜냐하면 간단하게 설명하면 Redis 가 싱글 스레드로 동작하기 때문입니다. Memcached도 마찬가지입니다. 다만 Memcached의 경우는 실제로는 멀티 스레드인데, 데이터에 접근하는 부분은 Mutex로 인해서 동기화되어서, Command 파싱까지는 멀티 스레드가 실제 데이터의 읽기 등은 전부 싱글 스레드처럼 동기화되어서 동작하게 됩니다.( 똑똑하신 분들은 이건 실제 싱글 스레드처럼 동작하는 serialization 이 아니라고도 하시지만 전 무식해서 그냥 이렇게 설명합니다.) 그래서 redis, memcached의 경우, 시간을 많이 먹는 단일 작업이 들어가면 전체적으로 성능이 떨어지는 것을 체감하시게 됩니다.

 

그래서 제가 다음과 같은 답변을 드립니다. 실제 싱글 스레드이므로 다른 동작이 블록되는 것이 맞는듯 합니다. 다만 워낙 동작이 달라서, 해당 지연이 눈에 띄지 않는 것이라는 내용입니다.(물론 이건 다시 정리한 표현이니 다르다고 돌은 던지지 마세요.)

Redis에서 약 200만개의 데이터를 임시로 넣어두고, 추가로 200만개를 넣으면서 FLUSHDB 명령을 날리니 약 1.1초 정도의 시간이 걸리고 그 동안에 블록이 되는 것을 볼 수 있었습니다. 시간 자체는 크게 의미를 가지지 마시기 바랍니다. 장비마다 다 틀립니다. 여기서 사용한 코드는 다음과 같습니다.

 


import redis

&nbsp;

r = redis.StrictRedis( host='127.0.0.1', port=1212 )

for i in xrange(0,4000000):

r.set( str(i), str(i) )

print i

 

그런데 재미있는 것은 같은 싱글 스레드 구조인 memcached 에서는 이런 지연이 없다는 것입니다. 즉, flush_all 이라는 명령을 주면 데이터가 바로 사라집니다. 왜 이런 차이가 있는 것일까요? 무지무지하게 궁금하지 않습니까?(안 궁금하시면 저만 알고 넘어가도록, 퍽퍽퍽!!!)

 

일단 힌트는 고수이신 @zerocool_kor 님이 말씀해주십니다.

네 그렇습니다. Memcached는 flush_all 이 들어온 시점에 데이터를 지우지 않습니다. 나중에 get 을 할 때, 발견하고 지워버리는 거죠. 간단하죠? 믿을 수 없다라는 분들을 위해서 이제 소스로 설명을 드리겠습니다. 먼저 Redis 부터 보시죠. Redis는 2.4.13 버전을 기준으로 합니다.

 

Redis의 db.c 파일의 198 라인에 flushdbCommand 라는 함수가 있습니다. 이 함수를 보시면 그냥 간단히 다음과 같습니다.

 


void flushdbCommand(redisClient *c) {

server.dirty += dictSize(c->db->dict);

signalFlushedDb(c->db->id);

dictEmpty(c->db->dict);

dictEmpty(c->db->expires);

addReply(c,shared.ok);

}

 

간단하죠? 여기서 실제로 dictEmpty 함수에서 삭제가 일어납니다. Redis는 내부적으로 dict 라는 자료구조 형태를 이용하는데, 이건 다음번에 다시 소개를 드리도록 하고 이번에 패스( 왜냐하면 제가 잘 모르니 ㅋㅋㅋ)

 

dictEmpty 함수는 다음과 같습니다. Dict.c 의 584라인에 있습니다.


void dictEmpty(dict *d) {

_dictClear(d,&d->ht[0]);

_dictClear(d,&d->ht[1]);

d->rehashidx = -1;

d->iterators = 0;

}

 

다시 _dictClear 함수를 봅니다. Dict.c의 358 라인에 있습니다. 소스를 보시면 루프를 돌면서 하나씩 제거해가는게 보입니다. 이게 200만개를 지우는 거죠.

 


int _dictClear(dict *d, dictht *ht)

{

unsigned long i;

&nbsp;

/* Free all the elements */

for (i = 0; i < ht->size && ht->used > 0; i++) {

dictEntry *he, *nextHe;

&nbsp;

if ((he = ht->table[i]) == NULL) continue;

while(he) {

nextHe = he->next;

dictFreeEntryKey(d, he);

dictFreeEntryVal(d, he);

zfree(he);

ht->used--;

he = nextHe;

}

}

/* Free the table and the allocated cache structure */

zfree(ht->table);

/* Re-initialize the table */

_dictReset(ht);

return DICT_OK; /* never fails */

 

간단히 정리하면 다음과 같은 순서로 흘러갑니다.

 

 

이제 memcached 의 소스를 봐야합니다. 뭐 이것은 두 부분을 봐야 하는데요. 먼저 flush_all을 통해서 어떤 동작이 일어나는지 알아보도록 하겠습니다. 이것도 간단합니다. Memcached는 1.4.13 버전을 기준으로 합니다.

 

Memcached.c 의 3290 라인을 살펴봅니다. 이 코드는 static void process_command(conn *c, char *command) 라는 함수 안에 존재합니다. 중요한 부분만 발췌하면, settings.oldest_live 가 셋팅되는 부분만 보면 됩니다. 이게 뭐하는 거냐 하면, flush_all 이 들어온 시간을 기록해둡니다. 아까 @zerocool_kor 님의 멘션을 기억하세요. 기준 시간을 정해놓고 이거 이전께 들어오면 그냥 다 NULL 입니다. 이러는 겁니다. 참 쉽죠잉?

 


time_t exptime = 0;

&nbsp;

set_noreply_maybe(c, tokens, ntokens);

&nbsp;

pthread_mutex_lock(&c->thread->stats.mutex);

c->thread->stats.flush_cmds++;

pthread_mutex_unlock(&c->thread->stats.mutex);

&nbsp;

if(ntokens == (c->noreply ? 3 : 2)) {

settings.oldest_live = current_time - 1;

item_flush_expired();

out_string(c, "OK");

return;

}

&nbsp;

exptime = strtol(tokens[1].value, NULL, 10);

if(errno == ERANGE) {

out_string(c, "CLIENT_ERROR bad command line format");

return;

}

&nbsp;

/*

If exptime is zero realtime() would return zero too, and

realtime(exptime) - 1 would overflow to the max unsigned

value.  So we process exptime == 0 the same way we do when

no delay is given at all.

*/

if (exptime > 0)

settings.oldest_live = realtime(exptime) - 1;

else /* exptime == 0 */

settings.oldest_live = current_time - 1;

item_flush_expired();

out_string(c, "OK");

return;

 

이제 실제로 get 할 때 어떻게 될 것인가를 봐야합니다. 이것 저것 다 빼고나면 실제로 items.c의 483라인의 do_item_get 이라는 함수가 호출됩니다.

 

못 믿기시겠다는 분은 gdb로 디버깅 걸어보시면 됩니다. Gdb 설명은 생략합니다. 다음이 해당 함수의 소스입니다. 참 길죠잉?

 


item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) {

mutex_lock(&cache_lock);

item *it = assoc_find(key, nkey, hv);

if (it != NULL) {

refcount_incr(&it->refcount);

/* Optimization for slab reassignment. prevents popular items from

* jamming in busy wait. Can only do this here to satisfy lock order

* of item_lock, cache_lock, slabs_lock. */

if (slab_rebalance_signal &&

((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) {

do_item_unlink_nolock(it, hv);

do_item_remove(it);

it = NULL;

}

}

pthread_mutex_unlock(&cache_lock);

int was_found = 0;

&nbsp;

if (settings.verbose > 2) {

if (it == NULL) {

fprintf(stderr, "> NOT FOUND %s", key);

} else {

fprintf(stderr, "> FOUND KEY %s", ITEM_key(it));

was_found++;

}

}

&nbsp;

if (it != NULL) {

if (settings.oldest_live != 0 && settings.oldest_live <= current_time &&

it->time <= settings.oldest_live) {

do_item_unlink(it, hv);

do_item_remove(it);

it = NULL;

if (was_found) {

fprintf(stderr, " -nuked by flush");

}

} else if (it->exptime != 0 && it->exptime <= current_time) {

do_item_unlink(it, hv);

do_item_remove(it);

it = NULL;

if (was_found) {

fprintf(stderr, " -nuked by expire");

}

} else {

it->it_flags |= ITEM_FETCHED;

DEBUG_REFCNT(it, '+');

}

}

&nbsp;

if (settings.verbose > 2)

fprintf(stderr, "\n");

&nbsp;

return it;

}

 

핵심만 보여달라는 분을 위해서 여기를 보시면 됩니다.

 


if (settings.oldest_live != 0 && settings.oldest_live <= current_time &&

it->time <= settings.oldest_live) {

…

…

}

 

보면 settings.oldest_live 가 current_time(현재시간) 보다 적고, 0이 아니라는 것은 한번 이상 flush_all이 실행되었다는 뜻입니다. 해당 초기값이 0이거든요. 그리고 발견된 item의 시간과 oldest_live를 비교하면, 해당 key가 flush_all 이전인지 이후인지 판단이 가능하겠죠? 그래서 memcached 에서는 실제로 아이템이 지워지는 시간이 거의 없는 것 처럼 느껴지는 겁니다.

 

그러나 뭐든지 장점과 단점이 있는 법!!!, memcached는 실제 키를 찾고 값을 비교하고 expire time이 지난 뒤에 삭제하는 형태이므로, get이 기존보다 조금 느려지게 됩니다. 뭐 그래도 엄청 빠니, 크게 신경을 안 써도 된다는 장점도 있습니다.

 

그럼 우리의 궁금점은 왜 -_-, why, 어째서, how come!!! Redis는 일일이 다 삭제하는 코드를 만든 것일까요? Redis 개발자가 memcached 개발자보다 덜똑똑해서( 물론, memcached 개발자가 괴물인건 분명합니다 1980년 생인데, 무슨 초천재를 보는듯한 T.T 난 뭐했나!!!) 일까요?

 

저는 개인적으로 redis 와 memcached의 구조적인 차이 때문이라고 생각합니다. Memcached 가 캐시 자체에만 집중했다면, 그래서 메모리를 관리하는 구조도 서로 다릅니다. Memcached는 slab 할당과 chunk 구조를 이용합니다. 반대로 Redis는 pub/sub 등의 기능과 key의 변경을 noti 받는 기능이 있습니다. 그래서 실제 flush가 되기 전에 이런 키들에 대해서 키가 삭제된다는 알람을 줘야 합니다. 해당 코드가 아까 살짝 넘어간 signalFlushedDb 라는 함수입니다. 그래서 어느 구조가 더 좋다라고 말할 수 없을 것 같습니다. 그래서 조금이라도 처리속도가 느려지면 안되는 경우에 Redis의 FLUSHDB는 위험할 수도 있습니다.

 

여담으로, Redis는 싱글 스레드다라고 말하면 안믿는 분들이 계십니다. 그럼 백그라운드로 메모리를 백업하는 BGSAVE는 어떻게 만들어진거냐!!! 말도 안된다라는 분들에게 한마디 하자면, BGSAVE는 fork 한 후에 child Process 가 처리해주는 겁니다. 속지마세요. 코드가 궁금하신 분은 rdb.c의 499라인을 보시면 됩니다.

 

[발 번역] Amazon EC2 – 데이터 센터에서 클라우드로 이동한 실제 사례

해당 내용은 http://gregsramblings.com/2012/05/02/amazon-ec2-a-real-world-case-study-of-moving-from-data-center-to-the-cloud-chessjam/ 라는 글을 발 번역한 것입니다. 오역에 주의하시길 바랍니다.

역자 주: 해당 케이스는 엄청나게 많은 서버가 이전한 케이스는 아니고 간단하게 한두대 수준에서 왜 클라우드로 이동했는가에 대한 글입니다.  amazon ec2에 관심있는 분들이 보시면 좋을듯 합니다. 그리고 아마존에서 AWS에 대한 한국어 페이지가 생겼습니다. 이제 영어일 때 보다 훨씬 쉽게 접근할 수 있을듯 합니다.

2년전에, 저는 ChessJam 에 대해서 블로깅을 했습니다. ChessJam은  세계의 떨어져있는 사람들이 함께 체스를 라이브로 즐길 수 있는 체스 프로그램입니다. ChessJam을 출시한 이후로,  매일 24시간 동안 잘 운영되었고, 아주 적은 다운타임을 가지며, 잘 성장해왔습니다.

현재의 ChessJam의 수치에 대해서 언급을 하자면,

  • 낮밤, 언제라도, 평균적으로 100여명의 유저가 체스를 두고 있으며,  주말에는 180명 정도의 동시 접속자를 볼 수 있습니다.
  • 우리의 평균 로드는 초당 2~3번의 체스말의 이동이 있습니다.
  • 2012년 3월 동안에 4,500 명 이상의 유저가 128,920 게임을 즐겼습니다.( 한달에 580만 건의 체스 말의 이동이 있었습니다.)

마케팅 없이 한것 치고는 나쁘지 않다고 생각합니다. 다른 많은 어플리케이션과 비교하면 엄청 큰 부하를 요구 받지는 않지만, 성능 요구 사항은 꽤 높습니다. 몇몇 민감한 체스 플레이어들은 적절한 응답시간 이상은 기다리지 않습니다. 그래서 우리는 좋은 CPU와 대역폭을 여분으로 가지고 있어야 합니다.

호스팅 히스토리:

2009 말/2010 초 — 초기 개발 – 서버는 집에서 돌림

개발 초반 몇달 동안은 ChessJam은 집 컴퓨터에서 운영했습니다. 가격은 고정 IP와 FiOS를 쓰고 전기세를 포함해서 한달에 $70 이었고, 이것은 플로리다의 여름 폭풍으로 인해서 파워를 두배로 사용하기 전까지는 꽤 괜찮았습니다. 이 때, 나는 실제 데이터 센터와 개인 데이터 센터의 차이점에 대해서 깨닫게 되었습니다.  또 내가  없느 사이에 아내가 늦은 밤 서버의 복구 담당이어야 해서 꽤 신경쓰였습니다.

2010 말 — rack mounted servers 를 진짜 데이터 센터로 옮기다.

우리는 두대의 중고 rack-mounted IBM 서버를 구입했고,  데이터 센터에 랙용 공간을 임대했습니다. 신뢰할 만한 파워와 인터넷은 더 이상 문제가 없었습니다.  그러나 하드웨어는 종종 수리를 해야 했습니다. 전보다 좋아지긴 했지만, 그래도 좋은 상황은 아니었습니다. 데이터 센터로 옮긴 후 비용은 한달에 $190 불 근처였습니다.( 랙 공간을 임대하는 것과 밴드위스 비용)

왜 데이터 센터 대신에 아마존 EC2 로 옮기기 않았을까요? 처음에, 데이터 센터 대신에 아마존 EC2 를 고려했습니다. 그러나, 그때는 아마존은 우리가 고려할 만한 장비 사양이 없었습니다.  small instance 가 매력적이었지만, 겨우 1.7GB 의 메모리만 제공되어서 우리의 서버를 운영할 수 없었습니다.(시도는 해봤지만)  두번째는 메모리를 7.5GB를  large instance 였는데, 너무 비쌌습니다.

2달전에, 아마존은 small instance 와 large instance type 사이의 큰 갭을 채웠습니다. “medium” instance type을 제공하기 시작한 것이죠. 그것은 3.75GB 메모리를 제공하고 적당한 가격에 적당한 CPU를 제공했습니다. 그래서 우리는 그것으로 옮길 시점이라고 결정했습니다.

우리의 EC2 설정:

  • Ubuntu Linux에서 동작하는 중간 크기의 EBS 사용.  EBS는 “Elastic Block Store”의 약어로 persistent한 저장소입니다. EC2를 한번도 본적이 없다면, EBS 저장소가 서버에서 사용하는 Persistent 저장소라는 것을 모를겁니다. 서버를 shutdown 할 때, 서버의 모든 데이터를 저장할 수 있습니다.( 실제 물리 서버 처럼 말입니다.
  • 200GB EBS 디스크 사이즈 – Mysql 데이터 베이스와 증가하는 로그 저장을 위해서 사용.

AWS 와 돈에 대한 걱정

대부분의 사람들이 아마존 EC2를 사요할 때 같은 질문을 하는데, 나도 마찬가지였다. “이게 실제로 비용이 얼마나 드나요?” EC2가 가격을 어떻게 결정하는지 깊게 들여다보면, 불확실성을 만들어내는 다양한 변수들을 보게 될것입니다. 기본적으로 instance type에 따른 비용, EBS 사용에 대한 비용, instance 에서 데이터가 외부로 나갈때 드는 비용. 또 추가로 I/O와 사용하지 않는 IP addresses 도 조금 돈이 들어가지만, 이 비용은 매우 작아서 계산서에 큰 영향을 주지않습니다.( 내 경혐에 기초한 것입니다.)

  • The server instance — 이것은 예측하기 쉽고 간단합니다. 아마존은 시간당 과금을 합니다. 예를 들면, 우리가 사용하는 medium instance는 한시간에 $0.16불입니다. 만약 medium instance를 한대 빌려서 8시간 동안 사용한다면 $1.30 만 내면 됩니다. 나쁘지 않습니다. 우리의 앱은 매일 24시간 동작하는데, 이러면 24 시간 * 30 일 해서 한달에 720시간 동안 동작합니다.  이것을 계산하면 $0.16 * 720 = 한달에 $115가 나옵니다.( 이 글의 말미에, reserved instance 가 어떻게 동작하고 , 거의 절반정도 할인이 된다는 것을 설명할 것입니다.)( 역자 주: 아마존 EC2의 Reserved Instance는 말 그대로 장기 임대입니다. 즉, 이렇게 장기 임대해두면, 매달 이용하는 것보다 싸게 파는 것이죠. CPU 사용률에 따라서, Lite, Medium, Heavy 의 세 가지 타입이 있고, Heavy 가 Lite 에 비해서 4배 정도 비쌉니다.)
  • Disk volume – 이것 역시 예측하기 쉽습니다. 한달에 1GB당 $0.10 씩 지불합니다. 200GB 를 사용하므로 200*$0.10 해서 한달에 $20씩 지불합니다.(역자 주:  중요한 시스템의 경우 madam 을 이용해서 Software 적으로 RAID10을 구성하는데 이러면 디스크가 4개가 필요하니 1TB로 * 4를 하면 한달에 장비 하나당 $400 달러가 더 나가게 됩니다.)
  • Disk I/Os — EC2 가격 페이지를 보면 백만 I/O 당 $0.10 로 책정되어 있습니다. 우리는 하루에 300,000 I/Os 가 있고(마지막 청구서를 기준으로) 한달에 9백만 I/Os 가 발생합니다. 그 결과로 한달에 $1 보다 적게 청구되고 있습니다. 아무래도 반올림 오류에 대해서 전화해야 할 듯 합니다.( 역자 주: 아무래도 관련 요금이 1$가 나왔나보네요. ㅎㅎㅎ).
  • Data transfer charges — 우리가 걱정하는 부분이 이부분입니다. 아마존에서는 EC2 instance 외부로 나가는 데이터 1GB당 $0.12 를 과금합니다. 들어오는 트래픽에 대해서는 과금하지 않습니다.  데이터에 대한 과금 방식이 데이터 센터에서 과금하는 방법과 많이 틀렸습니다. 데이터 센터에서는 “피크치의 95%를 기준으로 과금” 하는 방식을 취했고, 이는 전체 사용한 트래픽에 대한 과금이 아니라,  평균에 기초해서 과금을 했습니다. 세부 사항에 대해서는 좀 더 자세히 알아두시길 바랍니다. 아마존 모델은 이런 방식이 아닙니다. 데이터 센터에서는 매 달의 트래픽에 대한 보고서를 제공해주었고 우리는 그것으로 평가를 했습니다. 지금, 몇일 정도 서버를 돌리고 있는데, 매 시간당 250MB, 하루에 6GB 정도의 트래픽이 발생하고 있습니다. 그래서 6GB * 30 * $0.12 해서 한달에 $21 정도 과금이 되고 있습니다.

요약:  우리의 서버 장비에 한달에 $115.20, 디스크 볼륨에 한달에 $20, 네트웍 비용에  $21/month, 다 합쳐서 한달에 $156.20 과금되고 있습니다. 이것은 데이터 센터에서 우리가 지불하던 비용보다 $30 달러 정도 싼 비용입니다. 이것은 이미 이전보다 싸지만, 우리가 Reserved Instance를 사용하면 우리는 좀 더 비용을 줄일 수 있습니다.

Reserved instances를 사용해서 비용 줄이기

아마존 EC2를 사용하는 많은 업체들이 ChessJam 보다 훨씬 “유연함”을 이용하고 있습니다. 부하에 따라서 동적으로 서버가 늘어나고 줄어들도록 load balancer와 auto-scaling 을 셋업해 서 사용하고 있습니다.  그러나 아직 우리의 App은 그 정도로 복잡하지는 않습니다.( 종종 우리의 사용량에 따라서 백엔엔드를 뜯어고쳐야 하지만…) 우리의 App은 계속 서버를 사용하고 있습니다. 고맙게도 아마존에서는 이런 유형을 커버하기 위한 Reserved Instance 라는 것을 제공하고 있습니다. Reserved Instance는 각 instance 에 대해서 한번 지불하면 일정 기간 동안 계속 사용할 수 있고, 시간당 과금에 대해서 큰 할인을 받을 수 있습니다. 기본적으로 아마존은 장기 사용에 따라서 보상하는 것입니다.( 역자 주: 예를 들어 매달 과금보다는 1년 정기나 3년 정기로 하면 가격이 할인되는 것과 마찬가지입니다. 기업 입장에서는 할인을 해주더라도 운영이 안정적이 됩니다. 아마존의 경우 1년, 3년으로 계약을 할 수 있는데, 3년의 경우, 그 할인폭이 매우 큽니다. ) 3가지 타입이 있는데, light utilization, medium utilization, heavy utilization 입니다. 우리의 서버는 24/7로 운영되어야 하므로 우리에게 가장 적합한 선택은 heavy utilization 모델입니다.(역자 주: heavy가 가장 비쌉니다.)

1년 계약으로 medium instance에 $390 을 지불했습니다. 이제 한시간에 $0.16 불씩 과금되던 것에서 우리는 한시간에 $0.032 씩 밖에 지불하지 않습니다.( 겨우 3센트를 조금 넘습니다.) 우리가 아낀 것에 대해서 간단하게 계산해보면,

  • “Demand instance” 에서는 한시간에 $0.16 * 하루에 24시간 * 1년에 365 일을 해보면, 1년에 $1,401.60 이 듭니다.( 여기에 disk, I/O, transfer를 더해야합니다.)
  • “Reserved instance”에서는 먼저 $390 를 선불금으로 내고, 시간당 $0.032 * 24 시간 * 365 일 해서 일년에 $670.32 이 듭니다.( 여기에 disk, I/O, transfer를 더해야 합니다. ) ( 역자 주: 아, 여기에 제가 잘못 알고 있던 부분이 있었네요. 저는 선불금이 전부 인줄 알았는데, 선불금을 내면, 그 옆에 할인된 가격으로 이용할 수 있다는 뜻입니다. 자세한 건 http://aws.amazon.com/ko/ec2/reserved-instances/ 이걸 보시면 될듯 합니다. 이미 다 한글이예요!!!)

disk, data, 그 외 것들에 대한 최소 사용 비용

  • 한달에 $2,280 정도를 데이터 센터에 지불했습니다.
  • 현재는 아마존에 $1,160 정도를 지불합니다. 1년에 $1,000 정도를 절약하고 있씁니다.( 다른 하드웨어 비용은 포함하지도 않았습니다. )

micro 와 small 두 개의 작은 장비 타입이 있습니다. app과 site 에 따라서 훨씬 싼 것을 구입할 수 있습니다. Reserved Instance 가격 정책에 따르면, 1년에 $100 정도로 micro 장비를 구입할 수 있습니다. 저는 현재 6개의 작은 사이트를 한대의 micro 장비에 호스팅하고 있습니다. ( 지역의 베이글 가게 정보나, 개인적인 제 사진을 올리는 사이트 등이 있습니다.)

TIP: 만약 아마존 micro 장비를 사용할 계획이 있다면, 어떻게 그들이 CPU 파워를 올리는지에 대해서 알아야 합니다. 그것은 micro 장비의 독특한 특성입니다. 나는 이것에 대해서 지난달에 블로깅을 했습니다.(역자 주: micro 장비는 CPU Bursting 이 가능합니다. 박재호님이 쓰신 이 글을 보시길 추천합니다.)

TIP: 아마존의 과금 정보는 하루에 여러번 업데이트 됩니다. 그래서 빨리 모든 것의 과금 정보를 알 수 있습니다. 우리는 동작한지 24시간 후에 한달치 비용을 알 수 있었습니다.

우리는 돈을 아끼기 위해서 EC2로 이전한게 아니다!

물론, 우리는 돈을 좀 아꼈습니다. 이건 대단한 거죠. 하지만 정직하게 말하자면, 돈 때문에 옮긴건 아니었습니다. 진짜 이유는 하드웨어 장애로 인해서 발생하는 어려움을 EC2 같은 클라우드 인프라로 옮겨서 마음의 평화를 가지고 싶었기 때문입니다. 데이터 센터에 백업 서버가 있었지만, Primary 서버가 장애가 나면, 30분간 데이터 센터로 운전해 간 다음, 차가운  데이터 센터안에서, 몇 시간 동안, 하드 디스크를 옮겨 달아야 했고, 또, 그것이 잘 동작하기만을 빌어야 했습니다. 정말 끔찍한 상황이었죠. 지금은, 하드웨어 장애가 발생해도( 적기는 하지만, EC2에서도 발생합니다.),  간단히, 몇번의 마우스질로,  새로운 서버를 시작하고, 디스크를 마운트하고, 몇가지 설정만 해주면, 다시 정상으로 돌아가게 됩니다. 이것들이 전부 내가 잠옷있고, 빅뱅 이론을 보는 동안에 할 수 있습니다.

물론 몇몇의 IT 사고 방식을 가진 사람들이 나의 최종 아키텍처에서 뭔가 몇가지 문제점을 보고 있다는 것을 알고 있고, 당신이 옳습니다. 위의 사례에서, 디스크가 고장나는 경우에 대해서는 언급하지 않았습니다. 디스크 장애는 EC2에서는 정말 잘 안일어나긴 하지만, 해당 구조를 보완하는 몇가지 계획을 가지고 있습니다. 한 대의 서버를 더 추가해서 Mysql 메인 디비를 복제하고, 해 당 서버를 다른 zone에 다가 추가할려고 합니다. 이러면 기본적으로 다른 물리 장비에 할당된다는 것을 의미합니다. 이러면 1년 전처럼 몇몇 유명한 사이트가 몇시간 동안 장애가 발생했던 대규모 장애가 일어나더라도 재빨리 복구가 가능할 것 같습니다. 그러나, 현재는 가장 값싼 방법을 사용하기로 했습니다. 디스크만 하나 더 추가해서 메인 디스크의 mirror로 사용하는 것입니다.  누가 우리를 10억달러(역자주: 1조) 에 사가기 전까지는 충분할 것 같습니다.

 

다른 옵션은 Amazon RDS를 이용하는 것입니다. 우리가 DB로 RDS를 사용한다면,  좀 더 안정적이고 확장성이 좋아질 것 같습니다만, 지금은, 우리가 좀 더 성장할 때 까지는 장비 한대로 버티고 싶고, 비용을 줄이고 싶습니다.

모든 어플리케이션을 위한 것은 아니다.

특별히 우리의 경우에는 EC2는 아주 괜찮습니다. 그러나, 모든 종류의 어플리케이션에 좋은 것은 아닙니다. 서버는 여전히 “가상장비” 이고 성능은 완벽하지 않습니다.

Disk I/O 속도나, 네트웍 밴드위스 등에서, 아마존에 스왑 드라이브를 위해서 SSD를 설치해 달라거나, DBMS 디스크를 RPM 10,000 짜리로 바꿔달라는 말을 할 수 없습니다. 바꿔 말하면, 실제 장비에서는 할 수 있었던 튜닝을, 아마존 EC2에서는 할 수 없다는 얘기입니다. 그러나 EC2에서 당신의 app이 잘 돌아간다면, 이게 무슨 문제겠습니까?, 개인적으로는 내 경력의 대부분을 컴퓨터 실에 받쳤다고 생각합니다.(역자 주:  페이스북 CTO가 말했듯이, 하드웨어 장애에서 오는 문제에 대해서 처리해야 하는 시간을 줄여준다는 말입니다. 그리고 간단한 팁은 DBMS에서 어느 정도 성능 부하가 있을 경우, Memory를 많이 추가해서 DBMS 메모리 사용량을 높여주는 게, 가성비로서는 상당히 효과적입니다.)

아마존 웹 서비스 세계의 나머지…

본문에서 얘기한 것들이 전부 아마존 EC2의 소형 장비들에 대한 것들입니다. 그 외에도 AWS에는 다양한 장비 타입과, Load balances, auto-scailing, monitoring, automatic alerts 등 ㅁㄶ은 기능이 있습니다.  EC2는 아마존 AWS의 한 부분일 뿐입니다. 자세한 것은 다음 사이트에서 확인할 수 있습니다. http://aws.amazon.com/.

공짜로 사용해보기.

최근에 아마존은 EC2 신규 사용자를 위한  “free tier” 를 제공하기 시작했습니다. 기본적으로 1년동안 micro 장비를 적당한 disk와 네트웍 밴드위스를 공짜로 제공합니다. http://aws.amazon.com/free 를 보면 더욱 자세한 설명이 있습니다. 언제든지 장비의 타입을 바꿀 수 있습니다. 개발중에는 micro 를 사용하다가,  실제 서비스로 갈 때 필요하면 업그레이드 할 수 있습니다.