search_ads_recsys / 47 · personalized & e-commerce search search · 8 / 10

Personalized & e-commerce / marketplace search

A web query asks "what is the best answer?" An e-commerce query asks "what should I buy, in stock, right now?" Those are different objectives — and personalization is a scalpel, not a sledgehammer.

The plan

This lesson depends on three earlier ones. Query understanding tagged the query into attributes (brand, price, size) in 41; we consume those tags as hard constraints. Learning-to-rank in 44 already folds user/context features into a ranker; here we make the multi-objective blend explicit and decide when personalization is even appropriate. And the post-filter recall problem we hit was first introduced for ANN in 03 — we make it concrete with facet arithmetic. It sets up evaluation in 48, because once relevance is blended with business value, "is this result good?" stops being a single number.

When to personalize — the key judgement

The reflex of a junior engineer is "personalization is good, apply it everywhere." That is wrong, and the reason it's wrong is the single most valuable thing in this lesson. Personalization helps when the query is ambiguous and hurts when the query is clear.

Think about facebook login. There is exactly one correct answer and everyone wants the same one. Personalizing it does nothing useful (the top result is already locked) and risks doing harm (you push the canonical login page down to surface something "for you"). Now think about jaguar — animal, car, NFL team, guitar amp, OS version. Or python — language, snake, Monty. Here the query under-specifies intent, and a signal about who is asking resolves it: a user who searched django orm ten minutes ago means the language; a user browsing a zoo site means the animal. This is the partition that matters:

Query classExamplePersonalize?Why
Navigationalfacebook login, chase bankNoSingle correct target. Personalizing only risks demoting it. Everyone gets the same SERP.
Head / unambiguousiphone 15 caseLightlyIntent is clear; only tie-break personalization (size, prior brand) on an already-relevant set.
Ambiguousjaguar, python, mercuryYesIntent under-specified. User/session/profile signal disambiguates. Highest payoff.
Informational / exploratorybest running shoesYesNo single answer. Prior behaviour (trail vs road, budget) genuinely improves the set.
Long-tail / sparsevintage mid-century walnut credenzaYesFew results, weak relevance signal — personalization fills the gap that text matching can't.

How do you know which class a query is in? You measure query entropy — the spread of clicks across results, aggregated over all users. A navigational query has near-zero entropy (95%+ of clicks go to one URL); an ambiguous query has high entropy (clicks spread across many results). Concretely, if a query's top result earns a click-through rate above ~0.7 across the population, treat it as navigational and turn personalization off. This is a per-query gate, learned offline, not a global switch.

Personalize the re-rank, never the retrieval
The cardinal rule: personalization operates on the top set you already retrieved (the rerank stage, lesson 45), it does not change what you retrieve. Two reasons. First, recall safety: if you personalize retrieval, a user with a narrow history can have relevant results never enter the candidate pool — you can't rerank what you didn't fetch. Second, the filter bubble: re-retrieving on personal history compounds, narrowing the world a little more each session until the user only ever sees what they already clicked. Retrieve broadly and fairly; personalize the order of the top 50–200. That keeps a relevance floor under every user.

The signals you personalize with, roughly in order of how much they move the ranking and how risky they are:

SignalLatency to acquireStrengthRisk
Session (last N queries/clicks this visit)milliseconds, in-requeststrongest for disambiguationlow — reflects current intent
Short-term history (last few days)cached per-userstrong for category affinitymedium — intent decays
Long-term profile (segments, brand affinity)offline, see 31weak but stablemedium — stereotyping / filter bubble
Location (geo, store proximity)in-requeststrong for availability / shippinglow when it's a real constraint

Session beats profile almost always: what someone did sixty seconds ago is a far better predictor of the next click than a coarse segment built from months of history. The honest senior framing is that long-term profile mostly sets a weak prior, and session signal does the real disambiguation work. We built the segments themselves in 31; here they are just one input feature among several.

The over-personalization failure mode
Personalization that is too aggressive produces relevance regressions that don't show up in your headline CTR metric. You boost results the user has clicked before, online engagement ticks up 1%, the experiment ships — and three months later you discover the system has stopped showing anyone anything new, query reformulation rates are climbing (users are fighting the ranker), and the catalogue's long tail is starved of impressions. The tell in an interview: personalization wins should be evaluated on ambiguous queries specifically and guarded by a relevance floor and a diversity / novelty metric, not on global CTR. Cross-reference the diversity machinery in 20.

E-commerce search as multi-objective ranking

On the open web, "best result" is roughly one thing: relevance. In commerce it is emphatically not. The platform makes money when items sell, and not all sales are equal — a £400 item at 40% margin is worth more than a £20 item at 5% margin. So the score that orders the SERP is a blend:

s(item) = α·relevance + β·p(convert) + γ·margin + (other terms…)

where relevance is the LTR score from lesson 44, p(convert) is the predicted probability the user buys given a click, and margin is the platform's expected profit (or a proxy: price, take-rate, sponsored bid). The weights α, β, γ are not learned end-to-end against a single label — they are a product decision, dialled to trade user satisfaction against revenue, then validated by A/B test (lesson 33).

You cannot add components that aren't calibrated
α·relevance + β·p(convert) + γ·margin is only meaningful if the three terms live on comparable scales. A raw LTR score might span [−3, +5]; p(convert) is a probability in [0, 1]; margin is in currency. Adding them with fixed weights is nonsense unless you first calibrate each to a common footing — typically map relevance and p(convert) to true probabilities (Platt / isotonic, lesson 05) and express margin as expected revenue in the same currency, so that β·p(convert)·price is literally "expected revenue from this slot." Skip calibration and your weights silently absorb the scale mismatch — tune γ up a touch and margin swamps everything because it's numerically 100× the others. Half of "the ranker went crazy after we added margin" incidents are an uncalibrated-scale bug, not a weighting bug.

Worked numbers — how the weights reorder the page

Five candidate items for the query wireless headphones. Relevance and p(convert) are calibrated to [0,1]; margin is expected profit in £ (already on a comparable per-impression scale after we divide by a normalizer of £20, so margin column below is margin/£20).

Itemrelevancep(convert)margin (norm.)in stock?
A — Sony WH-1000 (top match)0.950.300.40yes
B — house-brand buds (high margin)0.550.451.00yes
C — Bose mid-range0.800.350.55yes
D — budget no-name (cheap, converts)0.450.600.25yes
E — premium audiophile set0.850.150.90no

Pure relevance (α=1, β=0, γ=0). Scores are just the relevance column:

A 0.95 > E 0.85 > C 0.80 > B 0.55 > D 0.45 → order: A, E, C, B, D

The best text match leads. But E is out of stock — a pure-relevance ranker happily ranks a thing you can't buy second. Hold that thought.

Balanced commercial (α=0.5, β=0.3, γ=0.2). Compute each:

Item0.5·rel0.3·p(conv)0.2·margins
A0.4750.0900.0800.645
B0.2750.1350.2000.610
C0.4000.1050.1100.615
D0.2250.1800.0500.455
E0.4250.0450.1800.650

order: E 0.650 > A 0.645 > C 0.615 > B 0.610 > D 0.455

Reasonable — but E (out of stock) still leads. We have not yet applied the availability constraint; the blend treats stock as a soft factor it never saw. Constraints are not weights; they are a gate. More on that in the next section.

Revenue-heavy (α=0.3, β=0.2, γ=0.5) — the configuration a revenue-chasing PM proposes:

Item0.3·rel0.2·p(conv)0.5·margins
A0.2850.0600.2000.545
B0.1650.0900.5000.755
C0.2400.0700.2750.585
D0.1350.1200.1250.380
E0.2550.0300.4500.735

order: B 0.755 > E 0.735 > C 0.585 > A 0.545 > D 0.380

Here is the cost, in one line. The house-brand buds B — relevance only 0.55, the fourth-best match — now lead the page, ahead of the Sony WH-1000 (A, relevance 0.95) which fell to fourth. Margin-weighting promoted a clearly-worse match to the top slot. Short-term: revenue per search rises. Long-term: the user who typed wireless headphones sees a mediocre house-brand first, learns the search "shows me junk," and trust erodes — which shows up later as lower repeat-search rate and higher bounce, metrics no single experiment cleanly attributes back to γ. The art of commerce ranking is finding the γ that captures margin without visibly burying the obvious right answer. That is exactly what the widget below lets you feel.

Hard constraints & facets

The blend above handles things that are matters of degree. But commerce queries also carry things that are matters of fact: red nike running shoes size 10 under $80 in stock. Size 10 is not a soft preference — a size 9 is simply wrong. These are hard constraints, extracted as structured attributes by query tagging (lesson 41): brand=nike color=red category=running shoes size=10 price<80 in_stock=true.

A constraint is a filter, not a feature. It partitions candidates into eligible / ineligible; ineligible items are removed regardless of how high they'd score. The UI surfaces these as facets — the clickable refinement rails ("Brand", "Price", "Size") with counts next to each value.

Facet count computation

A facet count is "how many results would remain if the user also selected this value, given everything currently selected." The subtlety: when computing the count for a value within a facet, you exclude that facet's own current selection (otherwise selecting "Nike" would show "Nike (240), Adidas (0), …" and make the rail useless). So for facet F, counts are computed against the result set filtered by all other active facets but not F itself.

Worked: query matches 2,400 items. User has selected category=running shoes (leaves 600) and price<80 (of those, 350 remain). To render the Brand facet, drop the Brand selection (there is none yet) but keep category and price filters, then bucket the 350 by brand:

Brand facet (computed over the 350 items passing category + price): Nike (180) Adidas (90) Asics (50) New Bal. (30) ── sums to 350 ✓ Price facet (computed over 600 items passing category only, NOT re-applying price<80): under $50 (120) $50–$80 (230) $80–$120 (160) over $120 (90) ── sums to 600 ✓ (price facet ignores its own active filter)

Mechanically this is a multi-dimensional aggregation: for each facet you maintain a count by value over the appropriately-filtered set. Inverted-index engines (Lucene/Elasticsearch, the index of lesson 42) compute these with per-field doc-value columns and bitset intersections; the cost is roughly one pass over the matching doc-id set per facet.

Pre- vs post-filtering, and the post-filter recall trap

Where you apply the constraint relative to retrieval is the whole ballgame for recall, and it's the e-commerce face of the ANN filtering problem from lesson 03.

Pre-filterPost-filter
Orderfilter first, then rank/ANN over the eligible setretrieve/ANN top-K, then drop ineligible
Recallsafe — every eligible item is consideredcan collapse — survivors of a tight filter may be near zero
Costneeds a filterable index (partitioned / faceted)cheap, reuses the plain ANN index
Worked numbers — the top-1000 that becomes 6
Dense retrieval (lesson 03) returns the ANN top-1000 by vector similarity for wireless earbuds. The user then applies in_stock=true price<$50 brand=Sony. We post-filter the 1000. Suppose, independently, ≈30% are in stock, ≈25% are under $50, and ≈8% are Sony. The survivors are roughly

1000 × 0.30 × 0.25 × 0.08 ≈ 6 items

Six. The page renders a near-empty SERP even though the catalogue has hundreds of in-stock Sony earbuds under $50 — they just weren't in the vector top-1000, which was dominated by the most semantically central earbuds (mostly other brands, many over $50). This is post-filter recall collapse: the filter is fine, retrieval just never surfaced the eligible items.

The fixes, in order of preference: Senior signal: you diagnose "empty SERP under filters" as a retrieval-stage recall problem, not a ranking bug, and you reach for a filtered index before you reach for a bigger K.

Marketplace search: two-sidedness changes the objective

A retailer owns its inventory; a marketplace matches independent buyers with independent sellers — Airbnb (guests↔hosts), Uber (riders↔drivers), eBay/Etsy (buyers↔sellers), Upwork (clients↔freelancers). Three structural facts make marketplace search a different problem from web or even retail search.

1. Availability is a first-class constraint, computed at query time. An Airbnb listing isn't "relevant" if it's booked on the requested dates — availability depends on the (listing × date-range) pair, which can't be a static index field. Uber must match the supply of drivers physically near the rider right now. eBay must hide sold-out listings. So the constraint set includes dynamic, perishable facts (dates, real-time geo supply, current inventory) that change minute to minute — you cannot bake them into the offline index; they gate at request time, and they make the post-filter recall problem above the normal case, not the edge case.

2. Inventory freshness dominates. A marketplace's catalogue churns constantly — listings appear and sell out within hours. A nightly-rebuilt index (lesson 03) would show stale, already-sold results. Marketplaces lean hard on the two-tier freshness pattern: a small near-real-time index for recent/changed listings merged with the large base index.

3. You cannot purely maximize buyer relevance — you must keep sellers alive. If ranking always showed the single most-relevant, most-reviewed, cheapest listing, a handful of incumbent sellers would win every impression, new sellers would get zero traffic, never accumulate reviews, and churn off the platform. Then buyers see less selection, and the marketplace dies from the supply side. This is the cold-start-seller problem, and it's why marketplaces deliberately inject seller fairness into ranking:

The senior framing: a marketplace ranker optimizes a long-run, two-sided objective (marketplace liquidity and GMV — gross merchandise value — over months) that can locally trade away buyer relevance for supply health. That tension — buyer relevance vs seller fairness vs marketplace liquidity — is the defining feature, and it's another term you'd add to the blended score from earlier, governed by the fairness machinery of lesson 20.

The e-commerce search stack

Putting it together, here is where each mechanism lives in the pipeline. Note that constraints gate before ranking, and personalization + business blend ride on top of relevance at the very end — exactly as the "personalize the rerank, not the retrieval" rule demands.

┌───────────────────────────────────────────────────────────────┐ │ "red nike running shoes size 10 under $80" │ └───────────────────────────────┬───────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 1. QUERY UNDERSTANDING (lesson 41) │ │ spell-correct, tag → {brand=nike, color=red, cat=running, size=10, │ │ price<80} + decide query class (navigational? ambiguous?) → person. ON?│ └───────────────────────────────┬───────────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 2. RETRIEVAL (lessons 42 lexical / 43 dense+hybrid) │ │ fetch candidate set — BROADLY, no personalization here (recall safety) │ └───────────────────────────────┬───────────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 3. CONSTRAINT FILTERING (this lesson; ANN-filter pitfall ← 03) │ │ apply HARD facts: in_stock, size=10, price<80, availability/dates. │ │ ⚠ pre-filter via partitioned index or over-fetch → avoid recall collapse│ └───────────────────────────────┬───────────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 4. RELEVANCE (LTR) (lesson 44) │ │ score eligible set: text match, popularity, query×item features │ └───────────────────────────────┬───────────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 5. BUSINESS + PERSONALIZATION BLEND (this lesson) │ │ s = α·rel + β·p(convert) + γ·margin + seller-fairness + personal rerank │ │ + business pins/boosts (audited) (fairness ← 20, calib ← 05)│ └───────────────────────────────┬───────────────────────────────────────────┘ ▼ ┌──────────────────┐ │ RESULTS + FACETS│ ← facet counts computed here └──────────────────┘

Governing the thumb on the scale

Beyond the learned blend, every commerce search system grows a layer of explicit, hand-placed overrides — and how you govern them separates a trustworthy system from a mess.

Boosts and pins must be auditable, time-boxed, and measured
The failure mode is boost-rot: dozens of boosts accumulate over years, nobody remembers why boost brand=X by 1.4 exists, they interact unpredictably, and the "learned" ranker is actually a pile of hand-tuned overrides nobody can reason about. The governance answer a senior gives: every boost/pin is (1) logged with an owner and a reason, (2) time-boxed with an expiry, (3) attributable in logs — when an item ranks #1, you can answer "did it earn that or was it pinned?", and (4) measured — boosts are A/B-tested like any change, not waved in. The thumb on the scale is legitimate; an unaccountable thumb on the scale is how search systems lose user trust and engineering sanity.

Interactive · blended ranking explorer

Six candidate items for a commerce query, each with calibrated relevance, p(convert), margin, and an in-stock flag. Slide the weights α (relevance), β (conversion), γ (margin), and toggle in-stock-only. The list reorders live, the KPIs show which objective is dominating the top slot, and a note fires when a clearly-relevant item (relevance ≥ 0.85) gets buried below rank 3 by business weighting. The model is a toy — calibrated, comparable inputs, a linear blend — but the reordering and the trust-cost dynamics are exactly the real thing.

Tune α / β / γ and watch the page reorder — and what it costs
Toy model: relevance and p(convert) are calibrated to [0,1], margin is normalized to [0,1], score is a plain weighted sum. Real systems add seller-fairness and personalization terms, but the trade-off you'll feel here is the real one.
top slot won by
dominant objective in slot 1
best-match item now at rank
items shown
#itemrelp(conv)marginstockscore
Reading

Interview prompts you should be ready for

  1. "When should you personalize a search query, and when shouldn't you?" (The whole judgement: navigational/head queries look the same for everyone — personalizing facebook login is pointless or harmful; ambiguous/informational/long-tail queries are where it pays. Gate per-query on query entropy / top-result CTR. A junior says "personalize everything.")
  2. "Where in the pipeline does personalization go, and why not in retrieval?" (Re-rank the top set, never re-retrieve. Re-retrieving on personal history kills recall for narrow-history users — you can't rerank what you didn't fetch — and compounds into a filter bubble. Retrieve broadly, reorder the top 50–200.)
  3. "You blend α·relevance + β·p(convert) + γ·margin and bumping γ makes the ranker show junk. What's the first thing you check?" (Calibration. The three terms must be on comparable scales — relevance/p(convert) mapped to probabilities, margin as expected revenue in currency — before weighted addition is meaningful. Uncalibrated scale mismatch, not weighting, is the usual culprit.)
  4. "ANN returns 1000 candidates, the user applies in-stock + under-$50 + brand=X, and the page is nearly empty. Diagnose and fix." (Post-filter recall collapse: 1000 × the product of independent filter pass-rates ≈ a handful. It's a retrieval-stage problem, not a ranking bug. Fix with a filtered/partitioned index (pre-filter), or over-fetch, or partition by the dominant filter, or fall back to lexical which filters structured fields natively.)
  5. "Why can't a marketplace just rank by buyer relevance?" (Two-sidedness: always showing the top incumbent starves new/cold-start sellers, supply churns off, selection collapses, the marketplace dies from the supply side. Need new-listing exploration, per-seller impression caps, geo/supply balancing. Optimize long-run two-sided liquidity, not single-query relevance.)
  6. "Your PM wants to hard-boost the private-label brand. How do you let them without wrecking the system?" (Boosts are legitimate but must be governed: logged with owner + reason, time-boxed with expiry, attributable in logs (did it earn #1 or was it pinned?), and A/B-measured like any change. The failure mode is unaccountable boost-rot — a pile of overrides nobody can reason about.)
Takeaway
A web query wants the best answer; a commerce query wants something you can buy, now, that's worth selling. So relevance stops being one number: it's a calibrated blend of relevance × conversion × margin (+ seller fairness on marketplaces), gated by hard constraints (availability, facets) and topped with surgical personalization — applied to the rerank of ambiguous queries, never to retrieval, never to navigational queries. The two recurring traps a senior names instantly: post-filter recall collapse (fix with a filtered index, not a bigger ranker) and the unaccountable thumb on the scale (every boost logged, time-boxed, attributable, measured). Next, lesson 48: once "good result" is a blend, how do you even measure it?