はじめに
この記事では、NoSQLのデータベースとして代表的なMongoDBの特徴と簡単な使い方を紹介したいと思います。
MongoDBの特徴
以下の公式マニュアルを読むと、MongoDB には以下のような特徴があります。
Key Features from "Introduction to MongoDB"
- High Performance
MongoDBはディスクI/Oを軽減するデータモデルに基づいており、高いパフォーマンスが期待できます。また、ドキュメント型や配列型のデータでもインデックスを活用することで高速処理が実現可能です。
- Rich Query Language
MongoDBには豊富なクエリ文法が用意されています。標準のSQL規格とは違いますが、基本的なREAD/WRITE処理(CRUD)を行うことができます。また、集合関数、全文検索、地理情報なども扱えます。 -
High Availability
MySQLなどと同様にレプリケーション機能が実装されていて、可用性の面でも問題は無いと考えられます。自動フェイルオーバー機能も使えて、本番環境でも安心して使える印象があります。 -
Horizontal Scalability
高負荷な環境に対応するため、MongoDBをスケールすることも可能です。特にデータをクラスタ内の各マシンに分散配置するシャーディング機能は大きいと思います。MongoDB 3.4からは、ゾーンという機能も追加され、より使いやすくなりました。 -
Support for Multiple Storage Engines
MongoDBはストレージエンジンを選択することができるようですが、基本的には新しいWiredTigerで良いと思います。また、ストレージエンジン以外にもAPIなどの拡張も活発です。
MongoDBを使ってみる
「百聞は一見に如かず」ではないですが、やはり未知のソフトウェアを理解するには実際に動かしてみるのがベストだと私は思います。そこで、早速インストールから試してみましょう。
1. インストール・起動
まずはインストールするパッケージを選択します。様々なプログラム言語のエディションが用意されていますが、今回は「MongoDB Shell (mongo) Edition」を選択します。バージョンも今回は最新版とします。
次にプラットフォームを選択します。MongoDBがサポートするプラットフォームは以下のページに記載されています。今回は CentOS 7.3 を使用します。
https://docs.mongodb.com/manual/installation/#supported-platforms
ここからはインストールガイドの内容に沿って作業を進めていきます。
1-1. yumリポジトリの設定
今回は初めてなのでソースのビルドなどは避け、yumで手軽にインストールします。そのためにまずはMongoDBのyumリポジトリ(mongodb-org-3.4.repo)を設定します。
1 2 3 4 5 6 7 8 |
[root@test-server ~]# cat << EOT >> /etc/yum.repos.d/mongodb-org-3.4.repo [mongodb-org-3.4] name=MongoDB Repository baseurl=https://repo.mongodb.org/yum/redhat/7/mongodb-org/3.4/x86_64/ gpgcheck=1 enabled=1 gpgkey=https://www.mongodb.org/static/pgp/server-3.4.asc EOT |
1-2. MongoDBのインストール
yumコマンドでMongoDB、および関連パッケージをまとめてインストールします。
1 2 3 4 5 6 7 8 9 10 |
[root@test-server ~]# yum install -y mongodb-org ... (省略)... Installed: mongodb-org.x86_64 0:3.4.9-1.el7 Dependency Installed: mongodb-org-mongos.x86_64 0:3.4.9-1.el7 mongodb-org-server.x86_64 0:3.4.9-1.el7 mongodb-org-shell.x86_64 0:3.4.9-1.el7 mongodb-org-tools.x86_64 0:3.4.9-1.el7 Complete! |
1-3. SELinuxの設定変更
MongoDBが正常に動作できるようSELinuxの設定を変更する必要があります。今回は検証なので、SELinux自体を無効にしてしまいます。
1 2 3 |
[root@test-server ~]# setenforce 0 [root@test-server ~]# getenforce Permissive |
1-4. MongoDBを起動・ログイン
MongoDBを起動します。その後ログインします。
1 2 3 4 5 6 7 8 9 10 11 |
[root@test-server ~]# systemctl start mongod [root@test-server ~]# ps aux | grep mongod mongod 7552 2.6 7.0 971684 35484 ? Sl 18:05 0:00 /usr/bin/mongod -f /etc/mongod.conf root 7575 0.0 0.1 112656 964 pts/0 S+ 18:05 0:00 grep --color=auto mongod [root@test-server ~]# mongo MongoDB shell version v3.4.9 connecting to: mongodb://127.0.0.1:27017 ... (省略)... 2017-10-26T10:37:37.021+0900 I CONTROL [initandlisten] > |
2. データ操作
続けて、データ操作の基本となるクエリ(CRUD)を試してみたいと思います。ただ、その前にMongoDBのデータ形式について理解しておく必要があります。
例えば、MySQLでusersテーブルを作成し1件のレコードをINSERTする場合は、以下のようになると思います。
1 2 |
mysql> CREATE TABLE users (name VACHAR(10), age TINYINT, hobby TEXT); mysql> INSERT INTO users ("John", 30, "baseball"); |
それに対して、MongoDBで同様の処理を行おうとすると、以下のようになります。
1 2 3 4 5 6 7 |
db.users.insertOne( { name : "John", age : 30, hobby : "baseball" } ); |
上記の2例を見比べると以下のような違いがあることに気が付きます。これがMongoDBの特徴である「スキーマレス」「ドキュメント型」の所以です。
- CREATE TABLEをせず、いきなりデータのINSERTができる
- カラムの定義が存在しない(文字列 or 数値 を自動判別)
- Key-Value 形式で単純なデータ構造
それでは、MongoDBのデータ操作を実際に試してみましょう。ここでもマニュアルを参考にしながら進めていきます。
2-1. Create Operations
まずはデータを新規作成する操作です。上記の例のように、MySQLで言えば「CREATE」「INSERT」を一つにまとめたものとなります。
最もシンプルなものは、上で実行したVer 3.2 で追加された “db.collection.insertOne()”です。別のユーザも追加してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
> db.users.insertOne( { name : "Mary", age : 25, hobby : ["travel", "tennis"], birth : "1991-06-23" } ); { "acknowledged" : true, "insertedId" : ObjectId("59f184c435f7b7c901a0cbc5") } > |
スキーマレスなので、最初の John のレコードとは全く別のカラムだったり、配列データを入れても何も問題はありません。ユーザ登録の時に未入力の欄があったり、逆に想定とは違う内容が書いてあったりしてもエラーが出ずに対応できるため、これは便利そうですね。
なお、複数のデータを一度に作成したい場合は、db.collection.insertMany() を使うのが良さそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
> db.users.insertMany( [ { name : "Ken", age : 18, birth : "2010-30-01" }, { age : 25, name : "Jean", favorite : ["beef"], } ] ); { "acknowledged" : true, "insertedIds" : [ ObjectId("59f195df35f7b7c901a0cbc7"), ObjectId("59f195df35f7b7c901a0cbc8") ] } |
2-2. Read Operations
MongoDBにはSELECT文がありません。データの読み込みは、db.collection.find() を使用します。ためしに格納した全データを読み取りしてみましょう。
1 2 3 4 5 |
> db.users.find(); { "_id" : ObjectId("59f184c435f7b7c901a0cbc5"), "name" : "Mary", "age" : 25, "hobby" : [ "travel", "tennis" ], "birth" : "1991-06-23" } { "_id" : ObjectId("59f18e3a35f7b7c901a0cbc6"), "name" : "John", "age" : 30, "hobby" : "baseball" } { "_id" : ObjectId("59f195df35f7b7c901a0cbc7"), "name" : "Ken", "age" : 18, "birth" : "2010-30-01" } { "_id" : ObjectId("59f195df35f7b7c901a0cbc8"), "age" : 25, "name" : "Jean", "favorite" : [ "beef" ] } |
limit条件を付けて、取得するレコード数を制限することもできます。
1 2 3 |
> db.users.find().limit(2); { "_id" : ObjectId("59f184c435f7b7c901a0cbc5"), "name" : "Mary", "age" : 25, "hobby" : [ "travel", "tennis" ], "birth" : "1991-06-23" } { "_id" : ObjectId("59f18e3a35f7b7c901a0cbc6"), "name" : "John", "age" : 30, "hobby" : "baseball" } |
また、データの読み取りには絞り込み条件が重要です。MongoDBの場合は、以下のように指定します。例えば、年齢が25歳のデータを抽出してみましょう。
1 2 3 4 5 |
> db.users.find( { age : 25} ); { "_id" : ObjectId("59f184c435f7b7c901a0cbc5"), "name" : "Mary", "age" : 25, "hobby" : [ "travel", "tennis" ], "birth" : "1991-06-23" } { "_id" : ObjectId("59f195df35f7b7c901a0cbc8"), "age" : 25, "name" : "Jean", "favorite" : [ "beef" ] } |
複数の絞り込み条件を指定する場合は、” , ” で併記します。
また値の範囲指定(>、<)を行う場合は、” $gt ” / ” $lt “(greater than / less than)を使用します。
1 2 3 4 |
> db.users.find( { name : "Mary", age : { $lt : 30 } } ); { "_id" : ObjectId("59f184c435f7b7c901a0cbc5"), "name" : "Mary", "age" : 25, "hobby" : [ "travel", "tennis" ], "birth" : "1991-06-23" } |
このように既存データの構造を意識せずに、欲しい情報を簡単に抽出することができます。
2-3. Update Operations
次は更新処理です。使用できるコマンドは updateOne() / updateMany() / replaceOne() の3種類です。
試しに26歳以上の人物の年齢を「18歳」に変更してみましょう。
1 2 3 4 5 6 7 8 9 10 |
> db.users.updateMany( { age : { $gt : 25 } } , { $set : { age : 18 } } ); { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } > db.users.find(); { "_id" : ObjectId("59f184c435f7b7c901a0cbc5"), "name" : "Mary", "age" : 25, "hobby" : [ "travel", "tennis" ], "birth" : "1991-06-23" } { "_id" : ObjectId("59f18e3a35f7b7c901a0cbc6"), "name" : "John", "age" : 18, "hobby" : "baseball" } { "_id" : ObjectId("59f195df35f7b7c901a0cbc7"), "name" : "Ken", "age" : 18, "birth" : "2010-30-01" } { "_id" : ObjectId("59f195df35f7b7c901a0cbc8"), "age" : 25, "name" : "Jean", "favorite" : [ "beef" ] } |
MongoDBの更新クエリは、最初に対象となるレコードの絞り込み条件を指定し、その後変更内容を記述します。その際、 $set 句を付ける必要があります。
「30歳」だった John が「18歳」に変更されているのが分かります。
2-4. Delete Operations
最後はDELETEです。こちらもシンプルに deleteOne() / deleteMany() で実行できます。
試しに John のレコードを削除してみましょう。
1 2 3 4 5 6 7 8 |
> db.users.deleteOne( { name : "John" } ); { "acknowledged" : true, "deletedCount" : 1 } > db.users.find( { name : "John" } ); > |
3. レプリケーション設定
MySQLではすっかりお馴染みのレプリケーション機能ですが、MongoDBでも同様の機能が実装されています。
もし本番環境で MongoDB を運用する場合は恐らく必須となる機能なので、実際にレプリケーション
環境を構築してみたいと思います。
今回は上記のマニュアルに記載されている「Primary 1台 / Secondly 2台」の構成を目指します。
3-1. 環境
先ほどまで使用していたサーバも含めて、以下の3台を使用します(全て vagrant で作成したものです)。
ホスト名 | IPアドレス | 役割 |
---|---|---|
test-server | 192.168.100.10 | Primary |
test-server2 | 192.168.100.20 | Secondly-1 |
test-server3 | 192.168.100.30 | Secondly-2 |
3-2. セカンダリのセットアップ
Deploy a Replica Set for Testing and Development
ここからは上記マニュアルの手順に則って進めていきます。まずは新しく追加した2サーバにMongoDB をインストールしましょう。
1 2 3 4 5 6 7 8 9 10 11 |
[root@test-server2 ~]# cat << EOT >> /etc/yum.repos.d/mongodb-org-3.4.repo [mongodb-org-3.4] name=MongoDB Repository baseurl=https://repo.mongodb.org/yum/redhat/7/mongodb-org/3.4/x86_64/ gpgcheck=1 enabled=1 gpgkey=https://www.mongodb.org/static/pgp/server-3.4.asc EOT [root@test-server2 ~]# setenforce 0 [root@test-server2 ~]# yum install -y mongodb-org |
次にMongoDB設定ファイルを編集します。デフォルトでは /etc/mongod.conf に配置されています。
新たに追加する設定は以下の通りです。
- bindIp: 0.0.0.0
今回はテスト環境のため、0.0.0.0 を設定して全てのネットワークから接続を許可します。 - replSetName: test-cluster
レプリカセットの名称を指定します。
設定ファイルの中に「#replication:」セクションがあるので、そこに追記しましょう。なお、全ノード同じ変更内容で問題ありません。
1 2 3 4 5 6 7 8 9 10 |
[root@test-server ~]# vi /etc/mongod.conf ... # network interfaces net: port: 27017 bindIp: 0.0.0.0 ... replication: replSetName: test-cluster ... |
編集後、MongoDBを再起動(or 起動)します。
1 2 3 |
[root@test-server ~]# systemctl restart mongod or [root@test-server ~]# systemctl start mongod |
3-3. PRIMARY から SECONDLY に接続
PRIMARYとなる server1 にログインして、レプリケーション設定を行います。
1 2 3 4 5 6 7 8 9 10 |
[root@test-server ~]# mongo MongoDB shell version v3.4.10 ... > rs.initiate( { _id : "test-cluster", members: [ { _id : 0, host : "192.168.100.10:27017" } ] }) { "ok" : 1 } test-cluster:OTHER> |
続けて server2 / server3 をグループに追加します。この時点で追加を行った server1 がPRIMARYで、それ以外が SECONDLY であると自動判別されます。
1 2 3 4 |
test-cluster:OTHER> rs.add("192.168.100.20:27017") { "ok" : 1 } test-cluster:PRIMARY> rs.add("192.168.100.30:27017") { "ok" : 1 } |
3-4. レプリケーションのステータスを確認
レプリケーションのステータスは、rs.status()コマンドで確認することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
test-cluster:PRIMARY> rs.status() { "set" : "test-cluster", "date" : ISODate("2017-11-22T07:03:03.302Z"), ... "members" : [ { "_id" : 0, "name" : "192.168.100.10:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", ... }, { "_id" : 1, "name" : "192.168.100.20:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", ... }, { "_id" : 2, "name" : "192.168.100.30:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", ... } ], "ok" : 1 } |
3-5. フェイルオーバの挙動確認
PRIMARYである server1 の mongod を kill コマンドで強制終了して、フェイルオーバが適切に動作するかを確認します。
1 2 3 4 |
[root@test-server ~]# ps aux | grep mongo mongod 1091 1.3 8.2 1433988 41344 ? Sl 09:52 5:06 /usr/bin/mongod -f /etc/mongod.conf root 3562 0.0 0.1 112660 972 pts/0 S+ 16:16 0:00 grep --color=auto mongo [root@test-server ~]# kill -9 1091 |
server2でステータスを確認すると、PRIMARYが server2 に切り替わっていることが分かります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
[root@test-server2 ~]# mongo MongoDB shell version v3.4.10 ... test-cluster:PRIMARY> rs.status() { "set" : "test-cluster", "date" : ISODate("2017-11-22T07:16:56.984Z"), ... }, "members" : [ { "_id" : 0, "name" : "192.168.100.10:27017", "health" : 0, "state" : 8, "stateStr" : "(not reachable/healthy)", ... }, { "_id" : 1, "name" : "192.168.100.20:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", ... }, { "_id" : 2, "name" : "192.168.100.30:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", ... } ], "ok" : 1 } |
このため不慮の障害でサーバがダウンしても、SECONDLYを用意しておけばサービスを停止せずに MongoDB の利用が可能となります。
余談
この記事を書いている間に、 Percona Performance Blog で MongoDB の構成について取り上げられました。もし興味があればご覧になってみてください。
※ Percona社ではMongoDBのサポートも行っています
Percona Server for MongoDB