accesspatterns.dev
← All models

iot

Sensor Readings

Append-only device telemetry, read by time window and expired by TTL.

Time-series in its simplest DynamoDB shape: one partition per device, a timestamp as the sort key, and readings only ever appended. Old readings expire rather than being deleted, so the table stays the size of the window you care about.

The model

Reading

One measurement from one device at one moment.

pk
DEVICE#<deviceId>
sk
TS#<takenAtIso>

Attributes: celsius (N), humidity (N), takenAt (N), expiresAt (N)

Access patterns

  • QueryRead a device's recent readings

    Every reading held for one device, oldest to newest.

  • ScanScan every device (for contrast)

    The same readings without a partition key: every row in the table is read.

Design notes

One partition per device, not per fleetwhy

Keying by device rather than by, say, the day spreads writes across as many partitions as there are devices. A day-keyed partition would take every device's writes at once, which is the textbook hot partition: correct, and unusably slow at volume. The usual answer for one very chatty device is a composite bucket - DEVICE#<id>#<yyyy-mm> - which keeps the device split without collecting the whole fleet.

The window is a key condition, not a filtertrade-off

A chart wants a range - between two timestamps - and that belongs in the key condition so the readings outside it are never read. The resolver handles only the partition half, so the console returns everything held for the device. On a real device with a year of readings that difference is the whole cost of the page.

Old readings expire rather than being deletedtrade-off

Each reading carries an expiresAt, and TTL removes it once that passes. One seeded reading has already gone past its expiry and is still present, because the in-browser engine runs no sweep and real DynamoDB deletes on its own schedule - typically within a few days, with no guarantee. Deleting explicitly would mean paying a write to remove every reading you already paid a write to store.

Run the Scan and watch what it costswhy

The Query reads one device's partition and nothing else. The Scan beside it reads every row in the table to find the same readings, and the instrument marks the ones it read and threw away. Both return the readings you asked for; only one of them charges you for the other device's.

The sort key needs the time, not the datewhy

A device taking a reading every few hours collides on a date-only sort key four times over, and each write silently replaces the last - a primary key is unique, so the second reading is not a second row. Anything keyed by time needs enough resolution to separate two events, or an id after the timestamp to break the tie.

Append-only means no read before writewhy

A reading is a new item at a new sort key, so nothing is read, merged and put back. That is what makes the write path cheap and free of contention: two readings arriving at once cannot conflict, because at a resolution fine enough to separate them they are not the same item.

Taught in the course

sensor-readings

These rows are real and already here. Running an operation against them downloads a DynamoDB-compatible engine, about 850 KB, and executes it in this tab.

Try an example

Every reading held for one device, oldest to newest.

PK(pk)
SK(sk)
celsius
expiresAt
humidity
takenAt
DEVICE#greenhouse-1
TS#2026-07-17T00:00:00ZS
19.6N
1786838400N
72N
1784246400N
DEVICE#greenhouse-1
TS#2026-08-25T04:48:00ZS
23.1N
1790208000N
58N
1787633280N
DEVICE#greenhouse-1
TS#2026-08-25T10:48:00ZS
20.9N
1790208000N
67N
1787654880N
DEVICE#greenhouse-1
TS#2026-08-25T16:48:00ZS
21.8N
1790294400N
64N
1787676480N
DEVICE#greenhouse-1
TS#2026-08-25T22:48:00ZS
22.4N
1790294400N
61N
1787698080N
DEVICE#coldstore-2
TS#2026-08-25T15:36:00ZS
3.9N
1790294400N
46N
1787672160N
DEVICE#coldstore-2
TS#2026-08-25T21:36:00ZS
3.2N
1790294400N
44N
1787693760N

Run an operation to see the raw engine response.

table sensor-readingskeys PK / SKitems 7

Transactions, streams, tags and TTL are among the operations the in-browser engine doesn't implement yet.