Skip to content
← All patterns

iot

Sensor Fusion

Which machines are behaving like this one?

Which machines are behaving like this one, across five channels at once.

Sensor Readings keeps one device's history in time order. This asks the other question: given a reading that just arrived, which machines in the fleet have looked like it? Five channels become one five-dimensional point and the index finds the nearest. The whole difficulty is in the numbers before they reach DynamoDB, which is why this table carries each fingerprint twice, raw and normalised, with an index on each.

The model

Fingerprint

One machine's current state across five channels, held raw and normalised.

pk
SITE#<site>
sk
MACHINE#<machineId>

Attributes: site (S), machineKind (S), raw (L), features (L)

Access patterns

  • SearchVectors · by-rawNearest on the raw readings

    The same question asked of the numbers as the sensors report them.

  • SearchVectors · by-normalisedNearest on the normalised features

    The identical question over the identical channels, each one put on a common scale first.

  • QueryEvery machine at a site

    The whole site's current state, by key, with both vectors on every row.

Design notes

Un-normalised, the biggest number is the only numbermodelling

Spindle speed runs to 3000 and vibration runs to 20, so the speed channel's range is a hundred and fifty times the vibration channel's. Euclidean has no idea those are different quantities in different units. It squares each gap and adds them, so a 150 rpm difference - a twentieth of the speed channel - contributes fifty-six times what the entire vibration range can, and the channel with the widest numeric range wins. Both searches agree that press-04 is the best match, which is the obvious answer and the one anybody would smoke-test with. After that the raw search offers mill-03 and press-02, the two healthiest machines on the floor, because their speed and pressure readings happen to sit nearest. That is not a ranking of behaviour, it is a ranking of rpm with rounding noise attached, and it agreed with the right answer for exactly one row.

Normalising is the model, and it is four arithmetic operationsmodelling

Each channel is mapped onto nought to one against a fixed operating range, declared once and written down: temperature 20 to 90 C, vibration 0 to 20 mm/s, pressure 900 to 1200 kPa, current 0 to 60 A, speed 0 to 3000 rpm. Subtract the floor, divide by the span. After that the normalised search follows press-04 with lathe-11 and press-07, the two machines carrying the same hot-and-shaking signature at 900 and 2850 rpm. Same five sensors, same metric, same index type, and the fleet splits the other way.

The scaling constants are part of the index's contractoperations

Those bounds are not a batch statistic to be recomputed when the fleet changes. Recompute them and every vector already in the table was built against a different scale, so a query normalised the new way lands somewhere the stored corpus does not mean. Nothing errors: the dimensions still match, which is the only thing the index checks. This is the same failure as querying with the wrong embedding model, with no model anywhere near it, and the fix is the same - write the constants down beside the index, version them, and re-derive the whole fleet when they move.

Adding a sixth sensor is a new indexoperations

Dimensions are fixed when the index is created. Fit oil-pressure sensors across the fleet and there is no way to widen these vectors to six: you build a second index over a second attribute, backfill every machine, and cut over. Plan for that, because the temptation is to reuse a spare slot in the existing vector, and a channel that means humidity on half the fleet and oil pressure on the other half is a corpus that cannot be searched at all.

Equality-only filtering bites harder here than it does on a tenanttrade-off

A SearchSchema takes equality and nothing else, and for a tenant id that is no loss - tenancy is an equality. A fleet's natural filters are not. In the last hour, above five millimetres a second, between two shift changes: every one of those is a range, and a search condition cannot express any of them. So the time axis is kept out of the index entirely and this table holds one current fingerprint per machine rather than a history. History belongs on a sort key, where BETWEEN works, which is what Sensor Readings is for. Trying to have both in one index means either searching stale rows or post-filtering inside a hundred-result budget.

The HASH keeps Derby out, and it is not a permissionsecurity

press-21 at Derby is the closest row in the table to the query on the normalised index, closer than anything in Leeds, and neither search returns it because site is the SearchSchema's HASH. That is the HASH doing exactly its job. It is still not a security boundary: fine-grained access control does not apply to SearchVectors, so anyone who can search this index can read Derby's fleet by changing one string.

Five channels is a decision, not a discoverytrade-off

Speed is in the vector, so two machines running at very different rpm are pushed apart even when everything else matches. Drop speed from the distance and press-07 is the better match on the four condition channels, at 0.045 against lathe-11's 0.048. Put speed back and lathe-11 wins, 0.27 to 0.39, because it is running closer to the rate the query came in at. If you want similarity that ignores speed, you drop the channel; if you want speed to matter more, you weight it. Nobody is going to tell you which is right, and no amount of index tuning substitutes for that choice. There is no neural network in this design and there is still a model in it.

Read it against

  • Sensor Readings

    One device through time against the whole fleet at one instant. The same telemetry, read along the two axes it has.

Taught in the course

sensor-fusionDynamoDB workbench
Ready to run
Explore an access patternSelect to load & run

The same question asked of the numbers as the sensors report them.

Request
Execute against the local Dynoxide engine
ReturnedFiltered outChanged
PK(pk)
SK(sk)
features
machineKind
raw
site
SITE#leeds
MACHINE#lathe-11S
[0.7714, 0.545, 0.8067, … 5]L
latheS
[74, 10.9, 1142, … 5]L
leedsS
SITE#leeds
MACHINE#mill-03S
[0.1571, 0.09, 0.2267, … 5]L
millS
[31, 1.8, 968, … 5]L
leedsS
SITE#leeds
MACHINE#press-02S
[0.2, 0.105, 0.2667, … 5]L
pressS
[34, 2.1, 980, … 5]L
leedsS
SITE#leeds
MACHINE#press-04S
[0.8286, 0.56, 0.8333, … 5]L
pressS
[78, 11.2, 1150, … 5]L
leedsS
SITE#leeds
MACHINE#press-07S
[0.8, 0.53, 0.7933, … 5]L
pressS
[76, 10.6, 1138, … 5]L
leedsS
SITE#leeds
MACHINE#press-09S
[0.2286, 0.12, 0.3167, … 5]L
pressS
[36, 2.4, 995, … 5]L
leedsS
SITE#derby
MACHINE#press-21S
[0.8143, 0.55, 0.8233, … 5]L
pressS
[77, 11, 1147, … 5]L
derbyS
Awaiting request
Your next query starts here.

Choose an access pattern above, or build your own request. See what comes back and what it costs.

Write transactions, streams, tags and TTL are among the operations this browser build leaves out. dynoxide's native build has them.