Application performance with Cassandra depends as much on driver usage as on schema.
Applications use a driver (Java, Python, Go, Node.js, C#...) speaking the native protocol on port 9042. Core practices are the same in every language:
- create one session per application and reuse it; it manages connection pools to all nodes,
- use prepared statements for anything executed repeatedly: the statement is parsed once, values are bound as parameters (no CQL injection), and the driver learns which columns form the partition key,
- keep the default token-aware load balancing policy (wrapped in a data-center-aware one): the driver computes the token of the bound partition key and sends the request directly to a replica, saving a network hop,
- set the consistency level per statement or as a profile default (
LOCAL_QUORUMis typical), - run independent writes asynchronously and in parallel, with a cap on in-flight requests, instead of batching them.
Large results are paged automatically: the driver fetches fetch_size rows (5000 by default) at a time as you iterate. For stateless APIs, return the opaque paging state to the client and resume with it.
Mark statements idempotent when they are (most upserts are; counter updates and list appends aren't) so the driver may retry or speculatively execute them safely after timeouts.
Don't bind null for missing values; leave them unset to avoid tombstones.
Example
from datetime import date, datetime, timezone
from cassandra.cluster import Cluster, ExecutionProfile, EXEC_PROFILE_DEFAULT
from cassandra.policies import DCAwareRoundRobinPolicy, TokenAwarePolicy
from cassandra import ConsistencyLevel
profile = ExecutionProfile(
load_balancing_policy=TokenAwarePolicy(DCAwareRoundRobinPolicy(local_dc="datacenter1")),
consistency_level=ConsistencyLevel.LOCAL_QUORUM)
cluster = Cluster(["10.0.0.1", "10.0.0.2"], execution_profiles={EXEC_PROFILE_DEFAULT: profile})
session = cluster.connect("shop") # one session per app
insert = session.prepare("INSERT INTO readings (sensor_id, day, ts, value) VALUES (?, ?, ?, ?)")
insert.is_idempotent = True
day = date(2026, 10, 17)
rows = [(day, datetime(2026, 10, 17, 9, m, tzinfo=timezone.utc), 20 + m / 10) for m in range(60)]
futures = [session.execute_async(insert, ("s-17", day, ts, v)) for day, ts, v in rows]
for f in futures:
f.result() # raise on failure
select = session.prepare("SELECT ts, value FROM readings WHERE sensor_id = ? AND day = ?")
for row in session.execute(select, ("s-17", day)): # pages transparently
print(row.ts, row.value)Remember
- One long-lived session per application
- Prepared statements: parsed once, safe binding, enable token-aware routing
- Run independent writes async in parallel, not in batches
- Results page automatically; mark idempotent statements
Watch out for
- Building CQL strings with f-strings for each request: no routing information, re-parsing, and injection risk.