accesspatterns.dev
← All models

search

Product Autocomplete

Type-ahead from a sort key, without a search service.

Autocomplete inside a category, using nothing but the sort key. Names are stored lowercased so ordering is predictable, and a prefix read returns the matches - enough for a type-ahead box, and a long way short of a search engine.

The model

Product

A product, keyed by its lowercased name within a category.

pk
CATEGORY#<categoryId>
sk
NAME#<normalisedName>

Attributes: displayName (S), pricePence (N), inStock (S)

Access patterns

  • QueryNames starting with a prefix

    Products in a category whose name begins with what has been typed.

  • ScanScan the whole catalogue (for contrast)

    The same search without a partition key: every product in the table is read.

  • GetItemFetch one product

    A single product by category and exact name.

Design notes

Left-anchored only, and that is the limittrade-off

A prefix read finds "coffee" from "cof". It cannot find it from "ffee", because a sort key is ordered from the left and nothing indexes the middle of a string. Every design here is a left-anchored match; wanting the middle is the point at which this technique stops and a search service starts.

Normalised so the ordering is knowablewhy

Sort keys compare byte by byte, so every capital sorts before every lowercase letter and a mixed-case column orders in a way no user expects. The key holds a normalised copy - lowercased, punctuation dropped, whitespace collapsed - while displayName keeps the real thing. Note that the transform is more than case: "Coffee, Decaf" becomes "coffee decaf", because a comma sorts before every letter and would scatter the run. Whatever normalisation you choose, the typed prefix has to go through exactly the same one before it is compared.

The head word is the whole indextrade-off

"Coffee, Decaf" is found by typing "cof". "Ground Coffee Tin" is not - it is filed under "gro", and no amount of typing "cof" will reach it. That inversion in the other names is not a formatting convention, it is the index: choosing which word leads decides what is findable, and anything that must be findable by two words has to be stored twice.

The category keeps the read smalltrade-off

A prefix read stays within a partition, so the category is not a filter - it is what stops the search touching the rest of the catalogue. Run the Scan beside it and the meters show the difference: the same rows come back, having read every product in every category to find them. That is also this design's limit: it cannot search across categories in one read, and a design needing that either duplicates rows per scope or accepts a query per category.

Read the sort-key columntrade-off

The console reads the whole category, since the preview resolves only the partition half of a key condition. The alphabetical run is what matters: a prefix read is a contiguous slice of exactly what you see, which is why it costs only the rows it returns.

Taught in the course

product-autocomplete
Try an example

Products in a category whose name begins with what has been typed.

PK(pk)
SK(sk)
displayName
inStock
pricePence
CATEGORY#drinks
NAME#cocoa darkS
Cocoa, DarkS
yS
349N
CATEGORY#drinks
NAME#coffee decafS
Coffee, DecafS
yS
615N
CATEGORY#drinks
NAME#coffee espressoS
Coffee, EspressoS
yS
725N
CATEGORY#drinks
NAME#coffee filterS
Coffee, FilterS
nS
580N
CATEGORY#drinks
NAME#cordial elderflowerS
Cordial, ElderflowerS
yS
410N
CATEGORY#drinks
NAME#ground coffee tinS
Ground Coffee TinS
yS
899N
CATEGORY#drinks
NAME#tea assamS
Tea, AssamS
yS
295N
CATEGORY#drinks
NAME#tea earl greyS
Tea, Earl GreyS
yS
310N
CATEGORY#bakery
NAME#croissant almondS
Croissant, AlmondS
yS
240N
CATEGORY#bakery
NAME#croissant plainS
Croissant, PlainS
yS
190N

Run an operation to see the raw engine response.

table product-autocompletekeys PK / SKitems 10

The in-browser engine is a preview build. Transactions, vector search, streams, tags and TTL are among the operations it doesn't implement yet.