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

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

  • 감사감사

    몽고디비 초보가 잘읽고 갑니다.. 글을 매우 재미있게 쓰시는군요..ㅎㅎ

  • 세연아빠

    MySQL Cluster가 있지 않습니까. ㅎㅎ

  • 바부팅이

    재미있게 보고 갑니다^^

  • 한햇빛

    필력이 정말 좋으시네요
    아무래도 유행이다 보니까 장점에만 많이 관심이 갔었는데,
    실제 구축 적용시에 고려해야 될 점들을 잘 정리해 주셔서 감사합니다
    ^^ 잘 보고 갑니다.

  • 디비여신

    ㅋㅋ글을 너무 재미나게 쓰시네요. 고맙게 잘 읽고 갑니다 ^^

  • 톡히

    레테 잘보고갑니다

  • 이경현

    지나가다 우연히 들려서 보고 갑니다. 어떤 db인지 궁금했는데 잘 정리해주셨네요.

  • Minsang Kim

    예전에 모회사에서 node.js 로 채팅 모듈을 만들 때 사용한 적이 있습니다. 이음처럼 성공한 서비스에서 사용한 경험은 어떨지 항상 궁금했었는데 소회가 거의 저랑 같네요ㅋㅋ

    사실 스케일링 하나를 위해 프로토타입 삼아 선택한 녀석이었는데 이왕 테스트 할거면 이것저것 섞지말자는 이상한 순혈주의로 전체 코드를 몽고로 짜는 무리수를 던졌습니다. 하지만 이 포스트에 나오는 모든 시행착오를 거친뒤 코드가 80%쯤 완성된 시점에, 회사가 망했어요ㅋㅋ 스케일링을 위해 config, shard 서버도 미리 셋팅해두었는데 영원히 쓸 기회가 없었습니다ㅋㅋ

    해외쪽에서도 과연 몽고디비가 스타트업에 적합한 대안 NoSQL 인지에 관해서는 의견이 분분한 것 같습니다. 제 결론은 아니다- 였어요ㅋ 스키마프리한 데이터인데 레퍼런스가 적은 데이터란 세상에 존재하지 않으며 있다면 그건 그냥 파일로 저장하는게 정답이라고 느꼈거든요ㅋㅋ 사용하는 내내 왜 나는 바퀴를 재발명하고 있나 회의가ㅜ 그리고 눈물이ㅠ

    서버개발자로서 앞으로도 쭉 퍼포먼스와 코드편의성을 신경쓰며 무의미할지도 모르는 스케일링의 고려를 쭉 하게 될 듯한데, RDBMS에서 인덱스 단위의 샤딩을 직접 구현하는 것이 스타트업에 가장 적합한 방법이 아닐까 생각하게 되었습니다.

    하지만 기술의 발전이란 누구도 예측하기 어려운 것이고 한명의 서버개발자 저 자신으로 본다면, 몽고를 사용한 것이 정말 큰 배움이었다고 봐요. 수많은 시행착오를 겪으며 NoSQL 스타일의 인덱싱, 샤딩, 그리드, 맵리듀스의 감을 익혔으니까요. 앞으로 새로운 또 편리하고 빠른 NoSQL 이 등장한다면 몽고와 비교해 어떤 점이 좋은지 빨리 캐치해내고 다른이들보다 빨리 학습해낼 수도 있을 것이고, RDBMS 를 사용하면서도 비슷한 유형의 기술을 적용해 볼 수도 있을 것 같습니다.

    사실 몽고를 쓰면서 이상하게 좀 외로웠어요ㅋㅋ 개발자에게 시행착오란 곧 이야기거리인데 아무도 이 이야기를 제대로 이해하고 들어줄 사람이 없었거든요ㅋㅋ 우연한 기회에 페북 타임라인에서 보게된 글 타래인데 너무 반가워서 정독하고 긴 댓글 남기고 갑니다ㅋ

  • e2goon

    잘 보고 갑니다. ^^

  • Uk Jo

    재미있게 읽었습니다. 정말 잘 정리해주셨네요!!!!

  • 이지민

    잘 보고 갑니다!

  • 김윤후

    잘보고 갑니다. 좋은 시간이었네요!

  • http://tonyj.in/ jsveron23

    기술적인 면에서만 스타트업을 고려한다면 당연히 더 좋은 데이타베이스를 고려하는게 맞는 답일거 같습니다. 그러나 모든 스타트업에서 모든 개발자가 똑같지 않고 익숙한 툴이 있습니다. 어느 디비가 인덱싱이 좋다 같은건 자료를 통해 말할 수 있으나, 스타트업에서 몽고 디비가 좋다 좋지 않다는 위의 상황 말고도 여러가지를 같이 고려해봐야 할 듯 합니다.

  • Min-Gyu Choi

    좋은 글 감사합니다.

  • dev-yt

    너무 좋은 정보 감사드립니다. 게시글 참고, 출처 표시하고 블로그에 포스팅하겠습니다.!

  • 스위프티

    mongodb를 사용하다 답답한마음에 검색했는데 이렇게 속시원한 글이 있다니 ㅋㅋㅋㅋㅋ 공감합니다. 쿼리쓰다 괄호 삐끗하는 것도그렇고 스킴이 없다는게 장점이자 단점이 될수 있지만 (이 밖에도 엄청나게 나열하자면 끝이 없지만 )어쨋든 이제는 핫한 디비가 되버렸네요 잘읽고 갑니다

  • 김철핫

    개발자가 다루기에는 여간 불편한 점이 없지 않네요. 전 사용하라고 하면 절대 사용안할거 같습니다.- 게으른 개발자 올림.

  • 닉스

    유익한 자료 재미나게 봤습니다. 감사합니다

  • 초보

    우연히 들러서 좋은 정보 얻고 갑니다. 이런 디비도 있구나 좀 충격이 갔는데.. 글을 잼나게 쓰셔서 잘이해가 됩니다 ㅎㅎㅎ

  • Yuria_a

    node.js 관련 서치하다가 이곳에 왔습니다. 이름만 들어보던 mongoDB.. 이포스팅으로 충분히 느끼고 도망가렵니다.^^

  • kim

    2013년에 쓰신 글을 이제야 본다니 잘 읽었습니다