앞으로 시절이 시절인 만큼, 공공 게시판에서 다양하게 의견을 주고 받을 수 없는 시절이 다가 올 수도 있습니다. 그래서 뜻이 맞는 사람들 (동지? ㅎㅎ) 끼리 모여서 이야기 할 수 있는 동호회 스러운 느낌의 게시판들이 성행할 수도 있습니다. 

포탈은 어떻게든 내용이 새어나갈 수 있지만 , 이런 동호회 기반의 커뮤니티는 그런 염려가 없습니다. 저도 하나 가입해 있어서 어려울 때 심심찮은 위로가 됩니다. 그런데 그런 작은 규모의 게시판일 때는 제로보드가 문제 없이 동작하나 사람들의 접속이 많아지면 많아질 수록 힘들어 하는 것이 느껴집니다. 따라서 다수의 접속을 처리할 수 있도록 최근 뜨겁게 인기를 끌고 있는 event-driven 방식의 웹서비스가 필요하지 않을까 해서 만들어본 기초 프로젝트 입니다. 

당연히 뼈대(skeleton)뿐 입니다. 완성도 있는 게시판들과 비교할 수가 없지만 앞으로 만들어 나갈려고 생각중입니다. 친구들 몇도 가입해서 개발할 준비가 되어 있습니다.

나중에 엄청 게시판이 흥했을 때 갖춰질 미래 예상도 입니다.

 
수평적으로 확장이 가능한 것이 특징이라고 할 수 있겠습니다. 현재는 간단한 글을 생성하고 볼 수 있습니다. (CR 만 가능합니다. UD 는 안됨 ㅎㅎ) 게다가 디자인은 형편 없습니다. 제가 워낙 미적 감각이 떨어지다 보니 게시판 에디터로는 CKEditor 를 붙였습니다.  이미지 업로드 까지 처리해 둔 상태입니다. 

소스는 https://github.com/crazia/nodejs-mongodb 여기에 올려뒀습니다. 

$ git clone git://github.com/crazia/nodejs-mongodb.git
$ cd nodejs-mongodb
$ npm install -d
$ node app.js



하시면 실제로 동작하는 것을 볼 수가 있습니다. node.js 나 npm 설치나, mongodb , redis 관련 설치법은 제 예전 블로그를 뒤져보시면 많이 나옵니다.

이글 참조
샤딩 (Sharding) 을 하는 방법 또는 복제 셋 (Replication) 을 하는 방법에 관한 예제는 잘 나와 있습니다. 그런데 실전에서는 복제와 샤딩을 동시에 하는 것이 일반적일 것입니다. 이것에 관한 예제가  많이 없더군요. 어쩌다 찾은 것이 한개 있지만 한 기계에서 가상으로 돌려보는 예제 입니다. 이러한 예제로는 샤딩을 제대로 테스트 할 수가 없더군요. 하지만 그 문서를 바탕으로 실제로 샤딩과 복제를 클러스터 환경에서 테스트 해 봤습니다. 

http://cookbook.mongodb.org/operations/convert-replica-set-to-replicated-shard-cluster/ 

위 내용은 한 기계안에서 샤딩과 복제 셋을 테스트 하는 예제 입니다. 


원칙대로라면 복제 셋 (Replication Set) 을 설정할 때 한 머신에서 동작시키는 것이 아니라 서로 다른  서버에서 구동되게 설계를 해야 하지만 그것 까지 세팅하기가 매우 귀찮기 때문에 다음 과 같이 설계를 해 봤습니다. 

   

    Machine 1 : 

    - name : super1
    - replication : first * 3


    Machine 2 :

    - name : super2
    - replication : second * 3

    


 

 즉 2대의 머신에서 샤딩이 이루어 지게 세팅을 하는 것인데, 각각의 머신은 3개의 복제 셋을 가지게 구성 되는 것입니다. 

mongoDB 의 bin 이 path 에 걸려 있다는 가정 하에 진행 합니다. 

super1 부터 설정을 시작합니다.
  먼저 데이터들이 저장될 db 디렉토리를 만들어 줍니다. 

   

    $ mkdir ~/db/first1 
    $ mkdir ~/db/first2
    $ mkdir ~/db/first3

    
    로그를 저장할 공간도 만들어 줍니다. 

    $ mkdir ~/db/log 


    이제 몽고 데몬들을 띄워줍니다. 

 

    $ mongod --dbpath ~/db/first1 --port 10001 --replSet first > ~/db/log/first1.log &
    $ mongod --dbpath ~/db/first2 --port 10002 --replSet first > ~/db/log/first2.log &
    $ mongod --dbpath ~/db/first3 --port 10003 --replSet first > ~/db/log/first3.log &


    이제 이 세개의 몽고 디비 프로세스들을 한개의 복제셋 (여기서는 first )으로 묶어주는 작업을 합니다. 

 

  $ mongo localhost:10001/admin 

    > db.runCommand({"replSetInitiate" : {"_id" : "first", "members" : [{"_id" : 1, "host" : "super1:10001"}, {"_id" : 2, "host" : "super1:10002"}, {"_id" : 3, "host" : "super1:10003"}]}})
    {
        "info" : "Config now saved locally.  Should come online in about a minute.",
"ok" : 1
    }


    이러면 성공적으로 복제 셋이 만들어 진것 입니다. 이제 데이타를 실제로 넣어보기로 하겠습니다. 

   

PRIMARY> use test
    switched to db test
    PRIMARY> people = ["Marc", "Bill", "George", "Eliot", "Matt", "Trey", "Tracy", "Greg", "Steve", "Kristina", "Katie", "Jeff"];
    PRIMARY> for(var i=0; i<1000000; i++){
          name = people[Math.floor(Math.random()*people.length)];
 user_id = i;
 boolean = [true, false][Math.floor(Math.random()*2)];
 added_at = new Date();
 number = Math.floor(Math.random()*10001);
 db.test_collection.save({"name":name, "user_id":user_id, "boolean": boolean, "added_at":added_at, "number":number });
    }

 
    이름을 백만건 정도 랜덤으로 순서를 매겨서 집어넣는 소스코드 입니다. 


이제는 구성된 복제 셋을 가지고 샤딩으로 추가해 주는 작업을 해 줘야 할 차례 입니다. 역시  super1  서버에서 작업을 해 줍니다. 

config 서버를 띄워줄 차례 입니다. 먼저 config 데이타들이 저장 될 디렉토리를 만들어 줘야 합니다. 

   $ mkdir ~/db/config1
   $ mkdir ~/db/config2
   $ mkdir ~/db/config3


컨피그 서버는 1대 아니면 3대를 추천합니다. (제가 아니라 몽고디비 쪽이 추천하는 것입니다) 

 

   $ mongod --configsvr --dbpath ~/db/config1 --port 20001 > ~/db/log/config1.log &
   $ mongod --configsvr --dbpath ~/db/config2 --port 20002 > ~/db/log/config2.log &
   $ mongod --configsvr --dbpath ~/db/config3 --port 20003 > ~/db/log/config3.log &


혹시 컨피그 서버가 제대로 띄워지지 않으면 다른 포트로 띄워주시면 됩니다. 이제 컨피그 서버에서 정보를 가져오는 mongos (라우터 개념)를 띄워줄 차례 입니다. 

 

 $ mongos --configdb super1:20001,super2:20002,super3:20003 --chunkSize 1 > ~/db/log/mongos.log &


 chunkSize 는 테스트에서만 추가하는 것으로 생각하시면 됩니다. 크기를 1메가로 만드는  것입니다. (기본은 64메가 입니다) 이제 띄워논 mongos 에 접속해야 한다. 

 

 $ mongo super1/admin
  
 mongos> db.runCommand( { addshard : "first/super1:10001,super2:10002,super3:10003" } )

   { "shardAdded" : "first", "ok" : 1 }
   mongos>
   
  
이제 두번째 기계에서 세팅을 할 차례 입니다. super2 (Machine 2) 입니다.  먼저 데이타베이스가 저장될 디렉토리를 만들어 줍니다. 

   

   $ mkdir ~/db/second1 
   $ mkdir ~/db/second2
   $ mkdir ~/db/second3 
   

로그용 디렉토리도 만들어 줍니다. 
   

$ mkdir ~/db/log 


몽고디비 데몬을 띄워줍니다. 

   

   $ mongod --dbpath ~/db/second1 --port 10001 --replSet second > ~/db/log/second1.log &
   $ mongod --dbpath ~/db/second2 --port 10002 --replSet second > ~/db/log/second2.log &
   $ mongod --dbpath ~/db/second3 --port 10003 --replSet second > ~/db/log/second3.log &


이제 이 프로세스들을 한 개의 복제 셋(replication set)으로 묶어 줍니다. 

 

 $ mongo super2:10001/admin

   > db.runCommand({"replSetInitiate" : {"_id" : "second", "members" : [{"_id" : 1, "host" : "super2:10001"}, {"_id" : 2, "host" : "super2:10002"}, {"_id" : 3, "host" : "super2:10003"}]}})
   {
      "info" : "Config now saved locally.  Should come online in about a minute.",
      "ok" : 1
   }



방금 만들어 진 복제 셋 (replication set) 을 새롭게 샤드 (shard) 에 추가해 줍니다. 먼저 mongos 를 띄워줍니다. 지금 super2 서버고 mongos 도 super2 에서 띄우는 것이지만 바라보는 컨피그 서버 는 super1 에서 띄운 것을 쳐다봐야 합니다. 

   $ mongos --configdb super1:20001,super2:20002,super3:20003 --chunkSize 1 >  ~/db/log/mongos.log &


몽고 shell 을 이용해서 mongos (이건 super2 에 띄워 놓은 것입니다) 에 접속합니다. 

   

$ mongo super2/admin
   mongos> use admin
   switched to db admin
   mongos> db.runCommand( { addshard : "second/super2:10001,super2:10002,super3:10003" } )
     { "shardAdded" : "secondset", "ok" : 1 }


이러면 두번째 복제 셋 (replication set) 을 샤드에 추가 해 주었습니다.  제대로 되어 있는지 테스트를 확인 해 보기로 합니다. 

 

 mongos> db.runCommand({listshards:1})
   {
        "shards" : [
  {
      "_id" : "first",
      "host" : "first/super1:10001,super1:10003,super1:10002"
  },
  {
      "_id" : "second",
      "host" : "second/super2:10001,super2:10002,super2:10003"
  }
     ],
     "ok" : 1
   }


샤드로 설정되어 있는 것하고 샤드를 직접적으로 하는 것은 차이가 있습니다. 실제로 샤드를 구현해 보겠습니다. mongos 에 접속되어 있는 상태에서 

 

 mongos> db.runCommand( { enablesharding : "test" } )
   { "ok" : 1 }


 
위 처럼 입력해 주면 'test' db 를 샤딩하겠다고 정해주는 것입니다. 이제 샤딩 키 (Sharding Key) 를 정해줘야 합니다. 샤딩 키를 바탕으로 (이 키로 정렬한다는 뜻입니다) 샤드들이 나눠지게 되기 때문에 샤딩키를 적절하게 정해주는 것은 아주 중요합니다. 인덱스를 정해준다고 보셔도 무방합니다. 

 

   mongos> use test
   switched to db test
   mongos> db.test_collection.ensureIndex({number:1})

   
   생각 난 김에 인덱스도 정해줍니다. test 디비에 test_collection 이라는 컬렉션에서 number 라는 컬럼을
   인덱스로 지정해 주라는 명령입니다. 

   

mongos> use admin
   switched to db admin
   mongos> db.runCommand( { shardcollection : "test.test_collection", key : {"number":1} })
   { "collectionsharded" : "test.test_collection", "ok" : 1 }
   mongos>


   
test 디비에 test_collection 이라는 컬렉션에서 number 라는 컬럼을 샤딩키로 해서 샤딩을 해 주라는 명령입니다. 

제대로 됐는지 확인을 해 보겠습니다. 

 

   mongos> use test
   switched to db test
   mongos> db.stats()


   
db.stats() 나 db.printShardingStatus() 로 확인하시면 됩니다. 



화살표는 신경 쓰지 맙시다..

Consistency : 일관성
  각각의 사용자가 항상 동일한 데이터를 조회한다.

Availability : 가용성
  모든 사용자가 항상 읽고 쓸 수 있다.

Partition Tolerance : 확장 가능성
  물리적 네트워크 분산 환경에서 시스템이 잘 동작한다.


기존의 RDBMS 로 칭해지는 데이타베이스들은 CA 에 취중합니다. 따라서 확장이 용이하지가 않고, 대신 최근 트렌드가 되고 있는 NoSQL 들은 기본적으로 P 의 성능이 좋습니다. 그래서 확장성은 기본입니다. 대신 C A 의 일부분을 희생합니다.

카산드라 (Cassandra) 는 AP 를 추구 하고, HBase 는 CP 를 추구합니다.

카산드라는 페이스북이 채택하고 개발해진 것으로 알려지고 유명해 졌는데요. Consistency 모델이 페이스북의 새로운 메시지 플랫폼을 구현하기에는 어렵다고 판단이 되서 , 새로운 메시지 플랫폼은 HBase 로 구현했다고 합니다. 즉 AP 대신 CP 로 페이스북의 데이타의 중요성에 대한 잣대가 이동한 것이지요.






프로그래밍을 하다보면 수학적 귀납법의 원리를 이용하여 함수가 제대로 작성 됐는지 검증하면 편할 때가 많습니다. 

수학적 귀납법

자연수 n과 관련된 명제 P(n)을 증명하려고 할 때, 다음 두 가지만 증명하면 된다.
1) n=1일 때, 참이다.
2) n=k일 때, 참이라고 가정하면 n=k+1일 때도 참이다.
1)과 2)에 따라서 모든 자연수일때 명제가 성립한다.

살아오면서 수학과 프로그래밍에 대해서는 별다른 연관성을 못 느끼고 있었습니다. '수학적 사고나 논리력 증대가 프로그래밍에 도움이 되는 것이겠지' 정도로만 치부하고 살았는데, 최근에 결국 프로그램의 대부분이 아무리 많은 데이터를 다루더라도 index 형태로 처리하게 된다면, 함수를 작성할 때 귀납법을 고려해서 작성하면 좋구나 하고 느낄 때가 있었습니다. 

함수를 작성할 때 위에 쓰여진 귀납법 형태대로 따르면 n=k 일 때 제대로 돌아간다고 생각하고 작성해 줍니다. 그리고 n=1 일때 실제로 맞게 돌아가는지 확인을 하고 n=k+1 일 때 제대로 돌아가는지 체크를 해주면 깔끔하게 작성이 됩니다. 

딱히 방법이랄 것은 아니지만, '맞아 이런거였지?' 정도로만 인식해도 함수를 잦은 디버그 없이 작성할 수 있게 되는 요령이 됩니다. 



+ Recent posts