Causal Consistency
If an operation logically depends on a preceding operation, there is a causal relationship between the operations. For example, a write operation that deletes all documents based on a specified condition and a subsequent read operation that verifies the delete operation have a causal relationship.
With causally consistent sessions, MongoDB executes causal operations in an order that respect their causal relationships, and clients observe results that are consistent with the causal relationships.
: Causally consistent sessions can only guarantee causal consistency for reads with "majority" read concern and writes with "majority" write concern.
Examples
Consider a collection items that maintains the current and historical data for various items. Only the historical data has a non-null end date. If the sku value for an item changes, the document with the old sku value needs to be updated with the end date, after which the new document is inserted with the current sku value. The client can 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("admatic-db")
.withReadConcern(ReadConcern.MAJORITY)
.withWriteConcern(WriteConcern.MAJORITY.withWTimeout(1000, TimeUnit.MILLISECONDS))
.getCollection("items");
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);
If another client needs to read all current sku values, you can 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("admatic-db")
.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);
}
Code
vim src/main/java/com/admatic/CausalConsistencyExamples.java
package com.admatic;
import com.mongodb.ClientSessionOptions;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
import com.mongodb.client.*;
import org.bson.BsonNull;
import org.bson.Document;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Updates.set;
public final class CausalConsistencyExamples {
private CausalConsistencyExamples() {
}
public static void main(final String[] args) {
Logger mongoLogger = Logger.getLogger("org.mongodb.driver");
mongoLogger.setLevel(Level.SEVERE);
setupDatabase(args);
MongoClient client;
if (args.length == 0) {
// connect to the local database server
client = MongoClients.create();
} else {
client = MongoClients.create(args[0]);
}
// 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("admatic-db")
.withReadConcern(ReadConcern.MAJORITY)
.withWriteConcern(WriteConcern.MAJORITY.withWTimeout(1000, TimeUnit.MILLISECONDS))
.getCollection("items");
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);
// 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("admatic-db")
.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);
}
client.close();
}
private static void setupDatabase(String[] args) {
MongoClient client;
if (args.length == 0) {
client = MongoClients.create();
} else {
client = MongoClients.create(args[0]);
}
client.getDatabase("admatic-db").drop();
MongoDatabase database = client.getDatabase("admatic-db");
database.getCollection("items").drop();
MongoCollection<Document> items = database.getCollection("items");
Document document = new Document("sku", "111")
.append("name", "Peanuts")
.append("start", new Date());
items.insertOne(document);
client.close();
}
}
Compile
mvn compile
Run
mvn exec:java -Dexec.mainClass=com.admatic.CausalConsistencyExamples \
-Dexec.args="mongodb+srv://admatic:admatic123@admatic-cluster-7qyyr.mongodb.net/test"
Document{{_id=5c74342079d84a0c88f48ea4, sku=nuts-111, name=Pecans, start=Mon Feb 25 23:59:52 IST 2019}}
Limitations
The following operations that build in-memory structures are not causally consistent:
collStats$collStatswithlatencyStatsoption$currentOpdbStatsgetMore$indexStatspingserverStatuscreateIndexeswithbackground: trueoption; i.e. background index builds