Skip to main content

20. Design Live E-commerce Homepage Feed

info

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 uses Elixir + 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

  1. Feeds serving latency < 250ms. The slower the response, the worse the user experience.
  2. 10M DAU, 6 live rooms per screen on the phone.
  3. 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.
  4. 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
  • 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 遵循 IdentifiableHashable,确保 SwiftUI 能通过 ForEach 高效 diff 列表项,避免不必要的重绘。
info
  • Whatnot Feeds 移动端无限滚动的核心逻辑是:通过 Pageable 协议定义分页数据源,PagingViewModel 管理数据和状态,利用 SwiftUIonAppear 检测列表项可见性,当达到阈值且状态允许时,异步加载下一页并更新视图。
  • 整个流程实现了 “用户滑动时无缝加载新内容” 的体验,同时通过状态管理和任务控制避免无效请求。

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