ApsaraDB for MongoDB インスタンスに多数のデータベースとコレクションが存在する場合、インスタンス内のデータベースのパフォーマンスが低下し、その他の問題が発生する可能性があります。
従来のリレーショナルデータベースにおけるデータベースとテーブルの概念は、ApsaraDB for MongoDB のデータベースとコレクションの概念に対応しています。
ApsaraDB for MongoDB では、WiredTiger ストレージエンジンはコレクションのディスクファイルを作成します。個々のインデックスは新しいディスクファイルになります。 WiredTiger ストレージエンジンでは、ファイルシステムオブジェクトなど、開かれている各リソースは、データハンドル(dhandle)と呼ばれる固有のデータ構造を使用して、チェックポイント、セッション参照の数、メモリ内 B-tree 構造へのポインター、およびデータ統計に関する情報を格納します。
したがって、ApsaraDB for MongoDB インスタンスに多数のコレクションが存在する場合、WiredTiger ストレージエンジンでより多くのオペレーティングシステムファイルが開かれ、より多くの対応する dhandle が生成されます。 メモリに多数の dhandle が存在する場合、ロックの競合が発生します。 これにより、インスタンスのパフォーマンスが低下します。
潜在的な問題
ハンドルロックまたはスキーマロックによって引き起こされる低速クエリとリクエストレイテンシの増加
多数のコレクションが存在する場合、次のスロークエリログが生成されます。
2024-03-07T15:59:16.856+0800 I COMMAND [conn4175155] command db.collections command: count { count: "xxxxxx", query: { A: 1, B: 1 }, $readPreference: { mode: "secondaryPreferred" }, $db: "db" } planSummary: COLLSCAN keysExamined:0 keysExaminedBySizeInBytes:0 docsExamined:1 docsExaminedBySizeInBytes:208 numYields:1 queryHash:916BD9E3 planCacheKey:916BD9E3 reslen:185 locks:{ ReplicationStateTransition: { acquireCount: { w: 2 } }, Global: { acquireCount: { r: 2 } }, Database: { acquireCount: { r: 2 } }, Collection: { acquireCount: { r: 2 } }, Mutex: { acquireCount: { r: 1 } } } storage:{ data: { bytesRead: 304, timeReadingMicros: 4 }, timeWaitingMicros: { handleLock: 40, schemaLock: 134101710 } } protocol:op_query 134268msこのログは、単一のドキュメントを含むコレクションに対してのみ count 操作を実行し、操作の完了に長い時間がかかることを示しています。 ログの
timeWaitingMicros: { handleLock: 40, schemaLock: 134101710 } } protocol:op_query 134268msフィールドは、多数のコレクションが原因で、読み取りリクエストが下位層に格納されているハンドルロックとスキーマロックの取得を長時間待機していることを示しています。ノードを追加するときに、同期初期化中にメモリ不足(OOM)エラーが発生します。
インスタンスの再起動に時間がかかります。
データの同期に時間がかかります。
データのバックアップとリストアに時間がかかります。
物理バックアップの失敗率が増加します。
障害からのインスタンスの回復に時間がかかります。
多数のコレクションが存在しても、必ずしも問題が発生するわけではありません。 問題は、ビジネスモデルや負荷などの要因によって発生します。 たとえば、次のシナリオでは、データベースは同じ仕様を使用し、10,000 個のコレクションと 100,000 個のドキュメントを持っています。 ただし、シナリオでは異なる問題が発生します。
会計ソフトウェアシステム:アクセスには明らかな集約特性があります。 ほとんどのコレクションはコールドデータとしてのみ保存され、頻繁にアクセスされるコレクションはごく一部です。
マルチテナント管理システム:テナントはコレクションによって分離されています。 ほとんどのコレクションがアクセスまたは使用されます。
最適化手法
不要なコレクションの削除
期限切れのコレクションや不要になったコレクションなど、データベースから削除できるコレクションをクエリします。 次に、dropCollection コマンドを実行してコレクションを削除します。 コマンドの詳細については、「dropCollection()」をご参照ください。
コマンドを実行する前に、完全バックアップファイルが使用可能であることを確認してください。
次のコマンドを実行して、データベースとデータベース内のコレクションの情報をクエリします。
次のコマンドを実行して、データベース内のコレクションの数をクエリします。
db.getSiblingDB(<dbName>).getCollectionNames().length次のコマンドを実行して、コレクションとインデックスの数、ドキュメントエントリの数、データの総量など、データベースの情報をクエリします。
// データベースの統計をクエリします。 db.getSiblingDB(<dbName>).stats()次のコマンドを実行して、コレクションの情報をクエリします。
/ コレクションの統計をクエリします。 db.getSiblingDB(<dbName>).<collectionName>.stats()
不要なインデックスの削除
前述の潜在的な問題を解決するには、インデックスの数を減らします。 インデックスの数が減ると、WiredTiger ストレージエンジンによって保存されるディスクファイルの数と対応する dhandle の数が減ります。
インデックスを最適化する場合は、次のルールに従ってください。
無効なインデックスを避ける
クエリによってアクセスされないフィールドのインデックスはヒットしません。 インデックスは無効です。 インデックスを削除できます。
インデックスのプレフィックスマッチングルール
たとえば、
{a:1}と{a:1,b:1}のインデックスが存在するとします。 {a:1} インデックスは、他のインデックスのプレフィックスとして機能する冗長なインデックスです。 インデックスを削除できます。等価クエリにおけるインデックスフィールドの順序
たとえば、
{a:1,b:1}と{b:1,a:1}のインデックスが存在するとします。 等価照合では、インデックスフィールドの順序は照合結果に影響しません。 ヒット数が最も少ないインデックスを削除できます。範囲クエリにおける ESR ルールの参照
実際のビジネスクエリ範囲に基づいて、
quality、Sort、Rangeの順序で最適な複合インデックスを構築します。 詳細については、「ESR(Equality、Sort、Range)ルール」をご参照ください。ヒット数が少ないインデックスを確認する
ほとんどの場合、ヒット数が少ないインデックスは、ヒット数が多い別のインデックスと重複しています。 すべてのクエリパターンに基づいて、インデックスを削除するかどうかを決定します。
ApsaraDB for MongoDB の $indexStats 集約ステージを使用して、コレクション内のすべてのインデックスの統計を表示できます。 集約ステージの詳細については、「$indexStats(集約)」をご参照ください。 次のコマンドを実行して、コレクション内のすべてのインデックスの統計をクエリします。 コマンドを実行する前に、必要な権限を持っていることを確認してください。
// コレクション内のすべてのインデックスの統計をクエリします。
db.getSiblingDB(<dbName>).<collectionName>.aggregate({"$indexStats":{}})結果例:
{
"name" : "item_1_quantity_1",
"key" : { "item" : 1, "quantity" : 1 },
"host" : "examplehost.local:27018",
"accesses" : {
"ops" : NumberLong(1),
"since" : ISODate("2020-02-10T21:11:23.059Z")
}
}次の表は、前の結果で返されるパラメーターについて説明しています。
パラメーター | 説明 |
name | インデックスの名前。 |
key | インデックスキーの詳細。 |
accesses.ops | インデックスを使用する操作の数。 このパラメーター値は、インデックスのヒット数も示します。 |
accesses.since | 統計が収集された時刻。 インスタンスが再起動された場合、またはインデックスが再構築された場合、accesses.since パラメーターと accesses.ops パラメーターは再構成されます。 |
インデックスのヒット数が少ない場合、たとえば accesses.ops パラメーターが 1 に設定されている場合、インデックスは冗長であるか無効である可能性があります。 インデックスを削除できます。 インスタンスで MongoDB 4.4 以降を実行している場合は、hiddenIndex コマンドを実行してインデックスを非表示にし、インスタンスが一定期間例外に遭遇しないことを確認してから、インデックスを削除します。 これにより、インデックスの削除によって引き起こされるリスクが軽減されます。 コマンドの詳細については、「db.collection.hideIndex()」をご参照ください。
例
たとえば、players という名前のコレクションが存在するとします。 コレクション内のプレイヤーは、プレイヤーが 20 コイン を集めるたびに、コインが自動的に スター に変換されるというルールに準拠しています。 コレクションには次のドキュメントが含まれています。
// players コレクション
{
"_id": "ObjectId(123)",
"first_name": "John",
"last_name": "Doe",
"coins": 11,
"stars": 2
}コレクションには次のインデックスが含まれています。
_id(デフォルトインデックス){ last_name: 1 }{ last_name: 1, first_name: 1 }{ coins: -1 }{ stars: -1 }
次のインデックス最適化ロジックが適用されます。
クエリは
coinsフィールドにアクセスしません。 したがって、{ coins: -1 }インデックスは無効です。{ last_name: 1, first_name: 1 }インデックスには、前述のインデックスプレフィックスマッチングルールに基づいて{ last_name: 1 }インデックスが含まれています。 したがって、{ last_name: 1 }インデックスを削除できます。$indexStatsコマンドを実行します。 コマンド出力は、{ stars: -1 }インデックスのヒット数が少ないことを示しています。 これは、インデックスが頻繁に使用されていないことを示しています。 ただし、ゲームの最終ラウンドの終わりにプレイヤーをスターの数に基づいて逆順にソートする必要があるため、{ stars: -1 }インデックスは保持する必要があります。
インデックスの最適化後、コレクションには次のインデックスが残ります。
_id{ last_name: 1, first_name: 1 }{ stars: -1 }
インデックスの最適化には、次の利点があります。
インデックスの記憶容量が削減されます。
データ書き込みパフォーマンスが向上します。
インデックスの最適化についてさらに質問がある場合は、チケットを送信する。
複数のコレクションのデータを統合する
コレクションの数を減らすには、複数のコレクションのデータを単一のコレクションに統合します。
たとえば、temperatures という名前のデータベースが存在するとします。 データベースは、センサーから取得されたすべての温度データを格納するために使用されます。 センサーは 10:00 から 22:00 まで動作します。 動作中、センサーは 30 分ごとに温度データを読み取り、データベースに保存します。 毎日の温度データは、名前に日付が含まれるコレクションに格納されます。
次のデータは、temperatures.march-09-2020 と temperatures.march-10-2020 という名前の 2 つのコレクションに格納されています。
temperatures.march-09-2020という名前のコレクション{ "_id": 1, "timestamp": "2020-03-09T010:00:00Z", "temperature": 29 } { "_id": 2, "timestamp": "2020-03-09T010:30:00Z", "temperature": 30 } ... { "_id": 25, "timestamp": "2020-03-09T022:00:00Z", "temperature": 26 }temperatures.march-10-2020という名前のコレクション{ "_id": 1, "timestamp": "2020-03-10T010:00:00Z", "temperature": 30 } { "_id": 2, "timestamp": "2020-03-10T010:30:00Z", "temperature": 32 } ... { "_id": 25, "timestamp": "2020-03-10T022:00:00Z", "temperature": 28 }
データベース内のコレクションの数は時間の経過とともに増加しています。 ApsaraDB for MongoDB にはコレクションの数に明確な上限がなく、上記の例では明確なデータライフサイクル関係が指定されていません。 したがって、データベース内のコレクションの数と対応するインデックスの数は増加しています。
例で使用されているデータモデリングは、複数日にわたるクエリを容易にするものではありません。 複数日のデータをクエリして長期間の温度トレンドを取得する場合は、$lookup に基づいてクエリを実行します。 クエリのパフォーマンスは、同じコレクションに対するクエリよりも劣ります。
最適化されたデータモデリングでは、2 つのコレクションの温度データを単一のコレクションに統合し、毎日の温度データを単一のドキュメントに格納します。 次のサンプルコードは、最適化されたデータモデリングの例を示しています。
// temperatures.readings
{
"_id": ISODate("2020-03-09"),
"readings": [
{
"timestamp": "2020-03-09T010:00:00Z",
"temperature": 29
},
{
"timestamp": "2020-03-09T010:30:00Z",
"temperature": 30
},
...
{
"timestamp": "2020-03-09T022:00:00Z",
"temperature": 26
}
]
}
{
"_id": ISODate("2020-03-10"),
"readings": [
{
"timestamp": "2020-03-10T010:00:00Z",
"temperature": 30
},
{
"timestamp": "2020-03-10T010:30:00Z",
"temperature": 32
},
...
{
"timestamp": "2020-03-10T022:00:00Z",
"temperature": 28
}
]
}最適化されたデータモデリングは、元のデータモデリングよりもはるかに少ないリソースを消費します。 毎日温度が読み取られる時刻に基づいてインデックスを作成する必要はありません。 temperatures.readings コレクションのデフォルトインデックス _id により、日付によるクエリが容易になります。 最適化されたデータモデリングは、コレクション数の増加という問題を解決します。
時系列データの時系列コレクションを使用して、前述の問題を解決できます。 詳細については、「時系列」をご参照ください。
時系列コレクションは、MongoDB 5.0 以降を実行しているインスタンスでのみサポートされています。
インスタンスの分割
ApsaraDB for MongoDB スタンドアロンインスタンスのコレクションの総数を減らすことができない場合は、インスタンスを分割し、インスタンスの構成を変更します。
次の表は、2 つのシナリオにおける分割ソリューションについて説明しています。
シナリオ | 分割ソリューション | 説明 |
コレクションが複数のデータベースに分散されている | 複数のデータベースにわたるビジネスが密接に関連付けられていない場合、たとえば複数のアプリケーションまたはサービスが同じインスタンスを共有している場合、一部のデータベースのデータを新しい ApsaraDB for MongoDB インスタンスに移行します。 詳細については、「ApsaraDB for MongoDB レプリカセットインスタンスから ApsaraDB for MongoDB レプリカセットまたはシャードクラスターインスタンスにデータを移行する」をご参照ください。 移行が完了する前に、ビジネスロジックとアクセスモードを分割する必要があります。 複数のデータベースにわたるビジネスが密接に関連付けられている場合は、コレクションが単一のデータベースに分散されているシナリオの分割ソリューションを参照してください。 |
|
コレクションが単一のデータベースに分散されている | アプリケーションはまず、地域、都市、優先度、またはビジネスに関連するその他のディメンションなど、すべての koleksi がディメンションによって分割できるかどうかを判断する必要があります。 次に、DTS を使用して、データベース内の一部のコレクションのデータを 1 つ以上の新しい ApsaraDB for MongoDB インスタンスに移行します。 これにより、コレクションは N 個のデータベースに分散されます。 移行が完了する前に、ビジネスロジックとアクセスモードを分割する必要があります。 |
|
例
マルチテナント管理プラットフォームシステムは MongoDB データベースを使用します。 初期モデリングでは、各テナントは別々のコレクションです。 ビジネスの成長に伴い、テナントの数は 100,000 を超え、データベースのデータ総量はテラバイトに達しました。 この場合、データベースへのアクセスが遅くなり、レイテンシが高くなります。
システムのアプリケーションは、地域ディメンションによって中国のテナントを分割します。 テナントは、華北、東北、華東、華中、華南、西南、西北の各地域に分類されます。 ApsaraDB for MongoDB インスタンスは地域のゾーンに作成され、複数ラウンドの DTS 移行が実行されます。 データの集約と分析に関するアプリケーションの要件を満たすために、データはインスタンスからデータウェアハウスに同期されます。
分割後、インスタンス内のコレクションの数は大幅に減少し、インスタンスの仕様はダウングレードされます。 アプリケーションは、地域ごとの近接アクセスの原則に従います。 これにより、リクエストレイテンシがミリ秒に短縮され、ユーザーエクスペリエンスが大幅に向上し、後続のインスタンス O&M が簡素化されます。
レプリカセットインスタンスのすべてのコレクションのデータをシャードクラスターインスタンスに移行し、シャードタグを使用してコレクションを管理する
すべてのコレクションがデータベースに分散されており、論理インスタンスによって管理する必要がある場合は、コレクションのデータをシャードクラスターインスタンスに移行し、コレクション管理にシャードタグを使用できます。 シャードタグを使用してコレクションを管理する場合は、sh.addShardTag コマンドと sh.addTagRange コマンドを実行する必要があります。 ただし、コレクションは引き続きシャードクラスターインスタンスによって管理され、ビジネス構成を変更する必要はありません。 元の接続文字列を網掛けクラスターインスタンスの接続文字列に置き換えるだけです。 コマンドの詳細については、「sh.addShardTag()」および「sh.addTagRange()」をご参照ください。
インスタンスに 100,000 個のアクティブなコレクションが含まれている場合は、10 個のシャードノードを含むシャードクラスターインスタンスを購入します。 次の手順を実行して構成を行い、データを移行できます。 移行が完了すると、シャードクラスターインスタンスの各シャードノードには 10,000 個のアクティブなコレクションが含まれます。
シャードクラスターインスタンスを購入します。 この例では、2 つのシャードを含むシャードクラスターインスタンスを使用します。 シャードクラスターインスタンスの作成方法の詳細については、「シャードクラスターインスタンスを作成する」をご参照ください。
シャードクラスターインスタンスの mongos ノードに接続します。 詳細については、「mongo シェルを使用して ApsaraDB for MongoDB シャードクラスターインスタンスに接続する」をご参照ください。
次のコマンドを実行して、すべてのシャードにシャードタグを追加します。
sh.addShardTag("d-xxxxxxxxx1", "shard_tag1") sh.addShardTag("d-xxxxxxxxx2", "shard_tag2")説明上記のコマンドを実行する前に、使用されているアカウントに必要な権限があることを確認してください。
Data Management (DMS) は
sh.addShardTagコマンドをサポートしていません。 mongo シェルまたは mongosh を使用してインスタンスに接続し、コマンドを実行することをお勧めします。
シャード内のすべてのコレクションに対して、範囲ベースのタグ配布ルールを事前に構成します。
use <dbName> sh.enableSharding("<dbName>") sh.addTagRange("<dbName>.test", {"_id":MinKey}, {"_id":MaxKey}, "shard_tag1") sh.addTagRange("<dbName>.test1", {"_id":MinKey}, {"_id":MaxKey}, "shard_tag2")この例では、
_idがシャードキーとして使用されています。 シャードキーを実際のシャードキーに置き換えます。 すべてのクエリにシャードキーフィールドが含まれていることを確認してください。 シャードキーは、後続の操作のフィールド内のシャードキーと同じである必要があります。[MinKey,MaxKey]で指定された境界値を使用して、単一のコレクション内のすべてのデータが単一のシャードにのみ格納されるようにします。移行するすべてのコレクションに対して shardCollection 操作を実行します。
sh.shardCollection("<dbName>.test", {"_id":1}) sh.shardCollection("<dbName>.test1", {"_id":1})sh.status()コマンドを実行して、指定されたルールが有効になっていることを確認します。
すべてのコレクションのデータをシャードクラスターインスタンスに移行します。 詳細については、「ApsaraDB for MongoDB レプリカセットインスタンスから ApsaraDB for MongoDB レプリカセットまたはシャードクラスターインスタンスにデータを移行する」をご参照ください。
説明シャードクラスターインスタンスでシャーディング操作を実行し、すべてのコレクション情報が存在します。 この場合、競合テーブルの処理モードパラメーターをエラーを無視して続行に設定します。
システムがデータの整合性をチェックした後、ビジネスをシャードクラスターインスタンスに切り替えます。
シャードをシャードクラスターインスタンスに追加する場合は、手順 3 を実行して新しいシャードにタグを追加します。
データベース内のコレクションの数が引き続き増加する場合は、手順 4 と手順 5 を実行します。 そうしないと、コレクションはプライマリシャードにのみ存在し、シャード内のコレクションの数が多くなります。 この場合、インスタンスは常にスタッター状態になるか、例外に遭遇します。
レプリカセットインスタンスのすべてのコレクションのデータをシャードクラスターインスタンスに移行し、ゾーンを使用してコレクションを管理する
ゾーンを使用して、移行されたコレクションを管理できます。 この方法は、シャードタグを使用する方法に似ています。 ゾーンを使用してコレクションを管理する場合は、sh.addShardToZone() コマンドと sh.updateZoneKeyRange() コマンドも実行する必要があります。 ゾーンの詳細については、「シャードゾーンの管理」をご参照ください。 コマンドの詳細については、「sh.addShardToZone()」および「sh.updateZoneKeyRange()」をご参照ください。
ゾーンを使用してコレクションを管理するには、次の手順を実行します。
シャードクラスターインスタンスを購入します。 この例では、2 つのシャードを含むシャードクラスターインスタンスを使用します。 シャードクラスターインスタンスの作成方法の詳細については、「シャードクラスターインスタンスを作成する」をご参照ください。
シャードクラスターインスタンスの mongos ノードに接続します。 詳細については、「mongo シェルを使用して ApsaraDB for MongoDB シャードクラスターインスタンスに接続する」をご参照ください。
次のコマンドを実行して、シャードクラスターインスタンスのすべてのシャードのゾーンを指定します。
sh.addShardToZone("d-xxxxxxxxx1", "ZoneA") sh.addShardToZone("d-xxxxxxxxx2", "ZoneB")説明上記のコマンドを実行する前に、使用されているアカウントに必要な権限があることを確認してください。
DMS は
sh.addShardToZoneコマンドをサポートしていません。 mongo シェルまたは mongosh を使用してインスタンスに接続し、コマンドを実行することをお勧めします。
シャード内のすべてのコレクションに対して、範囲ベースのゾーン配布ルールを事前に構成します。
use <dbName> sh.enableSharding("<dbName>") sh.updateZoneKeyRange("<dbName>.test", { "_id": MinKey }, { "_id": MaxKey }, "ZoneA") sh.updateZoneKeyRange("<dbNmae>.test1", { "_id": MinKey }, { "_id": MaxKey }, "ZoneB")この例では、
_idがシャードキーとして使用されています。 シャードキーを実際のシャードキーに置き換えます。 すべてのクエリにシャードキーフィールドが含まれていることを確認してください。 シャードキーは、後続の操作のフィールド内のシャードキーと同じである必要があります。[MinKey,MaxKey]で指定された境界値を使用して、単一のコレクション内のすべてのデータが単一のシャードにのみ格納されるようにします。移行するすべてのコレクションに対して shardCollection 操作を実行します。
sh.shardCollection("<dbName>.test", { _id: "hashed" }) sh.shardCollection("<dbName>.test1", { _id: "hashed" })sh.status()コマンドを実行します。 コマンド出力は、指定されたルールが有効になっていることを示します。
すべてのコレクションのデータをシャードクラスターインスタンスに移行します。 詳細については、「ApsaraDB for MongoDB レプリカセットインスタンスから ApsaraDB for MongoDB レプリカセットまたはシャードクラスターインスタンスにデータを移行する」をご参照ください。
説明シャードクラスターインスタンスでシャーディング操作を実行し、すべてのコレクション情報が存在します。 この場合、競合テーブルの処理モードパラメーターをエラーを無視して続行に設定します。
システムがデータの整合性をチェックした後、ビジネスをシャードクラスターインスタンスに切り替えます。
シャードをシャードクラスターインスタンスに追加する場合は、手順 3 を実行して新しいシャードのゾーンを指定します。
データベース内のコレクションの数が引き続き増加する場合は、手順 4 と手順 5 を実行します。 そうしないと、コレクションはプライマリシャードにのみ存在し、シャード内のコレクションの数が多くなります。 この場合、インスタンスは常にスタッター状態になるか、例外に遭遇します。
リスク
コマンドを実行して、多数のコレクションを含むデータベースを削除することはお勧めしません。dropDatabaseヒント:
dropDatabase コマンドが実行されると、WiredTiger ストレージエンジンはすべての必要なコレクションのメタデータと物理ファイルを非同期的に削除します。 これはセカンダリノードのプライマリ/セカンダリ同期に影響を与え、レプリケーションレイテンシが継続的に増加する可能性があります。 さらに、flowControl メカニズムが削除プロセスに関与するか、{writeConcern:majority} の設定を持つすべての書き込みが影響を受けます。
前述の問題を回避するには、次のいずれかの方法を使用できます。
必要なデータベース内のコレクションの 2 回の削除の間に適切な間隔を指定し、すべてのコレクションが削除された後に
dropDatabaseコマンドを実行します。DTS またはその他の移行ツールを使用して、保持するデータベースとコレクションを新しいインスタンスに移行し、カットオーバー移行が完了したら元のインスタンスを削除します。
インスタンスに適切なプライマリ/セカンダリレイテンシアラートアイテムを構成する必要があります。 インスタンスでこの問題が発生した場合は、チケットを送信することでテクニカルサポートに連絡できます。
まとめ
レプリカセットインスタンスのコレクションの総数は 10,000 を超えることはできません。 単一のコレクションのインデックスの総数が 15 を超える場合は、この値を下げます。
マルチテナントシステムをコレクションごとに分離する必要がある場合など、ビジネスで多数のコレクションが必要な場合は、コレクションを分割し、シャードクラスターインスタンスを使用します。
データベースに多数のコレクションがある場合は、チケットを送信することで、コレクションの数を減らし、ビジネスロジックを変更できます。