ApsaraDB for MongoDB 4.4 は 2020 年 11 月にリリースされ、Alibaba Cloud は世界で初めて MongoDB 4.4 を提供するクラウドプロバイダーとなりました。MongoDB 4.4 は、2020 年 7 月 30 日に正式リリースされました。本バージョンでは、これまでのリリースで最も多く要望されていた改善点に対応しており、インデックス管理、シャーディングの柔軟性、レプリケーションの同期性能、および集約(アグリゲーション)機能が強化されています。
本トピックでは、各新機能の概要、動作仕組み、および有効化方法について説明します。
非表示インデックス
必要と判明したインデックスを削除すると、再構築に多大な時間とリソースを要するため、コストが高くなります。非表示インデックス(Hidden Index)を使用すると、インデックスを実際に削除せずに、その削除による影響を評価できます。
Alibaba Cloud と MongoDB は戦略的パートナーとして共同で、非表示インデックス機能を開発しました。
collMod コマンドを使用してインデックスを非表示にします:
db.runCommand({
collMod: "testcoll",
index: {
keyPattern: "key_1",
hidden: false
}
})クエリプランナは非表示インデックスを無視するため、クエリは即座にそのインデックスを使用しなくなります。ただし、インデックスはバックグラウンドで引き続き更新されるため、以下の点に注意が必要です:
一意性制約(Unique Index Constraints)および生存時間(TTL)の有効期限は、引き続き適用されます。
インデックスを再表示(Unhide)すると、即座に利用可能になります — 再構築は不要です。
インデックスを非表示にしてもエラーが発生しない場合、そのインデックスは安全に削除できます。
拡張可能なシャードキー
| バージョン | シャードキーの動作 |
|---|---|
| 4.0 以前 | シャードキーおよびシャードキー値は不変(immutable) |
| 4.2 | シャードキー値の変更は可能だが、クロスシャード間のデータ移行を伴う |
| 4.4 | refineCollectionShardKey |
ワークロードの変化に伴い、当初適切に選択されたシャードキーでもホットスポットが発生することがあります。たとえば、初期段階では {customer_id: 1} というシャードキーが有効に機能しますが、主要顧客の注文数が増加すると、その顧客に対するすべてのクエリが単一のシャードに集中し、ホットスポットを引き起こします。customer_id 単体の変更ではこの問題を解決できません。なぜなら、注文はそのフィールドに紐付けられているためです。
refineCollectionShardKey コマンドを使用すると、データ移行を伴わずに既存のシャードキーにサフィックスフィールドを追加できます:
db.adminCommand({
refineCollectionShardKey: "orders",
key: { customer_id: 1, order_id: 1 }
})このコマンドは、設定サーバー上のメタデータのみを変更します。データはチャンクの分割または移行に伴って段階的に再配分されます。コマンド実行前に、新しいシャードキーをサポートするインデックスを作成してください。
シャードコレクション内のドキュメントは、refineCollectionShardKey によって追加されたサフィックスフィールドを欠落させることができます。これはサポートされていますが、ジャンボチャンク(jumbo chunk)を生成する可能性があるため、必要最小限の場合にのみ使用してください。複合ハッシュシャードキー
| バージョン | ハッシュインデックスのサポート |
|---|---|
| 4.4 より前 | 単一フィールドのハッシュインデックスのみ |
| 4.4 | ハッシュフィールドをプレフィックスまたはサフィックスとする複合インデックス |
複合ハッシュインデックスは、以下の 2 つのシャーディング課題を解決します:
ゾーンコンプライアンス:地理的なデータ所在要件を満たしつつ、ゾーン内のシャード間でデータを均等に分散します。
単調増加キー:
customer_idのように単調に増加するフィールドは、新規書き込みを常に同一のシャードにルーティングするため、パフォーマンスボトルネックを引き起こします。これをハッシュ化することで、書き込みを均等に分散できます。
例 — 複合シャードキー内で customer_id フィールドをハッシュ化する:
sh.shardCollection(
"examples.orders",
{ customer_id: "hashed", order_id: 1 }
)複合ハッシュインデックスが導入される前は、アプリケーション側でハッシュ値を手動計算し、専用フィールドに格納したうえで、そのフィールドに対して範囲ベースのシャーディングを行う必要がありました。この手法には追加のアプリケーションロジックが必要でした。複合ハッシュインデックスは、このような回避策を不要にします。
ヘッジドリード
ヘッジドリード(Hedged Reads)は、シャードクラスターにおける末尾レイテンシ(P95・P99)を低減します。読み取り操作が発行されると、mongos は対象シャードごとに 2 つのレプリカセットメンバーにリクエストを送信し、先に応答を返した方の結果を返します。
ヘッジドリードは、読み取りプリファレンス(read preference)設定の一部として、操作単位で有効化されます。読み取りプリファレンスが primary に設定されている場合は、ヘッジドリードはサポートされません。ステップ 1: mongos ノードでヘッジドリードを有効化します。
db.adminCommand({ setParameter: 1, readHedgingMode: "on" })ステップ 2: 操作単位で読み取りプリファレンスを設定します。プリファレンスが nearest の場合、ヘッジドリードは自動的に有効化されます。その他のモード(primary を除く)では、hedgeOptions を設定します:
db.collection.find({}).readPref(
"secondary",
[{ datacenter: "B" }, {}],
{ enabled: true }
)P95 および P99 レイテンシメトリックは、過去 10 秒間に実行されたリクエストのうち、最も遅い上位 5 % および 1 % の平均レイテンシーを表します。
ストリーミングレプリケーション
| バージョン | レプリケーション方式 |
|---|---|
| 4.4 より前 | セカンダリがプライマリから getMore を使用してポーリング(プルモデル) |
| 4.4 | プライマリがセカンダリへ継続的な oplog ストリームをプッシュ(プッシュモデル) |
ポーリングモデルでは、各バッチフェッチごとにプライマリとセカンダリ間の往復レイテンシ(RTT)が発生します。また、1 バッチあたりの最大サイズは 16 MB です。高レイテンシネットワーク環境下では、この方式によりレプリケーション性能が低下します。
ストリーミングレプリケーションでは、各バッチあたり少なくとも半分の RTT を節約できます。これには以下の 2 つの実用的なメリットがあります:
`"majority"` 書き込み: 高遅延ネットワークでは、ストリーミングレプリケーションにより、
"majority"書き込み操作の平均パフォーマンスが 50% 向上します。これらの書き込み操作は、複数ノードへのレプリケーション完了を待機するためです。因果的一貫性のあるセッション:セカンダリから自身の書き込みを読み取るには、セカンダリがプライマリの oplog を即時にレプリケート済みである必要があります。ストリーミングレプリケーションにより、この動作が確実になります。
同時インデックス作成
| バージョン | インデックス作成の動作 |
|---|---|
| 4.4 より前 | インデックスはまずプライマリで作成され、その後セカンダリへレプリケートされる |
| 4.4 | インデックスはプライマリおよびすべてのセカンダリで同時に作成される |
インデックスをプライマリで作成後、セカンダリへレプリケートする方式では、レプリケーションラグが発生します。また、インデックス作成時の CPU および I/O 負荷、あるいは collMod などの特殊操作により、oplog がブロックされたり、セカンダリが Recovering 状態に移行したりする可能性があります。
同時インデックス作成では、インデックス構築中にセカンダリを同期状態に保つことができます。そのため、構築中もセカンダリは読み取り要求に応答可能です。インデックスは、投票可能なメンバーの過半数が構築を完了した時点で初めて利用可能となります。これにより、インデックスの差異による読み書き分離(read/write splitting)時の不整合な結果の返却を防止できます。
ミラードリード
フェールオーバー後、新たに選出されたプライマリはキャッシュが未使用(cold cache)の状態で起動します。そのため、着信する読み取り要求はキャッシュミスを引き起こし、ディスクからデータを再読み込みする必要があり、キャッシュが十分にウォームアップするまで顕著なレイテンシのスパイクが発生します。この現象は、大容量メモリを搭載したインスタンスで特に顕著です。
ミラードリード(Mirrored Reads)は、プライマリが選択可能なセカンダリに対して読み取り操作の一部をミラー(転送)することで、セカンダリのキャッシュを事前にウォームアップします。セカンダリがプライマリに選出された際には、すでにキャッシュがウォームアップ済みとなります。
ミラードリードは「ファイア・アンド・フォーゲット」方式であり、プライマリは応答を待たないため、プライマリのパフォーマンスには一切影響しません。ただし、セカンダリのワークロードは増加します。
サンプリング率は mirrorReads パラメーターで設定できます(デフォルト:1 %):
db.adminCommand({ setParameter: 1, mirrorReads: { samplingRate: 0.10 } })ミラードリードの統計情報を確認するには:
SECONDARY> db.serverStatus({ mirroredReads: 1 }).mirroredReads
// 例:
// { "seen": NumberLong(2), "sent": NumberLong(0) }再開可能な初期同期
| バージョン | 初期同期中のネットワーク切断時の動作 |
|---|---|
| 4.4 より前 | 初期同期全体を最初から再開する必要がある |
| 4.4 | 再開の 2 回目の試行。リトライ期間内に再開が失敗した場合のみ再起動します。 |
デフォルトでは、セカンダリは 24 時間の間、再開を試行します。replication.initialSyncTransientErrorRetryPeriodSeconds を使用して、再試行期間を調整するか、同期元を変更できます。
ネットワークエラーが一時的でない場合(non-transient)、セカンダリは必ず初期同期全体を再開する必要があります。
時間ベースの oplog 保持
oplog は、すべてのデータ変更操作を記録する固定サイズのコレクションです。レプリケーション、増分バックアップ、データ移行、およびチェンジストリームコンシューマーなどに使用されます。MongoDB 3.6 以降では、replSetResizeOplog を使用して、oplog の最大サイズ(バイト単位)を調整できますが、特定のタイムウィンドウ内の oplog エントリが確実に保持されることを保証するものではありません。
時間ベースの保持は、以下のようなシナリオに対応します:
セカンダリが 02:00~04:00 の間、定期メンテナンスのために停止した場合。oplog の保持期間が不十分だと、再接続時にプライマリがフル同期を開始してしまう可能性があります。
チェンジストリームコンシューマーが最大 3 時間オフラインになった場合。その期間中に oplog がロールオーバーすると、コンシューマーは中断地点から再開できなくなります。
最小保持期間は、storage.oplogMinRetentionHours を使用して設定します:
// 現在設定されている値を確認
db.getSiblingDB("admin").serverStatus().oplogTruncation.oplogMinRetentionHours
// 最小保持期間を 2 時間に設定
db.adminCommand({
replSetResizeOplog: 1,
minRetentionHours: 2
})$unionWith
| ステージ | SQL との等価表現 | シャードコレクションのサポート |
|---|---|---|
$lookup | LEFT OUTER JOIN | 限定的 |
$unionWith | UNION ALL | はい |
$unionWith は、あるコレクションのパイプライン出力を別のコレクションのパイプライン出力と結合します。複数の $unionWith ステージを連鎖させることで、多数のコレクションにわたる結果をマージできます。
例: ある事業では、注文データを月ごとに 3 つのコレクションに分割して保存しています。四半期の売上レポートを生成するには:
// サンプルデータを挿入
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 }
]);
// 3 つのコレクション全体を対象に集約
db.orders_april.aggregate([
{ $unionWith: "orders_may" },
{ $unionWith: "orders_june" },
{ $group: { _id: "$item", total: { $sum: "$quantity" } } },
{ $sort: { total: -1 } }
])期待される出力:
[
{ _id: "A", total: 150 },
{ _id: "C", total: 120 },
{ _id: "B", total: 30 },
{ _id: "D", total: 10 }
]$unionWith が導入される前は、コレクション横断の集約処理を行うために、アプリケーション層で全データを読み込むか、データウェアハウスを利用する必要がありました。
各 $unionWith ステージは、pipeline パラメーターも受け付けるため、結合前に各コレクションのデータをフィルターまたは変換できます。
カスタム集約式
| アプローチ | 制限事項 |
|---|---|
$where 演算子 | アグリゲーションパイプラインを使用できない |
| MapReduce | アグリゲーションパイプラインを使用できない |
$accumulator / $function(4.4) | アグリゲーションパイプライン内で実行される |
MongoDB 4.4 では、アグリゲーションパイプライン内でカスタム JavaScript ロジックを定義できる 2 つの演算子が追加されました。
$accumulator — MapReduce と同様に動作します。カスタム状態管理を用いてドキュメント間でデータを集約する際に使用します:
init:各グループの状態を初期化します。accumulate:各ドキュメントごとに状態を更新します。merge:シャードコレクション上で実行される場合、状態を統合します。finalize(任意):統合された状態を最終結果に変換します。
$function — $where と同様に動作しますが、他のパイプラインステージと統合可能です。$expr とともに find コマンドで使用することで、$where と同等の機能を直接実現できます。
{ $accumulator: {
init: function() { return { count: 0, sum: 0 }; },
accumulate: function(state, value) {
return { count: state.count + 1, sum: state.sum + value };
},
accumulateArgs: ["$price"],
merge: function(s1, s2) {
return { count: s1.count + s2.count, sum: s1.sum + s2.sum };
},
finalize: function(state) { return state.sum / state.count; },
lang: "js"
} }新しいアグリゲーションパイプライン演算子
$accumulator および $function に加えて、MongoDB 4.4 では以下の演算子が導入されています:
| 演算子 | 説明 |
|---|---|
$accumulator | ユーザー定義の累算器の結果を返す |
$binarySize | 文字列またはバイナリオブジェクトのサイズ(バイト単位)を返す |
$bsonSize | BSON エンコード済みドキュメントのサイズ(バイト単位)を返す |
$first | 配列内の最初の要素を返す |
$function | JavaScript でカスタム集約式を定義する |
$last | 配列内の最後の要素を返す |
$isNumber | 式が整数、10 進数、double、または long の場合に true を返し、その他の BSON 型、null、または欠落フィールドの場合は false を返す |
$replaceOne | 一致した文字列の最初の出現箇所を置き換える |
$replaceAll | 一致した文字列のすべての出現箇所を置き換える |
接続監視および接続プーリング
MongoDB 4.4 では、ドライバー経由での接続プールの設定および監視に関する API が標準化されました。接続のオープン/クローズ、およびプールのクリアといった接続プールイベントを、標準イベント API を使用して購読できます。
設定可能なプールオプションには以下が含まれます:
接続の最大数および最小数
接続がアイドル状態で維持できる最大時間
スレッドが利用可能な接続を待機する最大時間
オプションの完全なリストについては、「接続プールオプション」をご参照ください。
グローバルな読み取りおよび書き込みコンサーン
| バージョン | デフォルトの読み取り/書き込みコンサーン動作 |
|---|---|
| 4.4 より前 | readConcern: local、writeConcern: {w: 1} — 固定値であり、設定不可 |
| 4.4 | setDefaultRWConcern |
setDefaultRWConcern を使用してグローバルデフォルトを設定します:
db.adminCommand({
setDefaultRWConcern: 1,
defaultWriteConcern: { w: "majority" },
defaultReadConcern: { level: "majority" }
})getDefaultRWConcern を使用して、現在のグローバルデフォルトを取得します:
db.adminCommand({ getDefaultRWConcern: 1 })MongoDB 4.4 では、スロークエリおよび診断ログにおいて、各読み取り/書き込みコンサーンの由来(provenance)も記録されます:
| 由来 | 説明 |
|---|---|
clientSupplied | アプリケーションで指定されたもの |
customDefault | setDefaultRWConcern |
implicitDefault | サーバーのデフォルト。他のコンサーンが指定されていない場合に適用される |
getLastErrorDefaults | (書き込みコンサーンのみ)レプリカセットの settings.getLastErrorDefaults フィールドから派生したもの |
新しい MongoDB Shell(ベータ版)
MongoDB 4.4 には、開発者体験を向上させる新しい MongoDB Shell が同梱されています。主な特長は以下のとおりです:
構文ハイライト
コマンドの自動補完
より明確なエラーメッセージ
新しいシェルはベータ版です。追加のコマンド開発が進行中のため、本番環境向けのスクリプトではなく、探索およびテスト目的でのご利用を推奨します。

まとめ
MongoDB 4.4 は、信頼性、運用の柔軟性、および開発者生産性に焦点を当てたメンテナンスリリースです。上記の機能に加えて、以下の機能も含まれています:
$indexStatsアグリゲーション演算子の最適化TCP Fast Open(TFO)接続のサポート
インデックス削除の最適化
構造化ログ(
logv2)新しい認証メカニズム
変更点の完全なリストについては、「MongoDB 4.4 のリリースノート」をご参照ください。