MongoDBでゆるふわDB体験

第6回レプリケーション+シャーディングでMongoDBをもっと使いこなす

概要

本連載では第4回ではレプリケーション、第5回ではシャーディングについて説明してきましたが、今回はレプリケーションとシャーディングを組み合わせた構成について紹介します。この構成を取ることにより、データを冗長化させつつも、分割して配置することができるため、可用性と読み取り性能を両方向上させることができます。

前回のシャーディングの構成では、単一のmongodをシャーディングサーバに割り当てていましたので、その1つのmongodが障害になると、シャーディングの機能が停止してしまうという問題がありました図1参照⁠⁠。

図1
図1

これを解決するために、シャーディングサーバに単一のmongodではなくレプリカセットを割り当てます。これによりレプリカセット内のmongodに障害が発生してもシャーディングの機能が停止しない構成にすることができます図2参照⁠⁠。

図2
図2

さらに信頼性を高めたい場合は、configサーバを冗長化することができます。configサーバの冗長化は、レプリカセットの非同期レプリケーションとは違い、2フェーズコミットによる同期レプリケーションになります。詳しくはdocs.mongodb.org/manual/administration/sharded-clusters/#config-servers>公式マニュアルを参照ください。

続いて、実際にレプリケーションとシャーディングを組み合わせた構成を作っていきます。

実際に構築する

構築する構成

今回は4台のサーバを使ってレプリカセットとシャーディングを組み合わせた構成を作ります。具体的には、3つのレプリカセットrs0,rs1,rs2をシャードサーバに割り当て、シャーディングを行います。また、レプリカセットを構成するmongodはそれぞれ別のサーバ(サーバA、サーバB、サーバC)上で動作させます。さらにサーバごとにプライマリが1つになるようにします。

configサーバとmongosサーバはフロントサーバ上で動作させます。configサーバの冗長化は行いません。ですので今回は4つのサーバを用います。

図3
図3

4つのサーバが用意できない場合は、1つのサーバで作ることも可能です。全てポートが分かれているためです。その場合、各サーバのIPアドレスを設定する箇所を変更してください。

準備

まず全てのサーバに同じバージョンのMongoDBをダウンロードし展開しておきます。また、dataディレクトリとlogディレクトリを作成します。

全サーバでの作業
cd (MongoDBインストールディレクトリ)
mkdir data log

レプリカセットの作成

最初に、レプリカセットrs0を作ります。各サーバで以下のコマンドを実行し、mongodを起動してください。ポイントは--replSetオプションでレプリカセットを指定する点です。

サーバAでの作業
mkdir  data/node0a
bin/mongod --replSet rs0 --port 30000 --dbpath=data/node0a --logpath=log/node0a --nojournal --fork
サーバBでの作業
mkdir data/node0b
bin/mongod --replSet rs0 --port 30001 --dbpath=data/node0b --logpath=log/node0b --nojournal --fork
サーバCでの作業
mkdir data/node0c
bin/mongod --replSet rs0 --port 30002 --dbpath=data/node0c --logpath=log/node0c --nojournal --fork

[注意]

1つのmongodでジャーナルファイルに3G程度のディスク容量が必要です。試しに使うだけでディスク容量を節約したい場合は、ジャーナルは必要ないので、--nojournalオプションを追加してジャーナルの機能を止めてください。

次にサーバAにてレプリケーションの設定を行います。サーバAにて設定を行うのはサーバAをプライマリにするためです。

サーバAでの作業
bin/mongo localhost:30000 # ←mongoシェルに接続
サーバAのMongoシェルで作業
cfg = {
 _id : "rs0", 
 members : [ 
  { _id : 0, host : "(サーバAのIP):30000" }, 
  { _id : 1, host : "(サーバBのIP):30001" }, 
  { _id : 2, host : "(サーバCのIP):30002" } ] } 
rs.initiate(cfg)  # → "ok" : 1 と表示されれば成功
rs.status()       # → 時間がたつとサーバAがプライマリになります

[注意]

IPアドレスは、1つのレプリカセット内でループバックインターフェース(localhostや127.0.0.1)とネットワークインターフェースを混在させることはできません。つまり(サーバAのIP)の部分は、サーバAのネットワークインターフェースのIPアドレスにしてください。

続いて、レプリカセットrs1を同様に作成していきます。

まずmongodを起動します。

サーバAでの作業
mkdir data/node1a
bin/mongod --replSet rs1 --port 30010 --dbpath=data/node1a --logpath=log/node1a --nojournal --fork
サーバBでの作業
mkdir data/node1b
bin/mongod --replSet rs1 --port 30011 --dbpath=data/node1b --logpath=log/node1b --nojournal --fork
サーバCでの作業
mkdir data/node1c
bin/mongod --replSet rs1 --port 30012 --dbpath=data/node1c --logpath=log/node1c --nojournal --fork

続いてレプリケーションを設定しますが、今度はサーバBをプライマリにするためサーバBのmongoシェルで作業します。

サーバBでの作業
bin/mongo localhost:30011
サーバBのMongoシェルでの作業
cfg = {
 _id : "rs1", 
 members : [ 
  { _id : 0, host : "(サーバAのIP):30010" }, 
  { _id : 1, host : "(サーバBのIP):30011" }, 
  { _id : 2, host : "(サーバCのIP):30012" } ] } 
rs.initiate(cfg)  # → "ok" : 1 と表示されれば成功
rs.status()       # → 時間がたつとサーバBがプライマリになります

最後に、レプリカセットrs2を同様に作成していきます。

まずmongodを起動します。

サーバAでの作業
mkdir data/node2a
bin/mongod --replSet rs2 --port 30020 --dbpath=data/node2a --logpath=log/node2a --nojournal --fork
サーバBでの作業
mkdir data/node2b
bin/mongod --replSet rs2 --port 30021 --dbpath=data/node2b --logpath=log/node2b --nojournal --fork
サーバCでの作業
mkdir data/node2c
bin/mongod --replSet rs2 --port 30022 --dbpath=data/node2c --logpath=log/node2c --nojournal --fork

続いてレプリケーションを設定を行いますが、今度はサーバCにて作業を実施します。

サーバCでの作業
bin/mongo localhost:30022
サーバCのMongoシェルでの作業
cfg = {
 _id : "rs2", 
 members : [ 
  { _id : 0, host : "(サーバAのIP):30020" }, 
  { _id : 1, host : "(サーバBのIP):30021" }, 
  { _id : 2, host : "(サーバCのIP):30022" } ] }
rs.initiate(cfg)  # → "ok" : 1 と表示されれば成功
rs.status()       # → 時間がたつとサーバCがプライマリになります

シャーディングの設定

いよいよ、レプリカセットをシャードサーバに割り当てて、シャーディングを構成していきます。まずフロントサーバにて、configサーバとmongosを起動します。この手順は前回の記事の手順と同じですので、詳しい解説は前回をご覧ください。

フロントサーバでの作業
mkdir data/config
bin/mongod --configsvr --port 20001 --dbpath data/config --logpath=log/config --nojournal --fork
bin/mongos --configdb (フロントサーバのIP):20001 --port 20000 --logpath=log/mongos --chunkSize 1 --fork

[注意]

チャンクサイズを1に設定しているのは、シャーディングの動作を確認しやすくするためです。実運用ではシステムの要件に合わせた値に設定してください。

続いて、mongosサーバにmongoシェルで接続して、シャードサーバを追加してきます。mongodを単体で登録する場合とは違い、レプリカセットを登録します。

フロントサーバでの作業
bin/mongo localhost:20000/admin
フロントサーバのmongoシェルでの作業
sh.addShard("rs0/(サーバAのIP):30000,(サーバBのIP):30001,(サーバCのIP):30002")   
{ "shardAdded" : "rs0", "ok" : 1 }

sh.addShard("rs1/(サーバAのIP):30010,(サーバBのIP):30011,(サーバCのIP):30012")   
{ "shardAdded" : "rs1", "ok" : 1 }

sh.addShard("rs2/(サーバAのIP):30020,(サーバBのIP):30021,(サーバCのIP):30022")   
{ "shardAdded" : "rs2", "ok" : 1 }
確認
sh.status()
--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
        {  "_id" : "rs0",  "host" : "rs0/(サーバAのIP):30000,(サーバBのIP):30001,(サーバCのIP):30002" }
        {  "_id" : "rs1",  "host" : "rs1/(サーバAのIP):30010,(サーバBのIP):30011,(サーバCのIP):30012" }
        {  "_id" : "rs2",  "host" : "rs2/(サーバAのIP):30020,(サーバBのIP):30021,(サーバCのIP):30022" }
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }

最後にデータを投入し、そのデータに対してシャーディングを有効にします。この手順も前回の記事の手順と同じです。

フロントサーバのmongoシェルでの作業
use logdb
データ投入
for(var i=1; i<=100000; i++) db.logs.insert({"uid":i, "value":Math.floor(Math.random()*100000+1)})
データ投入の確認
db.logs.count();
インデックス作成
db.logs.ensureIndex( { uid : 1 } );
admin DBに切り替え
use admin
シャーディングの有効化
sh.enableSharding("logdb")
sh.shardCollection("logdb.logs" , { uid : 1 })
確認
sh.status()

次は実際にサーバ障害を発生させ、挙動を観察します。

障害時の動作実験

これまでの作業で、シャーディング構成はできていますが、ここでは障害を発生させ、処理を続けられるか確認します。

図4
図4

まず、以下のコマンドによりクライアント相当の簡単なプログラムを動かします。

フロントサーバでの作業
watch "bin/mongo localhost:20000/logdb --eval 'db.logs.count() ;'"

このコマンドは2秒に1回logdbデータベースのlogsコレクション数をカウントしているので、ずっと10000が表示されているはずです。このコマンドはmongoシェルの--evalオプションを用いて、DBの中身をカウントするコマンドを実行し、それをwatchコマンドにより2秒に1回実行しています。

この状態でサーバCの障害を想定して、サーバCのmongodをすべて落としてください(より本格的にやりたい人はサーバCの電源を落としても良いですw⁠⁠。

サーバCでの作業
killall mongod

するとどうでしょうか? フロントサーバのdb.logs.count()コマンドは、フェイルオーバー中は失敗するもの、数秒のうちにフェイルオーバーが完了し、その後は成功すると思います。現時点でのシステムの状態を確認してみましょう。

まずシャーディングの状況ですが、以下のコマンドで確認します。

フロントサーバでの作業
bin/mongo localhost:20000/admin
sh.status()

見てわかるように、シャーディングの状況は何ひとつ変わっていません。mongosからはレプリカセットしか管理していないため、各mongodの状態までは管理していません。

続いてレプリカセットの状況確認です。

サーバAでの作業
bin/mongo localhost:30020  # →rs2のmongodにログイン
rs.status()

おそらく、サーバAかサーバBのどちらかがプライマリになっていると思います。また、サーバCは"(not reachable/healthy)"と表示されていると思います。

次回のテーマ

今回はレプリケーションとシャーディングを組み合わせた構成について紹介しました。本構成を用いることで、可用性と読み取り性能を両方向上させることができます。

次回の記事ではMonogoDBでサイズの大きなファイルを扱う機能である、GridFSについて紹介する予定です。

おすすめ記事

記事・ニュース一覧