Chapter 5
Consistency
In the context of databases, consistency refers to the guarantee that all users or systems accessing the
data see the same, correct version of it after any transaction or update. When a database is consistent,
it means that every transaction takes the database from one valid state to another, ensuring that the
data always follows certain rules or constraints.
5.1 Update Consistency
The concept of update consistency deals with making sure that data stays correct when multiple
people try to update it at the same time. Imagine two people, Martin and Pramod, both noticing that a
phone number on a website is wrong, and they both try to update it at the same time. However, they
enter the number in slightly different formats, leading to a write-write conflict—when two people try
to update the same data at the same time.
The server has to decide which update to apply first. For example, it might apply Martin’s change
first, then Pramod’s, which would overwrite Martin’s update. This means Martin’s change is lost,
which can be a problem. This situation shows a failure of consistency because Pramod’s update was
based on the old number, but it ended up overwriting Martin’s new update.
There are two main ways to deal with this issue: pessimistic and optimistic approaches:
1. Pessimistic approach: This method tries to prevent conflicts by using locks. Before anyone
can update the data, they have to get a lock on it, ensuring only one person can make changes
at a time. So, Martin would get the lock first, make his update, and Pramod would have to
wait until Martin is done before making his own update. This way, Pramod sees Martin’s
change before deciding to update it.
2. Optimistic approach: This method allows conflicts to happen but checks for them before
finalizing the update. For example, Pramod’s update would fail if the system detects that
Martin’s update has already changed the data. Pramod would then need to review the new
data and decide if he still wants to update it.
In both cases, with a single server, it's easy to decide the order of updates. But with multiple servers
(like in peer-to-peer replication), updates could be applied in a different order on different servers,
leading to inconsistent data across them. To avoid this, systems try to ensure that all updates happen
in the same order on all servers, which is called sequential consistency.
Another optimistic way to handle this is to save both updates and flag that they conflict. This is
similar to how version control systems (like Git) work when there are conflicting changes. The user
is then asked to manually resolve the conflict, or the system may automatically merge the updates
(e.g., standardizing a phone number format).
While it might seem safer to always prevent conflicts (using the pessimistic approach), it can make
the system slower because it has to wait for locks, which can lead to issues like deadlocks, where
two processes are stuck waiting for each other. This is especially true in systems with replication,
where different copies of data exist on different servers, making conflicts more likely. One way to
avoid many conflicts is to direct all updates for specific data to a single server.
5.2 Read Consistency
1
Martinb
modifying
2
Pramodreads
bothrecords
Martin
Pramod
Martin
Figure5.1A read-write conflict in logical consistency
processedbyLondon Update has not yet been
Martinseesroom Martin
asavailable
Inconsistent read
Pramod
Mumbai
Martin
Pramodbookslasthotel
roomonMumbainode
Boston
Cindy
Cindyseesroom asbooked
Figure5.2 An example of replication in consistency
Read consistency means ensuring that when someone reads data from a system, they get accurate and up-to-date
information, even when updates are happening at the same time. Here’s a simple breakdown of the concept:
1. Inconsistent Read Problem: Imagine you have an order with line items and a shipping charge. When Martin
adds a new item to the order, he also needs to update the shipping cost. But while he's doing this, Pramod reads
the data before Martin updates the shipping charge. This is called a read-write conflict, where someone reads
data in the middle of an update, leading to inconsistent information.
2. Transactions to Ensure Consistency: Relational databases solve this with transactions. A transaction ensures
that either all of Martin’s changes (line item and shipping charge) are applied together, or not at all. That way,
Pramod sees the data either before or after the change, but never in an inconsistent state.
3. NoSQL and Consistency: Some people think NoSQL databases don’t support transactions, but that’s not entirely
true. While certain NoSQL databases (like aggregate-oriented ones) don’t support full transactions across all
data, they do support atomic updates (all changes happen at once) within an aggregate (like an order and its line
items). This prevents inconsistencies within that aggregate.
4. Inconsistency Window: If updates affect more than one aggregate (e.g., updating multiple parts of an order),
there is a short period called the inconsistency window where someone could read inconsistent data before all
updates are applied. This window is usually very short, sometimes less than a second, but it can vary.
5. Replication Consistency: When data is copied across multiple servers (replication), replication consistency
ensures that all servers have the same data at the same time. For example, if someone books the last hotel room,
one server may show it as booked while another shows it as available due to a delay in updating all copies. This is
a replication inconsistency, but eventually all servers will catch up, making the data eventually consistent.
6. Stale Data: Data that hasn’t been updated yet on a server is called stale. This can happen with caching too,
where a copy of the data (from the cache) is shown instead of the updated version from the database.
7. Managing Consistency Levels: In many systems, you can choose how strong the consistency needs to be for
different requests. For example, you might not care about strong consistency for some data, but for important
transactions (like financial records), you want strong consistency.
8. Read-Your-Writes Consistency: This is a type of consistency where, once you make an update, you should
always see that update when you read the data again, even if other users might not see it yet. This prevents
confusion, such as posting a comment and then not seeing it because the system hasn’t fully updated.
9. Session Consistency: To ensure read-your-writes consistency within a user’s session, systems can use sticky
sessions (where a user’s session is always handled by the same server) or version stamps (where each update
has a version number, and the system ensures the latest version is used).
10. Handling Writes in Replicated Systems: In systems that use replication, writing to one server (master) and
reading from another (slave) can be tricky. Solutions include having the slave handle forwarding writes to the
master, or temporarily switching the session to the master for writes and switching back once updates are
synced.
5.3 Relaxing Consistency
Relaxing consistency means that, while it’s good to have data that is always accurate and up-to-
date, sometimes we have to accept a lower level of consistency in order to improve other aspects
of a system, such as speed or performance. Here’s a simplified breakdown:
1. Trade-offs in System Design: In designing a system, it’s often impossible to ensure perfect
consistency without sacrificing other important qualities. For example, making a system super
reliable might slow it down significantly. Different applications and industries have different levels
of tolerance for inconsistencies, and designers must consider this when making decisions.
2. Consistency in Relational Databases: Even in traditional relational databases, where transactions
help ensure consistency, there are ways to relax those guarantees. Transactions can be adjusted to
allow more flexibility, such as letting queries read data that hasn’t been fully saved yet. This is
known as relaxing isolation levels, where systems allow certain types of reads that could lead to
inconsistencies but improve overall performance.
3. Performance vs. Transactions: Many systems choose not to use transactions at all because they
can slow down performance. For example, MySQL was very popular before it had transaction
support because it was faster, and many websites were okay with this compromise. Large platforms
like eBay have also decided to skip transactions to ensure they perform well, especially when they
use sharding (breaking up data across multiple servers).
4. Interacting with Other Systems: Sometimes, applications need to communicate with other
systems that cannot be included in a transaction. This makes it common for enterprises to perform
updates outside of transactions, accepting the risk of inconsistencies in favor of speed and
efficiency.
5.3.1 The CAP Theorem
Overview of Durability: When people discuss the ACID properties of database transactions, they often focus on
consistency. Consistency ensures that requests are handled in a way that maintains order and correctness.
However, many would argue that relaxing durability is a bad idea, since the main purpose of a data store is to
avoid losing updates.
What is Durability?: Durability means that once a transaction is confirmed, it will remain safe even if the system
crashes. If a database loses updates, it raises concerns about its reliability.
Performance vs. Durability: There are situations where sacrificing some durability can lead to better
performance. For example, if a database mainly runs in memory (which is faster than using a disk), it can handle
requests quickly and only save changes to disk occasionally. The downside is that if the server crashes before it
saves those changes, any recent updates will be lost.
Example of Session Data: A good case for this trade-off is user session data on large websites. These websites
need to track many users' activities in real-time, which requires fast processing. Losing some session data isn't a
huge problem; it might annoy users but would be less frustrating than a slow website. Therefore, it's often
acceptable to write this session data in a non-durable way, meaning it doesn't have to be saved immediately.
Capturing Telemetry Data: Another example is collecting data from physical devices (like sensors). Here, it
might be more important to gather data quickly than to ensure every last piece is saved. If a server goes down,
losing some recent data might be acceptable.
Replication Durability Issues: Another aspect of durability involves replicating data across multiple nodes
(servers). If one server (the master) processes an update but crashes before that update reaches other servers
(the slaves), those updates can be lost. This is especially important in a setup where a slave can automatically
become the master if the original master fails.
Auto-Failover Risks: If the master server fails, any updates it processed but didn't replicate to the slaves will be
lost. When the master comes back online, it might have conflicts with updates that occurred during its
downtime. This is a durability issue because you think your update went through since the master confirmed it,
but the failure caused it to be lost.
Improving Replication Durability: To avoid losing updates, you can require the master server to wait for some
replicas to confirm they received the update before telling the client that the update was successful. However,
this approach can slow down the system and make it less available, especially if any slaves fail.
Flexibility in Durability Requirements: Just like other aspects of durability, you can often set different levels of
durability for individual requests. This means you can choose stronger durability for critical updates while
allowing less important updates to be processed more quickly without guarantees that they won't be lost.
✘ London Beijing
Mumbai
✘
Chicago
SanFrancisco
Sydney
PortoAlegre
Figure 5.3With two breaks in the communication lines, the network partitions into twogroups.
Relaxing Durability
Overview of Durability: When people discuss the ACID properties of database transactions, they often focus
on consistency. Consistency ensures that requests are handled in a way that maintains order and
correctness. However, many would argue that relaxing durability is a bad idea, since the main purpose of a
data store is to avoid losing updates.
1. What is Durability?: Durability means that once a transaction is confirmed, it will remain safe even if
the system crashes. If a database loses updates, it raises concerns about its reliability.
2. Performance vs. Durability: There are situations where sacrificing some durability can lead to better
performance. For example, if a database mainly runs in memory (which is faster than using a disk), it
can handle requests quickly and only save changes to disk occasionally. The downside is that if the
server crashes before it saves those changes, any recent updates will be lost.
3. Example of Session Data: A good case for this trade-off is user session data on large websites. These
websites need to track many users' activities in real-time, which requires fast processing. Losing some
session data isn't a huge problem; it might annoy users but would be less frustrating than a slow
website. Therefore, it's often acceptable to write this session data in a non-durable way, meaning it
doesn't have to be saved immediately.
4. Capturing Telemetry Data: Another example is collecting data from physical devices (like sensors).
Here, it might be more important to gather data quickly than to ensure every last piece is saved. If a
server goes down, losing some recent data might be acceptable.
5. Replication Durability Issues: Another aspect of durability involves replicating data across multiple
nodes (servers). If one server (the master) processes an update but crashes before that update
reaches other servers (the slaves), those updates can be lost. This is especially important in a setup
where a slave can automatically become the master if the original master fails.
6. Auto-Failover Risks: If the master server fails, any updates it processed but didn't replicate to the
slaves will be lost. When the master comes back online, it might have conflicts with updates that
occurred during its downtime. This is a durability issue because you think your update went through
since the master confirmed it, but the failure caused it to be lost.
7. Improving Replication Durability: To avoid losing updates, you can require the master server to wait
for some replicas to confirm they received the update before telling the client that the update was
successful. However, this approach can slow down the system and make it less available, especially if
any slaves fail.
8. Flexibility in Durability Requirements: Just like other aspects of durability, you can often set different
levels of durability for individual requests. This means you can choose stronger durability for critical
updates while allowing less important updates to be processed more quickly without guarantees that
they won't be lost.
5.5 Quorums
Understanding Quorums: When designing a system, it’s possible to balance between consistency and
durability instead of choosing one or the other. The more nodes (servers) you involve in a request, the less
likely you are to have inconsistencies. This raises the question of how many nodes are needed to achieve
strong consistency.
Write Quorum: Let’s say you have the same data copied over three nodes. You don’t need all three nodes
to confirm a write for strong consistency; you only need two. This is called a write quorum. It can be
summarized with this rule: W > N/2, where W is the number of nodes that need to participate in the write,
and N is the total number of nodes that hold a copy of the data.
Read Quorum: There’s also a concept called the read quorum, which determines how many nodes need to
be contacted to ensure you have the most recent data. This is a bit more complex, as it depends on how
many nodes confirmed a write.
Example: If the replication factor is 3 and two nodes need to confirm a write (W = 2), then you need to
contact at least two nodes to ensure you get the latest data. If only one node is required to confirm a write
(W = 1), then you would need to contact all three nodes to guarantee you have the latest updates. If you
don’t have a write quorum, there could be conflicts, but contacting enough nodes allows you to detect
those conflicts.
Inequality for Strongly Consistent Reads: You can achieve strongly consistent reads with this rule: R + W >
N. Here, R is the number of nodes contacted for a read, W is the number of nodes confirming a write, and N
is the replication factor.
Master-Slave Distribution: In a master-slave setup (where one node is designated as the master), you only
need to write to the master to avoid conflicts. For reads, you should also read from the master to prevent
inconsistencies.
Replication Factor: It’s common to have a replication factor of 3, which is usually sufficient for good
resilience. This means that even if one node fails, you can still perform reads and writes successfully. If
automatic rebalancing is in place, the system can quickly create a new copy of the data to maintain this
resilience.
Operation Variability: The number of nodes involved can change based on the operation. For example, you
might need a quorum for some updates but not for others, depending on how important consistency and
availability are. If you want faster reads that can tolerate some staleness, you might contact fewer nodes.
Trade-offs: If you require fast, strongly consistent reads, you could set it up so that all nodes must
acknowledge writes. This would mean that writes take longer because they need confirmation from all
nodes, and you wouldn’t be able to afford to lose a node.
Flexibility in Choices: The main takeaway is that you have various options to choose from and can decide
which combination of consistency, availability, and speed suits your needs. While some discussions about
NoSQL systems suggest a simple trade-off between consistency and availability, it’s actually more complex
and flexible than that.