Tunable Consistency
In the world of distributed databases, there is no “one size fits all” for consistency. Sometimes you need to know for a fact that your data is safe (e.g., a bank transfer). Other times, you just want the request to complete as fast as possible, even if the data is a few milliseconds stale (e.g., a “like” counter on a social media post).
Cassandra gives you a superpower called Tunable Consistency. Unlike traditional RDBMS (which force strong consistency) or some NoSQL stores (which force eventual consistency), Cassandra lets you decide per request.
1. The CAP Theorem: Cassandra’s Choice
The CAP Theorem dictates that in the event of a network partition (P), you must choose between Availability (A) and Consistency (C).
Cassandra is fundamentally an AP system. It prioritizes Availability and Partition Tolerance. This means if some nodes are down or cut off from the network, the cluster can still accept reads and writes. However, using Tunable Consistency, you can configure it to behave like a CP system when needed.
2. The Formula: R + W > N
The golden rule of consistency in quorum-based systems is:
R + W > N
If the number of nodes read (R) plus the number of nodes written (W) is greater than the replication factor (N), you have Strong Consistency.
- N (Replication Factor): The total number of copies of your data. (Standard production value is 3).
- W (Write Consistency): How many replicas must acknowledge a write for it to be successful.
- R (Read Consistency): How many replicas must respond to a read for it to be successful.
Why does this work?
If R + W > N, there must be at least one overlapping node that participated in both the write and the read. That node holds the latest data.
Example:
- N=3: Nodes A, B, C.
- W=2: Write goes to A, B. (C misses it).
- R=2: Read goes to B, C.
- Overlap: Node B has the new data.
Cassandra uses Last Write Wins (LWW) based on client-provided timestamps to determine which version is the most recent. When the coordinator receives responses from R replicas, it compares their timestamps. If they differ, it returns the latest version to the client and triggers a Read Repair to update the stale nodes.
3. Interactive: Consistency Calculator
Experiment with different values to see how they affect Consistency, Availability, and Latency.
Strong Consistency ✓
4. Common Consistency Levels
| Level | Description | Use Case |
|---|---|---|
ONE |
Only 1 replica needs to ack. Fastest, lowest consistency. | Logs, IoT sensor data, “Likes”. |
QUORUM |
Majority (N/2 + 1) replicas must ack. | Standard for critical data (User profiles, balances). |
ALL |
All replicas must ack. Zero fault tolerance (1 node down = fail). | Rare. Only when you absolutely cannot afford stale reads. |
LOCAL_QUORUM |
Majority in the local datacenter only. | Multi-region apps wanting fast local consistency. |
EACH_QUORUM |
Majority in each datacenter. | Global strong consistency (very slow). |
5. Code Implementation
Cassandra drivers allow you to set consistency levels at the query level.
Java (Datastax Driver)
In Java, you use the SimpleStatement or QueryBuilder to apply consistency settings.
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.ConsistencyLevel;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
public class ConsistencyExample {
public void executeQuery(CqlSession session) {
// Create a statement for reading user data
// Strong Consistency (QUORUM) is recommended for reads
SimpleStatement readStmt = SimpleStatement.builder("SELECT * FROM users WHERE id = ?")
.addPositionalValue(123)
.setConsistencyLevel(ConsistencyLevel.QUORUM)
.build();
// Execute the read
session.execute(readStmt);
// For writing non-critical data (e.g., logs), we might prioritize speed
// using ConsistencyLevel.ONE.
SimpleStatement writeStmt = SimpleStatement.builder("INSERT INTO logs (id, msg) VALUES (?, ?)")
.addPositionalValue(456)
.addPositionalValue("User logged in")
.setConsistencyLevel(ConsistencyLevel.ONE)
.build();
session.execute(writeStmt);
}
}
Go (Gocql)
In Go, the gocql library uses the Consistency field in the Query struct.
package main
import (
"log"
"github.com/gocql/gocql"
)
func main() {
// Connect to the cluster
cluster := gocql.NewCluster("127.0.0.1")
cluster.Keyspace = "example_keyspace"
// Set default consistency for the cluster session
cluster.Consistency = gocql.Quorum
session, err := cluster.CreateSession()
if err != nil {
log.Fatal(err)
}
defer session.Close()
// 1. Strong Consistency Read (QUORUM)
// Even if cluster default is Quorum, it's good practice to be explicit for critical queries.
var id int
var name string
if err := session.Query("SELECT id, name FROM users WHERE id = ?", 123).
Consistency(gocql.Quorum).
Scan(&id, &name); err != nil {
log.Fatal(err)
}
// 2. Low Latency Write (ONE)
// We only need 1 replica to acknowledge this write.
if err := session.Query("INSERT INTO logs (id, message) VALUES (?, ?)", 456, "User logged in").
Consistency(gocql.One).
Exec(); err != nil {
log.Fatal(err)
}
}
[!TIP] Pro Tip: Use
LOCAL_QUORUMfor multi-datacenter deployments. It avoids the latency penalty of cross-region network calls while maintaining strong consistency within the local region. This allows you to survive the loss of a node in your local DC without failing requests.
[!WARNING] Avoid
ALL: UsingConsistencyLevel.ALLdestroys availability. If a single node is down during maintenance or a minor glitch, your entire cluster becomes unavailable for that operation. Only use it if you understand the risks.
6. Summary
- Cassandra is an AP system by default.
- Tunable Consistency allows you to choose CP behavior when needed.
- Strong Consistency requires
R + W > N. - Most production apps use
QUORUM(orLOCAL_QUORUM) for a balance of safety and performance.