Deepanshu's Diary

Designing Spotify-Style Discovery: balancing recall, re-ranking, and freshness constraints

--system-designrecommendationspersonalization

Opening tension

I once shipped a "Daily Mix" style feature in a prototype fitness app: users expected variety that matched their current energy and goals, but the first rollout kept surfacing the same three exercises. The engineering trade-off was obvious — aggressive personalization based on sparse signals improved short-term engagement for power users but crushed perceived freshness for casual users. That tension — relevance vs novelty, personalization vs safety — is at the heart of Spotify-style discovery systems.

Mental model: what a discovery system must manage

Think of a discovery engine as four interacting systems:

- Content plane: catalog metadata (tracks, workouts), dense representations (embeddings), and freshness signals. - User plane: user taste vectors, short-term context (current session), and long-term history. - Candidate plane: fast, approximate retrieval that fetches a broad set of possible items (recall). - Ranking plane: contextual re-ranker that orders candidates by predicted utility, while applying filters and constraints.

These planes are connected by feedback: impressions, plays/completions, skips, and explicit feedback feed model training and business rules.

Architecture / data-flow example

Below is a compact architecture that I use as a working mental model.

1) Ingestion: content and metadata enter a pipeline (ETL). Tracks/workouts get embeddings computed by an offline worker, stored in a vector index (pgvector in Postgres or a dedicated ANN index). 2) Candidate generation: a request arrives with user id + session context (e.g., current tempo, workout intensity, recent meeting topics). The system performs k-NN on the vector index and also pulls category-based candidates (fresh releases, trending). 3) Feature assembly: for each candidate we assemble sparse features (popularity, recency), dense features (embedding similarity), and context features (time-of-day, device, recent Listenly meeting topics). 4) Scoring & re-ranking: an online model (lightweight MLP or boosted trees) scores candidates. Business rules (safety filters, tenant constraints) are applied. Results are returned. 5) Logging & feedback: impressions and interactions are written to a durable event sink for offline training and exploration/exploitation experiments.

A minimal flow table

| Step | Data stores | Latency target | |---|---:|---:| | Embedding retrieval | pgvector / ANN | 10–50ms | | Feature fetch | feature store / tenant DB | 5–30ms | | Re-rank | in-memory scorer | 1–20ms | | Logging | event stream | asynchronous |

Practical notes

- Use tenant-scoped stores if you host multiple customers: isolation reduces accidental leakage and simplifies RBAC. - Keep the online scorer small and deterministic; heavy models should be used offline to produce distilled scoring signals. - Assemble a local short-term context bundle per request — ephemeral windows (last N plays or recent meeting topics) often outperform full-history scans.

What I built around this idea

I explored these principles across several projects in my portfolio:

- ARIL local monorepo: I implemented tenant-scoped knowledge bases with docs, chunks, embeddings, pgvector retrieval, and evaluation metrics. That stack demonstrates how a vector-backed recall layer and tenant isolation can be structured—production Postgres migration is an environment gate (implemented: embeddings/retrieval/citations; gate: real production Postgres migration). [see repo]

- Listenly: a local-first meeting copilot that assembles grounded context windows and session summaries. Listenly shows how session-aware context (recent-turn windows and citation-numbered summaries) can feed candidate selection without uploading raw recordings (implemented: local context assembly; gate: real meeting recordings & durable cloud sessions). [see repo]

- GymOS: a prototype workout product with tenant-isolated Supabase schema, secure admin onboarding, and a 3D anatomy surface. I used GymOS as a testbed for personalized workout candidates (implemented: tenant isolation, UI/visual system; gate: broad production rollout and live auth at scale). [see repo]

I deliberately kept the online code small and relied on offline embeddings + retrieval for recall, then a compact re-ranker for latency-sensitive responses. The portfolio evidence is intentionally local-first and prototype-focused — deployments and live traffic remain gated in several projects.

Where this breaks (failure modes and mitigations)

1) Cold start (new users/items): With sparse signals you over-personalize or return bland popular items. - Mitigation: hybrid recall (content-based + collaborative), lightweight preference onboarding, and popularity-tempered priors.

2) Filter bubble / stale tastes: overfitting to short-term history reduces discovery. - Mitigation: inject controlled randomness (epsilon-greedy), freshness quotas, and session-level exploration parameters.

3) Embedding drift & selector drift: content changes or scrapers break selectors (if you scrape metadata), degrading retrieval quality. - Mitigation: automated extraction tests, schema/selector fixtures, periodic re-embedding, and monitoring for distribution shifts.

4) Privacy & multi-tenant leakage: mixing signals across tenants causes data leakage or regulatory exposure. - Mitigation: tenant-scoped DBs, strict RBAC, encrypted keys, consented memory patterns, and fail-closed tenant checks (as implemented in several repos).

5) Latency & cold caches: large vector indexes or heavy feature joins slow responses. - Mitigation: serve pre-computed candidate lists, use approximate ANN, and keep the online model tiny; offload heavy computation to async pipelines.

A practical checklist

1) Instrumentation: event sink for impressions, plays, skips, and reasons. 2) Tenant isolation: separate schemas or namespaces and RBAC tests. 3) Short-term context bundle: assemble N-turn session context locally for each request. 4) Hybrid recall: combine embedding nearest-neighbors with metadata filters. 5) Lightweight online scorer + rules: keep latency below your SLO and apply safety filters last. 6) Offline evaluation: A/B and offline metric pipelines for novelty and fairness. 7) Drift detection: monitor embedding similarity distributions and selector health. 8) Rollout gates: require staged approvals and dry-run defaults before any live send (I follow this pattern in projects like Amazon Voice Agent and others).

Conclusion

Spotify-style discovery is an engineered balance — fast, broad recall plus contextual re-ranking, guarded by safety rules and robust feedback loops. In practice, start small: separate recall from ranking, make context assembly local and lightweight, and design tenant isolation and observability from day one. The prototypes I describe show the core building blocks: vector-backed recall, session-aware context, and operational controls — but moving to production requires careful gating, monitoring, and privacy safeguards.

References

[1] Anthropic — Model Context Protocol: https://www.anthropic.com/news/model-context-protocol

[2] Model Context Protocol — Introduction: https://modelcontextprotocol.io/introduction

[3] arXiv:2005.11401: https://arxiv.org/abs/2005.11401

[4] arXiv:2307.03172: https://arxiv.org/abs/2307.03172

My repos referenced above:

- https://github.com/deepanshuvermaa/my-portfolio - https://github.com/deepanshuvermaa/museum-of-failure - https://github.com/deepanshuvermaa/trading-engine

Portfolio projects mentioned:

- ARIL local monorepo (tenant-scoped KBs, embeddings, pgvector retrieval) — implemented features; production Postgres migration gated. - Listenly (local-first meeting copilot; context assembly) — implemented features; real recordings and cloud sessions gated. - GymOS (tenant-isolated Supabase schema, 3D anatomy UI) — implemented; production rollout gated.

Copied!
Back to all posts