20. Design Live E-commerce Homepage Feed
Whatnot: https://www.whatnot.com/
Whatnot is a popular livestream shopping platform where users can watch live shows and buy products in real time.
This post uses Whatnot as a reference to explore how to design the homepage feed for a livestream e-commerce platform.
- Whatnot was valued at $5 billion in 2024.
- Whatnot 2024 GMV exceeded $3 billion
- Whatnot backend uses
Python
, Live Service usesElixir + Phoenix
Framework.
01 Understanding the Problem

Whatnot 是一个面向直播电商的购物平台,用户可实时观看直播、出价、抢购、下单。首页 Feed 是用户进入 App 后最先看到的内容流,类似抖音首页或 Instagram Feed,用于展示当前或即将开播的直播间。
1.1 Functional Requirements
- Users can view a personalized live content feed on the app homepage
- Each feed item displays key live room info: cover image, seller name, current viewer count, tags, and category. 每个 Feed 项目展示关键的直播间信息:封面图片、卖家名称、当前观众数量、标签和类别。
- Have a tab to switch between different categories of live content, like "Fashion", "Electronics", "Home & Garden"
- Support Pull-to-refresh and infinite scrolling on the phone.
- Recommended content includes:
- Live rooms that are currently streaming
- Upcoming scheduled livestreams
- High-conversion historical live rooms (available via limited-time replays or highlight clips)
- 高转换率的历史直播室, 可通过限时重播或精彩片段观看
1.2 Non-Functional Requirements
- Feeds serving latency < 250ms. The slower the response, the worse the user experience.
- 10M DAU, 6 live rooms per screen on the phone.
- Eventual Consistency: Minor data delays (within a few minutes) are acceptable. For online model updates, feature and parameter updates can be delayed by 1–2 minutes.
- High availability (HA) and low latency (LL) are required.

02 Clarification and Estimation
2.1 Assumption
- User & Seller
- Total users: 100M
- Daily Active Users (DAU): 10M
- Total sellers: 10K
- Each hosts 1–5 livestreams per day
- Each user
- Follows 10–20 sellers
- Subscribes to 3–5 interest categories
- Visits Feed page ~10 times/day
- Views 50% of the Feed items
- Clicks on 10% of viewed items
- Engagement time: 1min per item
- Conversion rate: 5% (click to purchase)
- Home Feeds Service (Compute Heavy)
- Returns
10
items - Includes: cover image, seller metadata, viewer count, tags, category, trend score, live room metadata
- Latency target:
<500ms
. The slower the response, the worse the user experience. - QPS =
(10M DAU * 10 times) / 86400s ≈ 1k QPS
- Peak QPS =
(10M DAU * 10 times * 30%) / 2hr ≈ 5k QPS
- Read Write Rate =
100 : 0
- Returns
- Types of Feed content
- Currently live shows
- Upcoming shows
- Historical high-conversion replays highlights
- Peak hours
- 30% of total daily traffic concentrated in 2 hours, 19:00–21:00 PM
- 50%+ conversions of total happens during peak hours
03 Key Flows, System Services and APIs
3.1 User Request Flow 用户链路
- HomeFeed Gateway
- Handles user requests, authentication, and authorization
- Provides APIs for fetching personalized feeds
- Recommender Engine
- Multi-source Retrieval: retrieves items from various sources/algorithms
- Cascading Ranking: ranks user-seller pairs
- Ranking Service
- Predicts click-through rate (CTR), conversion rate (CVR), and engagement time (ET)
- Combines scores into a final ranking
- Re-rank and Filter
- Filters out duplicates, irrelevant and blacklisted items based on business rules
- 基于业务规则过滤掉重复、无关和黑名单中的 item
- User Profile Service
- Provides APIs for fetching user profiles and interests
- Live Service
- Manages live room metadata, including status, title, category, tags, and cover image
- Provides APIs for fetching live room details
- Index Service
- Provides APIs for indexing and searching content
- Including inverted index for fast retrieval, like
{"men's fashion": [seller1, seller2, ...]}
3.2 ML Data Pipeline Flow 模型数据链路
- User Behavior Events Collecter
- Collects user interactions with feeds, such as clicks, views, and purchases from devices (Web, iOS, Android)
- Sends events to Kafka for real-time processing
- Feature Service and Store
- Provides APIs for fetching user and seller features (dense or sparse, structured or vector)
- Stores pre-computed features for fast access
- Parameter Server
- Provides APIs for fetching, saving and updating ML training parameters
- Streaming Service (Kafka/Flink)
- Handles real-time data streams for user behavior, live room updates, and feature generation
- Provides APIs for subscribing to real-time events
- Model Service
- Model management with versions, serving, and monitoring
- Provides APIs for serving ML models
3.3 Public APIs
Feeds RPC
getForYouFeed(request: GetForYouFeedRequest): GetCategoryFeedResponse
interface GetForYouFeedRequest {
api_token_id: string; // 调用方身份标识(限流/权限控制用)
user_id: string; // 用户 ID
page_token?: string; // 游标分页字段(可选)
page_size?: number; // 每页条数(默认 10,最大 50)
}
getCategoryFeed(request: GetCategoryFeedRequest): GetCategoryFeedResponse
interface GetCategoryFeedRequest {
api_token_id: string; // 调用方身份标识(限流/权限用)
user_id: string; // 用户 ID
category_id: string; // 类目 ID,如 "electronics"
page_token?: string; // 游标分页字段(可选)
page_size?: number; // 每页条数(默认 10,最大 50)
}
User Tracking RESTful APIs
POST /tracking/feed_impression
{
"user_id": "u123", // 用户唯一标识,用于行为归因和模型训练
"session_id": "s456", // 会话 ID,用于区分不同浏览会话(每次打开 App 可生成一次)
"tab": "for_you", // 当前所在的 Feed tab,如 "for_you" 或 "electronics"
"feed_items": [ // 当前屏幕内展示的 Feed 项(可见的直播房间)
{ "item_id": "r1", "position": 1 }, // item_id:直播间ID,position:在页面中的展示顺序
{ "item_id": "r2", "position": 2 },
{ "item_id": "r3", "position": 3 }
],
"timestamp": "2025-08-03T10:11:00Z" // 上报时间(UTC 格式),用于行为排序与实时统计
}
POST /tracking/feed_click
{
"user_id": "u123", // 用户 ID
"session_id": "s456", // 当前 session
"item_id": "r2", // 被点击的房间 ID
"position": 2, // 房间在当前页面中的位置(从 1 开始)
"tab": "for_you", // 当前 tab(用于统计哪个 tab 的点击率高)
"timestamp": "2025-08-03T10:11:07Z" // 点击行为发生的时间戳
}
POST /tracking/watch_time
{
"user_id": "u123", // 用户 ID
"item_id": "r2", // 正在观看的房间 ID
"watch_time_sec": 63, // 观看时长(单位:秒)
"timestamp": "2025-08-03T10:12:10Z" // 行为发生时间
}
POST /tracking/engage_time
{
"user_id": "u123", // 用户 ID
"session_id": "s456", // 当前 session ID,用于聚合用户行为
"tab": "for_you", // 用户所在的 tab(如 For You、Electronics)
"engage_time_sec": 83, // 用户在当前 Feed 页的停留时间(单位:秒)
"timestamp": "2025-08-03T10:12:30Z" // 上报时间或离开该页面的时间
}
POST /tracking/purchase
{
"user_id": "u123", // 用户 ID
"session_id": "s456", // 当前 session ID
"item_id": "r2", // 触发购买的直播间或商品来源 ID
"order_id": "o789", // 订单 ID(用于去重和追踪)
"amount": 59.99, // 订单金额(单位:美元或本地币种)
"currency": "USD", // 币种(支持多币种平台)
"quantity": 2, // 商品数量(可选)
"tab": "for_you", // 当前 Feed tab 来源(如首页推荐)
"timestamp": "2025-08-03T10:13:55Z" // 购买行为发生的时间戳
}
POST /tracking/page_leave
- Not all users engage — some just scroll and leave without clicking. 很多用户只曝光不点击
- Without a page leave event, the behavior chain breaks, making it impossible to distinguish between disinterest, lack of relevance, or app-side (like, app crashes) issues.
- 可以明确归因用户行为路径: 不感兴趣、进入直播间、切换 tab 还是退出。
{
"user_id": "u123", // 用户 ID
"session_id": "s456", // 当前 session
"page": "homepage_feed", // 当前离开的页面标识
"leave_reason": "enter_live_room", // 用户离开原因:enter_live_room / switch_tab / close_app / timeout
"target": {
"room_id": "r123" // 如果是点击进入直播间,可以附上目标房间 ID
},
"timestamp": "2025-08-03T10:15:30Z" // 离开的时间
}
04 Data Storage
05 Propose High-Level Design
5.1 User Behavior Pipeline and Offline Compute
5.2 Feed Ranking Flow


06 Deep Dive - Infinite Scroll
6.1 Context and Background
Reference: The Next Page: Building Infinite Scroll with SwiftUI
- 移动端首页(For You Feed)采用无限滚动设计(Infinite Scroll),用户向下滑动时不断加载新的直播房间卡片.
- Web 端首页则是分页加载 + 点击进入房间详情页,没有“滚动一屏再加载下一页”的需求,因此无需复杂的 scroll handler.
- This section only discusses mobile scenarios.
6.2 Key Components
6.2.1 可分页协议 Pageable Protocol
- PageInfo 结构体
- 描述分页元数据,基于 GraphQL 的 Relay 规范设计,用于标识当前页的结束位置和是否有下一页。
public struct PageInfo: Equatable, Codable {
public let hasNextPage: Bool // 是否有下一页
public let endCursor: String? // 当前页结束位置的标记(用于请求下一页)
public static let `default`: PageInfo = PageInfo(hasNextPage: true, endCursor: nil) // 默认初始值
}
- Pageable 协议
- 定义加载下一页的接口,要求实现者提供 “根据当前页信息加载下一页” 的能力。
public protocol Pageable {
associatedtype Value: Identifiable & Hashable // 页内容的类型(需可识别、可哈希,方便SwiftUI渲染)
// 加载下一页:参数为当前页信息(endCursor)和页大小,返回下一页内容和新的页信息
func loadPage(after currentPage: PageInfo, size: Int) async throws -> (items: [Value], info: PageInfo)
}
6.2.2 分页视图模型 PagingViewModel
封装分页逻辑的核心类,管理数据、状态和加载行为,作为 SwiftUI 视图的数据来源 ObservableObject.
public final class PagingViewModel<T: Pageable>: ObservableObject {
@Published private(set) var items = [T.Value]() // 所有已加载的内容(供SwiftUI视图渲染)
let source: T // 分页数据源(遵循Pageable协议)
let pageSize: Int // 每页加载的数量
let threshold: Int // 触发下一页加载的阈值(如“距离列表底部还有2项时触发”)
private(set) var pageInfo: PageInfo // 当前页的元数据(用于请求下一页)
private(set) var state: PagingState // 加载状态(防止重复请求)
}
- 状态管理:PagingState 枚举
- 通过状态控制加载行为,避免重复请求或无效操作
public enum PagingState {
case loadingFirstPage // 加载第一页中
case loaded // 已加载完成
case loadingNextPage // 加载下一页中
case error(error: Error) // 加载出错
}
6.3 Core Logic and Flow
6.3.1 触发机制 - 基于 onAppear 的滚动检测
利用 SwiftUI 的 onAppear 视图修饰符,当列表项进入屏幕时触发 “是否需要加载下一页” 的判断。
触发流程
- 视图层绑定: 在 SwiftUI 列表中,为每个 item 绑定 onAppear 事件,调用视图模型的 onItemAppear 方法
- onItemAppear 方法: 检查是否满足加载条件,通过 “早期返回” 过滤无效场景
var body: some View {
ForEach(viewModel.items) { item in
content(item) // 渲染item内容
.onAppear {
viewModel.onItemAppear(item) // 当item出现时通知视图模型
}
}
}
- 这段逻辑的核心设计目标是:仅在用户滚动到 “距离列表底部特定距离” 由 threshold 定义、且系统处于 “可加载状态” 时,才触发下一页加载,兼顾了用户体验与性能稳定性
- 与 onItemAppear 配合的 currentTask 用于管理加载任务的生命周期,避免任务冲突
private var currentTask: Task<Void, Never>? {
willSet { // 在设置新任务前触发
if let task = currentTask { // 若存在当前任务
if task.isCancelled { return } // 若任务已取消,无需处理
task.cancel() // 取消当前任务,确保新任务启动前,旧任务被终止
// 防止用户快速滑动时,多个任务同时加载导致数据混乱
}
}
}
public func onItemAppear(_ model: T.Value) {
// 1. 检查是否还有下一页:若已无更多页面(hasNextPage为false),直接返回,不触发加载
// 这一步确保用户滚动到最底部后,不会继续尝试加载不存在的内容
if !canLoadMorePages {
return
}
// 2. 检查当前是否正在加载:若处于“加载第一页”或“加载下一页”状态,直接返回
// 防止同一时间触发多个加载任务,避免数据重复或请求冲突
if state == .loadingNextPage || state == .loadingFirstPage {
return
}
// 3. 查找当前可见项在列表中的索引:通过item的id匹配,若未找到索引(如数据已被移除),直接返回
// 确保后续的阈值判断基于有效位置
guard let index = items.firstIndex(where: { $0.id == model.id }) else {
return
}
// 4. 检查是否达到加载阈值:计算“触发加载的临界索引”(距离列表末尾还有threshold个位置)
// 若当前可见项的索引未达到该临界值,直接返回,不触发加载
// 例如:threshold=2时,当可见项是列表的倒数第2项时,才满足触发条件
let thresholdIndex = items.index(items.endIndex, offsetBy: -threshold)
if index != thresholdIndex {
return
}
// 5. 所有条件均满足:更新状态为“加载下一页”,并启动加载任务
// 通过currentTask管理异步任务,确保旧任务被取消(由currentTask的willSet实现)
state = .loadingNextPage
currentTask = Task {
await loadMoreItems() // 调用加载下一页的具体逻辑
}
}
6.3.2 Core Flow: From Trigger to Loading Completion 核心链路
异步加载下一页数据,并更新视图模型状态和数据,最终同步到 SwiftUI 视图。
func loadMoreItems() async {
do {
// 1. 调用Pageable数据源的loadPage方法,请求下一页
let rsp = try await source.loadPage(after: pageInfo, size: pageSize)
// 2. 如果任务已取消(如用户快速滑动触发新请求),终止执行
if Task.isCancelled { return }
// 3. 合并新数据(第一页直接替换,后续页追加)
let models = rsp.items
let allItems = state == .loadingFirstPage ? models : items + models
// 4. 更新页信息(记录新的endCursor和hasNextPage)
pageInfo = rsp.info
// 5. 主线程更新数据和状态(通知SwiftUI刷新视图)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.items = allItems
self.state = .loaded
}
} catch {
// 6. 加载失败:更新错误状态
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.state = .error(error: error)
}
}
}
6.4 Key Design Points
- 防止重复请求:通过
PagingState
状态判断(如loadingNextPage
时不触发新请求),并使用Task
取消前一次未完成的请求。 - 高效渲染:Value 遵循
Identifiable
和Hashable
,确保SwiftUI
能通过ForEach
高效 diff 列表项,避免不必要的重绘。
- Whatnot Feeds 移动端无限滚动的核心逻辑是:通过
Pageable
协议定义分页数据源,PagingViewModel
管理数据和状态,利用SwiftUI
的onAppear
检测列表项可见性,当达到阈值且状态允许时,异步加载下一页并更新视图。 - 整个流程实现了 “用户滑动时无缝加载新内容” 的体验,同时通过状态管理和任务控制避免无效请求。
07 Deep Dive - Cold Start Problems
7.1 Requirements, Context and Pain Points
Seller Side
新商家入驻平台后,需快速获得曝光以启动业务,但受限于 “零历史数据”,容易陷入 “无曝光→无转化→更难获得曝光” 的恶性循环。
- Key Requirements
- 获得初始曝光机会,打破冷启动僵局.
- 曝光需精准匹配潜在用户,提升首次开播的转化率 lile view, click, purchase.
- 扶持机制需公平,不挤压成熟商家的合理流量.
- Pain Points
- 缺乏历史数据(如直播记录、用户反馈),推荐系统难以评估其质量,导致曝光权重低;
- 若盲目分配流量,可能因内容与用户不匹配导致转化低,既浪费流量,也打击新商家积极性;
- 过度扶持可能破坏平台流量生态,影响成熟商家的正常运营。
User Side
新用户注册后,平台缺乏其行为数据(如点击、停留、购买),难以生成符合其偏好的推荐,可能导致用户因 “推荐不精准” 而流失。
- Key Requirements
- 注册后快速获得符合兴趣的内容推荐,降低决策成本;
- 推荐结果需与注册时提供的信息(如兴趣标签)高度匹配;
- 平台需快速 “学习” 其真实偏好,缩短从 “冷启动” 到 “精准推荐” 的周期。
- Pain Points
- 零历史行为数据导致推荐系统 “无的放矢”,推荐内容泛化、相关性低;
- 若注册时需填写过多信息(如兴趣标签),可能降低注册转化率;
- 首次推荐体验差(如内容与兴趣不符),直接影响用户留存(如 3 天内流失)。
7.2 Engineer Solutions
Seller Side Cold Start
1. Tiered Cold Start Traffic Pool 冷启动流量池分层机制
在不影响大盘的前提下,为新商家提供定向流量扶持:
- 设立独立的 “冷启动流量池”,配额占总流量的 5%-10%,与成熟商家流量池隔离;
- 新商家初始仅在冷启动池内曝光,推荐权重 = 默认 Embedding 与用户兴趣的匹配度 × 扶持系数(如 1.2),优先推送给类目标签高度匹配的用户;
- 设置升级门槛(如单场直播观看≥50 人、转化率≥3%),达标后自 动进入 “成长池”,逐步降低扶持系数,与成熟商家公平竞争;未达标则减少冷启动池配额,避免无效流量消耗。
2. Real-time Feedback Adjustment 实时反馈调权机制
基于新商家的实时数据动态优化推荐策略,加速 “去冷启动”:
- 实时采集新商家直播的用户交互数据(如点击、停留、购买),通过在线学习算法(如 FTRL)更新其 Embedding,从 “默认值” 向 “个性化特征” 迭代;
- 若某新商家在冷启动池中对 “足球卡兴趣用户” 的转化高于类目均值,1 小时内提升其在该用户群体中的推荐权重;
- 若连续 3 场直播转化低于阈值(如<2%),暂停冷启动扶持,提示商家优化内容(如调整直播主题)。
User Side Cold Start
1. Initial Information-based Feature Construction 基于初始信息的特征构建
利用注册阶段的有限信息生成初始 User Embedding:
- 强制但轻量化的兴趣收集:要求用户注册时至少选择 3 个子类目(如 “Baseball Cards”“NFL Breaks”),按选择顺序赋予权重(主标签 0.6、次标签 0.3、第三标签 0.1);
- 融合基础属性:将年龄、性别、地区等信息通过映射表转化为特征(如 “25-30 岁 + 男性 + 美国西部” 映射为 “体育卡牌高频消费群体” 标签);
- 捕捉隐性信号:基于注册渠道(如通过 “足球卡直播” 链接注册)强化对应类目权重,生成初始 Embedding。
2. Similar User Clustering 相似用户聚类匹配
复用存量用户的行为数据,为新用户推荐 “群体偏好内容”:
- 对平台存量用户进行聚类(采用 K-means 或 DBSCAN 算法),聚类特征包括历史交互类目、消费能力、活跃时段等;
- 新用户注册后,基于其初始 Embedding 计算与各聚类中心的相似度,归入最接近的集群(如 “25-30 岁男性足球卡爱好者集群”);
- 向新用户推荐该集群内近 7 天高转化的内容(如 Top 10 点击的直播房间),快速匹配群体共性偏好。
3. Default Category Embedding for Users 用户侧类目默认嵌入向量
为初始特征稀疏或聚类匹配效果有限的新用户,提供基于类目共性的推荐基准,作为冷启动的 “保底策略”。
- 核心机制
- 为每个一级类目(如 “Sports Cards”“Collectibles”)及子类目(如 “Football Cards”“Basketball Cards”)预训练默认用户嵌入向量,该向量基于类目中高活跃度、高转化用户的历史交互特征(如点击偏好、停留时长、购买频率)聚合生成,代表该类目的 “典型用户偏好”。
- 应用场景
- 当新用户初始 Embedding 与存量聚类的匹配度低于阈值(如余弦相似度<0.5),或注册信息过于宽泛(如仅选择一级类目)时,自动启用对应类目的默认 Embedding 作为补充;
- 例如:新用户仅选择 “Sports Cards” 一级类目,未细化子标签,则使用 “Sports Cards” 类目的默认 Embedding,推荐该类目下的热门内容(如近期高互动的足球卡、篮球卡直播),避免推荐范围过泛。
- 动态维护策略
- 每日基于类目中用户的最新交互数据更新默认 Embedding,确保其反映当前类目下的主流偏好(如某子类目因赛事热度上升时,对应默认 Embedding 会向该赛事相关内容倾斜);
- 为每个类目维护 “基础版” 和 “精细化” 两个默认 Embedding:
- 基础版:覆盖类目中所有用户的共性特征,用于初始推荐;
- 精细化:按用户属性(如年龄、性别)细分(如 “25-30 岁男性 Football Cards 用户默认 Embedding”),当新用户提供基础属性时,优先匹配精细化版本,提升推荐精准度。
- 价值
- 作为初始特征构建和聚类匹配的补充,在新用户信息不足或聚类效果不佳时,仍能基于类目共性提供有针对性的推荐,避免 “无内容可推” 或 “推荐杂乱” 的问题,保障冷启动阶段的用户体验底线。
7.3 ML Infra & Guarantees 基础设施与保障机制
- 默认 Embedding 动态更新:每日凌晨基于前 24 小时全量数据更新类目默认 Embedding,剔除异常样本(如刷量商家),确保特征准确性;
- AB 测试验证:对冷启动策略(如流量池配额、聚类粒度)进行 AB 测试,核心指标包括新商家 7 天开播率、新用户 3 天留存率、冷启动推荐 CTR/CVR;
- 熔断机制:若新商家推荐导致用户举报率>5% 或跳出率>80%,自动暂停其扶持流量;若新用户对初始推荐点击率持续<1%,触发 “兴趣标签重选” 提示,补充偏好信息。
TBD Turn to Online predictions
References
- Whatnot | Keeping Up with the Fans: Scaling for Big Events at Whatnot, with Elixir and Phoenix
- Phoenix Framework
- Whatnot’s Data-Driven Approach to Scalability & Reliability for Big On-Platform Events
- Scaling our Data Stack with Kafka and Real-Time Stream Processing
- Building a Modern Data Stack at Whatnot
- Whatamix: Blendable feed construction
- Feeds with Real-time Signals (Home Feed — Part 2)
- Evolving Feed Ranking at Whatnot
- The Next Page: Building Infinite Scroll with SwiftUI
- 腾讯音乐|全民 K 歌推荐系统架构及粗排设计