7つのデータベース7つの世界 MongoDB メモ


# はじめに
[7つのデータベース 7つの世界](http://www.amazon.co.jp/gp/product/4274069087/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4274069087&linkCode=as2&tag=nownabe-22)の第5章 MongoDBのメモです。

# 1日目
## MongoDBインストール
http://qiita.com/nownabe/items/123a8fd04ff5252b3036

## 1日目の宿題
### やってみよう
#### 1.
JSONドキュメント`{ "hello" : "world" }`の中身を印字してみよう。

```mongo
> db.yattemiyo.insert({"hello": "world"})
WriteResult({ "nInserted" : 1 })
> print(JSON.stringify(db.yattemiyo.findOne({hello: "world"}, {_id: false})))
{"hello":"world"}
>
```

#### 2.
大文字小文字を区別しない正規表現を使って、文字列newが含まれる町を選択してみよう。

```mongo
> db.towns.find({name: /new/i})
{ "_id" : ObjectId("540bd726c265200143a187d6"), "name" : "New York", "population" : 22200000, "last_census" : ISODate("2009-07-31T00:00:00Z"), "famous_for" : [ "statue of liberty", "food" ], "mayor" : { "name" : "Michael Bloomberg", "party" : "I" } }
>
```

#### 3.
名前にeが含まれていて、foodかbeerで有名な市をすべて検索してみよう。

```mongo
> db.towns.find({name: /e/, famous_for: {$in: ["food", "beer"]}})
{ "_id" : ObjectId("540bd726c265200143a187d6"), "name" : "New York", "population" : 22200000, "last_census" : ISODate("2009-07-31T00:00:00Z"), "famous_for" : [ "statue of liberty", "food" ], "mayor" : { "name" : "Michael Bloomberg", "party" : "I" } }
>
```

#### 4.
新しいデータベース「blogger」とコレクション「articles」を作ってみよう。新しい記事に作者の名前・メールアドレス・作成日・本文を挿入してみよう。

```mongo
> use blogger
switched to db blogger
> db.articles.insert({author: "nownabe", email: "nownabe@hogehoge.com", created_on: ISODate("2014-09-09"), text: "foo\nbar"})
WriteResult({ "nInserted" : 1 })
> db.articles.find()
{ "_id" : ObjectId("540f1cc1128b0ea47aa14735"), "author" : "nownabe", "email" : "nownabe@hogehoge.com", "created_on" : ISODate("2014-09-09T00:00:00Z"), "text" : "foo\nbar" }
>
```

#### 5.
名前と本文を持つコメントの配列を記事に追加してみよう。

```mongo
> db.articles.update({_id: ObjectId("540f1cc1128b0ea47aa14735")}, {$set: {comments: [{name: "kani", text: "hyoe"}]}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.articles.find()
{ "_id" : ObjectId("540f1cc1128b0ea47aa14735"), "author" : "nownabe", "email" : "nownabe@hogehoge.com", "created_on" : ISODate("2014-09-09T00:00:00Z"), "text" : "foo\nbar", "comments" : [ { "name" : "kani", "text" : "hyoe" } ] }
>
```

#### 6.
外部のJavascriptファイルにあるクエリを実行してみよう。

```bash
$ echo '"count(countries): " + db.countries.count()' > day1-6.js
$ mongo --quiet book < day1-6.js
```

# 2日目
## 2日目の宿題
### 調べてみよう
#### 1.
adminコマンドのショートカットを探してみよう。

```mongo
> db.adminCommand
function ( obj ){
    if ( this._name == "admin" )
        return this.runCommand( obj );
    return this.getSiblingDB( "admin" ).runCommand( obj );
}
>
```

#### 2. 
クエリとカーソルのオンラインドキュメントを探してみよう。

* [クエリ](http://docs.mongodb.org/manual/core/crud-introduction/#query)
* [カーソル](http://docs.mongodb.org/manual/core/cursors/)

#### 3.
MongoDBのmapreduceのドキュメントを探してみよう。

http://docs.mongodb.org/manual/core/map-reduce/

#### 4. JavaScriptのインターフェイスを使って、help()・findOne()・stats()の3つの関数を調査してみよう。

```mongo
> db.cities.help
> db.cities.findOne
> db.cities.stats
```

### やってみよう
#### 1. 
カウントの合計値を出力するfinalize()メソッドを実装してみよう。

※おそらく、`code/mongo/reduce_2.js`のようなコードにしなくてもいいように出力のキーをcountからtotalにしろって話

```javascript:shukudai2-1.js
reduce = function(key, values){
  var total = 0;
  for(var i=0; i<values.length; i++) {
    total += values[i].count
  }
  return {count: total}
}

finalize = function(key, value) {
  return {total: value.count}
}

results = db.runCommand({
  mapReduce: "phones",
  map: map,
  reduce: reduce,
  out: "phones.report2",
  finalize: finalize
})
```

#### 2.
あなたが選んだ言語のドライバをインストールして、データベースに接続してみよう。コレクションを作って、フィールドにインデックスを作成してみよう。

Rubyでやりました。
http://docs.mongodb.org/ecosystem/drivers/ruby/

```ruby:shukudai2-2.rb
require 'mongo'
client = Mongo::MongoClient.new
db = client["book"]
coll = db["shukudai_2_2"]
coll.remove

1.upto(100) do |i|
  coll.insert({
    _id: i,
    created_at: Time.now,
    fizzbuzz: [i%3, i%5].include?(0) ? ( (i%3).zero? ? "fizz" : "" ) + ( (i%5).zero? ? "buzz" : "") : i
  })
end
```

気づいたらfizzbuzzになってました。


# 3日目
## 3日目の宿題
### 調べてみよう
#### 1.
レプリカセットの設定オプションについて、オンラインドキュメントで調べてみよう

http://docs.mongodb.org/manual/reference/replica-configuration/

#### 2. 
球体ジオインデックスの作り方を探してみよう

http://docs.mongodb.org/manual/core/2dsphere/

### やってみよう
#### 1.
Mongoは、境界となる形（四角形や丸）をサポートしている。ロンドンの中心から50マイル四方の範囲にあるすべての都市を見つけてみよう。

MongoDBのクエリでは四角形の左下と右上の座標を2つ渡す必要があるので、それを計算する。
http://docs.mongodb.org/manual/reference/operator/query/box/

ロンドンのGeoは[51.507778, -0.128056]らしい。
（[GeoHack](http://tools.wmflabs.org/geohack/geohack.php?language=ja&pagename=%E3%83%AD%E3%83%B3%E3%83%89%E3%83%B3&params=51_30_28_N_0_07_41_W_type:city)より）

このGeoってのは`[Latitude, Longtitude]`（`[緯度, 経度]`）になってる。

また、1マイル=1.6kmとすると50マイル=80km。
地球は周囲4万キロの真球として計算する。

緯度は簡単に計算できる。
`90 / 40000 * 80 = 0.18`
なので、ロンドンの南北50マイルのGeoは、`[51.327778, -0.128056]`〜`[51.687778, -0.128056]`

経度はちょっと複雑になる。
経度と距離の関係は、緯度によって変化する。
しかし、[ココ](http://ja.wikipedia.org/wiki/Wikipedia:%E7%B7%AF%E5%BA%A6%E3%83%BB%E7%B5%8C%E5%BA%A6%E3%81%AE%E8%AA%BF%E3%81%B9%E6%96%B9)によると過剰な精度はいらぬと書いてあるので、このページの表を参考にする。
といっても緯度50度の値はないので、だいたい1度70kmぐらいだろうってことにする。

すると、50マイルというのは`80 / 70 = 1.1429`度になる。

よって、ロンドンの50マイル四方で南西の座標は`[51.327778, -1.270956]`、北東の座標は`[51.687778, 1.014844]`となる。

```javascript:mongo
mongos> db.cities.find({location: {$geoWithin: {$box: [[51.327778, -1.270956], [51.687778, 1.014844]]}}})
{ "_id" : ObjectId("541e7dc1051a80368833da31"), "name" : "Holborn", "country" : "GB", "timezone" : "Europe/London", "population" : 22000, "location" : { "latitude" : 51.52124, "longitude" : -0.11347 } }
{ "_id" : ObjectId("541e7dc1051a80368833d486"), "name" : "London", "country" : "GB", "timezone" : "Europe/London", "population" : 7556900,"location" : { "latitude" : 51.50853, "longitude" : -0.12574 } }
{ "_id" : ObjectId("541e7dc1051a80368833d6b0"), "name" : "Farnham Royal", "country" : "GB", "timezone" : "Europe/London", "population" : 6187, "location" : { "latitude" : 51.53333, "longitude" : -0.61667 } }
{ "_id" : ObjectId("541e7dc1051a80368833d310"), "name" : "Potters Bar", "country" : "GB", "timezone" : "Europe/London", "population" : 22192, "location" : { "latitude" : 51.68333, "longitude" : -0.16667 } }
{ "_id" : ObjectId("541e7dc1051a80368833da43"), "name" : "Gerrards Cross", "country" : "GB", "timezone" : "Europe/London", "population" : 7342, "location" : { "latitude" : 51.5895, "longitude" : -0.55043 } }
{ "_id" : ObjectId("541e7dc1051a80368833d2f4"), "name" : "Radlett", "country" : "GB", "timezone" : "Europe/London", "population" : 8213, "location" : { "latitude" : 51.68333, "longitude" : -0.31667 } }
{ "_id" : ObjectId("541e7dc1051a80368833d943"), "name" : "Beaconsfield", "country" : "GB", "timezone" : "Europe/London", "population" : 12566, "location" : { "latitude" : 51.6, "longitude" : -0.63333 } }
{ "_id" : ObjectId("541e7dc1051a80368833d0cc"), "name" : "Windlesham", "country" : "GB", "timezone" : "Europe/London", "population" : 4194, "location" : { "latitude" : 51.36667, "longitude" : -0.65 } }
{ "_id" : ObjectId("541e7dc1051a80368833d9c7"), "name" : "Amersham", "country" : "GB", "timezone" : "Europe/London", "population" : 21731, "location" : { "latitude" : 51.66667, "longitude" : -0.61667 } }
{ "_id" : ObjectId("541e7dc1051a80368833d371"), "name" : "Ottershaw", "country" : "GB", "timezone" : "Europe/London", "population" : 3451, "location" : { "latitude" : 51.35, "longitude" : -0.53333 } }
{ "_id" : ObjectId("541e7dc1051a80368833d831"), "name" : "Chalfont Saint Peter", "country" : "GB", "timezone" : "Europe/London", "population" : 20059, "location" : { "latitude" : 51.60885, "longitude" : -0.55618 } }
{ "_id" : ObjectId("541e7dc1051a80368833d37a"), "name" : "Old Windsor", "country" : "GB", "timezone" : "Europe/London", "population" : 7168, "location" : { "latitude" : 51.45, "longitude" : -0.58333 } }
{ "_id" : ObjectId("541e7dc1051a80368833d832"), "name" : "Chalfont Saint Giles", "country" : "GB", "timezone" : "Europe/London", "population" : 4815, "location" : { "latitude" : 51.63184, "longitude" : -0.57026 } }
{ "_id" : ObjectId("541e7dc1051a80368833d0cb"), "name" : "Windsor", "country" : "GB", "timezone" : "Europe/London", "population" : 28324, "location" : { "latitude" : 51.48333, "longitude" : -0.6 } }
{ "_id" : ObjectId("541e7dc1051a80368833da53"), "name" : "Amersham on the Hill", "country" : "GB", "timezone" : "Europe/London", "population" : 17719, "location" : { "latitude" : 51.67468, "longitude" : -0.60742 } }
{ "_id" : ObjectId("541e7dc1051a80368833d228"), "name" : "Slough", "country" : "GB", "timezone" : "Europe/London", "population" : 134072, "location" : { "latitude" : 51.5, "longitude" : -0.58333 } }
{ "_id" : ObjectId("541e7dc1051a80368833d75a"), "name" : "Denham", "country" : "GB", "timezone" : "Europe/London", "population" : 6548, "location" : { "latitude" : 51.56667, "longitude" : -0.5 } }
{ "_id" : ObjectId("541e7dc1051a80368833d250"), "name" : "Shepperton", "country" : "GB", "timezone" : "Europe/London", "population" : 10106, "location" : { "latitude" : 51.38333, "longitude" : -0.43333 } }
{ "_id" : ObjectId("541e7dc1051a80368833d601"), "name" : "Harefield", "country" : "GB", "timezone" : "Europe/London", "population" : 6683, "location" : { "latitude" : 51.6, "longitude" : -0.48333 } }
{ "_id" : ObjectId("541e7dc1051a80368833d1bf"), "name" : "Sunbury", "country" : "GB", "timezone" : "Europe/London", "population" : 27784, "location" : { "latitude" : 51.4, "longitude" : -0.4 } }
Type "it" for more
mongos>
```

#### 2. 
6つのサーバーを起動してみよう。2つのレプリカセットに3つのサーバーがあり、レプリカセットはいずれもシャードである。設定サーバーとmongosを起動してみよう。それらに対して、GridFSを実行してみよう。

```bash
$ mkdir data
$ cd data
$ mkdir mongod1{a,b,c} mongod2{a,b,c} mongo{c,s}
$ mongod --shardsvr --replSet rs1 --dbpath ./mongod1a --port 27011 --noprealloc --smallfiles --nojournal
$ mongod --shardsvr --replSet rs1 --dbpath ./mongod1b --port 27012 --noprealloc --smallfiles --nojournal
$ mongod --shardsvr --replSet rs1 --dbpath ./mongod1c --port 27013 --noprealloc --smallfiles --nojournal
$ mongod --shardsvr --replSet rs2 --dbpath ./mongod2a --port 27021 --noprealloc --smallfiles --nojournal
$ mongod --shardsvr --replSet rs2 --dbpath ./mongod2b --port 27022 --noprealloc --smallfiles --nojournal
$ mongod --shardsvr --replSet rs2 --dbpath ./mongod2c --port 27023 --noprealloc --smallfiles --nojournal
$ mongod --configsvr --dbpath ./mongoc --port 27018 --nojournal
$ mongos --configdb localhost:27018 --chunkSize 1 --port 27017
$ mongo localhost:27011
> rs.initiate({_id: "rs1", members: [
...   {_id: 1, host: "localhost:27011"},
...   {_id: 2, host: "localhost:27012"},
...   {_id: 3, host: "localhost:27013"}
... ]})
{
        "info" : "Config now saved locally.  Should come online in about a minute.",
        "ok" : 1
}
> exit
bye
$ mongo localhost:27021
> rs.initiate({_id: "rs2", members: [
...   {_id: 1, host: "localhost:27021"},
...   {_id: 2, host: "localhost:27022"},
...   {_id: 3, host: "localhost:27023"}
... ]})
{
        "info" : "Config now saved locally.  Should come online in about a minute.",
        "ok" : 1
}
> exit
bye
$ mongo admin
mongos> sh.addShard("rs1/localhost:27011,localhost:27012,localhost:27013")
{ "shardAdded" : "rs1", "ok" : 1 }
mongos> sh.addShard("rs2/localhost:27021,localhost:27022,localhost:27023")
{ "shardAdded" : "rs2", "ok" : 1 }
mongos> sh.enableSharding("test")
{ "ok" : 1 }
mongos> sh.shardCollection("test.cities", {name: 1})
{ "collectionsharded" : "test.cities", "ok" : 1 }
mongos> exit
bye
$ cd ~/code/mongo
$ mongoimport --db test --collection cities --type json mongo_cities1000.json
$ mongo
MongoDB shell version: 2.6.4
connecting to: test
mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "version" : 4,
        "minCompatibleVersion" : 4,
        "currentVersion" : 5,
        "clusterId" : ObjectId("541e9966c4aeff3a3ef20a54")
}
  shards:
        {  "_id" : "rs1",  "host" : "rs1/localhost:27011,localhost:27012,localhost:27013" }
        {  "_id" : "rs2",  "host" : "rs2/localhost:27021,localhost:27022,localhost:27023" }
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
        {  "_id" : "test",  "partitioned" : true,  "primary" : "rs1" }
                test.cities
                        shard key: { "name" : 1 }
                        chunks:
                                rs2     8
                                rs1     17
                        too many chunks to print, use verbose if you want to force print

mongos> exit
bye
$ mongofiles put distinct_digits.js
connected to: 127.0.0.1
added file: { _id: ObjectId('541ea0bc7926af426fbf6bfa'), filename: "distinct_digits.js", chunkSize: 261120, uploadDate: new Date(1411293372268), md5: "b2ff3a355d0c6532933c60fe2a0d0592", length: 767 }
done!
$ mongofiles list
connected to: 127.0.0.1
distinct_digits.js      767
```