MongoDB.Driver version 2.11.5, Server version: 4.2.2-ent
I use 5 threads to execute transactions in parallel and encounter lots of 251 errors:
MongoCommandException, 251, “NoSuchTransaction”, “Command insert failed: cannot continue txnId 35 for session 618e6cd1-4db1-40ea-8b22-6386e204c36b - xxx with txnId 36”
MVP code to reproduce:
public class TransactionTest
{
private const string DatabaseName = "PressureTest";
private const string CollectionName = "Test";
public const string ConnectionString = "";
public MongoClient GetMongoClient(int timeout = 5)
{
var clientSettings = MongoClientSettings.FromConnectionString(ConnectionString);
clientSettings.ConnectTimeout = TimeSpan.FromSeconds(5);
clientSettings.ServerSelectionTimeout = TimeSpan.FromSeconds(timeout);
clientSettings.AllowInsecureTls = true;
var mongoClient = new MongoClient(clientSettings);
return mongoClient;
}
public async Task TestTransactionAsync()
{
var client = GetMongoClient();
var tasks = new List<Task>();
for (int i = 0; i < 5; ++i)
{
tasks.Add(DoAsync(client));
}
await Task.WhenAll(tasks);
}
private async Task DoAsync(IMongoClient mongoClient)
{
Console.WriteLine(mongoClient.GetHashCode());
while (true)
{
var collection = mongoClient.GetDatabase(DatabaseName).GetCollection<BsonDocument>(CollectionName);
var uuid1 = Guid.NewGuid().ToString("N").Substring(24);
var uuid2 = Guid.NewGuid().ToString("N").Substring(24);
try
{
using (var session = await mongoClient.StartSessionAsync())
{
session.StartTransaction();
await collection.InsertOneAsync(session, new BsonDocument("Uuid", uuid1));
await collection.InsertOneAsync(session, new BsonDocument("Uuid", uuid2));
await session.CommitTransactionAsync();
}
Console.WriteLine($"[{uuid1}] [{uuid2}]");
}
catch (Exception e)
{
Console.WriteLine("$$$" + e.Message);
}
}
}
}
If we not reuse the mongoClient by changing TestTransactionAsync(): create a dedicated mongoClient for each thread, then no error happens:
public async Task TestTransactionAsync()
{
var tasks = new List<Task>();
for (int i = 0; i < 5; ++i)
{
var client = GetMongoClient(i + 5);
tasks.Add(DoAsync(client));
}
await Task.WhenAll(tasks);
}
The above modification intentionally pass different ServerSelectionTimeout value to prevent mongoclient from reusing.
Refer to: Connecting
multiple MongoClient instances created with the same settings will utilize the same connection pools underneath.
The document suggests re-use mongoclient by store it in a global place. However, a singleton mongoclient leads to parallel transaction failure.
What’s the right way to execute transactions in parallel?
Thanks a lot!