Apache Cassandra is an open-source NoSQL database with high availability and scalability. This guide explains how to install and start Cassandra on macOS using Homebrew.
1. Installing Cassandra
This assumes that Homebrew is already installed. If not, please refer to the official Homebrew website to install it.
Open a terminal and run the following command to install Cassandra:
brew install cassandra
This command installs Cassandra itself along with command-line tools for interacting with Cassandra (such as cqlsh).
2. Starting the Cassandra Server
Once the installation is complete, start the Cassandra server with the following command. Using Homebrew’s services command allows you to start and manage Cassandra as a background service.
brew services start cassandra
To verify that Cassandra has started successfully, use the following command:
brew services list
If the status of cassandra shows started, it was successful.
3. Starting CQLSH (Cassandra Query Language Shell)
Once Cassandra is running, you can use cqlsh to connect to the Cassandra cluster and execute CQL (Cassandra Query Language) commands.
cqlsh
If the connection is successful, a cqlsh> prompt will appear.
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 6.0.0 | Cassandra 4.0.6 | CQL spec 3.4.5 | Native protocol v12]
Use HELP for help.
cqlsh>
With this, the basic Cassandra development environment is set up on macOS.
Appendix: Stopping and Restarting Cassandra
- Stop:
brew services stop cassandra - Restart:
brew services restart cassandra
4. Simulating the Consistency-Level Tradeoff
Installing Cassandra alone doesn’t let you experience its defining feature: tunable consistency, the ability to choose a consistency level per read/write. Instead of standing up a real cluster, this section simulates a replicated key-value store in Python (replication factor N=3) and measures, with actual numbers, how ONE / QUORUM / ALL trade off latency against staleness.
The model
We fix the number of replicas at \(N=3\) and run two experiments.
(a) Read latency vs. consistency level. The coordinator fans a read request out to all N replicas in parallel and completes once the required number of acks arrives (ONE=1, QUORUM=2, ALL=3). Each replica’s response time is drawn from a lognormal distribution (median 2ms, σ=0.6), modeling network + disk I/O jitter inside a datacenter. The read latency for a consistency level requiring k acks is the k-th order statistic of the N response times.
(b) Stale-read probability in a write-then-read race. A write is sent to all N replicas; the coordinator returns success to the client as soon as the fastest W replicas ack. At that instant, only the W fastest-committing replicas hold the new value. If a read for a random set of R replicas is issued immediately afterward and none of those R replicas already hold the new value, the read is stale. This probability has an exact hypergeometric form:
\[ P(\text{stale}) = \frac{\binom{N-W}{R}}{\binom{N}{R}} \]When \(R+W > N\) , we have \(N-W < R\) , forcing the numerator to zero — stale reads become impossible in theory. This is the precise mechanism behind Cassandra’s “R+W>N gives strong consistency” rule of thumb.
Simulation code
import numpy as np
from math import comb
rng = np.random.default_rng(42)
N = 3
TRIALS = 200_000
# (a) Read latency: fan out to N replicas, take the k-th arrival time
MEDIAN_MS, SIGMA = 2.0, 0.6
mu = np.log(MEDIAN_MS)
latencies = rng.lognormal(mean=mu, sigma=SIGMA, size=(TRIALS, N))
sorted_latencies = np.sort(latencies, axis=1)
for k, label in [(1, "ONE"), (2, "QUORUM"), (3, "ALL")]:
lat_k = sorted_latencies[:, k - 1]
print(label, "median=", np.median(lat_k), "p99=", np.percentile(lat_k, 99))
# (b) Stale-read probability in a write-then-read race (Monte Carlo)
def simulate_stale_probability(n_trials, n_replicas, W, R, rng):
stale = 0
for _ in range(n_trials):
d = rng.random(n_replicas) # commit order per replica
order = np.argsort(d)
updated_set = set(order[:W].tolist()) # W fastest to ack the write
read_set = set(rng.choice(n_replicas, size=R, replace=False).tolist())
if updated_set.isdisjoint(read_set):
stale += 1
return stale / n_trials
def analytic_stale_probability(n_replicas, W, R):
not_updated = n_replicas - W
if R > not_updated:
return 0.0
return comb(not_updated, R) / comb(n_replicas, R)
Results
With N=3 and 100k-200k Monte Carlo trials, the measured results were:
(a) Read latency (ms)
| Consistency level | Median | p95 | p99 |
|---|---|---|---|
| ONE (k=1) | 1.22 | 2.45 | 3.22 |
| QUORUM (k=2) | 2.00 | 3.87 | 5.12 |
| ALL (k=3) | 3.27 | 7.15 | 10.20 |
Moving from ONE to ALL degrades median latency by about 2.7x and p99 by about 3.2x. Notably, the tail (p99) degrades more than the median — a direct, measured illustration of ALL’s weakness: it is bottlenecked by whichever single replica is slowest.
(b) Stale-read probability in a write-then-read race (100k trials)
| Write CL / Read CL | R+W | Simulated | Analytic (hypergeometric) |
|---|---|---|---|
| ONE / ONE | 2 | 66.3% | 66.7% |
| ONE / QUORUM | 3 | 33.2% | 33.3% |
| QUORUM / ONE | 3 | 33.7% | 33.3% |
| ONE / ALL | 4 | 0.0% | 0.0% |
| QUORUM / QUORUM | 4 | 0.0% | 0.0% |
| ALL / ONE | 4 | 0.0% | 0.0% |
The simulated values match the analytic (hypergeometric) values to within 1%. Combinations with \(R+W \le N\) (ONE/ONE, ONE/QUORUM, QUORUM/ONE) show a non-trivial 33-66% stale-read probability, while every combination satisfying \(R+W > N\) (QUORUM/QUORUM, ONE/ALL, ALL/ONE) produced zero stale reads across 100,000 trials. This confirms that the common production setting “QUORUM write + QUORUM read” achieves effectively guaranteed strong consistency without paying the full latency cost of ALL.

Takeaways
- Raising the consistency level increases read latency, especially at the tail (ALL is 2.7x the median and 3.2x the p99 of ONE)
- \(R+W \le N\) combinations permit stale reads, at a probability exactly predicted by the hypergeometric formula \(\binom{N-W}{R} / \binom{N}{R}\)
- Satisfying \(R+W > N\) (e.g., QUORUM/QUORUM) makes stale reads theoretically impossible while keeping the latency penalty far below ALL
When you build a real cluster, try switching consistency levels per session in cqlsh with CONSISTENCY QUORUM; after creating a table, and observe the behavior yourself.