MongoDB Atlas は、インフラストラクチャの停止やシステムのメンテナンスなどに関係なくアップタイムを維持するように設計されている高性能データベースです。このページのガイダンスを使用して、アプリケーションとデータベースの回復力を最大化するための設定を計画します。
Atlas の回復力に関する機能
データベース レプリケーション
Atlas クラスターは、少なくとも 3 つのノードを含む レプリカセットで構成されており、ノード数は必要な任意の奇数のノードに増やすことができます。Atlas はまず、アプリケーションからデータをプライマリノードに書き込み、その後、クラスター内のすべてのセカンダリノードにそのデータを段階的に複製して保存します。データストレージの耐久性を制御するには、アプリケーションコードの書込み保証を調整し、特定の数のセカンダリが書き込みをコミットした後にのみ書き込みを完了させます。詳細については、「読み取りおよび書込み保証の設定」を参照してください。
デフォルトでは、Atlas は、選択したクラウドプロバイダーのアベイラビリティーリージョン内のいずれかのアベイラビリティーゾーンにクラスター ノードを分散します。例、クラスターがクラウドプロバイダーリージョン :us-east に配置されている場合、Atlas はデフォルトで 、us-east-a、us-east-b、us-east-c にノードを配置します。
高可用性とリージョン間でのノード分散の詳細については、「Atlas の高可用性に関するガイダンス」を参照してください。
自己修復型配置
Atlas クラスターは、奇数のノードで構成されている必要があります。これは、ノードプールが と に、アプリケーションが直接書込みと読み取りを行うプライマリノードを選択する必要があるためです。偶数のノードで構成されているクラスターでは、デッドロックが発生し、プライマリノードが選出されなくなる可能性があります。
インフラの停止、メンテナンスウィンドウまたはその他の理由でプライマリノードが利用できない場合、Atlas クラスターは既存のセカンダリ ノードをプライマリ ノードに昇格させて自己修復し、データベースの可用性を維持します。このプロセスの詳細については、「MongoDB Atlas はどのように高可用性を提供するのか」をご覧ください。
メンテナンスウィンドウのアップタイム
Atlas は、一度に 1 つのノードにローリング方式で更新を適用することで、スケジュールされたメンテナンス中にアップタイムを維持します。このプロセス中、Atlas は、他の計画外のプライマリ ノードの停止時と同様に、必要に応じて新しいプライマリ ノードを選出します。
メンテナンスウィンドウを設定する際は、アプリケーションのトラフィック量が最も少ない時間に対応する時間を選択します。
モニタリング
Atlas は、クラスター パフォーマンス、クエリ パフォーマンスなどを監視するための組み込みツールを提供します。さらに、Atlas は サードパーティのサービス と簡単に統合できます。
クラスターをアクティブに監視することで、クエリと配置のパフォーマンスに関する価値のあるインサイトを得ることができます。Atlas でのモニタリングの詳細については、「クラスターのモニタリング」および「モニタリングとアラート」を参照してください。
配置回復力テスト
障害復旧ワークフローを必要とするさまざまなシナリオをシミュレートして、そのようなイベントへのレディネスを測定できます。具体的には、Atlas を使用すると、プライマリ ノードのフェイルオーバーをテストし、リージョンの停止をシミュレートできます。アプリケーションを本番環境にデプロイする前に、これらのテストを実行することを強く推奨します。
クラスター終了の予防手段
終了保護 を有効にすることで、Atlas クラスターの誤った削除を防ぐことができます。Terraform のようなIaC ツールを活用する際、再デプロイメントが新しいインフラをプロビジョニングしないようにするために、終了保護を有効にすることは特に重要です。終了保護が有効になっているクラスターを削除するには、まず終了保護を無効にする必要があります。デフォルトでは、Atlas はすべてのクラスターの終了保護を無効にします。
データベースのバックアップ
Atlas クラウドバックアップは、クラスターが配置されているクラウド サービス プロバイダーのネイティブ スナップショット機能を使用して、クラウド バックアップ ストレージを容易にします。たとえば、AWS にクラスターを配置した場合、AWS S3 で設定可能な間隔で取得されたスナップショットを使用して、クラスターのデータをバックアップすることを選択できます。
データベースのバックアップとスナップショットの取得の詳細については、「クラスターのバックアップ」をご覧ください。
バックアップに関する推奨事項については、「Atlas のバックアップに関するガイダンス」を参照してください。
Atlas の回復力に関する推奨事項
MongoDB 8.0 以降を使用
クラスターの回復力を向上させるには、クラスターをMongoDB 8.0 にアップグレードします。MongoDB 8.0 では、回復力に関連する次のパフォーマンスの向上と新機能が導入されています。
コストのかかるクエリをリアクティブに軽減する操作拒否フィルター
コストのかかる読み取り操作に対するプロアクティブな保護のためのクラスターレベルのタイムアウト
moveCollection コマンドによるワークロードのより優れた分離
Atlas へのアプリケーションの接続
可能な限り、アプリケーションのプログラミング言語の最新のドライバー バージョンに構築された接続メソッドを使用することをお勧めします。Atlas が提供するデフォルトの接続文字列を開始するのに適していますが、特定のアプリケーションと配置アーキテクチャのコンテキストでのパフォーマンスを高めるために、これを調整することをお勧めします。
例、ログイン機能を提供するマイクロサービスには短い maxTimeMS を設定できますが、アプリケーションコードが長時間実行される分析ジョブリクエストである場合は、maxTimeMS をはるかに大きな値に設定する必要があるかもしれません。クラスター。
接続プールの設定を調整することは、エンタープライズ レベルのアプリケーション配置のコンテキストで特に重要です。
高性能なアプリケーションのための接続プールに関する考慮事項
データベースクライアント接続の開始は、Atlas クラスターへのアプリケーションアクセスを容易にするクライアント接続プールの維持に最もリソースが集中するプロセスの 1 つです。
このため、クライアント接続を開くこのプロセスを特定のアプリケーションのコンテキストでどのように展開するか、またいつ展開するかを検討する価値があります。
例、ユーザーの需要に合わせて Atlas クラスターをスケーリングしている場合は、アプリケーションに一貫して必要な接続の最小プール サイズが一貫して必要になることを検討してください。これにより、アプリケーションプールが新しいクライアント接続の開始に伴うネットワークとコンピューティング負荷を増やすと、データベース操作の増加というアプリケーションの時間に依存する必要を損なわないようにします。
接続プールの最小接続数と最大接続数
minPoolSize と maxPoolSize の値が似ている場合、データベースクライアント接続の過半数はアプリケーションのスタートアップ時に開きます。例、minPoolSize が 10 に設定され、maxPoolSize が 12 に設定されている場合、10クライアント接続はアプリケーションのスタートアップ時に開き、2 個の接続のみがアプリケーションの実行中に開くことができます。 。ただし、minPoolSize が 10 に設定され、maxPoolSize が 100 に設定されている場合は、アプリケーションの実行中に必要に応じて最大 90 の追加接続を開くことができます。
新しいクライアント接続の開始に関連する追加のネットワーク オーバーヘッド。したがって、アプリケーションのスタートアップ時にそのネットワークコストを発生させたいか、アプリケーションの実行中に必要に応じて で動的に発生させるかを検討してください。これにより、エンドユーザーの運用レイテンシと認識済みのパフォーマンスに影響可能性があります。大量の追加の接続を一度に開く必要があるリクエストが急増した場合。
アプリケーションのアーキテクチャは、この考慮事項の中心となります。例、アプリケーションを マイクロサービス として配置する場合は、接続プールの動的な拡張と縮小を制御する方法として、どのサービスが Atlas を直接呼び出すかを検討してください。あるいは、 Amazon Web Services Lambdaなどのアプリケーション配置でシングルスレッド リソースを活用している場合、アプリケーションは1 つのクライアント接続しか開いて使用できないため、minPoolSize と maxPoolSize の両方を { に設定する必要があります。 1。
クエリのタイムアウト
ほとんどの場合、アプリケーションからのワークロード固有のクエリは、Atlas での実行期間とアプリケーションが応答を待機できる期間に応じて異なります。
Atlas ではクエリタイムアウトの動作をグローバルに設定することができ、クエリレベルで定義することもできます。
再試行可能なデータベースの読み取りおよび書き込み
Atlas は、再試行可能な読み取り と 再試行可能な書込み 操作 をサポートしています。有効にすると、Atlas は断続的なネットワーク停止の予防手段として、読み取りおよび書込み操作を 1 回再試行します。
読み取り保証と書込み保証の構成
Atlas クラスターは、最終的にすべてのノードにわたってすべてのデータを複製します。ただし、読み取りまたは書き込み操作が成功したと報告される前に、データを複製する必要があるノードの数を設定できます。読み取り保証と書込み保証は Atlas でグローバルに定義できます。また、接続文字列でクライアントレベルで定義することもできます。Atlas のデフォルトの書込み保証は majority であり、つまり、データは Atlas が成功を報告する前に、クラスター内のノードの過半数で複製される必要があります。逆に、Atlas のデフォルトの読み取り保証は local であり、つまり、クエリ時に Atlas は、クラスター内の 1 つのノードからのみデータを取得します。
シャーディングされていないコレクションの影響を分離する
シャー増やすにより、クラスターを水平方向にスケーリングできます。 MongoDBを使用すると、一部のコレクションをシャーディングしながら、同じクラスター内の他のコレクションはシャーディングされないままにできます。 新しいデータベースを作成すると、クラスター内のデータ量が最も少ないシャードがデフォルトでそのデータベースのプライマリシャードとして選択されデフォルト。 そのデータベースのシャーディングされていないコレクションは、デフォルトでそのプライマリシャードに配置されます。 これにより、特にワークロードロードの増加がプライマリシャードのシャーディングされていないコレクションに集中する場合、ワークロードの増加に伴いプライマリシャードへのトラフィックが増加する可能性があります。
このワークロードをより分散するために、 MongoDB 8.0では、 moveCollectionコマンドを使用して、シャーディングされていないコレクションをプライマリシャードから他のシャードに移動できます。 これにより、アクティブで多目的なコレクションを、リソース使用量の少ないシャードに配置できます。 これを使用すると、次のことが可能になります。
大規模で複雑なワークロードのパフォーマンスを最適化します。
リソース使用率の向上を実現します。
シャード間で日付をより均等に分散します。
次の状況では、コレクションを分離することをお勧めします。
高スループットのシャーディングされていないコレクションが複数あるためにプライマリシャードに重大なワークロードが発生した場合。
シャーディングされていないコレクションでは将来、増加が予想され、他のコレクションのボトルネックになる可能性があります。
クラスターごとに 1 コレクションの配置設計を実行中していて、優先順位やワークロードに基づいてそれらのカスタマーを分離したいと考えています。
シャードには、シャーディングされていないコレクションの数が含まれているため、データ量が按分されています。
mongoshを使用してシャーディングされていないコレクションを移動する方法については、「コレクションの移動 」を参照してください。
障害復旧
Atlas の障害復旧におけるベストプラクティスに関する推奨事項については、「Atlas の障害復旧に関するガイダンス」および「高可用性と復旧のための推奨構成」を参照してください。
回復力のあるサンプルアプリケーション
サンプルアプリケーションには、ネットワーク停止やフェイルオーバー イベントに対する回復力を確保するために、次の推奨事項をまとめています。
Atlas が提供する接続文字列を、再試行可能な書き込み、過半数の書込み保証(write concern)、デフォルトの読み取り保証(read concern)とともに使用します。
optimemaxTimeMS メソッドを使用して 制限を指定します。
maxTimeMSを設定する方法については、特定のドライバーのドキュメント を参照してください。重複キーとタイムアウトのエラーを処理します。
アプリケーションは、クライアントがユーザー レコードを作成または一覧表示できる HTTP APIです。 これにより、GET および POST リクエストを受け入れるエンドポイントが公開されます http://localhost:3000 :
方式 | エンドポイント | 説明 |
|---|---|---|
|
|
|
|
| リクエスト本文に |
注意
次のサーバー アプリケーションは NaHTTPD を使用します および 実行前に、プロジェクトに 依存関係JSON として追加する必要があります。
1 // File: App.java 2 3 import java.util.Map; 4 import java.util.logging.Logger; 5 6 import org.bson.Document; 7 import org.json.JSONArray; 8 9 import com.mongodb.MongoException; 10 import com.mongodb.client.MongoClient; 11 import com.mongodb.client.MongoClients; 12 import com.mongodb.client.MongoCollection; 13 import com.mongodb.client.MongoDatabase; 14 15 import fi.iki.elonen.NanoHTTPD; 16 17 public 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 = "<atlas-connection-string>"; 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 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 }
注意
次のサーバー アプリケーションは Express を使用します は、実行前にプロジェクトに依存関係として追加する必要があります。
1 const express = require('express'); 2 const bodyParser = require('body-parser'); 3 4 // Use the latest drivers by installing & importing them 5 const MongoClient = require('mongodb').MongoClient; 6 7 const app = express(); 8 app.use(bodyParser.json()); 9 app.use(bodyParser.urlencoded({ extended: true })); 10 11 const uri = "mongodb+srv://<db_username>:<db_password>@cluster0-111xx.mongodb.net/test?retryWrites=true&w=majority"; 12 13 const client = new MongoClient(uri, { 14 useNewUrlParser: true, 15 useUnifiedTopology: true 16 }); 17 18 // ----- API routes ----- // 19 app.get('/', (req, res) => res.send('Welcome to my API!')); 20 21 app.get('/users', (req, res) => { 22 const collection = client.db("test").collection("users"); 23 24 collection 25 .find({}) 26 .maxTimeMS(5000) 27 .toArray((err, data) => { 28 if (err) { 29 res.send("The request has timed out. Please check your connection and try again."); 30 } 31 return res.json(data); 32 }); 33 }); 34 35 app.post('/users', (req, res) => { 36 const collection = client.db("test").collection("users"); 37 collection.insertOne({ name: req.body.name }) 38 .then(result => { 39 res.send("User successfully added!"); 40 }, err => { 41 res.send("An application error has occurred. Please try again."); 42 }) 43 }); 44 // ----- End of API routes ----- // 45 46 app.listen(3000, () => { 47 console.log(`Listening on port 3000.`); 48 client.connect(err => { 49 if (err) { 50 console.log("Not connected: ", err); 51 process.exit(0); 52 } 53 console.log('Connected.'); 54 }); 55 });
注意
次のウェブ アプリケーションは FastAPI を使用します 。新しいアプリケーションを作成するには、 FastAPI サンプル ファイル を使用します 構造体。
1 # File: main.py 2 3 from fastapi import FastAPI, Body, Request, Response, HTTPException, status 4 from fastapi.encoders import jsonable_encoder 5 6 from typing import List 7 from models import User 8 9 import pymongo 10 from pymongo import MongoClient 11 from pymongo import errors 12 13 # Replace the uri string with your |service| connection string 14 uri = "<atlas-connection-string>" 15 db = "test" 16 17 app = FastAPI() 18 19 20 def startup_db_client(): 21 app.mongodb_client = MongoClient(uri) 22 app.database = app.mongodb_client[db] 23 24 25 def shutdown_db_client(): 26 app.mongodb_client.close() 27 28 ##### API ROUTES ##### 29 30 def list_users(request: Request): 31 try: 32 users = list(request.app.database["users"].find().max_time_ms(5000)) 33 return users 34 except pymongo.errors.ExecutionTimeout: 35 raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="The request has timed out. Please check your connection and try again.") 36 37 38 def new_user(request: Request, user: User = Body(...)): 39 user = jsonable_encoder(user) 40 try: 41 new_user = request.app.database["users"].insert_one(user) 42 return {"message":"User successfully added!"} 43 except pymongo.errors.DuplicateKeyError: 44 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Could not create user due to existing '_id' value in the collection. Try again with a different '_id' value.")