次のセクションでは、Atlas 配置のレイテンシを削減するために実行できる構成の選択について説明します。
物理的距離
物理的距離はレイテンシの主な原因です。ユーザーとアプリケーションの間、アプリケーションとデータの間、クラスターノード間の距離は、いずれもシステムのレイテンシやアプリケーションのパフォーマンスに影響を与えます。
読み取りおよび書込み操作のレイテンシを軽減するには、アプリケーションとデータの両方を地理的にユーザーに近いものに配置することが重要です。データを保持する Atlas ノードは、データベースのデータを保存し、読み取りおよび書込み操作を処理する Atlas クラスター内のサーバーノードです。アプリケーションユーザーのデータアクセスを高速化するには、アプリケーションユーザーの過半数に地理的に近いクラウドプロバイダーのリージョンに、データを含む Atlas ノードを配置します。
アプリケーションのユーザーが米国やヨーロッパなどの複数の地理に分散している場合は、各場所のユーザーのレイテンシを軽減するために、各地理の 1 つ以上のリージョンに配置することをお勧めします。マルチリージョン配置の詳細については、「 マルチリージョン配置パラダイム 」を参照してください。
データが地理ごとに分割されており、各地域のユーザーが異なるデータセットにアクセスする場合は、各地域のユーザーの読み取りと書込みパフォーマンスを最適化するために、リージョンまたは地理的にデータをシャーディングすることもできます。このアプローチにより、データローカリティを確保しつつ、大規模なデータセットと高スループットを処理できます。
レプリケーション構成
レプリケーションとは、プライマリノードからセカンダリ ノードへのデータのコピーです。レプリカセットでの読み取りおよび書込み (write) 操作のレイテンシを最小限に抑えるには、次のレプリケーション構成オプションを調整できます。
書込み保証 (write concern) レベル: 書込み書込み保証 (write concern)を設定する場合、書込みレイテンシと書込み耐久性の間にはトレードオフがあります。 MongoDB のデフォルトのグローバル書込み保証 (write concern)は
majorityに設定されています。このため、Atlas がクライアントに対して操作の完了と成功を確認する前に、各書込み操作がレプリカセット内の投票権を持つデータを保持するノードの過半数に複製される必要があります。書込み保証 (write concern)を高く設定すると、 書込みレイテンシが増加しますが、書込みの耐久性も向上し、レプリカセットフェイルオーバー中にロールバックが発生するのを防止できます。読み取り保証 (read concern) と読み込み設定 (read preference):読み取り保証 (read concern)と読み込み設定 (read preference)を構成する場合、
localクエリレイテンシ、データ可用性、クエリ応答の一貫性の間にトレードオフがあります。 MongoDB のデフォルトのグローバル読み取り保証primary(read concern)は です。これにより、読み取り操作は、データが他のノードにレプリケーションされることを確認するのを待つことなく、ローカルレプリカセット内の 1 つのノードからのみ読み取る必要があります。このノードがプライマリ ノードかセカンダリノードかは、読み込み設定 (read preference)によって決まります。Atlas はデフォルトで に設定します。このデフォルトの読み取り保証 (read concern)と設定の組み合わせは、レプリカセット内の最新のノードからのレイテンシではレイテンシが最低となります。ただし、プライマリ ノードのデータが永続的ではなく、次の実行中にロールバックされる可能性があるリスクもあります:フェイルオーバー以降、使用可能な プライマリノードがない場合は、ユーザーはデータをクエリできません。読み込み設定 (read preference)を
primaryPreferredに変更すると、使用可能な プライマリノードがない場合、または セカンダリノードがある場合に、読み取り操作でセカンダリノードから読み取ることができますが、セカンダリがノードが最新ではない。このリスクは、書込み保証 (write concern)を増やして、より多くのセカンダリ ノードが最新状態に保たれることで軽減できますが、これにより書込みレイテンシ ( write) レイテンシが増加します。重要
レプリケーションラグが原因で、セカンダリノードが古いデータを返す可能性があることに注意してください。
クエリ タイムアウトの制限: 配置にグローバルおよび操作レベルのクエリ タイムアウト制限を設定して、アプリケーションがタイムアウトするまでの応答を待つ時間を短縮できます。これにより、進行中のクエリが長期間にわたって配置のパフォーマンスに悪影響を与えるのを防ぐことができます。
ノード選挙の優先順位:レプリカセットの選挙中にメインのデータセンターのノードが代替のデータセンターのノードよりも先にプライマリとして選出される可能性を高めるには、代替のデータセンターのノードの を次のように設定します。は、
members[n].priorityプライマリデータセンターのノードよりも低いです。例、クラスターを AWSリージョンus-east-1us-west-1(バージニア州北部)と (カリフォルニア州北部)に配置し、ユーザーの過半数がカリフォルニア州に所属する場合は、 AWS のノードを優先できます。us-west-1}(カリフォルニア州北部)リージョンを使用して、プライマリノードが常に大過半数のユーザーに地理的に近い場所にあり、最小限のレイテンシで読み取りおよび書込み操作に応答できるようにします。ミラーリングされた読み取り: ミラーリングされた読み取りは、セカンダリ ノードのキャッシュを事前ウォーミングすることで、停止後のプライマリ選挙の影響を軽減します。詳細については、「ミラーリングされた読み取り」を参照してください。
ニーズに最適なレプリケーション構成の実装に関する詳細なガイダンスについては、MongoDB の プロフェッショナル サービス にお問い合わせください。
ネットワーク構成
次のネットワーク接続オプションを使用することで、セキュリティを強化し、レイテンシをさらに削減できます。
プライベートエンドポイント: プライベートエンドポイントは、アプリケーションの仮想ネットワークと Atlas クラスター間で直接かつ安全な接続を確立し、ネットワーク コストを削減し、レイテンシ を改善する可能性があります。
VPCピアリング:レプリカセットにVPCピアリングを構成して、アプリケーションがピアリングされたリージョンに接続できるようにします。フェイルオーバーが発生したイベント、 VPCピアリングは、アプリケーションサーバーが新しいプライマリノードを検出し、それに応じてトラフィックをルーティングする方法を提供します。
データモデリングとクエリの最適化
アプリケーションがデータにアクセスする速度は、レイテンシに影響します。適切なデータ モデリングとクエリを最適化することで、データアクセス速度が向上します。例では、次のことができます。
ドキュメント サイズを縮小: ネットワークを介して転送されるデータ量を減らすために、フィールド名と値の長さを短縮することを検討します。
クエリ パターンの最適化: インデックスを効果的に使用して、リージョン全体で読み取られる必要があるデータ量を最小限に抑えます。
レイテンシのモニタリングとテスト
Atlas は、さまざまなリージョンのレイテンシメトリクスを観察するためにリアルタイム パフォーマンス パネル(リアルタイム パフォーマンス パネル)を提供しています。また、アプリケーションレベルのモニタリングを実装して、アプリケーションとの間のエンドツーエンドのレイテンシを追跡することもできます。最終本番環境に配置する前に、さまざまなマルチリージョンシナリオでパフォーマンス テストを実行し、レイテンシのボトルネックを特定して対処することをお勧めします。
配置のモニタリングの詳細については、「 Atlas のモニタリングとアラート に関するガイダンス 」を参照してください。
接続構成
可能な限り、アプリケーションのプログラミング言語の最新のドライバー バージョンに構築された接続メソッドを使用することをお勧めします。 Atlas が提供するデフォルトの接続文字列を開始するのに適していますが、接続文字列に接続文字列オプションを追加して、特定のアプリケーションと配置アーキテクチャのコンテキストでのパフォーマンスを向上させることができます。
エンタープライズレベルのアプリケーション配置の場合、 操作レイテンシ を最小限に抑えながら、ユーザーの需要に合わせて接続プールの設定を調整することは特に重要です。例、minPoolSize と オプションを使用して、データベースクライアント接続の過半数が開く方法とタイミングを調整できるため、関連するネットワークmaxPoolSize オーバーヘッドに伴うレイテンシの急増を防止または計画できます。
これらの設定をどの程度構成できるかは、配置アーキテクチャによって異なります。例、アプリケーション配置でAWS Lambdaなどの単一スレッド リソースを活用している場合、アプリケーションは1 つのクライアント接続のみを開いて使用できます。接続プールを作成して使用する方法と場所、および接続プール設定を指定する場所の詳細については、「 接続プールの概要 」を参照してください。
低レイテンシ アプリケーションの例
次のサンプルアプリケーションでは、 データ操作のレイテンシを削減するために、このページに記載されている重要な推奨事項をまとめています。
Atlas が提供する接続文字列を、再試行可能な書き込み、過半数の書込み保証(write concern)、デフォルトの読み取り保証(read concern)とともに使用します。
optimemaxTimeMS メソッドを使用して 制限を指定します。
maxTimeMSを設定する方法については、特定のドライバーのドキュメント を参照してください。重複キーとタイムアウトのエラーを処理します。
アプリケーションは、クライアントがユーザー レコードを作成または一覧表示できるHTTP APIです。これにより、GET および POST リクエストを受け入れるエンドポイントが公開されます http://localhost::3000
方式 | エンドポイント | 説明 |
|---|---|---|
|
|
|
|
| リクエスト本文に |
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 });
注意
次の Webアプリケーションは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.")