2011. 9. 23. 15:39

[mongodb] Advanced topics

mongodb는 다음과 같은 추가 기능을 제공한다.

- database command 사용하기
- 특별 collection인 capped collections와 함께 작업하기
- 큰 파일 저장을 위한 gridfs 지원
- server-side JavaScript 지원

* database command

Commands 작동 원리

drop은 shell에서 db.test.drop()와 같이 실행하면 된다.
이는 db.runCommand({"drop":"test"});와 같다.

Command reference

db.listCommands()를통해 확인 가능
http://localhost:28017/_commands를 통해 확인 가능

db.runCommand({"buildInfo":1})
{
 "version" : "1.8.3",
 "gitVersion" : "c206d77e94bc3b65c76681df5a6b605f68a2de05",
 "sysInfo" : "Linux bs-linux64.10gen.cc 2.6.21.7-2.ec2.v1.2.fc8xen #1 SMP Fri Nov 20 17:48:28 EST 2009 x86_64 BOOST_LIB_VERSION=1_41",
 "bits" : 64,
 "debug" : false,
 "maxBsonObjectSize" : 16777216,
 "ok" : 1
}
admin-only command로, mongodb server의 버전을 알려줌.

db.runCommand({"collStats":"a"})
{
 "ns" : "sss.a",
 "count" : 3,
 "size" : 108,
 "avgObjSize" : 36,
 "storageSize" : 2048,
 "numExtents" : 1,
 "nindexes" : 1,
 "lastExtentSize" : 2048,
 "paddingFactor" : 1,
 "flags" : 1,
 "totalIndexSize" : 8192,
 "indexSizes" : {
  "_id_" : 8192
 },
 "ok" : 1
}
주어진 collection의 data 크기, 할당 크기, index 개수 등을 알려준다.

db.runCommand({"distinct":"a", "key":"a", "query":{}})
{
 "values" : [
  1,
  2,
  3
 ],
 "stats" : {
  "n" : 3,
  "nscanned" : 3,
  "nscannedObjects" : 3,
  "timems" : 0
 },
 "ok" : 1
}
주어진 key와 query로 해당 collection의 분산값들을 알려준다.

db.runCommand({"drop":"a"})
{
 "nIndexesWas" : 1,
 "msg" : "indexes dropped for collection",
 "ns" : "sss.a",
 "ok" : 1
}
주어진 collection을 삭제한다.

db.runCommand({"dropDatabase":1})
{ "dropped" : "sss", "ok" : 1 }
현재 database를 삭제한다.

db.runCommand({"dropIndexes":"a", "index":"*"})
{
 "nIndexesWas" : 2,
 "msg" : "non-_id indexes dropped for collection",
 "ok" : 1
}
주어진 index를 삭제한다. *는 모든 index이다.

db.runCommand({"getLastError":1})
현재 connection에서 마지막 수행한 operation의 error 혹은 상태 정보를 알려준다.

db.runCommand({"isMaster":1})
{ "ismaster" : true, "maxBsonObjectSize" : 16777216, "ok" : 1 }
현재 서버가 master인지 slave인지 알려준다.

db.runCommand({"ping":1})
{ "ok" : 1 }
서버가 살아 있는지 확인한다. 서버가 lock 상태일 때에도 즉각 리턴된다.

db.runCommand({"renameCollection":"sss.a", "to":"sss.z"}) 
{ "ok" : 1 }
collection 이름을 변경한다.

db.runCommand({"repairDatabase":1})
{ "ok" : 1 }
현재 database를 repair, compact 한다.
오래 걸린다.

db.runCommand({"serverStatus":1})
{
 "host" : "ubuntu",
 "version" : "1.8.3",
 "process" : "mongod",
 "uptime" : 1459578,
 "uptimeEstimate" : 88153,
 "localTime" : ISODate("2011-09-23T01:31:12.355Z"),
 "globalLock" : {
  "totalTime" : 1459577533573,
  "lockTime" : 6870046,
  "ratio" : 0.000004706872942324854,
  "currentQueue" : {
   "total" : 0,
   "readers" : 0,
   "writers" : 0
  },
  "activeClients" : {
   "total" : 0,
   "readers" : 0,
   "writers" : 0
  }
 },
 "mem" : {
  "bits" : 64,
  "resident" : 8,
  "virtual" : 3347,
  "supported" : true,
  "mapped" : 3200
 },
 "connections" : {
  "current" : 10,
  "available" : 15990
 },
 "extra_info" : {
  "note" : "fields vary by platform",
  "heap_usage_bytes" : 2962544,
  "page_faults" : 643
 },
 "indexCounters" : {
  "btree" : {
   "accesses" : 6,
   "hits" : 6,
   "misses" : 0,
   "resets" : 0,
   "missRatio" : 0
  }
 },
 "backgroundFlushing" : {
  "flushes" : 2304,
  "total_ms" : 2332,
  "average_ms" : 1.0121527777777777,
  "last_ms" : 1,
  "last_finished" : ISODate("2011-09-23T01:30:54.191Z")
 },
 "cursors" : {
  "totalOpen" : 0,
  "clientCursors_size" : 0,
  "timedOut" : 0
 },
 "network" : {
  "bytesIn" : 232543,
  "bytesOut" : 312930,
  "numRequests" : 2993
 },
 "opcounters" : {
  "insert" : 574,
  "query" : 328,
  "update" : 51,
  "delete" : 6,
  "getmore" : 0,
  "command" : 2056
 },
 "asserts" : {
  "regular" : 0,
  "warning" : 0,
  "msg" : 0,
  "user" : 34,
  "rollovers" : 0
 },
 "writeBacksQueued" : false,
 "ok" : 1
}
서버의 상태를 알린다.

* capped collections

mongodb는 동적으로 생성되고, 추가 data를 맞추기 위해 size가 자동으로 커진다.
mongodb는 capped collection이라는 다른 종류의 collection을 제공한다.
이는 미리 생성되고 고정 크기를 가진다.
그럼 Full되면 어떻게 되는가? capped collection은 circular queue이다.
그래서 oldest가 삭제된다. 즉, capped collection은 자동으로 age-out되는
구조이다.

capped collection에서 몇몇 operation은 실행되지 않는다.
document는 이동/삭제가 되지 않는다.(물론 age-out은 된다)

특징과 사용예

1) 극단적으로 빠르다.
2) insert 순서대로 document를 구하는 것이 정말 빠르다.
3) 자동으로 aging-out되는 구조에 적합하다.

주로 log에 사용될 수 있다. 그리고 작은수의 document를 caching하는데 적합하다.

Capped collection 생성예

db.createCollection("capped_c", {capped:true, size:100000})
{ "ok" : 1 }

100000byte 크기의 capped collection을 생성하였다.
max:100을 추가하면, collection size도 제한할 수 있다.

이미 있는 일반 collection을 변환하는 것은 다음과 같다.
db.runCommand({convertToCapped:"a", size:10000})
{ "ok" : 1 }

Au Naturel sort

capped collection은 natural sort라는 sort를 제공한다.
natural order는 disk상 보여지는 순서를 의미한다.

capped collection은 insert순으로 저장되기 때문에, natural order는 insert order이다.
역순으로 sort하는 것은 다음과 같다.

db.a.find().sort({"$natural":-1})
{ "_id" : ObjectId("4e7be67ea832633d57ff5465"), "a" : 3 }
{ "_id" : ObjectId("4e7be67ba832633d57ff5464"), "a" : 2 }
{ "_id" : ObjectId("4e7be5d9a832633d57ff5463"), "a" : 1 }

tailable cursors

tailable cursor는 result가 소진되었을때 close되지 않는 영구 cursor를 의미한다.
이는 tail -f 명령에서 영감받았다. 물론 capped collection에서만 지원된다.

* GridFS : 파일 저장소

GridFS는 mongodb의 binary file 저장 mechanism을 따른다.
GridFS를 file 저장소로 사용하는데 다음의 몇가지 이유가 있다.

- stack을 단순화한다. 다른 file 저장소 architecture를 배제시킨다.
- autosharding을 지원한다. scale-out이 쉽다.
- user upload에 따른 filesystem 이슈를 완화한다. 예를 들어, 동일 directory의 파일 개수 제한이 없다.
- 2GB chunk로 data를 할당하므로, 좋은 disk locality(집약성)을 가진다.

GridFS 시작하기

mongofiles utilty를 사용하여 쉽게 GridFS를 실행할 수 있다.
이는 upload, download를 지원한다.
mongofiles --help를 통해 명령을 구할 수 있다.
다음과 같이 file을 upload/download할 수 있다.

~$ echo "Hello, world" > foo.txt
~$ cd /usr/bin
/usr/bin$ mongofiles put ~/foo.txt
connected to: 127.0.0.1
added file: { _id: ObjectId('4e7bea0cd9d16165bf521e6a'), filename: "/home/greenfish/foo.txt", chunkSize: 262144, uploadDate: new Date(1316743692450), md5: "a7966bf58e23583c9a5a4059383ff850", length: 13 }
done!
/usr/bin$ mongofiles list
connected to: 127.0.0.1
/home/greenfish/foo.txt 13
/usr/bin$ rm ~/foo.txt
/usr/bin$ mongofiles get /home/greenfish/foo.txt
connected to: 127.0.0.1
done write to: /home/greenfish/foo.txt
/usr/bin$ cat ~/foo.txt
Hello, world

mongodb driver로부터 GridFS 실행하기

pyMongo는 다음과 같다.

>>> from pymongo import Connection
>>> import gridfs
>>> db = Connection().test
>>> fs = gridfs.GridFS(db)
>>> file_id = fs.put("Hello, world", filename="foo.txt")
>>> fs.list()
[u'foo.txt']
>>> fs.get(file_id).read()
'Hello, world'

뚜껑을 열고...

GridFS의 기본 idea는 큰 file을 chunk로 쪼개고 그것을 document로 하는 것이다.
mongodb는 binary data를 저장하는 기능을 지원하기 때문에, chunk를 저장하는데
overhead가 크지 않다.
각각의 chunk file을 저장하는데 함께 묶어서 저장하며, file의 meta 정보도 포함시킨다.

GrindFS를 위한 chunk는 그들 고유의 collection에 저장된다.
디폴트로 chunk는 fs.chunks를 사용하며 필요시 무시된다.
chunk collection내에서 개별 document는 다음과 같이 단순하다.

{
 "_id":ObjectId("..."),
 "n":0,
 "data":BinData("..."),
 "files_id":ObjectId("...")
}

file_id는 이 chunk의 metadata를 포함하고 있는 문서의 id이다.
n은 chunk number이다. original file에 표현된 chunk 순서를 따른다.
"data"는 chunk를 나타낸 binary data이다.

각 file을 위한 metadata는 다른 collection에 저장되는데, 디폴트로 fs.files이다.
각 document는 단일 file을 의미한다.
사용자 정의 key를 추가할 수 있다.

_id
 file의 id. chunk에서 files_id로 사용

length
 크기

chunkSize
 개별 chunk 크기. 디폴트로 256K

uploadData
 저장된 싯점

md5
 md5 checksum

db.fs.files.distinct("filename")
[ "foo.txt" ]
와 같은 결과를 얻는다.

* Server-side scripting

JavaScript는 db.eval function으로 서버에서 실행된다.

db.eval

주로 multidocument transaction을 모방하는데 사용됟나.
db.eval은 db lock을 건 다음 JavaScript를 실행하고 db lock을 푼다.
rollback은 없으나 특정 순서로 operation 작업들 실행하는것을 보장한다.

다음과 같이 code를 전송하여 실행할 수 있다.

db.eval("return 1;")
1
db.eval("function() {return 1;}")
1

저장된 JavaScript

system.js라는 특별한 collection이 있는데, JavaScript 변수가 저장된다.
이 값들은 JavaScript context에서 사용되는 변수로, "$where"나 db.eval 그리고
MapReduce등이 포함된다.
system.js에 다음과 같이 변수를 추가할 수 있다.

db.system.js.insert({"_id":"x", "value":1})
db.system.js.insert({"_id":"y", "value":2})
db.system.js.insert({"_id":"z", "value":3})
db.eval("return x+y+z;")
6

system.js는 간단한 JavaScript function도 저장할 수 있다.

보안

JavaScript를 실행하는 것은 보안에 신경써야 한다.

올바르지 않게 완료되었다면, server-side JavaScript는 injection 공격에
취약해진다. 운좋게도, 이러한 공격은 쉽게 예방할 수 있다.

"Hello, <username>!" 프로그래이 있을때, 만약 username이 username이라는 변수라면,
다음과 같이 사용할 수 있다.

func = "function() {print('Hello, "+username+"!');}"

혹은 만약 username이 user-=defined 변수라면, "문자를 포함할 수 있다.
즉, "'); db.dropDatabase(); print('"와 같을 수 있다. 이런 경우 다음과 같은 효과가 있다.

func = "function() { print('Hello, '); db.dropDatabase(); print('!'); }"

그러면, 전체 db가 삭제될 것이다!

이를 방지하려면, username을 전달할 때 scope를 사용해야 한다.
예를 들어, PHP에서,
$func = new MongoCode("function() {print('Hello, "+username+"!');}", array("username" => $username));
와 같을 것이다.
그러면, database는 유해하지 않게 이와 같이 출력한다.
Hello, '); db.dropDatabase(); print('!

대부분의 driver는 database에 code를 전송할 때 특정 type이 있는데,
code는 string과 scope가 결합되어 있을 수 있다.
scope는 변수명과 값으로 mapping된 document일 뿐이다.
이러한 mapping은 실행될 JavaScript 함수의 local scope가 된다.

* database reference

mongodb는 database reference(DBRefs)를 지원한다.
이는 URL과 비슷하다.
document를 unique하게 가리킬 수 있도록 한다.

DBRef란?

DBRef는 embeded document로, mongodb의 다른 embeded document와 유사하다.
DBRef는 그러나 다음과 같이 특별한 키가 포함되어야 한다.
{"$ref":collection, "$id":id_value}

DBRef는 특정 collection과 id_value를 참고한다.
이러한 두개의 정보는 mongodb database 내의 어떤 다른 document들과도 unique id를 부여한다.

만약 다른 database를 참고하고 싶다면, $db를 추가하면 된다.
{"$ref":collection, "$id":id_value, "$db":database}

db.A.find()
{ "_id" : 1, "AKEY" : 1 }
{ "_id" : 2, "AKEY" : 2 }
라면,
db['A'].find()
{ "_id" : 1, "AKEY" : 1 }
{ "_id" : 2, "AKEY" : 2 }
도 가능하다.
$ref를 받아 db[ref.$ref]로 전달하여 참조된다.

Driver에서 DBRef 지원

note = {"_id": 20, "author": "kristina",
   "text": "... and DBRefs are easy, too",
   "references": [DBRef("users", "mike"), DBRef("notes", 5)]}
와 같이 pyMongo에서 사용가능하다.

DBRef는 언제 사용되는가?

다른 collection에 있는 document의 reference를 저장할 때나,
driver나 tool의 DBRef 함수를 사용할 수 있는 장점이 있다.
물론, "_id"를 reference로 사용하는 것이 간단하고 쉽다.

'Research > mongodb' 카테고리의 다른 글

[mongodb] Replication  (0) 2011.09.28
[mongodb] Administration  (0) 2011.09.23
[mongodb] Aggregation (MapReduce)  (0) 2011.09.22
[mongodb] Indexing  (0) 2011.09.22
[mongodb] Querying  (0) 2011.09.19