Every chapter so far answered a question about something you could name — a customer, a region, a status. The last question in this story has no name in it at all: "like that one, but brighter." No WHERE clause matches a resemblance. This chapter builds the one kind of index that can — and closes the series where it started: with a customer, a question, and a query.
You'll learn
See why similarity is not equality — no attribute filter or GSI can rank by resemblance
Put a preference into numbers a roaster already uses: a small, interpretable taste vector
Build the door: CREATE VECTOR INDEX — the on-demand requirement, the double-run confirmation, and the backfill you wait out
Walk through it: a VECTOR_DISTANCE top-k search, and the deliberately narrow WHERE that keeps it native
The question no filter can hold
First instinct: filter for it. RoastLevel < 4? That's a threshold pretending to be a preference — and on the base table it's a Scan anyway. A GSI on RoastLevel? A GSI gives a known attribute another door; it can order coffees by roast, but it can't rank them by resemblance to Midnight Decaf. The buyer didn't give you an attribute value. They gave you a direction from a reference point — and that's a geometry problem, not a lookup.
The geometry is already on paper. The roasters score every product on the cupping sheet: Body, Acidity, Sweetness, Roast, each 0–1. Written as four numbers, Midnight Decaf is [0.70, 0.35, 0.55, 0.85] — full body, low acidity, dark roast. That four-number list is a vector, and "tastes similar" becomes "sits nearby". Nobody generated these with a model; the roasting team authored them, the way they always have. DynoStudio validates and searches vectors — it never invents them.
Build the door
sql
CREATE VECTOR INDEX "TasteIndex" ON "BeanAndBark" (TasteProfile) DIMENSIONS 4 DISTANCE EUCLIDEAN PARTITION BY (Category) FILTER BY (InStock) PROJECT (Sku, Name, RoastLevel, TastingNotes);
DIMENSIONS 4, DISTANCE EUCLIDEAN — four cupping axes, and straight-line distance between profiles, where intensity matters: a 0.9 roast really is farther from 0.3 than 0.5 is.
PARTITION BY (Category) — Bean & Bark sells coffee and dog treats. The partition becomes a search-schema field every search must pin with equality, so a tasting flight can never fetch a biscuit.
FILTER BY (InStock) — an optional equality filter inside the native search; recommending something you can't ship is worse than no recommendation.
PROJECT (Sku, Name, RoastLevel, TastingNotes) — the projection is a query contract: a search returns only projected fields (plus one aliased score). Project what the meeting needs.
Then the lifecycle the studio makes you respect. The table must be on-demand — BeanAndBark has been since the day you created it. The editor completes every clause from the live DescribeTable descriptor, and the create is double-run confirmed: the first Run validates and arms the statement, the second unchanged Run dispatches UpdateTable.VectorIndexUpdates.Create. And then — you wait. The index backfills asynchronously, and every search against it refuses until it's ACTIVE with backfill complete. Declaring the door doesn't open it; the Background Tasks panel tracks it, and at 1:58pm it flips ready.
Walk through it
Now translate the buyer. Start from Midnight Decaf [0.70, 0.35, 0.55, 0.85]. Brighter means acidity up: 0.35 → 0.80. Less bitter means roast down: 0.85 → 0.30. Body and sweetness they loved — leave them. The query vector is the conversation, written in the roasters' own axes:
sql
SELECT Sku, Name, RoastLevel, TastingNotes, VECTOR_DISTANCE(TasteProfile, [0.70, 0.80, 0.55, 0.30]) AS closenessFROM "BeanAndBark"."TasteIndex"WHERE Category = 'COFFEE' AND InStock = trueORDER BY VECTOR_DISTANCE(TasteProfile, [0.70, 0.80, 0.55, 0.30]) ASCLIMIT 5
The five nearest coffees to what they describedLowered
A native top-k search over the taste index — pinned to coffee, filtered to what can ship, nearest profile first.
sql
SELECT Sku, Name, RoastLevel, TastingNotes, VECTOR_DISTANCE(TasteProfile, [0.70, 0.80, 0.55, 0.30]) AS closenessFROM "BeanAndBark"."TasteIndex"WHERE Category = 'COFFEE' AND InStock = trueORDER BY VECTOR_DISTANCE(TasteProfile, [0.70, 0.80, 0.55, 0.30]) ASCLIMIT 5
The score arrives from the service and needs its explicit alias; EUCLIDEAN reads nearest-first with ASC.
LIMIT is required (1–100) — a similarity search is always "the k nearest", never "everything within earshot".
The bill is metered in request bytes, not RCUs — a different door with a different meter, disclosed like every other run.
Top of the list: Sunrise Ridge, a washed Ethiopian — bright, medium-bodied, roasted light. The tasting notes read like the buyer's sentence run backwards. Nadia walks in at two with three coffees and the story of why each one is close — and the lobby-bar account signs that week. The last query of the series didn't match a value. It understood a description.
Recap
A GSI answers a known attribute through another door; a vector index answers "something like this" — similarity is not equality, and no filter ranks by resemblance.
A vector doesn't have to be an opaque embedding: four interpretable cupping axes made the search explainable — you can see why two coffees are close — and the roasters authored them without a model in sight.
Creating the index is a lifecycle, not a statement: on-demand table, live-descriptor validation, double-run confirmation, asynchronous backfill — and searches refuse until it's ACTIVE.
The search itself is one native SearchVectors request: partition pinned by equality, optional inline filters, required top-k LIMIT, service-computed score, byte-metered and disclosed.
Similarity check2
1Why couldn't a GSI on RoastLevel answer the buyer?
2You run the search moments after CREATE VECTOR INDEX succeeds. What happens?
Try it yourself 2
1Translate another buyer
A café wants something "like Sunrise Ridge [0.55, 0.80, 0.60, 0.30] but sweeter and with more body — their customers found it thin." Write the search.
Start from the reference profile and move the axes the sentence names: sweetness up, body up, leave the rest.
Show solution
Body 0.55 → 0.80 and sweetness 0.60 → 0.85; acidity and roast stay. The vector is the sentence, translated axis by axis.
sql
SELECT Sku, Name, RoastLevel, TastingNotes, VECTOR_DISTANCE(TasteProfile, [0.80, 0.80, 0.85, 0.30]) AS closenessFROM "BeanAndBark"."TasteIndex"WHERE Category = 'COFFEE' AND InStock = trueORDER BY VECTOR_DISTANCE(TasteProfile, [0.80, 0.80, 0.85, 0.30]) ASCLIMIT 5
2Retire a door without touching the room
A year from now TasteIndex is superseded by a v2 with a fifth axis. Write the statement that removes the old read path — and say what happens to the products.
Dropping an index removes a way of reading, never the data being read.
Show solution
Nothing happens to the products — the base table and every item are unchanged. Searches against the dropped index fail as deletion begins; the data keeps living in the table, reachable through every other door.