Docs Menu
Docs Home
/ /

MongoDB によるレジリエントなアプリケーションのビルド

MongoDB の機能を活用して、レプリカセットの選挙をグレースフルに処理するアプリケーション コードを作成するには、次の手順を実行する必要があります。

  • 最新のクライアントライブラリをインストールします。

  • すべてのホストを指定する接続文字列を使用します。

  • 再試行可能な書込み と 再試行可能な読み取り を使用します。

  • アプリケーションに適した majorityの書込み保証と読み取り保証を使用します。

  • アプリケーション内のエラーを処理します。

First, install the latest client library for your language from MongoDB Client Libraries. Client Libraries connect and relay queries from your application to your database. Using the latest client libraries enables the latest MongoDB features.

次に、アプリケーションに依存関係をインポートします。

Maven を使用している場合は、次のものを pom.xml 依存関係リストに追加します。

<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>

Gradle を使用している場合は、次のものを build.gradle 依存関係リストに追加します。

dependencies {
compile 'org.mongodb:mongodb-driver-sync:4.0.1'
}
// Latest 'mongodb' version installed with npm
const MongoClient = require('mongodb').MongoClient;

配置内のすべてのホストを指定する接続文字列を使用して、アプリケーションをデータベースに接続します。 配置でレプリカセットの選挙が実行され、新しいプライマリが選択された場合、配置内のすべてのホストを指定する接続文字列によって、アプリケーション ロジックなしで新しいプライマリが検出されます。

次のいずれかを使用して、配置内のすべてのホストを指定できます。

接続文字列では、オプション( retryWriteswriteConcern など)を指定することもできます。

Tip

接続文字列 の形式の詳細については、 「 MongoDBクライアント ライブラリを使用して配置に接続する 」を参照してください。

接続文字列を使用して、アプリケーション内で MongoDB クライアントをインスタンス化します。

// Create a variable for your connection string
String uri = "mongodb://[<username>:<password>@]hostname0<:port>[,hostname1:<port1>][,hostname2:<port2>][...][,hostnameN:<portN>]";
// Instantiate the MongoDB client with the URI
MongoClient client = MongoClients.create(uri);
// Create a variable for your connection string
const uri = "mongodb://[<username>:<password>@]hostname0<:port>[,hostname1:<port1>][,hostname2:<port2>][...][,hostnameN:<portN>]";
// Instantiate the MongoDB client with the URI
const client = new MongoClient(uri);

注意

MongoDBバージョン3.6 および4.2 互換クライアントライブラリ以降、 MongoDB はデフォルトで 書込みと読み取りの両方を 1 回再試行します。

再試行可能な書込みを使用して、特定の書込み操作が失敗した場合に 1 回再試行します。

書き込みを 1 回だけ 再試行する ことは、アプリケーションが正常な プライマリ ノード を一時的に見つけられない場合に、一時的なネットワークエラーやレプリカセットの選挙を処理するための最善の戦略です。 再試行が成功すると、操作全体が成功し、エラーは返されません。 操作が失敗した場合は、次の理由が原因で失敗した可能性があります。

  • 永続的なネットワークエラー、または

  • 無効なコマンド。

Tip

再試行可能な書込みを有効にする方法の詳細については、「 再試行可能な書込みの有効化 」を参照してください。

操作が失敗した場合、アプリケーションは自分自身を処理する必要があります

MongoDBバージョン3.6 および4.2 互換クライアントライブラリ以降、読み取り操作が失敗した場合、読み取り操作は 1 回自動的に再試行されます。読み取りを再試行するようにアプリケーションを構成する必要はありません。

書込み保証 (write concern) と読み取り保証 (read concern) を使用して、アプリケーションの整合性と可用性を調整できます。 懸念が深い場合は、データベース操作により強力なデータ整合性保証が必要になりますが、整合性要件を緩和すると可用性が向上します。

アプリケーションが金銭の残高を処理する場合、整合性は非常に重要です。 majorityの書込み保証と読み取り保証 を使用して、古いデータやロールバックされる可能性のあるデータからの読み取りを行わないようにできます。

あるいは、アプリケーションが 1 秒ごとに数百のセンサーからの温度データを記録する場合、最新の読み取り値を含まないデータを読み取る場合は問題が発生しない場合があります。 整合性要件を緩和すると、そのデータへのアクセスが速くなります。

接続string URI を使用してレプリカセットの 書込み保証レベル を設定できます。majority書込み保証(write concern)を使用して、データがデータベースに正常に書き込まれ、永続化されるようにします。 これは推奨のデフォルトであり、ほとんどのユースケースで十分です。

majorityなど、確認応答が必要な書込み保証を使用する場合は、そのレベルの確認応答を実現するための書込みの最大時間制限を指定することもできます。

  • すべての書き込み (write) のwtimeoutMS接続文字列パラメータ、または

  • 単一の書込み (write) 操作のwtimeoutオプション。

時間制限を使用するかどうかと、使用する値は、アプリケーションのコンテキストによって異なります。

Tip

書込み保証 (write concern) レベルの設定の詳細については、「 書込み保証 (write concern)オプション 」を参照してください。

重要

書込みに時間制限を指定せず、書込み保証 (write concern) レベルが達成できない場合、書込み (write) 操作は完了しません。

接続文字列 URI を使用して、レプリカセットの 読み取り保証レベル を設定できます。理想的な読み取り保証はアプリケーションの要件によって異なりますが、ほとんどのユースケースではデフォルトで十分です。 デフォルトの読み取り保証を使用する場合、接続文字列パラメーターは必要ありません。

読み取り保証 (read concern) を指定すると、アプリケーションがデータベースから受信するデータの保証が強化されます。

Tip

読み取り保証 (read concern) レベルの設定の詳細については、「読み取り保証 (read concern) オプション 」を参照してください。

注意

アプリケーションが使用する書込み保証 (write concern) と読み取り保証 (read concern) の特定の組み合わせは、操作順序の保証に影響します。 これは 因果整合性 と呼ばれます。 因果整合性の保証の詳細については、「因果整合性、読み取り保証、書込み保証 」を参照してください。

再試行可能な書き込みで処理されていないコマンド、ネットワークの停止、ネットワークエラーはエラーを返します。エラーの詳細については、クライアントライブラリの APIドキュメントを参照してください。

例、アプリケーションが重複した _id を含むドキュメントを挿入しようとすると、クライアントライブラリは次のようなエラーを返します。

Unable to insert due to an error: com.mongodb.MongoWriteException:
E11000 duplicate key error collection: <db>.<collection> ...
{
"name": : "MongoError",
"message": "E11000 duplicate key error collection on: <db>.<collection> ... ",
...
}

適切なエラー処理を行わないと、エラーによってアプリケーションが再起動されるまでリクエストの処理がブロックされる可能性があります。

アプリケーションは、クラッシュしたり副作用のないエラーを処理する必要があります。 重複する_idを挿入するアプリケーションの以前の例では、そのアプリケーションは次のようにエラーを処理できます。

// Declare a logger instance from java.util.logging.Logger
private static final Logger LOGGER = ...
...
try {
InsertOneResult result = collection.insertOne(new Document()
.append("_id", 1)
.append("body", "I'm a goofball trying to insert a duplicate _id"));
// Everything is OK
LOGGER.info("Inserted document id: " + result.getInsertedId());
// Refer to the API documentation for specific exceptions to catch
} catch (MongoException me) {
// Report the error
LOGGER.severe("Failed due to an error: " + me);
}
...
collection.insertOne({
_id: 1,
body: "I'm a goofball trying to insert a duplicate _id"
})
.then(result => {
response.sendStatus(200) // send "OK" message to the client
},
err => {
response.sendStatus(400); // send "Bad Request" message to the client
});

この例の挿入操作では、 _idフィールドが一意である必要があるため、2 回目に呼び出されるときに「重複キー」エラーがスローされます。 アプリケーションはエラーをキャッチし、クライアントに通知され、アプリは実行を続行します。 しかし、挿入操作は失敗します。ユーザーに メッセージを表示するか、操作を再試行するか、または別の操作を実行するかは、ユーザーが決定する必要があります。

エラーは常にログに記録する必要があります。 これ以上の処理エラーを発生させる一般的な戦略は次のとおりです。

  • エラーを、エラー メッセージとともにクライアントに返します。 これは、エラーを解決できず、アクションが完了できないことをユーザーに通知する必要がある場合に適した戦略です。

  • バックアップ データベースに書き込みます。 これは、エラーを解決できないが、リクエスト データが失われるリスクを避けたい場合に適した戦略です。

  • 操作を1 回のデフォルトの 再試行 を超えて再試行します。 これは、エラーの原因をプログラムで解決して再試行する場合に適した戦略です。

アプリケーションのコンテキストに最適な戦略を選択する必要があります。

重複キー エラーの例では、エラーをログに記録する必要がありますが、操作は成功しないため、再試行しないでください。 代わりに、フォールバック データベースに書き込みを行い、後でそのデータベースの内容を確認して、情報が失われることを確認することができます。 ユーザーは他に何もする必要がなく、データが記録されるため、クライアントにエラーメッセージを送信しないことを選択できます。

操作が完了せず、アプリケーションが新しい操作を実行する際にブロックされる場合は、エラーを返すことが推奨されます。 maxTimeMSメソッドを使用して、個々の操作に時間制限を設定でき、その時間制限を超えた場合にアプリケーションが処理するエラーを返します。

各操作に設定する時間制限は、その操作のコンテキストによって異なります。

アプリケーションがinventoryコレクションから簡単な製品情報を読み取って表示する場合、これらの読み取り操作にかかる時間は 1 時間のみであることがかなり確実です。 クエリの実行時間が異常に長い場合は、ネットワークの問題が永続していることを示す適切なインジケーターです。 この操作でmaxTimeMSを 5000、つまり 5 秒に設定すると、アプリケーションはネットワークの問題があることを確認するとすぐにフィードバックを受け取ることを意味します。

次のサンプルアプリケーションでは、回復力のあるアプリケーションを構築するための推奨事項をまとめています。

このアプリケーションは、 http://localhost:3000 で 2 つのエンドポイントを公開するシンプルなユーザー レコードAPIです。

方式
エンドポイント
説明

GET

/users

usersコレクションからユーザー名のリストを取得します。

POST

/users

リクエスト本文にnameが必要です。 新しいユーザーをusersコレクションに追加します。

1// File: App.java
2
3import java.util.Map;
4import java.util.logging.Logger;
5
6import org.bson.Document;
7import org.json.JSONArray;
8
9import com.mongodb.MongoException;
10import com.mongodb.client.MongoClient;
11import com.mongodb.client.MongoClients;
12import com.mongodb.client.MongoCollection;
13import com.mongodb.client.MongoDatabase;
14
15import fi.iki.elonen.NanoHTTPD;
16
17public class App extends NanoHTTPD {
18 private static final Logger LOGGER = Logger.getLogger(App.class.getName());
19
20 static int port = 3000;
21 static MongoClient client = null;
22
23 public App() throws Exception {
24 super(port);
25
26 // Replace the uri string with your MongoDB deployment's connection string
27 String uri = "mongodb://<username>:<password>@hostname0:27017,hostname1:27017,hostname2:27017/?retryWrites=true&w=majority";
28 client = MongoClients.create(uri);
29
30 start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
31 LOGGER.info("\nStarted the server: http://localhost:" + port + "/ \n");
32 }
33
34 public static void main(String[] args) {
35 try {
36 new App();
37 } catch (Exception e) {
38 LOGGER.severe("Couldn't start server:\n" + e);
39 }
40 }
41
42 @Override
43 public Response serve(IHTTPSession session) {
44 StringBuilder msg = new StringBuilder();
45 Map<String, String> params = session.getParms();
46
47 Method reqMethod = session.getMethod();
48 String uri = session.getUri();
49
50 if (Method.GET == reqMethod) {
51 if (uri.equals("/")) {
52 msg.append("Welcome to my API!");
53 } else if (uri.equals("/users")) {
54 msg.append(listUsers(client));
55 } else {
56 msg.append("Unrecognized URI: ").append(uri);
57 }
58 } else if (Method.POST == reqMethod) {
59 try {
60 String name = params.get("name");
61 if (name == null) {
62 throw new Exception("Unable to process POST request: 'name' parameter required");
63 } else {
64 insertUser(client, name);
65 msg.append("User successfully added!");
66 }
67 } catch (Exception e) {
68 msg.append(e);
69 }
70 }
71
72 return newFixedLengthResponse(msg.toString());
73 }
74
75 static String listUsers(MongoClient client) {
76 MongoDatabase database = client.getDatabase("test");
77 MongoCollection<Document> collection = database.getCollection("users");
78
79 final JSONArray jsonResults = new JSONArray();
80 collection.find().forEach((result) -> jsonResults.put(result.toJson()));
81
82 return jsonResults.toString();
83 }
84
85 static String insertUser(MongoClient client, String name) throws MongoException {
86 MongoDatabase database = client.getDatabase("test");
87 MongoCollection<Document> collection = database.getCollection("users");
88
89 collection.insertOne(new Document().append("name", name));
90 return "Successfully inserted user: " + name;
91 }
92}
1const express = require('express');
2const bodyParser = require('body-parser');
3
4// Use the latest client libraries by installing & importing them
5const MongoClient = require('mongodb').MongoClient;
6
7const app = express();
8app.use(bodyParser.json());
9app.use(bodyParser.urlencoded({ extended: true }));
10
11// Use a connection string that lists all hosts
12// with retryable writes & majority write concern
13const uri = "mongodb://<username>:<password>@hostname0:27017,hostname1:27017,hostname2:27017/?retryWrites=true&w=majority";
14
15const client = new MongoClient(uri);
16
17// ----- API routes ----- //
18app.get('/', (req, res) => res.send('Welcome to my API!'));
19
20app.get('/users', (req, res) => {
21 const collection = client.db("test").collection("users");
22
23 collection
24 .find({})
25 // In this example, 'maxTimeMS' throws an error after 5 seconds,
26 // alerting the application to a lasting network outage
27 .maxTimeMS(5000)
28 .toArray((err, data) => {
29 if (err) {
30 // Handle errors in your application
31 // In this example, by sending the client a message
32 res.send("The request has timed out. Please check your connection and try again.");
33 }
34 return res.json(data);
35 });
36});
37
38app.post('/users', (req, res) => {
39 const collection = client.db("test").collection("users");
40 collection.insertOne({ name: req.body.name })
41 .then(result => {
42 res.send("User successfully added!");
43 }, err => {
44 // Handle errors in your application
45 // In this example, by sending the client a message
46 res.send("An application error has occurred. Please try again.");
47 })
48});
49// ----- End of API routes ----- //
50
51app.listen(3000, () => {
52 console.log(`Listening on port 3000.`);
53 client.connect(err => {
54 if (err) {
55 console.log("Not connected: ", err);
56 process.exit(0);
57 }
58 console.log('Connected.');
59 });
60});

戻る

Kubernetes Operator の既知の問題

項目一覧