MongoDB では、書き込みをデータベースに永続化させるための方法を選べます。 この方法のことを 書き込み確認 (Write Concern) と呼びます。 あらゆるエラーを無視することもできるし、 特定のサーバーへの書き込みを確認するまで書き込み完了と見なさないようにもできます。
書き込み操作 (MongoCollection::insert() や MongoCollection::update()、 MongoCollection::remove()) で Write Concern オプション ("w") を指定すると、ドライバはクエリを MongoDB に送信してから getLastError コマンド (GLE) に Write Concern オプションを付けて実行します。 これは Write Concern 条件を満たすかタイムアウト ("wtimeout" で指定でき、デフォルトは 10000 ミリ秒) に達するまでブロックします。
getLastError コマンドがタイムアウトしたとしても、 ほとんどの場合はデータはプライマリサーバーに書き込まれており、 そのうちにすべてのセカンダリにレプリケートされるでしょう。
タイムアウトの発生要因として一番ありがちなのは、 書き込み確認を指定するときに現在使えるサーバー数よりも大きなサーバー数を指定したというものです。
書き込み確認を使っているときにレプリカセットのフェイルオーバーが発生すると、 ドライバは自動的にプライマリとの接続を切断して例外をスローし、 次の操作のときに新しいプライマリを探そうとします (新しいプライマリでもう一度操作をやり直すかそうしないかは、 アプリケーション側で決めないといけません)。
書き込み確認をしない (w=0) の場合にレプリカセットのフェイルオーバーが発生すると、 ドライバ側でそれを知る手段がありません。そのため、何も気づかずに操作を続け、 結果的に書き込みは失敗してしまいます。
MongoClient でのデフォルトの書き込み確認は 1 になっており、書き込み完了を確認するようになっています。
書き込み確認 | 意味 | 説明 |
---|---|---|
w=0 | 確認しない | 書き込みの後に GLE を呼びません。つまり、何もチェックしません ("投げっぱなし/fire and forget") |
w=1 | 確認する | サーバー (レプリカセット構成の場合はプライマリ) に書き込まれたことを確認します。 |
w=N | レプリカセットで確認する | プライマリに書き込まれ、そしてそれが N-1 のセカンダリにレプリケートされたことを確認します。 |
w=majority | 過半数で確認する | レプリカセットの過半数 (プライマリを含む) に書き込まれたことを確認します。この文字列は予約語になっています。 |
w=<tag set> | レプリカセットのタグセットで確認する | タグセットで指定されたメンバーに書き込まれたことを確認します。 |
j=true | ジャーナルする | プライマリに書き込まれ、そしてその記録がディスクに書き出されたことを確認します。 |
書き込みを行うメソッド (MongoCollection::insert()、 MongoCollection::update()、 MongoCollection::remove() そして MongoCollection::batchInsert()) はどれも、オプションの引数で MongoDB サーバーに送るオプションを設定できます。 このオプション配列を使って、次の例のように書き込み確認を指定できます。
例1 書き込み操作での書き込み確認の指定
<?php
// w=0 を登録用に設定します
$collection->insert($someDoc, array("w" => 0));
// w=majority を更新用に設定します
$collection->update($someDoc, $someUpdates, array("w" => "majority"));
// w=5 と j=true を削除用に設定します
$collection->update($someDoc, array("w" => 5, "j" => true));
// w="AllDCs" を一括登録用に設定します
$collection->update(array($someDoc1, $someDoc2), array("w" => "AllDCs"));
?>
書き込み確認を操作ごとに option 引数で設定するだけでなく、 デフォルトの書き込み確認方法を設定することもできます。
最初の方法は、接続文字列 を使うものです。接続文字列には journal や w そして wTimeoutMS というオプションを指定できます。
例2 接続文字列での書き込み確認の指定
<?php
$m = new MongoClient("mongodb://localhost/?journal=true&w=majority&wTimeoutMS=20000");
?>
ドライバのバージョン 1.5 以降では、 MongoDB::setWriteConcern() や MongoCollection::setWriteConcern() を呼んでデフォルトの書き込み確認方法を設定できるようになりました。その MongoDB や MongoCollection から作るすべての操作のデフォルトを設定できます。
例3 MongoDB::setWriteConcern および MongoCollection::setWriteConcern
<?php
$m = new MongoClient("mongodb://localhost/");
$d = $m->demoDb;
$c = $d->demoCollection;
// データベースオブジェクトで w=3 として、タイムアウトを 25000ms にします
$d->setWriteConcern(3, 25000);
// コレクションオブジェクトで w=majority として、タイムアウトは変更しません
$c->setWriteConcern("majority");
?>
サーバー側で書き込み確認をしないようにすると、書き込み操作が極めて高速になります。 ただし、書き込みが本当に成功したのかどうかは確認できません。 いろんな理由で書き込みが失敗する可能性があります。 ネットワーク障害、データベースサーバーがダウンしている、 書き込み操作自体が無効 (system コレクションに書き込もうとしたり、 キーの重複エラーになったり) などが考えられます。
開発時には常に、確認付き書き込みを使うべきです (構文エラーや不正な操作、キーの重複などのうっかりミスを防ぐためです)。 実運用のときには、「あまり重要ではない」データについては確認なしで書き込んでもいいでしょう。 何が重要で何が重要でないかはアプリケーションによって異なりますが、一般的に 「重要でない」とみなされるのは、ユーザーが生成したのではない自動生成されるデータ (クリックトラッキング情報や GPS の位置情報など) です。 これらは秒間何千件ものレコードを受け取ることになります。
一連の確認なし書き込みの最期は、必ず確認付き書き込みで終えることを強く推奨します。 そのせいでパフォーマンスが大きく落ちることはないし、 何かエラーがあればそれを捕捉できるようになります。
例4 確認なしの書き込みの後に確認付き書き込みを続ける例
<?php
$collection->insert($someDoc, array("w" => 0));
$collection->update($criteria, $newObj, array("w" => 0));
$collection->insert($somethingElse, array("w" => 0));
try {
$collection->remove($something, array("w" => 1));
} catch(MongoCursorException $e) {
/* 例外処理 */
/* ここでは、find() クエリを使って
$somethingElse や $someDoc で生成された ID を調べ、
実際にデータベースに書き込まれているかどうかを確認しないといけません。
これで、一連の処理でいったい何が起こったのかがわかります。 */
}
?>
最後の書き込みで例外が発生すれば、データベースに何か問題が発生したことがわかります。
この書き込みの場合、データベースが書き込み操作を受け付けたことを確認するまでは、 書き込みが成功したとはみなしません。書き込みが失敗した場合は MongoCursorException をスローして失敗の内容を説明します。 MongoClient のデフォルト設定は、確認付き書き込み (w=1) です。
レプリカセットの中で何台のメンバーに書き込み終える (レプリケートされる) まで書き込み完了とみなさないかを指定することもできます。
例5 確認付き書き込み
<?php
// プライマリに書き込まれたかどうかだけを確認します
$collection->insert($doc, array("w" => 1));
// プライマリの他に、レプリカセットのどれか一つのメンバーに書き込まれたことを確認します
$collection->insert($doc, array("w" => 2));
// プライマリの他に、レプリカセットのどれか六つのメンバーに書き込まれたことを確認します
// (まさかこんな設定をすることはないでしょう)
$collection->insert($doc, array("w" => 7));
?>
確認付き書き込みの設定には十分注意しましょう。もしレプリカセットのメンバー数が 5 の場合に確認付き書き込みの値を 4 にすると、レプリカセットのどれか一つのメンバーがメンテナンス中だったり 一時的にネットワーク障害が発生した場合などに、書き込み操作がブロックされてしまいます。
確認付き書き込みの設定に文字列を渡すと、特別な意味になります (レプリカセットのタグセットとして扱います)。数字を指定するつもりで文字列を使ってしまう (array("w" => "1") など) ことが ない ように気をつけましょう。これはタグセット名として扱われてしまいます。
書き込み確認オプションとして、特別な文字列 majority を指定することができます。これは書き込み用におすすめの設定で、 大災害に巻き込まれないようにするために必須です。 この設定にすると、レプリカセットの過半数に書き込みが行き渡るまで成功したとみなさないので、 ありがちなあらゆる障害に対応できるようになります。
例6 過半数の確認付き書き込み
<?php
$collection->insert($someDoc, array("w" => "majority"));
?>
レプリカセットへの接続時のデフォルトの書き込み確認は、 プライマリへの書き込みさえできていればよいというものです。 しかし、プライマリへの書き込みがディスクに反映されるまでには、 数百ミリ秒単位の遅延があります。 書き込みがディスクに記録されるまで成功とみなさないようにするには、 j オプションを指定します。
例7 確認付きジャーナル書き込み
ジャーナルのフラッシュを強制します。
<?php
$options = array(
"w" => 1,
"j" => true,
);
try {
$collection->insert($document, $options);
} catch(MongoCursorException $e) {
/* 例外処理 */
}
?>
バージョン | 説明 |
---|---|
1.3.0 | MongoClient が導入され、デフォルトで 確認付き 書き込みをするようになりました。デフォルトで確認なしの書き込みをする Mongo は非推奨となりました。 writes. |
1.3.0 | 書き込みオプション "safe" が非推奨になりました。 新しい MongoClient クラスでは使えません。 かわりに "w" オプションを使います。 |