For decades, transactions have been the mechanism of choice for simplifying issues like crashes, failures, and interruptions. A transaction is a way for an application to group several reads and writes together into a logical unit. Conceptually, all the reads and writes in a transaction are executed as one operation: either the entire transaction succeeds (commit) or it fails (abort, rollback). If it fails, the application can safely retry. With transactions, error handling becomes much simpler for an application, because it doesn't need to worry about partial failure.
Information on this page is taken from Designing Data-Intensive Applications by Martin Kleppmann.
The safety guarantees provided by transactions are often described by the well-known acronym ACID: Atomicity, Consistency, Isolation, and Durability.
In general, atomic refers to something that cannot be broken down into smaller parts. In ACID, atomicity describes what happens if a client wants to make several writes, but a fault occurs after some of the writes have been processed. If the writes are grouped together into an atomic transaction, and the transaction cannot be committed due to a fault, then the transaction is aborted and the database must discard or undo any writes it has made so far.
In ACID, consistency means that you have certain statements about your data (invariants) that must always be true. If a transaction starts with a database that is valid according to these invariants, and any writes during the transaction preserve the validity, then you can be sure that the invariants are always satisfied. Consistency is really a property of the application rather than the database.
Isolation in the sense of ACID means that concurrently executing transactions are isolated from each other: they cannot step on each other's toes. The classic database textbooks formalize isolation as serializability, which means that each transaction can pretend that it is the only transaction running on the entire database. The database ensures that when the transactions have committed, the result is the same as if they had run serially (one after another), even though in reality they may have run concurrently.
Durability is the promise that once a transaction has committed successfully, any data it has written will not be forgotten, even if there is a hardware fault or the database crashes. In a single-node database, durability typically means that the data has been written to disk and potentially a write-ahead log. In a replicated database, durability may mean that the data has been successfully copies to some number of nodes.
Storage engines almost universally aim to provide atomicity and isolation at the level of a single object. But do we need multi-object transactions? In many cases, writes to several different objects need to be coordinated.
- In a relational database, multi-object transactions allow you to ensure that foreign key references remain valid even when inserting several records that refer to one another.
- In a document data model, multi-object transactions help ensure that denormalized (duplicated) data doesn't go out of sync.
- Transactions can help ensure secondary indexes are up to date.
A simple and effective error handling mechanism is to retry an aborted transaction. But this isn't perfect. For example, if the error was really due to a network failure, a retry might cause the operation to happen twice. Additionally, if the error is due to overload, a retry will only make the problem worse.