Isolamento de leitura, consistência e atualidade
Garantias de isolamento
Leitura não confirmada
Dependendo da preocupação de leitura, os clientes podem ver os resultados das gravações antes que elas sejam duráveis:
Independentemente da preocupação de gravação de uma gravação, outros clientes que usam a preocupação de leitura
"local"
ou"available"
podem ver o resultado de uma operação de gravação antes que a operação de gravação seja confirmada para o cliente emissor.Os clientes que usam a read concern
"local"
ou"available"
podem ler dados que podem ser revertidos posteriormente durante failovers de conjuntos de réplicas.
Para operações em uma transação de vários documentos, quando uma transação é confirmada, todas as alterações de dados feitas na transação são salvas e ficam visíveis fora da transação. Ou seja, uma transação não confirmará algumas de suas alterações enquanto reverte outras.
Até que uma transação seja confirmada, as alterações de dados feitas na transação não serão visíveis fora da transação.
No entanto, quando uma transação é gravada em vários fragmentos, nem todas as operações de leitura externas precisam esperar que o resultado da transação confirmada fique visível nos fragmentos. Por exemplo, se uma transação estiver comprometida e escrever 1 estiver visível no fragmento A, mas escrever 2 ainda não estiver visível no fragmento B, uma leitura externa em questão de leitura "local"
poderá ler os resultados da escrita 1 sem ver a escrita 2.
Leitura não confirmada é o nível de isolamento padrão e se aplica a instâncias autônomas mongod
, bem como a conjuntos de réplicas e clusters sharded.
Leitura não confirmada e atomicidade de documento único
As operações de gravação são atômicas em relação a um único documento; ou seja, se uma gravação estiver atualizando múltiplos campos no documento, uma operação de leitura nunca verá o documento com apenas alguns dos campos atualizados. No entanto, embora um cliente possa não ver um documento atualizado parcialmente, ler sem compromisso significa que as operações de leitura simultâneas ainda podem ver o documento atualizado antes que as alterações fiquem duráveis.
Com uma instância mongod
autônoma, um conjunto de operações de leitura e gravação em um único documento é serializável. Com um conjunto de réplicas, um conjunto de operações de leitura e gravação em um único documento é serializável somente na ausência de uma reversão.
Leitura não confirmada e gravação de múltiplos documentos
Quando uma única operação de gravação (por exemplo, db.collection.updateMany()
) modifica vários documentos, a modificação de cada documento é atômica, mas a operação como um todo não é atômica.
Ao realizar operações de escrita de vários documentos, seja por meio de uma única operação de escrita ou de várias operações de escrita, outras operações podem ser intercaladas.
Para situações que exigem atomicidade de leituras e escritos em vários documentos (em uma única coleção ou várias coleções), o MongoDB suporta transações distribuídas, incluindo transações em conjuntos de réplicas e clusters fragmentados.
Para obter mais informações, consulte transações
Importante
Na maioria dos casos, uma transação distribuída incorre em um custo de desempenho maior do que as gravações de um único documento, e a disponibilidade de transações distribuídas não deve substituir o design eficaz do esquema. Em muitos cenários, o modelo de dados desnormalizado (documentos e arrays incorporados) continuará a ser ideal para seus dados e casos de uso. Ou seja, para muitos cenários, modelar seus dados adequadamente minimizará a necessidade de transações distribuídas.
Para considerações adicionais sobre o uso de transações (como limite de tempo de execução e limite de tamanho do oplog), consulte também Considerações de produção.
Sem isolar as operações de gravação de múltiplos documentos, o MongoDB exibe o seguinte comportamento:
Operações de leitura não pontuais. Suponha que uma operação de leitura comece no momento t 1 e comece a ler documentos. Em seguida, uma operação de gravação confirma uma atualização em um dos documentos em um momento posterior t 2. O leitor pode ver a versão atualizada do documento e, portanto, não vê um instantâneo pontual dos dados.
Operações não serializáveis. Suponha que uma operação de leitura leia um documento d 1 no tempo t 1 e uma operação de gravação atualize d 1 em algum momento posterior t 3. Isso introduz uma dependência de leitura-gravação tal que, se as operações forem serializadas, a operação de leitura deve preceder a operação de gravação. Mas também suponha que a operação de gravação atualize o documento d 2 no tempo t 2 e a operação de leitura subsequentemente leia d 2 em algum momento posterior t 4. Isso introduz uma dependência de leitura-gravação que, em vez disso, exigiria que a operação de leitura viesse após a operação de gravação em uma agenda serializável. Há um ciclo de dependência que torna a serializabilidade impossível.
As leituras podem perder documentos correspondentes que são atualizados durante a operação de leitura.
Snapshot do cursor
Os cursores do MongoDB podem retornar o mesmo documento mais de uma vez em algumas situações. À medida que um cursor retorna documentos, outras operações podem intercalar-se com a consulta. Se uma dessas operações alterar o campo indexado no índice usado pela consulta, o cursor poderá retornar o mesmo documento mais de uma vez.
Consultas que usam índices únicos podem, em alguns casos, retornar valores duplicados. Se um cursor que usa um índice exclusivo se intercalar com uma exclusão e inserção de documentos que compartilham o mesmo valor exclusivo, o cursor poderá retornar o mesmo valor exclusivo duas vezes de documentos diferentes.
Considere usar o isolamento de leitura. Para saber mais, consulte Ler Preocupação "snapshot"
.
Gravações monotônicas
O MongoDB fornece garantias de gravação monotônica, por padrão, para instâncias mongod
autônomas e conjunto de réplicas.
Para leituras monotônicas e clusters fragmentados, consulte Consistência causal.
Ordem em tempo real
Para operações de leitura e gravação no primário, a emissão de operações de leitura com preocupação de leitura do "linearizable"
e operações de gravação com preocupação de gravação do "majority"
permite que vários threads executem leituras e gravações em um único documento como se um único thread executasse essas operações em tempo real; ou seja, o agendamento correspondente para essas leituras e gravações é considerado linearizável.
Consistência causal
Se uma operação depende logicamente de uma operação anterior, existe uma relação causal entre as operações. Por exemplo, uma operação de gravação que exclui todos os documentos com base em uma condição especificada e uma operação de leitura subsequente que verifica se a operação de exclusão tem uma relação causal.
Com sessões causalmente consistentes, o MongoDB executa operações causais em uma ordem que respeita seus relacionamentos causais, e os clientes observam resultados consistentes com os relacionamentos causais.
Sessões de clientes e garantias de consistência causal
Para fornecer consistência causal, o MongoDB permite a consistência causal nas sessões do cliente. Uma sessão com consistência causal indica que a sequência associada de operações de leitura com preocupação de leitura "majority"
e operações de escrita com preocupação de gravação "majority"
têm um relacionamento causal que é refletido por sua ordem. Os aplicativos devem garantir que somente um thread por vez execute essas operações em uma sessão do cliente.
Para operações causalmente relacionadas:
Um cliente inicia uma sessão de cliente.
Importante
As sessões de cliente garantem apenas consistência causal para:
Operações de leitura com
"majority"
; ou seja, os dados de retorno foram reconhecidos pela maioria dos membros do conjunto de réplicas e são duráveis.Operações de gravação com preocupação de gravação
"majority"
; ou seja, as operações de gravação que solicitam a confirmação de que a operação foi aplicada à maioria dos nós votantes do conjunto de réplicas.
Para obter mais informações sobre a consistência causal e diferentes read e write concerns, consulte Consistência causal e read e write concerns.
Como o cliente emite uma sequência de leitura com
"majority"
leia a preocupação e escreva operações (com"majority"
escreva a preocupação), o cliente inclui as informações da sessão com cada operação.Para cada operação de leitura com
"majority"
preocupação de leitura e operação de gravação com"majority"
preocupação de gravação associada à sessão, o MongoDB retorna o tempo de operação e o tempo de cluster, mesmo se a operação for errada. A sessão do cliente controla o tempo de operação e o tempo de agrupamento.Observação
O MongoDB não retorna o tempo de operação e o tempo de cluster para operações de gravação não reconhecidas (
w: 0
). Escritos não reconhecidos não implicam qualquer relação causal.Embora o MongoDB retorne o tempo de operação e o tempo de cluster para operações de leitura e escrita confirmadas em uma sessão de cliente, somente as operações de leitura com read concern
"majority"
e operações de escrita com write concern"majority"
podem garantir consistência causal. Para obter detalhes, consulte Consistência causal e read e write concerns.A sessão de cliente associada acompanha esses dois campos de tempo.
Observação
As operações podem ser causalmente consistentes em diferentes sessões. MongoDB Os drivers e do
mongosh
fornecem os métodos para avançar o optime e o tempo de cluster para uma sessão de cliente. Assim, um cliente pode adiantar o tempo de cluster e o optime de uma sessão de cliente para ser consistente com as operações de outra sessão de cliente.
Garantias de consistência causal
A tabela a seguir lista as garantias de consistência causal fornecidas por sessões causalmente consistentes para operações de leitura com "majority"
preocupação de leitura e operações de gravação com "majority"
preocupação de gravação.
Garantias | Descrição |
---|---|
Ler suas gravações | As operações de leitura refletem os resultados das operações de gravação que as precedem. |
Leituras monotônicas | As operações de leitura não retornam resultados que correspondam a um estado anterior dos dados em relação a uma operação de leitura precedente. Por exemplo, se em uma sessão:
então read 2 não pode retornar resultados de write 1. |
Escritas monotônicas | As operações de gravação que devem preceder outras gravações são executadas antes dessas outras gravações. Por exemplo, se a gravação 1 precisar preceder a gravação 2 em uma sessão, o estado dos dados no momento da gravação 2 deverá refletir o estado dos dados após a gravação 1. Outras gravações podem ser intercaladas entre a gravação 1 e a gravação 2, mas a gravação 2 não pode ocorrer antes da gravação 1. |
Escritas que seguem as leituras | As operações de gravação que devem ocorrer após as operações de leitura são executadas após essas operações de leitura. Ou seja, o estado dos dados no momento da gravação deve incorporar o estado dos dados das operações de leitura anteriores. |
readPreference
Essas garantias são válidas para todos os membros da implantação do MongoDB. Por exemplo, se você emitir uma gravação em uma sessão causalmente consistente com "majority"
preocupação de gravação seguida por uma leitura que lê de um secundário (ou seja, preferência de leitura secondary
) com "majority"
preocupação de leitura, a operação de leitura refletirá o estado do banco de dados após a operação de gravação.
Isolamento
As operações dentro de uma sessão causalmente consistente não são isoladas de operações fora da sessão. Se uma operação de gravação simultânea interceptar entre as operações de gravaçãoe leitura da sessão, a operação de leitura da sessão poderá retornar resultados que refletem uma operação de gravação que ocorreu após a operação de gravação da sessão.
MongoDB Drivers
Dica
Os aplicativos devem garantir que apenas um thread de cada vez execute essas operações em uma sessão do cliente.
Os clientes exigem drivers do MongoDB atualizados para o MongoDB 3.6 ou posterior:
Java 3.6+ Python 3,6+ C 1.9+ Go 1.8+ | C# 2.5+ Nó 3.0+ Ruby 2,5+ Rust 2.1+ Swift 1.2+ | Perl 2.0+ PHPC 1.4+ Scala 2,2+ C++ 3.6.6+ |
Exemplos
Importante
Sessões causalmente consistentes só podem garantir consistência causal para leituras com preocupação de leitura "majority"
e escrever com preocupação de gravação "majority"
.
Considere uma collection items
que mantém os dados atuais e históricos de vários itens. Somente os dados históricos possuem uma data end
não nula. Se o valor sku
de um item for alterado, o documento com o valor sku
antigo precisará ser atualizado com a data end
, após o que o novo documento será inserido com o valor sku
atual. O cliente pode usar uma sessão causalmente consistente para garantir que a atualização ocorra antes da inserção.
➤ Use o menu suspenso Selecione a linguagem no canto superior direito para definir a linguagem deste exemplo.
/* Use a causally-consistent session to run some operations. */ wc = mongoc_write_concern_new (); mongoc_write_concern_set_wmajority (wc, 1000); mongoc_collection_set_write_concern (coll, wc); rc = mongoc_read_concern_new (); mongoc_read_concern_set_level (rc, MONGOC_READ_CONCERN_LEVEL_MAJORITY); mongoc_collection_set_read_concern (coll, rc); session_opts = mongoc_session_opts_new (); mongoc_session_opts_set_causal_consistency (session_opts, true); session1 = mongoc_client_start_session (client, session_opts, &error); if (!session1) { fprintf (stderr, "couldn't start session: %s\n", error.message); goto cleanup; } /* Run an update_one with our causally-consistent session. */ update_opts = bson_new (); res = mongoc_client_session_append (session1, update_opts, &error); if (!res) { fprintf (stderr, "couldn't add session to opts: %s\n", error.message); goto cleanup; } query = BCON_NEW ("sku", "111"); update = BCON_NEW ("$set", "{", "end", BCON_DATE_TIME (bson_get_monotonic_time ()), "}"); res = mongoc_collection_update_one (coll, query, update, update_opts, NULL, /* reply */ &error); if (!res) { fprintf (stderr, "update failed: %s\n", error.message); goto cleanup; } /* Run an insert with our causally-consistent session */ insert_opts = bson_new (); res = mongoc_client_session_append (session1, insert_opts, &error); if (!res) { fprintf (stderr, "couldn't add session to opts: %s\n", error.message); goto cleanup; } insert = BCON_NEW ("sku", "nuts-111", "name", "Pecans", "start", BCON_DATE_TIME (bson_get_monotonic_time ())); res = mongoc_collection_insert_one (coll, insert, insert_opts, NULL, &error); if (!res) { fprintf (stderr, "insert failed: %s\n", error.message); goto cleanup; }
using (var session1 = client.StartSession(new ClientSessionOptions { CausalConsistency = true })) { var currentDate = DateTime.UtcNow.Date; var items = client.GetDatabase( "test", new MongoDatabaseSettings { ReadConcern = ReadConcern.Majority, WriteConcern = new WriteConcern( WriteConcern.WMode.Majority, TimeSpan.FromMilliseconds(1000)) }) .GetCollection<BsonDocument>("items"); items.UpdateOne(session1, Builders<BsonDocument>.Filter.And( Builders<BsonDocument>.Filter.Eq("sku", "111"), Builders<BsonDocument>.Filter.Eq("end", BsonNull.Value)), Builders<BsonDocument>.Update.Set("end", currentDate)); items.InsertOne(session1, new BsonDocument { {"sku", "nuts-111"}, {"name", "Pecans"}, {"start", currentDate} }); }
// 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 )
let s1 = client1.startSession(options: ClientSessionOptions(causalConsistency: true)) let currentDate = Date() var dbOptions = MongoDatabaseOptions( readConcern: .majority, writeConcern: try .majority(wtimeoutMS: 1000) ) let items = client1.db("test", options: dbOptions).collection("items") let result1 = items.updateOne( filter: ["sku": "111", "end": .null], update: ["$set": ["end": .datetime(currentDate)]], session: s1 ).flatMap { _ in items.insertOne(["sku": "nuts-111", "name": "Pecans", "start": .datetime(currentDate)], session: s1) }
let s1 = client1.startSession(options: ClientSessionOptions(causalConsistency: true)) let currentDate = Date() var dbOptions = MongoDatabaseOptions( readConcern: .majority, writeConcern: try .majority(wtimeoutMS: 1000) ) let items = client1.db("test", options: dbOptions).collection("items") try items.updateOne( filter: ["sku": "111", "end": .null], update: ["$set": ["end": .datetime(currentDate)]], session: s1 ) try items.insertOne(["sku": "nuts-111", "name": "Pecans", "start": .datetime(currentDate)], session: s1)
Se outro cliente precisar ler todos os valores de sku
atuais, é possível adiantar o tempo de cluster e o tempo de operação para o da outra sessão, para garantir que esse cliente seja causalmente consistente com a outra sessão e leia após as duas gravações:
/* Make a new session, session2, and make it causally-consistent * with session1, so that session2 will read session1's writes. */ session2 = mongoc_client_start_session (client, session_opts, &error); if (!session2) { fprintf (stderr, "couldn't start session: %s\n", error.message); goto cleanup; } /* Set the cluster time for session2 to session1's cluster time */ cluster_time = mongoc_client_session_get_cluster_time (session1); mongoc_client_session_advance_cluster_time (session2, cluster_time); /* Set the operation time for session2 to session2's operation time */ mongoc_client_session_get_operation_time (session1, ×tamp, &increment); mongoc_client_session_advance_operation_time (session2, timestamp, increment); /* Run a find on session2, which should now find all writes done * inside of session1 */ find_opts = bson_new (); res = mongoc_client_session_append (session2, find_opts, &error); if (!res) { fprintf (stderr, "couldn't add session to opts: %s\n", error.message); goto cleanup; } find_query = BCON_NEW ("end", BCON_NULL); read_prefs = mongoc_read_prefs_new (MONGOC_READ_SECONDARY); cursor = mongoc_collection_find_with_opts (coll, query, find_opts, read_prefs); while (mongoc_cursor_next (cursor, &result)) { json = bson_as_relaxed_extended_json (result, NULL); fprintf (stdout, "Document: %s\n", json); bson_free (json); } if (mongoc_cursor_error (cursor, &error)) { fprintf (stderr, "cursor failure: %s\n", error.message); goto cleanup; }
using (var session2 = client.StartSession(new ClientSessionOptions { CausalConsistency = true })) { session2.AdvanceClusterTime(session1.ClusterTime); session2.AdvanceOperationTime(session1.OperationTime); var items = client.GetDatabase( "test", new MongoDatabaseSettings { ReadPreference = ReadPreference.Secondary, ReadConcern = ReadConcern.Majority, WriteConcern = new WriteConcern(WriteConcern.WMode.Majority, TimeSpan.FromMilliseconds(1000)) }) .GetCollection<BsonDocument>("items"); var filter = Builders<BsonDocument>.Filter.Eq("end", BsonNull.Value); foreach (var item in items.Find(session2, filter).ToEnumerable()) { // process item } }
// 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)
let options = ClientSessionOptions(causalConsistency: true) let result2: EventLoopFuture<Void> = client2.withSession(options: options) { s2 in // The cluster and operation times are guaranteed to be non-nil since we already used s1 for operations above. s2.advanceClusterTime(to: s1.clusterTime!) s2.advanceOperationTime(to: s1.operationTime!) dbOptions.readPreference = .secondary let items2 = client2.db("test", options: dbOptions).collection("items") return items2.find(["end": .null], session: s2).flatMap { cursor in cursor.forEach { item in print(item) } } }
try client2.withSession(options: ClientSessionOptions(causalConsistency: true)) { s2 in // The cluster and operation times are guaranteed to be non-nil since we already used s1 for operations above. s2.advanceClusterTime(to: s1.clusterTime!) s2.advanceOperationTime(to: s1.operationTime!) dbOptions.readPreference = .secondary let items2 = client2.db("test", options: dbOptions).collection("items") for item in try items2.find(["end": .null], session: s2) { print(item) } }
Limitações
As seguintes operações que criam estruturas in-memory não são causalmente consistentes:
(operação) | Notas |
---|---|
$collStats com a opção latencyStats . | |
Retorna um erro se a operação estiver associada a uma sessão de cliente causalmente consistente. | |
Retorna um erro se a operação estiver associada a uma sessão de cliente causalmente consistente. | |
Retorna um erro se a operação estiver associada a uma sessão de cliente causalmente consistente. | |
Retorna um erro se a operação estiver associada a uma sessão de cliente causalmente consistente. | |