NoSQL Transactions with C#

Basics of Transactions using Couchbase to perform ACID transactions on JSON documents and C#.

Transactions, NoSQL and C#

Traditional RDBMS have had transactions for many years to ensure the accuracy, completeness and data integrity via ACID properties: Atomicity, Consistency, Isolation and Durability. With the influx of newer, NoSQL databases that focus on scalability, transactions took a sideshow. This is changing however, as transaction support is added to these distributed, multi-node systems. Recently, Couchbase, a low latency, high-performance distributed database has offered support for transactions in a NoSQL database. In this post I’ll over Couchbase Transactions for .NET, discuss when to use it over other built-in transaction like support features in Couchbase and go over the performance implications of using it!

Introducing Couchbase Transactions for .NET

While still in Beta at the time of writing, Couchbase Transactions for .NET is available as a standalone download from the server. The package is available on NuGet and uses the popular Couchbase .NET SDK as the driver for persisting data to Couchbase server.

We’ll start by creating a .NET Core Console Application in Visual Studio or VSCODE and then add a NuGet dependency to the Couchbase .NET Transaction package using the Package Manager:

Install-Package Couchbase.Transactions -Version 1.0.0-beta.1

This will install both Couchbase .NET SDK and Couchbase.Transactions. You may want to upgrade the SDK version as the GA uses CouchbaseNetClient 3.0.7 and the latest at the time of writing is 3.1.X. For this example, the SDK version shouldn’t matter as long as it’s 3.0.7 or greater.

Next add the following code to initialize the Couchbase .NET SDK:

Assuming you have a locally installed Couchbase server, this would be “localhost” and your username and password. Once you have done this and connected to Couchbase server, you will want to create a Transaction object.

Please note that only one Transaction object can be created per application as it creates background resources to allow the transaction and more than one may cause unexpected results.

The actual transaction is done within a lambda statement passed into the Transaction.RunAsync(..) method. Within this lambda you can write whatever code you wish to scope this particular transaction.

“ctx” is the AttemptContext object that is proxy to the equivalent SDK methods for CRUD. The difference is that it abstracts the transaction logic added to ensure ACID properties are preserved.

Here we fetch the two documents that will be included within the transaction. We’ll perform some writes to them and then these will be committed when Dispose is called within the using statement above.

Assuming all the above happens without another context mutating any of them, the transaction will complete. If not, all mutations will fail, and they will be “rolled back” to their original state. The example is fairly complex and you can download the code directly from GitHub to help you understand it. The important thing to understand the transaction creation and that the lambda statement is the scope of the transaction. What you write within it depends on your problem domain.

Performance Implications

If you were ever a student of economics you likely learned of the concept of TINSTAAFL; There is no such thing as a free lunch. In a nutshell, somebody always ends of paying for it; you can’t get something for nothing! The same concept applies to transactions. For everyone update to a document, there is at least 2 operations done on that bucket and 2 to complete the transaction. They are not optimized for high throughput, low latency scenarios but for when you need to ensure ACID properties across two or more document mutations. Use it wisely!

When to use Transactions vs Sub-Document vs CAS

Couchbase offers other types of atomicity that is much less expensive and is a fit for some scenarios. For example, CAS (Check and Swap) can be used for concurrent document mutations via optimistic locking. It works by using the CAS value for a document which changes every time it is mutated. By sending the CAS to the server the value can be checked to see if it has changed. If so, the server will reply with a CAS_MISMATCH error response which can trigger retries until the operation succeeds.

Sub-Document API allows for multiple operations to ran against a single document without the overhead of fetching the entire document over the network. Sub-Document operations are atomic on a single document.

More information on Couchbase.Transactions can be found here.

Senior SDK Engineer who enjoys surfing, fishing and jiu-jitsu in there spare time.