Keep One Asset’s History Together Without Growing Forever
A technician wants a time range for one pump. Give that request a bounded partition and an ordering that makes the required records reachable.
14 sections · 4 questions · 1 coding challenge
The arrow keys move between sections too.
Keeping Nearby Records Together for a Known Query
BOTIE
For our maintenance example, locality is the useful idea: store the records needed by one bounded query together, and order them by the field the query ranges over. Cassandra’s partition and clustering columns express those different jobs explicitly.
Botie Needs the Readings Around a Fault, Not Every Reading Ever Taken
The useful question is small: what happened to P7 around the reported fault? Storing all sensors together makes that request search unrelated histories. Putting P7’s entire lifetime into one partition eventually creates another problem: the group has no useful bound.
We will divide an asset’s history into time buckets and order observations inside each bucket. Those two decisions explain the partition key and clustering key used by a wide-column design.
Invented maintenance records. These deterministic local models expose a storage decision; they do not run a distributed database or establish its guarantees.
Step 1 of 1
Which fields locate the group, and which order its contents?
This is an invented time-series design.
| Field | Role | Example |
|---|---|---|
| tenant, asset, day | Complete partition key | north, P7, d1 |
| time, id | Ordering and unique event position | 10, R1 |
| value and unit | Measured payload | 24, C |
tenant, asset, day
- Role
- Complete partition key
- Example
- north, P7, d1
time, id
- Role
- Ordering and unique event position
- Example
- 10, R1
value and unit
- Role
- Measured payload
- Example
- 24, C
A query has an address and an order inside that address
The address, interval and event order each remove a different ambiguity.
Find the fault window inside one bucket
Choose the partition, apply the time interval, order equal-time events by identity and take the requested result count.
BOTIE
Selected reading IDs
- 0R1
- 1R3
Follow the operations
1. History records
[ { "id": "R3", "tenant": "north", "asset": "P7", "day": "d1", "time": 12 }, { "id": "R1", "tenant": "north", "asset": "P7", "day": "d1", "time": 10 }, { "id": "R2", "tenant": "north", "asset": "P8", "day": "d1", "time": 11 }, { "id": "R4", "tenant": "north", "asset": "P7", "day": "d2", "time": 14 } ]2. Complete bucket key
{ "tenant": "north", "asset": "P7", "day": "d1" }3. Select one complete partition key
[ { "id": "R3", "tenant": "north", "asset": "P7", "day": "d1", "time": 12 }, { "id": "R1", "tenant": "north", "asset": "P7", "day": "d1", "time": 10 } ]4. Time interval
{ "start": 10, "end": 13 }5. Keep events inside a time interval
[ { "id": "R3", "tenant": "north", "asset": "P7", "day": "d1", "time": 12 }, { "id": "R1", "tenant": "north", "asset": "P7", "day": "d1", "time": 10 } ]6. Order events by time and identity
[ { "id": "R1", "tenant": "north", "asset": "P7", "day": "d1", "time": 10 }, { "id": "R3", "tenant": "north", "asset": "P7", "day": "d1", "time": 12 } ]7. Result limit
2
8. Take the first event identities
- 0R1
- 1R3
9. Result
- 0R1
- 1R3
This record-list calculation neither queries Cassandra nor measures its partition reads.
1. Give each part of the key one job
The partition key identifies a bounded group. Our key combines tenant, asset and day. The clustering order arranges rows within that group by event time and a tie-breaking event ID.
Two sensors can report at the same timestamp, so time alone may not identify an observation. Include the actual identity needed to prevent accidental replacement. An asset label without tenant scope can also refer to the wrong customer’s pump.
The complete primary key identifies the row. Its prefix locates the group we can read together.
2. Choose a bucket from the expected volume
A daily bucket is convenient for the example, but its suitability depends on measurement rate and item size. A busy asset may need smaller time buckets or another bounded split. Very small buckets create more queries and merge work for long intervals.
Estimate the largest expected partition and test it with the database’s documented guidance. A partition containing one quiet day is not evidence about the busiest customer’s busiest day. Retention and late-arrival rules belong in the estimate too.
The bucket size is a workload choice, not an automatic property of wide-column storage.
Check yourself
Questions on Parts 1 to 2
Question 1 of 2
What keeps their positions distinct?
Use the information below.
A reading table uses partition (tenant, asset, day) and ordering (time, id). Two readings share a timestamp.3. Apply the partition before the range
The local Python model first checks the complete tenant, asset and day selector. It then keeps events in a half-open interval: start is included and end is excluded. Finally it sorts by time and ID before applying a result limit.
The dictionary list makes these operations visible. A real database can use its stored layout to avoid scanning every record; our Python list cannot demonstrate that performance difference.
A result limit must follow the ordering if the request means the earliest matching events.
Observe a timestamp tie
The event identity gives equal-time observations a stable order.
events = [{"id":"B","time":12},{"id":"A","time":12},{"id":"C","time":10}]
ordered = sorted(events,key=lambda r:(r["time"],r["id"]))
print([r["id"] for r in ordered])['C', 'A', 'B']Ordering only by timestamp would leave the meaning of the tie dependent on the input order.
4. Express that layout in a real Cassandra table
CQL looks familiar to a SQL reader, but its supported access paths follow Cassandra’s data model. The inner parentheses below identify a compound partition key; the remaining primary-key columns give the clustering order.
Use a separate table or supported index for a materially different access pattern when justified. ALLOW FILTERING is not a general substitute for designing a bounded query. Its cost can grow with the data the server must examine.
The browser model runs without Cassandra. This reference needs an external development cluster.
Run in your own environment
A query-shaped Cassandra history table
The reference uses the Python Cassandra driver to show the real CQL declaration.
External setup: install cassandra-driver and run an authorised local Cassandra development cluster.
Create the maintenance keyspace with replication settings appropriate to that development cluster before running this DDL. The browser does not execute it.
from cassandra.cluster import Cluster
cluster = Cluster(["127.0.0.1"])
session = cluster.connect("maintenance")
session.execute("""
CREATE TABLE IF NOT EXISTS readings_by_asset_day (
tenant text, asset text, day date, event_time timestamp,
event_id text, value double,
PRIMARY KEY ((tenant, asset, day), event_time, event_id)
) WITH CLUSTERING ORDER BY (event_time ASC, event_id ASC)
""")
cluster.shutdown()Choose the key from the actual request. This declaration does not establish a suitable production partition size.
Official implementation documentation. Documentation checked .
5. Merge the buckets the requested interval actually touches
A request crossing a bucket boundary needs every overlapping bucket. Apply the time bounds within each, then merge by the same event ordering. Taking a limit independently from each bucket is not the same as proving a global result is complete; the merge still has to select the requested global prefix.
Paging needs a stable continuation meaning. A cursor can include the bucket and final clustering key, or use an opaque database paging token according to the driver contract. Changing the query while reusing a token can invalidate its meaning.
An empty result from one day does not establish that an asset has no readings on the next day.
6. Expiration leaves work for the storage engine
Retention can expire old measurements, but storage engines may retain deletion markers until it is safe to remove them. Cassandra uses tombstones to represent deleted data. Large scans through expired or deleted entries can still be expensive before compaction and repair requirements are satisfied.
Keep retention, repair and compaction settings together in the operating plan. Do not change a deletion-grace setting solely to make disk space disappear faster without understanding how replicas learn the deletion.
Logical disappearance and physical reclamation are different events.
7. Keep the source of repeated query views clear
A table by asset and day may serve the technician while a separate analytical system serves fleet-wide comparisons. If the same fact appears in several query views, define which source owns it and how updates or deletions reach the copies.
We will return to projections later. For now, test that an event in another tenant, asset or day cannot leak into this bucket lookup. A fast query that returns the wrong partition is still the wrong query.
The workshop’s changed day leaves the earlier records untouched while changing which partition is addressed.
Check yourself
Questions on Parts 1 to 7
Question 1 of 2
What must a complete query consider?
Use the information below.
A request covers the end of d1 and the start of d2.Try it with Botie
Build with Botie
Write and run Python