MongoDB の戦略的パートナーとして、Alibaba Cloud は、MongoDB 4.4 を提供する最初のクラウドベンダーとなりました。このバージョンは、2020 年 7 月 30 日の正式リリースに続き、2020 年 11 月に利用可能になりました。以前のメジャーバージョンとは異なり、今回のリリースは、ユーザーが直面する最も重要な課題に対処するために設計された包括的な機能強化です。
非表示インデックス
過剰なインデックスを保持すると、書き込みパフォーマンスが低下します。しかし、運用上の複雑さから、管理者は効率の悪い可能性のあるインデックスを削除することをためらいがちです。誤った削除がパフォーマンスの変動を引き起こす可能性があるためです。インデックスの再構築も、コストのかかる操作です。
この課題に対処するため、ApsaraDB for MongoDB と MongoDB は、戦略的パートナーシップの一環として非表示インデックス機能を共同開発しました。この機能により、collMod コマンドを使用してインデックスを非表示にし、クエリプランナーがそれを使用しないようにできます。一定期間の観察を経て、アプリケーションに悪影響がないことを確認した後、安全にインデックスを削除できます。
例:
db.runCommand( {
collMod: 'testcoll',
index: {
keyPattern: 'key_1',
hidden: true
}
} )
インデックスを非表示にしても、MongoDB のクエリプランナーから見えなくなるだけです。一意インデックス制約や TTL 有効期限など、インデックスの特殊な動作は変更されません。
インデックスは非表示の間も更新され続けます。再びインデックスが必要になった場合は、非表示を解除することで、すぐに利用可能になります。
改良可能なシャードキー
MongoDB シャードクラスターでは、特定のワークロードの下で優れたスケーラビリティを実現するために、適切に設計された シャードキー が不可欠です。しかし実際には、慎重に選択されたシャードキーでも、ワークロードの変化に伴って問題が発生する可能性があります。これにより、ジャンボチャンク (事前設定されたサイズを超えるチャンク) が発生したり、トラフィックのほとんどが単一のシャードに集中したりすることがあります。
MongoDB 4.0 以前では、コレクションのシャードキーとそれに対応する値は不変でした。バージョン 4.2 では、シャードキーの値を変更できるようになりましたが、そのプロセスには分散トランザクションに基づくシャード間のデータ移行が必要で、高いパフォーマンスオーバーヘッドが発生し、ジャンボチャンクやクエリホットスポットの問題を完全に解決することはできませんでした。たとえば、シャードキーが {customer_id:1} である注文テーブルを考えてみましょう。ビジネスが新しく、各顧客の注文数が少ない場合、このキーで十分かもしれません。しかし、ビジネスが成長し、主要顧客が大量の注文を蓄積すると、その顧客のデータは単一のシャード上でクエリホットスポットになります。注文は本質的に customer_id に紐づいているため、単に customer_id の値を変更しても、アクセスの偏りの問題は解決されません。
このようなシナリオでは、MongoDB 4.4 では refineCollectionShardKey コマンドを使用して、既存のシャードキーに 1 つ以上のサフィックスフィールドを追加できます。これにより、チャンク間でのドキュメントの分散が改善されます。上記の注文処理シナリオでは、refineCollectionShardKey コマンドを使用してシャードキーを {customer_id:1, order_id:1} に変更することで、単一のシャード上でのクエリホットスポットを防ぐことができます。
refineCollectionShardKey コマンドは、コンフィグサーバーノード上のメタデータのみを変更し、データ移行を必要としないため、パフォーマンスオーバーヘッドが非常に低くなっています。データの再分散は、通常の自動チャンク分割と移行を通じて徐々に行われます。シャードキーには対応するインデックスが必要なため、refineCollectionShardKey コマンドを実行する前に、新しいシャードキー用のインデックスを作成する必要があります。
すべてのドキュメントに新しいサフィックスフィールドが含まれているとは限らないため、MongoDB 4.4 では、欠落したシャードキーフィールドを暗黙的にサポートしています。つまり、新しく挿入されるドキュメントには、シャードキーのすべてのフィールドを含める必要はありません。ただし、この方法はジャンボチャンクを引き起こしやすいため、推奨されません。
複合ハッシュシャードキー
バージョン 4.4 以前では、MongoDB は複合ハッシュインデックスをサポートしていなかったため、単一フィールドのハッシュシャードキーのみを指定できました。これにより、シャード間でコレクションデータの分散が不均一になることがよくありました。
MongoDB 4.4 では、複合ハッシュインデックスのサポートが導入されました。複合インデックス内で単一のハッシュフィールドを指定できるようになりました。このフィールドは、プレフィックスまたはサフィックスとして、任意の位置に配置できます。これにより、複合ハッシュシャードキーのサポートが可能になります。
例:
sh.shardCollection(
"examples.compoundHashedCollection",
{ "region_id" : 1, "city_id": 1, "field1" : "hashed" }
)
sh.shardCollection(
"examples.compoundHashedCollection",
{ "_id" : "hashed", "fieldA" : 1}
)
複合ハッシュインデックスには、いくつかの利点があります。次の 2 つのシナリオを考えてみましょう。
-
法的または規制上の要件を遵守するために、MongoDB の ゾーンシャーディング 機能を使用して、特定の地理的リージョン内のシャード間でデータをできるだけ均等に分散させます。
-
コレクションのシャードキーに単調増加する値があります。たとえば、キーが
{customer_id:1, order_id:1}の場合、customer_idが常に増加し、アプリケーションが最新の顧客のデータに頻繁にアクセスする場合、トラフィックのほとんどが単一のシャードに向けられます。
複合ハッシュシャードキーのサポートがない場合、唯一の解決策は、必要なフィールドのハッシュを事前に計算し、その結果をドキュメント内の別のフィールドに保存してから、そのフィールドで範囲ベースのシャーディングを使用することでした。
バージョン 4.4 では、対象フィールドをハッシュとして指定するだけで、この問題を解決できます。2 番目のシナリオでは、シャードキーを {customer_id:'hashed', order_id:1} に設定することで、アプリケーションロジックが大幅に簡素化されます。
ヘッジドリード
ページの応答時間が遅いと、経済的損失が発生する可能性があります。Google の 調査レポート によると、ページの読み込みに 3 秒以上かかると、直帰率が 50% 増加します。これに対処するため、MongoDB 4.4 では、ヘッジドリードが導入されました。シャードクラスターでは、mongos ノードが、シャードの 2 つのレプリカセットメンバーに読み取りリクエストを送信し、最初のレスポンスを返すことができます。これにより、アプリケーションの P95 (95 パーセンタイル) および P99 (99 パーセンタイル) のレイテンシを削減できます。
ヘッジドリード機能は、リードプリファレンス パラメータの一部として提供され、操作ごとに設定できます。リードプリファレンスが nearest に設定されている場合、ヘッジドリードはデフォルトで有効になります。プリファレンスが primary に設定されている場合、ヘッジドリードはサポートされません。他のリードプリファレンスモードでは、以下に示すように、hedgeOptions を設定してヘッジドリードを明示的に有効にする必要があります。
db.collection.find({ }).readPref(
"secondary", // モード
[ { "datacenter": "B" }, { } ], // タグセット
{ enabled: true } // ヘッジオプション
)
ヘッジドリードを使用するには、mongos ノードでも readHedgingMode パラメータを on に設定してサポートを有効にする必要があります。
例:
db.adminCommand( { setParameter: 1, readHedgingMode: "on" } )
レプリケーション遅延の削減
MongoDB 4.4 では、プライマリ/セカンダリのレプリケーション遅延が削減されました。MongoDB では、この遅延が読み取りおよび書き込み操作に大きく影響する可能性があります。特定のシナリオでは、セカンダリノードは、読み取りと書き込みの処理を続けるために、プライマリノードからの増分更新を迅速にレプリケートして適用する必要があります。したがって、レプリケーション遅延が低いほど、一貫性が向上します。
ストリーミングレプリケーション
バージョン 4.4 以前では、セカンダリノードは増分更新を取得するために、アップストリームソースを継続的にポーリングする必要がありました。各ポーリングサイクルで、セカンダリはプライマリに getMore コマンドを送信して Oplog を読み取りました。データが利用可能な場合、プライマリは最大 16 MB のバッチを返しました。利用できない場合、セカンダリは awaitData オプションを使用して、不要な getMore のオーバーヘッドを削減しながら、新しい Oplog エントリが表示されるとすぐに取得できるようにしていました。単一の OplogFetcher スレッドがこのプル操作を処理し、各バッチの取得には完全なラウンドトリップタイム (RTT) が必要でした。ネットワーク状態が悪いレプリカセットでは、ネットワークレイテンシがレプリケーションパフォーマンスを深刻に制限していました。
バージョン 4.4 では、増分 Oplog エントリは、プルされるのではなく、セカンダリノードに継続的にストリーミングされます。ポーリング方式と比較して、これにより Oplog 取得の RTT が少なくとも半分節約されます。ストリーミングレプリケーションは、次の 2 つのシナリオでパフォーマンスを大幅に向上させます。
-
ユーザーの書き込み操作で ライトコンサーン を
"majority"に指定した場合、操作はレプリカセットメンバーの過半数からの確認応答を待つ必要があります。新しいレプリケーションメカニズムにより、高レイテンシのネットワーク環境でも、majority書き込みのパフォーマンスが平均 50% 向上する可能性があります。 -
ユーザーが因果整合性を使用して「自分の書き込みを読む」保証を確保する場合、アプリケーションは、プライマリからセカンダリノードへの Oplog エントリのタイムリーなレプリケーションに依存します。
同時インデックス作成
バージョン 4.4 以前では、プライマリノードがインデックスを完全に構築してから、セカンダリノードがそのプロセスを開始できました。セカンダリでのインデックス作成方法はバージョンによって異なり、Oplog への影響も異なっていました。
フォアグラウンドとバックグラウンドのインデックス構築を統合し、細かい粒度のロック (排他ロックはビルドの最初と最後にのみ保持される) を使用したバージョン 4.2 でさえ、インデックス作成による CPU と I/O のオーバーヘッドは、依然としてレプリケーションのレイテンシーを引き起こす可能性がありました。コレクションのメタデータを collMod コマンドで変更するなどの特定の操作は、Oplog の適用をブロックする可能性がありました。最悪の場合、セカンダリの遅延が大きくなりすぎてプライマリの Oplog エントリが上書きされ、セカンダリが リカバリ中 状態に強制的に移行させられることがありました。
バージョン 4.4 では、インデックス構築はプライマリノードとセカンダリノードで同時に行われます。これにより、この原因によるレプリケーション遅延のリスクが劇的に減少し、インデックス構築中でもセカンダリノードが最新のデータにアクセスできるようになります。
さらに、新しいインデックス構築メカニズムでは、インデックスが使用可能になる前に、投票ノードの過半数が構築を正常に完了する必要があります。これにより、ノードで利用可能なインデックスが異なることによって引き起こされる、読み取り/書き込み分離シナリオでのパフォーマンスの不一致も削減されます。
ミラー読み取り
ApsaraDB for MongoDB で観察される一般的なパターンは、3 ノードのレプリカセットインスタンスを持つ多くのユーザーが、すべての読み取りおよび書き込み操作をプライマリノードに向けていることです。セカンダリノードの 1 つはアイドル状態のままで、読み取りトラフィックを処理していません。時折発生するフェールオーバー時に、ユーザーはアプリケーションのレイテンシが顕著に増加することを経験し、それはしばらくしてから正常に戻ります。これは、新しく選出されたプライマリノードは以前に読み取りを処理しておらず、キャッシュがコールド状態であるためです。アプリケーションのアクセスパターンを認識しておらず、関連データをキャッシュしていません。その結果、読み取り操作により大量のキャッシュミスが発生し、ディスク読み取りが必要になり、アクセスレイテンシが増加します。この問題は、大量のメモリを搭載したインスタンスで特に顕著です。
この問題を解決するため、MongoDB 4.4 では、ミラー読み取り 機能が導入されました。プライマリノードは、読み取りトラフィックの設定可能な部分をセカンダリノードにミラーリングして、セカンダリのキャッシュを事前にウォームアップできます。これは、ノンブロッキングの「ファイア・アンド・フォーゲット」アクションであり、プライマリノードに目に見えるパフォーマンスへの影響はありませんが、セカンダリノードの負荷はわずかに増加します。
ミラーリングするトラフィックの割合は、mirrorReads パラメータを使用して動的に設定できます。デフォルトでは、トラフィックの 1% がミラーリングされます。
例:
db.adminCommand( { setParameter: 1, mirrorReads: { samplingRate: 0.10 } } )
また、以下に示すように、db.serverStatus( { mirroredReads: 1 } ) コマンドを使用して、ミラー読み取りに関連する統計を表示することもできます。
SECONDARY> db.serverStatus( { mirroredReads: 1 } ).mirroredReads
{ "seen" : NumberLong(2), "sent" : NumberLong(0) }
再開可能な初期同期
バージョン 4.4 以前では、セカンダリノードが初期同期を実行中にネットワークの変動によって接続が切断された場合、セカンダリはプロセス全体を最初からやり直す必要がありました。大規模なデータセットの場合、これにより数時間が無駄になり、運用に大きな影響を与える可能性がありました。
バージョン 4.4 では、セカンダリノードは中断された場所から初期同期プロセスを再開できるようになりました。一時的なエラーの後に接続を再確立できない場合、システムは新しい同期ソースを選択し、新しい初期同期を開始します。再試行のデフォルトタイムアウトは 24 時間で、起動時に replication.initialSyncTransientErrorRetryPeriodSeconds パラメータを使用して変更できます。
初期同期中に一時的でないエラーによって中断された場合、完全な同期プロセスを最初から再開する必要があることに注意してください。
時間ベースの Oplog 保持
MongoDB の Oplog は、すべてのデータ変更操作を記録します。これはレプリケーションだけでなく、増分バックアップ、データ移行、データサブスクリプションなどのシナリオにも使用され、MongoDB データエコシステムの重要な部分となっています。
Oplog は、固定サイズコレクション として実装されています。MongoDB はバージョン 3.6 以降、replSetResizeOplog コマンドによる Oplog の動的なサイズ変更をサポートしていますが、サイズベースの保持は、ダウンストリームコンシューマーの時間ベースのニーズを正確に反映しないことがよくあります。次のシナリオを考えてみましょう。
-
午前 2:00 から午前 4:00 までセカンダリノードのメンテナンスを実行する予定です。この間、アップストリームプライマリの Oplog がクリアされないようにする必要があります。クリアされると、完全な再同期がトリガーされます。
-
ダウンストリームデータサブスクリプションコンポーネントがエラーにより停止する可能性がありますが、3 時間以内に回復してデータのプルを再開することが期待されます。アップストリームソースからの増分データの損失を防ぐ必要があります。
ほとんどのアプリケーションシナリオでは、Oplog を特定の期間保持する必要があります。ただし、その期間中に生成される Oplog データの量を判断することは困難です。
バージョン 4.4 では、storage.oplogMinRetentionHours パラメータを使用して Oplog エントリの最小保持期間を定義できるようになりました。replSetResizeOplog コマンドを使用して、この値をオンラインで変更することもできます。例:
// 最初に、現在設定されている値を表示します
db.getSiblingDB("admin").serverStatus().oplogTruncation.oplogMinRetentionHours
// 変更
db.adminCommand({
"replSetResizeOplog" : 1,
"minRetentionHours" : 2
})
Union
複数コレクションのクエリの場合、バージョン 4.4 以前では、SQL の left outer join に似た $lookup ステージ のみが提供されていました。バージョン 4.4 では、SQL の union all のように機能する $unionWith ステージ が導入されました。これにより、2 つ以上のコレクションからのデータを単一の結果セットに結合し、さらにクエリとフィルタリングを行うことができます。$lookup ステージ とは異なり、$unionWith ステージ はシャードコレクションをサポートします。アグリゲーションパイプラインで複数の $unionWith ステージ を使用して、複数のコレクションからデータを集約できます。構文は次のとおりです。
{ $unionWith: { coll: "<collection>", pipeline: [ <stage1>, ... ] } }
$unionWith ステージ内でパイプラインを指定して、Union の前に他のコレクションからのデータをフィルタリングまたは変換することもできます。これにより、非常に柔軟性が高まります。たとえば、ビジネスが注文データを月ごとに個別のコレクションに保存しているとします。第 2 四半期のデータは次のようになります。
db.orders_april.insertMany([
{ _id:1, item: "A", quantity: 100 },
{ _id:2, item: "B", quantity: 30 },
]);
db.orders_may.insertMany([
{ _id:1, item: "C", quantity: 20 },
{ _id:2, item: "A", quantity: 50 },
]);
db.orders_june.insertMany([
{ _id:1, item: "C", quantity: 100 },
{ _id:2, item: "D", quantity: 10 },
]);
第 2 四半期の各製品の総売上をリストする必要があるとします。バージョン 4.4 以前では、すべてのデータをアプリケーションに読み込んで、そこで集計を実行するか、データウェアハウスに依存する必要がありました。バージョン 4.4 では、単一の集計クエリでこれを解決できます。
db.orders_april.aggregate( [
{ $unionWith: "orders_may" },
{ $unionWith: "orders_june" },
{ $group: { _id: "$item", total: { $sum: "$quantity" } } },
{ $sort: { total: -1 }}
] )
カスタム集計式
バージョン 4.4 以前では、find コマンドで $where 演算子 を使用するか、MapReduce 機能を使用して、サーバー上でカスタム JavaScript を実行し、複雑なクエリを実行できました。ただし、これらの機能はアグリゲーションパイプラインと統合されていませんでした。
バージョン 4.4 では、MongoDB はアグリゲーションパイプライン用に $accumulator および $function 演算子を導入しました。これらの演算子は、$where 演算子 および MapReduce を置き換えます。これにより、サーバー側の JavaScript を使用してカスタム集計式を定義できます。これにより、複雑なクエリ機能がアグリゲーションパイプラインに統合され、API の一貫性とユーザーエクスペリエンスが向上すると同時に、パイプラインの実行モデルが活用されます。
$accumulator 演算子は MapReduce に似ています。最初に init 関数を使用して初期状態を定義し、次に指定された accumulate 関数を使用して各入力ドキュメントの状態を更新します。必要に応じて、merge 関数も実行します。
たとえば、シャードコレクションで $accumulator 演算子を使用する場合、異なるシャードからの結果を merge する必要があります。finalize 関数が指定されている場合、すべての入力ドキュメントが処理された後に適用され、状態が最終出力に変換されます。
$function と $where 演算子 は、ほぼ同じ機能を持っています。ただし、$function 演算子は、他のアグリゲーションパイプライン演算子と一緒に使用できるため、より強力です。さらに、$function 演算子 を find コマンドで $expr 演算子 と一緒に使用できます。これは、$where 演算子 と同等です。MongoDB は公式ドキュメントで、$function 演算子 を優先的に使用することを推奨します。
その他の使いやすさの機能強化
$accumulator および $function 演算子に加えて、MongoDB 4.4 では、他にもいくつかの新しいアグリゲーションパイプライン演算子が追加されました。これには、文字列操作、配列の最初と最後の要素の取得、ドキュメントまたはバイナリ文字列のサイズの取得のための演算子が含まれます。詳細については、以下の表を参照してください。
|
演算子 |
説明 |
|
$accumulator |
ユーザー定義の アキュムレータ演算子 の結果を返します。 |
|
$binarySize |
指定された文字列またはバイナリデータのサイズをバイト単位で返します。 |
|
$bsonSize |
BSON エンコードされたドキュメントのサイズをバイト単位で返します。 |
|
$first |
配列の最初の要素を返します。 |
|
$function |
カスタム集計式を定義します。 |
|
$last |
配列の最後の要素を返します。 |
|
$isNumber |
指定された式が整数、10 進数、double、または long と評価される場合、 |
|
$replaceOne |
指定されたパターンに一致する部分文字列の最初の出現を置き換えます。 |
|
$replaceAll |
指定されたパターンに一致する部分文字列のすべての出現を置き換えます。 |
接続モニタリング・プーリング
詳細については、「MongoDB 公式ドキュメント」をご参照ください。
グローバルな読み取りと書き込みの懸念
4.4 より前のバージョンでは、操作で明示的に readConcern または writeConcern が指定されていない場合、MongoDB はデフォルトの動作を適用していました。たとえば、 readConcern のデフォルトは local で、 writeConcern のデフォルトは {w: 1} でした。このデフォルトの動作を変更することはできませんでした。すべての insert 操作で writeConcern を {w: "majority"} にしたい場合は、操作ごとにアプリケーションコードで明示的に指定する必要がありました。
バージョン 4.4 では、setDefaultRWConcern コマンドを使用して、グローバルなデフォルトの readConcern および writeConcern を設定できます。 例:
db.adminCommand({
"setDefaultRWConcern" : 1,
"defaultWriteConcern" : {
"w" : "majority"
},
"defaultReadConcern" : { "level" : "majority" }
})
また、getDefaultRWConcern コマンドを使用して、現在のデフォルトの readConcern と writeConcern を取得することもできます。
さらに MongoDB 4.4 では、操作の readConcern または writeConcern 設定のソース (発生元) が、低速クエリログおよび診断ログに記録されます。両方に共通する発生元は次のとおりです。
|
来歴 |
説明 |
|
clientSupplied |
アプリケーションによって指定されます。 |
|
customDefault |
ユーザーが |
|
implicitDefault |
サーバーのデフォルトで、他の設定が行われていない場合に使用されます。 |
writeConcern には、追加の来歴が 1 つあります。
|
来歴 |
説明 |
|
getLastErrorDefaults |
レプリカセットの |
新しい MongoDB シェル (ベータ)
MongoDB Shell は、MongoDB 管理者が最も頻繁に使用するツールの 1 つです。バージョン 4.4 では、構文の強調表示、インテリジェントなオートコンプリート、読みやすいエラーメッセージなど、ユーザーフレンドリーな機能を含む新しいバージョンの MongoDB Shell が導入されました。このベータリリースには、サポートされていないコマンドがいくつかあり、試用とフィードバックを目的としています。
function topActors(howMany = 5) {
return db.movies.aggregate([
{ $unwind: '$cast'},
{ $group: {_id: '$cast', movieCount: { $sum: 1 } } },
{ $sort: {movieCount: -1} },
{ $limit: howMany }
])
}
> topActors(3)
[
{ _id: 'Gérard Depardieu', movieCount: 68 },
{ _id: 'Robert De Niro', movieCount: 60 },
{ _id: 'Michael Caine', movieCount: 53 }
]
> db.movies.fnd()
TypeError: db.movies.fnd is not a function
> db.movies.find.help()
db.collection.find(query, projection):
Selects documents in a collection or view.
For more information on usage: https://docs.mongodb.com/manual/reference/method/db.collection.find
> db.movies.find({year: {$gt: 2016}})
まとめ
4.4 リリースは、主にメンテナンスバージョンであり、多くの機能強化がもたらされました。説明した機能に加えて、$indexStats の改善、接続確立を高速化するための TCP Fast Open のサポート、最適化されたインデックス削除など、多くの小さな最適化があります。新しい構造化ログ形式 LogV2 や新しいセキュリティメカニズムなど、より大きな機能強化もあります。詳細については、公式の「リリースノート」をご参照ください。