태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

MongoDB 읽고쓰기

Daily life/Hard study 2012.07.26 23:10

몽고DB의 C++ 클라이언트는 github 에서 구할 수 있다.

mongodb c++ client: github.com/mongodb/mongo
mongodb 서버는 빌드해서 설치할 수도 있지만 귀찮으니, apt-get 으로 설치하기로 한다.

a. debian: mongodb-server mongodb-dev mongodb-clients mongodb
b. ubuntu: mongodb
c. etc: http://docs.mongodb.org/manual/tutorial/install-mongodb-on-debian-or-ubuntu-linux/

세번째가 제일 나은 듯 하다. 그 와중에 upstart 라는걸 보게 되었다. 나중에 천천히 읽어볼 것: http://upstart.ubuntu.com/
사용한 client 라이브러리는 C++ 2.1.2 이다. API DOC: http://api.mongodb.org/cplusplus/2.1.0/


0. Install
  mongodb server:
  sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
  echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" >> /etc/apt/sources.list
  sudo apt-get update
  sudo apt-get install mongodb-10gen


  mongodb client library:
  wget http://downloads.mongodb.org/cxx-driver/mongodb-linux-x86_64-2.1.2.tgz
  tar zxvf mongodb-linux-x86_64-2.1.2.tgz
  cd mongo-cxx-driver-nightly
  sudo scons install

이렇게 하면 /usr/local 밑에 몽고DB 클라이언트 라이브러리가 설치된다.
/usr/local/include/mongo 아래쪽 권한이 좀 이상하게 설치되어서 sudo chmod 755 /usr/local/include/mongo -R 을 해 주었다.
언제나 그런건지, scons 사용을 잘못한건지는 잘 모르겠다.

C++ 튜토리얼은 이걸 보면 된다. http://www.mongodb.org/pages/viewpage.action?pageId=133415
이 아래의 내용은 기본적으로 튜토리얼 내용을 그냥 따라간 로그이다.

1. Connect
#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


g++ -I/usr/local/include/mongo test.cpp -lboost_system -lboost_thread -lboost_filesystem -lmongoclient -o test

이렇게 빌드하면 ./test 를 실행했을때 아무일도 안 일어나게 된다. localhost 를 remotehost 같은걸로 바꾸고 해보면 에러가 나는걸 확인할 수 있다.


$ ./test
Thu Jul 26 21:36:19 getaddrinfo("remotehost") failed: No address associated with hostname
Error: can't connect couldn't connect to server remotehost:27017

2. Insert
접속은 했으니 이제 읽기/쓰기를 해보자. 아직 아무것도 들어있는게 없으므로, 먼저 쓰기를 하고 나서 쓴 값을 읽어보도록 하자. 몽고DB는 데이터를 BSON이라는 형식으로 저장하는데, JSON이랑 닮은 - 정확히는 json with custom extension - 포맷이다. 방법은 간단하다. BSON 객체를 생성 -> DB에 추가, 하면 된다.

a. BSON 객체 생성
BSON 객체는 BSONObjBuilder 를 사용해서 만든다. 사용법은 아래와 같다.

BSONObjBuilder builder;
builder.append("HERE_GOES_KEY", "HERE_GOES_VALUE");
builder.append("name", "dgoon");
BSONObj obj = builder.obj();

이걸 아래와 같이 체이닝 할 수도 있다

BSONObj obj = BSONObjBuilder().append("HERE_GOES_KEY", "HERE_GOES_VALUE").append("name", "dgoon").obj();

하지만 난 여러 줄에 풀어 쓰는걸 선호한다.
그 외에 stream 체이닝이나, Object ID 를 명시적으로 지정하는 방법 등 몇 가지 화두가 더 있지만 과감하게 생략.

b. DB 에 추가
이렇게 만든 BSONObj 객체를 DB에 넣기 위해서는,

conn.insert("database.collection", obj);

를 해주면 된다. database, collection 은 무엇인가! 대충 RDB 개념을 빌리자면 database=database, collection=table 정도로 생각하면 된다.

#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        mongo::BSONObjBuilder b;
        b.append("name","dgoon");
        b.append("addr", "nakseongdae");
        mongo::BSONObj p=b.obj();

        c.insert("mydb.character", p);
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


소스코드를 이렇게 수정하고 나서 실행해보면, 역시 아무런 말도 없다. 잘 된거겠지… 확인을 해보자. 읽는 코드를 만들기 전에 잠깐 shell 에서 보도록 한다.

c. SHELL

몽고DB는 강력한지는 잘 모르겠고, 여튼 쉘 환경이 있다. 위의 c로 설치했다면 mongo 라는 커맨드가 있을 것이다.

dgoon@katy:~/works/mongo-cxx-driver-nightly$ mongo
MongoDB shell version: 2.0.6
connecting to: test
>

위에서 mydb.character 에 데이터를 넣었던 것이 기억나는가? db=mydb, collection=character 이다.

> show dbs
local     (empty)
mydb     0.078125GB
> use mydb
switched to db mydb
> show collections
character
coll
system.indexes
things
> db
mydb
> db.character
mydb.character
> db.character.find()
{ "_id" : ObjectId("50113dbb9aa68f690df58a39"), "name" : "dgoon", "addr" : "nakseongdae" }
{ "_id" : ObjectId("50113dbe9aa68f690df58a3a"), "name" : "dgoon", "addr" : "nakseongdae" }
{ "_id" : ObjectId("50113dbf9aa68f690df58a3b"), "name" : "dgoon", "addr" : "nakseongdae" }
>

이렇게 들어가 있다. … 왜 3개냐구? 내가 실행을 3번 했으니까. … 그렇다, 딱히 KEY 를 지정해 준 것이 아니기 때문에 여러번 추가해도 중복이 아니므로, 실행한 횟수만큼 여러개가 들어간다.


3. Retrieve

읽어오는건 코드가 조금 더 길다. 읽어올 조건과 함께 query를 날린다 -> 커서를 받는다 -> 커서를 순회하며 데이터를 꺼낸다, 순서로 작업하면 된다.

데이터를 꺼내오기 위해 명시해 주어야 하는 것은, 어디에서? 어떤 녀석을? 이다. connection.query 는 2개의 인자를 받는데, 하나는 어디에서? 또 하나는 어떤 녀석을? 에 대한 것이다. 여기서 조건 역시 BSONObj 타입으로 들어간다. 그냥 빈 객체를 넣으면 모두 다 꺼내오라는 뜻이다. BSONObj 객체의 각 필드가 AND로 조합되어 조건으로 들어간다고 한다. 우리는 모두다 꺼내와보자.

test.cpp 는 이제 아래와 같이 길어졌다.

#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        mongo::BSONObjBuilder b;
        b.append("name","dgoon");
        b.append("addr", "nakseongdae");
        mongo::BSONObj p=b.obj();

        c.insert("mydb.character", p);

        auto cursor=c.query("mydb.character",
                            mongo::BSONObj());
        while (cursor->more()) {
            printf("%s\n", cursor->next().toString().c_str());
        }
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


auto는 타이핑을 줄이기 위해 사용했는데, 정확한 타입은 auto_ptr<mongo::DBClientCursor> 이다. auto_ptr을 사용한 이유는 소유권을 넘기면서 객체 소멸을 라이브러리 사용자(caller)에게 넘기고 난 귀찮으니 잊어버리겠어 -_- 라는 것이라 생각한다. auto 사용때문에 아래 컴파일 옵션에 -std=c++0x 가 추가된 것에 주의하자.

dgoon@katy:~/works/mongo-cxx-driver-nightly$ g++ -I/usr/local/include/mongo test.cpp -lboost_system -lboost_thread -lboost_filesystem -lmongoclient -std=c++0x -o test
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$

실행할 때마다 엔트리가 하나씩 늘어나면서 가져오는 갯수도 늘어나고 있다. 참고로 b.append("age", 18); 처럼 value 에는 문자열 이외의 타입도 들어갈 수 있다.


4. Update

데이터를 가져오는 것과 별로 다르지 않다. 어디에 있는? 어떤 아이들에? 어떤 값을? 을 지정해주면 된다.


#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        c.update("mydb.character", // 어디에 있는?
                 BSON("age" << 18),  // 어떤 아이들에게?
                 BSON("$inc" << BSON("visits" << 1)));  // 어떤 값을??

        auto cursor=c.query("mydb.character",
                            mongo::BSONObj());
        while (cursor->more()) {
            printf("%s\n", cursor->next().toString().c_str());
        }
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}

이렇게 하면 mydb.character 에서 age=18 인 아이들을 찾아서(설명엔 없지만 위에서 몇개 추가해 두었다) visits 을 1 증가시킨다고 한다. $inc 가 뭔가 특별한 의미가 있나보다. 그래서 위의 프로그램을 여러번 실행하면,

dgoon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011421d9aa68f690df58a40'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 10 }
{ _id: ObjectId('5011421f9aa68f690df58a41'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 1 }
dgoon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011421d9aa68f690df58a40'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 11 }
{ _id: ObjectId('5011421f9aa68f690df58a41'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 1 }

이런 결과가 나온다. 처음 한 번은 5011421f9aa68f690df58a41 를 고치더니 그 후로는 5011421d9aa68f690df58a40 를 고치고 있다. 어어?
… 요약하면 해당 조건에 맞는 데이터가 여러개 있는 경우에, 그 중 임의로 하나를 선택하여 갱신, 한다고 생각한다.
혹은 선택 기준은 있지만, 갱신할 때마다 선택되는 아이들이 달라지게 될 수도 있다. 여튼 곤란.

uniqueness 를 보장하기 위해서는 매번 업데이트 할 때마다 query를 보내어 확인을 한번 해 주어야 하는건가?

조건에 맞는 엔트리가 하나도 없는 경우는 아무일도 일어나지 않는다.



+
http://api.mongodb.org/cplusplus/2.1.0/dbclient_8h_source.html


ensureIndex의 함수 시그니쳐는 다음과 같다. unique=false가 기본값인데, true를 줄 수 있다.


virtual bool ensureIndex( const string &ns , BSONObj keys , bool unique = false, const string &name = "", bool cache = true, bool background = false, int v = -1 );


주의할 점은 특정 key에 unique=true가 걸리면 그 키가 동일한 경우는 물론 이고, 해당 키가 "없는 것도 하나의 값" 이 되어 버린다. 즉 ensureIndex("mydb.character", fromjson("{age:1}"), true); 로 인덱스를 생성했다면

mydb.character 에 age 가 없는 데이터는 단 하나밖에 존재할 수 없다

는 사실.


+ 도깨비의 백엔드 저장소로 몽고디비 사용할 예정.


Trackbacks 0 : Comments 6
  1. zzugg 2012.07.27 02:07 Modify/Delete Reply

    c++ client에는 sparse 옵션이 없나보네요?

  2. Favicon of http://blog.shurain.net 슈레인 2012.07.27 02:19 Modify/Delete Reply

    Mongo DB is Web Scale. http://youtu.be/b2F-DItXtZs

  3. Favicon of http://9901ccgenevois.com/clfrance.php christian louboutin 2013.07.14 09:05 Modify/Delete Reply

    당신은 내가사랑할 만한 사람이 아니예요,사랑하지 않으면 안될 사람이예요.

  4. Favicon of http://18028hiphopweekly.com/mag/thankhot.php nike shoes 2013.07.14 20:33 Modify/Delete Reply

    당신 매력있어, 자기가 얼마나 매력있는지 모르는게 당신매력이야

Write a comment