Docs Menu
Docs Home
/ /
CRUD の概念

読み取りの分離、整合性、最新性

読み取り保証(read concern)に応じて、クライアントは、書込みが 耐久性を持つ前に、書込みの結果を確認できます。

マルチドキュメントトランザクションの操作では、トランザクションがコミットされると、トランザクション内のすべてのデータ変更が保存され、トランザクションの外部に表示されます。つまり、トランザクションが他の変更をロールバックしながら、一部の変更をコミットすることはありません。

トランザクションがコミットされるまで、トランザクションで行われたデータ変更はトランザクションの外部には表示されません。

ただし、トランザクションが複数のシャードに書き込む場合、すべての外部読み取り操作が、コミットされたトランザクションの結果がシャード全体で表示されるまで待機する必要はありません。たとえば、トランザクションがコミットされ、書込み 1 がシャード A で表示されているものの、書込み 2 がシャード B にまだ表示されていない場合、読み取り保証(read concern) "local" での外部読み取りは、書き込み 2 を見ることなく書き込み 1 の結果を読み取ることができます。

読み取りコミットなしとは、デフォルトの分離レベルであり、 mongodスタンドアロン インスタンスだけでなく、レプリカセットとシャーディングされたクラスターにも適用されます。

書き込み操作は単一のドキュメントに対してアトミックです。つまり、書き込みがドキュメント内の複数のフィールドを更新する場合、読み取り操作では一部のフィールドのみが更新されたドキュメントが表示されることはありません。ただし、 クライアントには部分的にアップデートされたドキュメントは表示されない場合がありますが 、読み取りコミットがない場合は、同時に実行された読み取り操作でも、変更が永続化される前に更新されたドキュメントが表示される可能性があります。

スタンドアロンの mongod インスタンスでは、1 つのドキュメントに対する一連の読み取り操作と書込み操作をシリアル化できます。レプリカセットでは、1つのドキュメントに対する一連の読み取りおよび書込み操作をシリアル化できるのは、ロールバックがない場合だけです。

単一の書込み操作(例: db.collection.updateMany())は複数のドキュメントを変更します。各ドキュメントの変更は不可分ですが、操作全体は不可分ではありません。

複数のドキュメントへの書込み操作を実行する場合、1 回の書込み操作でも複数の書込み操作でも、他の操作がインターリーブすることがあります。

MongoDB は(単一または複数のコレクション内の)複数のドキュメントへの読み取りと書込みにアトミック性を必要とする状況で、レプリカセットやシャーディングされたクラスターでのトランザクションを含む分散トランザクションをサポートします。

詳細についてはトランザクションを参照してください。

重要

ほとんどの場合、分散トランザクションでは 1 つのドキュメントの書き込み (write) よりもパフォーマンス コストが高くなります。分散トランザクションの可用性は、効果的なスキーマ設計の代わりにはなりません。多くのシナリオにおいて、非正規化されたデータモデル(埋め込みドキュメントと配列)が引き続きデータやユースケースに最適です。つまり、多くのシナリオにおいて、データを適切にモデリングすることで、分散トランザクションの必要性を最小限に抑えることができます。

トランザクションの使用に関するその他の考慮事項(ランタイム制限や oplog サイズ制限など)については、「本番環境での考慮事項」も参照してください。

マルチドキュメントの書込み操作を分離しない場合、MongoDB は次の動作を示します。

  1. 非ポイント・イン・タイム読み取り操作。ドキュメントの読み取り操作が時刻t 1に開始されるとします。その後、書込み操作によって、後の時間 t 2 にドキュメントの 1 つに対する更新がコミットされます。読者はドキュメントの更新されたバージョンを参照できますが、データの特定時点のスナップショットは参照できません。

  2. シリアル化できない操作。読み取り操作によってドキュメント d 1 が時刻 t 1 に読み取られ、書込み操作によってその後の時刻 t 3d 1 が更新されるとします。これにより、読み取りと書込みの依存関係が導入され、操作をシリアル化する場合、読み取り操作が書込み操作に先行する必要があります。しかし、書込み操作によってドキュメント d 2 が時刻 t 2 に更新され、その後読み取り操作によって時刻 t 4d 2 が読み取られるとします。これにより、書込みと読み取りの依存関係が導入され、シリアル化可能なスケジュールで読み取り操作が書込み操作のに実行されることが必要になります。依存関係のサイクルが存在するため、シリアル化が不可能になります。

  3. 読み取り操作の過程で更新された一致するドキュメントが読み取られない場合があります。

MongoDB カーソルは、状況によっては、同じドキュメントを複数回返すことがあります。カーソルによってドキュメントが返されると、他の操作がクエリとインターリーブする可能性があります。これらの操作のいずれかによって、クエリで使用されるインデックスのインデックス付きフィールドが変更されると、カーソルは同じドキュメントを複数回返す可能性があります。

ユニークインデックスを使用するクエリでは、重複した値が返される場合があります。ユニークインデックスを使用するカーソルが、同じユニークな値を共有するドキュメントの削除と挿入をインターリーブする場合、カーソルは異なるドキュメントから同じユニークな値を 2 回返すことがあります。

読み取り分離の使用を検討してください。詳しくは、「懸念事項を読む"snapshot"」を参照してください。

MongoDB は、スタンドアロンの mongod インスタンスとレプリカ セットに対して、デフォルトで単調な書込み保証を提供します。

単調な書込みとシャーディングされたクラスターについては、「因果整合性」を参照してください。

バージョン 3.4 で追加

プライマリでの読み取りおよび書込み操作の場合、"linearizable" 読み取り保証(read concern)を使用して読み取り操作を発行し、"majority"書込み保証(write concern)を使用して書込み操作を発行すると、1 つのスレッドがこれらの操作をリアルタイムで実行したかのように、複数のスレッドが 1 つのドキュメントに対して読み取りおよび書込みを実行できるようになります。つまり、これらの読み取りおよび書込みの対応するスケジュールは線形化可能とみなされます。

Tip

バージョン 3.6 の新機能

ある操作が先行する操作に論理的に依存する場合、それらの操作の間には因果関係があります。たとえば、指定された条件に基づいてすべてのドキュメントを削除する書込み操作と、削除操作を検証する後続の読み取り操作には因果関係があります。

因果的に一貫性のあるセッションでは、MongoDB は因果関係を尊重する順序で因果操作を実行し、クライアントは因果関係と一貫性のある結果を観察します。

因果一貫性を提供するために、MongoDB はクライアント セッションで因果一貫性を有効にします。因果的に一貫性のあるセッションとは、"majority" 読み取りコンテントを持つ読み取り操作の関連シーケンスと、"majority" 書込みコンテントを持つ書込み操作の関連シーケンスに、順序によって反映される因果関係があることを示します。アプリケーションでは、クライアント セッションで一度に 1 つのスレッドのみがこれらの操作が実行されるようにする必要があります。

因果関係のある操作の場合:

  1. クライアントがクライアント セッションを開始します。

    重要

    クライアント セッションでは、次の因果整合性のみが保証されます。

    • "majority" を使用した読み取り操作、つまり、返されたデータは、レプリカ セット ノードの大多数によって確認されており、耐久性があります。

    • "majority"書込み保証(write concern)付き書込み(write)操作つまり、レプリカセットの投票ノードの過半数に操作が適用済みであることを確認するようリクエストする書込み操作です。

    因果関係の一貫性と読み取りと書込み保証(write concern)に関するさまざまな問題について詳しくは、「因果整合性、読み取り保証、書込み保証」を参照してください。

  2. クライアントが、"majority" 読み取り保証(read concern)を持つ読み取り操作と"majority" 書込み保証(write concern)を持つ書込み操作のシーケンスを発行するため、クライアントは各操作にセッション情報を含めることができます。

  3. セッションに関連付けられた "majority" 読み取り保証(read concern)持つ読み取り操作と "majority" 書込み保証(write concern)を持つ書込み操作ごとに、操作にエラーがあった場合でも、MongoDB は操作時間とクラスター時間を返します。クライアント セッションは操作時間とクラスター時間を記録します。

    注意

    MongoDB では、未確認のw: 0)書込み操作の操作時間とクラスター時間が返されません。未確認の書込みは、因果関係を意味するものではありません。

    MongoDB は、クライアント セッションでの読み取り操作と確認済みの書き込み操作の操作時間とクラスター時間を返しますが、因果一貫性を保証できるのは、 "majority" 読み取り保証(read concern)を持つ読み取り操作と "majority" 書込み保証(write concern)を持つ書き込み操作のみです。詳細については、「因果整合性、読み取り保証、書込み保証」を参照してください。

  4. 関連付けられているクライアント セッションは、これら 2 つの時間フィールドを追跡します。

    注意

    異なるセッション間でも、操作は因果的に一貫している場合があります。MongoDB ドライバーとmongosh 、クライアント セッションの操作時間とクラスター時間を進めるメソッドを提供します。そのため、クライアントは、1 つのクライアント セッションのクラスター時間と操作時間を、別のクライアント セッションの操作と一致するように進めることができます。

次の表は、"majority" 読み取り保証(read concern)を持つ読み取り操作と "majority" 書込み保証(write concern)を持つ書込み操作に対して因果的に整合性のあるセッションによって提供される因果整合性の保証をリスとしたものです。

保証
説明

書込み操作の結果を読み取る

読み取り操作は、その前の書込み操作の結果を反映します。

単調な読み取り

読み取り操作では、前の読み取り操作よりも前のデータの状態に対応する結果は返されません。

たとえば、セッション中の場合:

  • 書込み1 が書込み2に優先され、

  • 読み取り1 が読み取り2に先行し、

  • 読み取り1は書込み2を反映した結果を返し、

そのため、読み取り2は書込み1の結果を返すことができません。

単調書込み

他の書込みに優先する必要がある書込み操作は、他の書込みの前に実行する必要があります。

たとえば、セッションで書込み1が書込み2に優先する必要がある場合、書込み2の時点でのデータの状態は、書込み1後のデータの状態を反映している必要があります。書込み1と書込み2の間で他の書込みをインターリーブすることはできますが 書込み1の前に書込み2を行うことはできません

書込み操作の前に読み取り操作をする

読み取り操作の後に実行する必要がある書込み操作は、それらの読み取り操作の後に実行されます。つまり、書込み時のデータの状態には、前の読み取り操作のデータの状態が反映されている必要があります。

これらの保証は、MongoDB 配置のすべてのノードに適用されます。たとえば、因果関係が整合しているセッションで、"majority" 書込み保証(write concern)が懸念される書込みを実行した後に、セカンダリからの読み取りを実行した場合(つまり、読み込み設定(read preferencesecondary)と読み取り保証(read concern)"majority" の場合、読み取り操作は書込み操作後のデータベースの状態を反映します。

因果的に整合したセッション内の操作は、セッション外の操作から分離されません。セッションの書込み操作と読み取り操作の間に同時書込み操作が交互に行われる場合、セッションの読み取り操作は、セッションの書込み操作の後に発生した書込み操作を反映した結果を返すことがあります

Tip

アプリケーションでは、クライアント セッションで一度に 1 つのスレッドのみがこれらの操作が実行されるようにする必要があります。

クライアントには、MongoDB 3.6 以降用の最新 MongoDB ドライバーが必要です。

Java 3.6+

Python 3.6+

C 1.9+

Go 1.8+

C# 2.5+

Node 3.0+

Ruby 2.5+

Rust 2.1+

Swift 1.2+

Perl 2.0+

PHPC 1.4+

Scala 2.2+

C++ 3.6.6+

重要

因果的に整合性のあるセッションでは、 "majority" 読み取り保証(read concern)のある読み取りと "majority" 書込み保証(write concern)のある書込みに対してのみ因果整合性が保証されます。

さまざまなアイテムの現在のデータと履歴データを保持するコレクションitemsを検討します。履歴データのみに null 以外の end 日付があります。アイテムの sku 値が変更された場合は、古い sku 値を持つドキュメントを end 日付で更新し、その後、現在の sku 値を持つ新しいドキュメントを挿入する必要があります。クライアントは因果的に一貫したセッションを使用して、挿入の前に更新が行われるようにすることができます。


➤ 右上の [言語の選択] ドロップダウン メニューを使用して、この例の言語を設定します。


// Example 1: Use a causally consistent session to ensure that the update occurs before the insert.
ClientSession session1 = client.startSession(ClientSessionOptions.builder().causallyConsistent(true).build());
Date currentDate = new Date();
MongoCollection<Document> items = client.getDatabase("test")
.withReadConcern(ReadConcern.MAJORITY)
.withWriteConcern(WriteConcern.MAJORITY.withWTimeout(1000, TimeUnit.MILLISECONDS))
.getCollection("test");
items.updateOne(session1, eq("sku", "111"), set("end", currentDate));
Document document = new Document("sku", "nuts-111")
.append("name", "Pecans")
.append("start", currentDate);
items.insertOne(session1, document);
async with await client.start_session(causal_consistency=True) as s1:
current_date = datetime.datetime.today()
items = client.get_database(
"test",
read_concern=ReadConcern("majority"),
write_concern=WriteConcern("majority", wtimeout=1000),
).items
await items.update_one(
{"sku": "111", "end": None}, {"$set": {"end": current_date}}, session=s1
)
await items.insert_one(
{"sku": "nuts-111", "name": "Pecans", "start": current_date}, session=s1
)
my $s1 = $conn->start_session({ causalConsistency => 1 });
$items = $conn->get_database(
"test", {
read_concern => { level => 'majority' },
write_concern => { w => 'majority', wtimeout => 10000 },
}
)->get_collection("items");
$items->update_one(
{
sku => 111,
end => undef
},
{
'$set' => { end => $current_date}
},
{
session => $s1
}
);
$items->insert_one(
{
sku => "nuts-111",
name => "Pecans",
start => $current_date
},
{
session => $s1
}
);
$items = $client->selectDatabase(
'test',
[
'readConcern' => new \MongoDB\Driver\ReadConcern(\MongoDB\Driver\ReadConcern::MAJORITY),
'writeConcern' => new \MongoDB\Driver\WriteConcern(\MongoDB\Driver\WriteConcern::MAJORITY, 1000),
],
)->items;
$s1 = $client->startSession(
['causalConsistency' => true],
);
$currentDate = new \MongoDB\BSON\UTCDateTime();
$items->updateOne(
['sku' => '111', 'end' => ['$exists' => false]],
['$set' => ['end' => $currentDate]],
['session' => $s1],
);
$items->insertOne(
['sku' => '111-nuts', 'name' => 'Pecans', 'start' => $currentDate],
['session' => $s1],
);
with client.start_session(causal_consistency=True) as s1:
current_date = datetime.datetime.today()
items = client.get_database(
"test",
read_concern=ReadConcern("majority"),
write_concern=WriteConcern("majority", wtimeout=1000),
).items
items.update_one(
{"sku": "111", "end": None}, {"$set": {"end": current_date}}, session=s1
)
items.insert_one(
{"sku": "nuts-111", "name": "Pecans", "start": current_date}, session=s1
)

別のクライアントが現在の sku 値をすべて読み取る必要がある場合は、クラスター時間と操作時間を他のセッションに合わせて進めて、このクライアントが他のセッションと因果的に一貫していること、および 2 回の書込み後に読み取ることができることを確認できます。

// Example 2: Advance the cluster time and the operation time to that of the other session to ensure that
// this client is causally consistent with the other session and read after the two writes.
ClientSession session2 = client.startSession(ClientSessionOptions.builder().causallyConsistent(true).build());
session2.advanceClusterTime(session1.getClusterTime());
session2.advanceOperationTime(session1.getOperationTime());
items = client.getDatabase("test")
.withReadPreference(ReadPreference.secondary())
.withReadConcern(ReadConcern.MAJORITY)
.withWriteConcern(WriteConcern.MAJORITY.withWTimeout(1000, TimeUnit.MILLISECONDS))
.getCollection("items");
for (Document item: items.find(session2, eq("end", BsonNull.VALUE))) {
System.out.println(item);
}
async with await client.start_session(causal_consistency=True) as s2:
s2.advance_cluster_time(s1.cluster_time)
s2.advance_operation_time(s1.operation_time)
items = client.get_database(
"test",
read_preference=ReadPreference.SECONDARY,
read_concern=ReadConcern("majority"),
write_concern=WriteConcern("majority", wtimeout=1000),
).items
async for item in items.find({"end": None}, session=s2):
print(item)
my $s2 = $conn->start_session({ causalConsistency => 1 });
$s2->advance_cluster_time( $s1->cluster_time );
$s2->advance_operation_time( $s1->operation_time );
$items = $conn->get_database(
"test", {
read_preference => 'secondary',
read_concern => { level => 'majority' },
write_concern => { w => 'majority', wtimeout => 10000 },
}
)->get_collection("items");
$cursor = $items->find( { end => undef }, { session => $s2 } );
for my $item ( $cursor->all ) {
say join(" ", %$item);
}
$s2 = $client->startSession(
['causalConsistency' => true],
);
$s2->advanceClusterTime($s1->getClusterTime());
$s2->advanceOperationTime($s1->getOperationTime());
$items = $client->selectDatabase(
'test',
[
'readPreference' => new \MongoDB\Driver\ReadPreference(\MongoDB\Driver\ReadPreference::SECONDARY),
'readConcern' => new \MongoDB\Driver\ReadConcern(\MongoDB\Driver\ReadConcern::MAJORITY),
'writeConcern' => new \MongoDB\Driver\WriteConcern(\MongoDB\Driver\WriteConcern::MAJORITY, 1000),
],
)->items;
$result = $items->find(
['end' => ['$exists' => false]],
['session' => $s2],
);
foreach ($result as $item) {
var_dump($item);
}
with client.start_session(causal_consistency=True) as s2:
s2.advance_cluster_time(s1.cluster_time)
s2.advance_operation_time(s1.operation_time)
items = client.get_database(
"test",
read_preference=ReadPreference.SECONDARY,
read_concern=ReadConcern("majority"),
write_concern=WriteConcern("majority", wtimeout=1000),
).items
for item in items.find({"end": None}, session=s2):
print(item)

メモリ内構造を構築する次の操作は因果整合性はありません。

操作
ノート

$collStatslatencyStats オプション。

操作に因果整合性があるクライアント セッションに関連付けられている場合はエラーを返します。

操作に因果整合性があるクライアント セッションに関連付けられている場合はエラーを返します。

操作に因果整合性があるクライアント セッションに関連付けられている場合はエラーを返します。

操作に因果整合性があるクライアント セッションに関連付けられている場合はエラーを返します。

戻る

ピリオドとドル記号

項目一覧