1)BI 怎么设计(对标 Shopify/独立站“搜索/推荐”常见报表)
Shopify 的 Search & Discovery/行为报表里,搜索常看的就是 query、no results、no clicks、click rate、purchase rate(会话口径漏斗)。推荐系统的看板更强调 曝光→点击→加购→购买→收入,并按推荐位/算法/实验拆解。
1.1 搜索 BI(核心看板)
搜索总览(KPI + 漏斗)
- 搜索 PV/UV:search request 次数 / 搜索用户数
- Click rate(会话口径,Shopify 常用):有点击的搜索会话 / 搜索会话
- ATC rate:有加购的搜索会话 / 搜索会话
- Purchase rate(会话口径,Shopify 常用):有购买的搜索会话 / 搜索会话
- 结果曝光 CTR(结果口径):点击次数 / 结果曝光次数(更适合诊断排序/样式)
- 延迟&稳定性:P50/P95 搜索耗时、错误率、空响应率
Query 分析(运营/选品/词库最常用)
- 高热搜索词:按搜索 UV、曝光 UV、点击 UV、购买 UV 排序
- 飙升词/新词:与过去 (7/14/28) 天对比的增速
- 无结果 query(no results)
- 有结果但无点击 query(no clicks)——强信号:相关性差/图片价位不吸引/首屏无货等
- 建议词(suggestion):suggest 点击发起的搜索占比、suggest→点击/购买转化
结果质量诊断(给算法/工程用)
- 点击位置分布:position=1/2/3… 的点击占比(反推排序质量)
- 类目/品牌/价格带分布:哪些 query 对哪些类目贡献转化
- 筛选/排序使用率:filter 使用率、排序切换率、filter 后的 CTR/CVR 变化
用户与渠道拆解(增长/产品用)
- 新/老客、地域、语言、设备、来源(自然/广告/社媒)
- 页面维度:首页搜索框 / 搜索页 / 分类页搜索 / 详情页内搜索等
Session/链路回放(你提到的 track_id/trace_id)
- 单用户或单 trace:搜索→换词→曝光→点击→详情→加购→支付
- 支持按 trace_id 串联一次“搜索请求”与后续行为(你们文档也强调这一点)
参考:Shopify 官方对搜索分析的核心指标包括 click rate、purchase rate、queries、no results、no clicks(见 Shopify Search & Discovery analytics)。
1.2 推荐 BI(核心看板)
推荐位总览(按 placement/module)
- Impressions/Clicks/CTR
- ATC、Purchase、Revenue
- Revenue per impression / per click
- AOV(推荐归因订单)
- 覆盖与多样性:覆盖商品数、长尾曝光占比、重复率(避免“只推爆款”)
按算法/实验拆解
- 算法版本(model_version/strategy_id)
- 实验(experiment_id/variant_id)
- Lift(增量):相对对照组的 CTR/CVR/Revenue uplift(最好有 holdout)
健康度与风控
- 缺货/下架命中率、被过滤原因分布(无库存/不可售/地域不发货)
- 冷启动占比(新用户/新商品)与表现
2)数据层面的设计(埋点→数仓→指标→推荐特征)
你们已有统一事件骨架的方向(tenant_id / session_id / trace_id / event(oneof))。建议在数据层分成 ODS(原始)→DWD(清洗明细)→DWS(汇总)→ADS(看板语义层),同时给推荐做 离线+在线特征库。
2.1 关键 ID 设计(决定 BI/推荐能否串起来)
- tenant_id:多租户隔离必备(所有表分区/主键都带)
- user_key:归一后的用户主键(优先 login/user_id;否则 anonymous_id/cookie_id)
- session_id:会话(SDK 产生或用 30min inactivity 规则补齐)
- trace_id(最重要):一次搜索/一次推荐刷新生成一个,后续点击/详情/加购尽量继承
- request_id:后端请求日志关联(用于延迟、错误、召回/排序 debug)
- order_id/cart_id/item_id(sku/spu):交易与商品归因
2.2 ODS:原始事件(“可回放、可重放”)
ods_ua_event_raw- tenant_id, event_time, received_time
- identities(原始字段:distinct_id/login_id/anonymous_id/cookie_id…)
- page/device/geo/referrer
- trace_id, session_id, experiment 信息
- event_name + properties(原始 JSON,保留全量)
你现在的
$pageview/$WebClick自动采集能覆盖点击和页面,但 推荐/搜索“曝光”必须补埋点(否则 CTR、归因、训练样本都会缺)。
2.3 DWD:清洗后的事实表(BI/训练统一口径)
建议把“强分析对象”拆成事实表(便于 join、去重、做漏斗):
搜索域
dwd_search_request:一次“搜索结果刷新/请求”的主表(强烈建议:每次请求一个 request_id,同时每次请求一个 trace_id)- 用途:search PV/UV、零结果、延迟、query 聚合、会话口径漏斗分母(search_session)
- 主键建议:
(tenant_id, request_id) - 核心索引建议:
(tenant_id, created_at)(tenant_id, user_key, created_at)(tenant_id, query_normalized, created_at)(tenant_id, trace_id)(用于 join 曝光/点击)(tenant_id, search_session_id, created_at)
- 字段建议(MySQL 参考类型,可按你们存储调整)
tenant_idVARCHAR(64) NOT NULL:店铺/租户request_idVARCHAR(64) NOT NULL:该次搜索请求唯一 ID(前端生成或后端返回)trace_idVARCHAR(64) NOT NULL:该次搜索链路 ID(一次结果集 = 一个 trace)session_idVARCHAR(64) NULL:访问会话(30min inactivity)search_session_idVARCHAR(64) NULL:搜索会话(一次“找东西”的连续过程)user_idVARCHAR(64) NULL:登录用户 IDanonymous_idVARCHAR(64) NULL:匿名用户(cookie 级稳定)user_keyVARCHAR(64) NOT NULL:归一用户主键(ETL 生成)queryTEXT NOT NULL:原始 queryquery_normalizedVARCHAR(512) NOT NULL:归一化 query(聚合用)is_suggestionTINYINT NOT NULL DEFAULT 0:是否由 suggestion 触发suggestion_textVARCHAR(512) NULL:命中的 suggestion(如有)page_numberINT NOT NULL DEFAULT 1:翻页页码(1-based)sortVARCHAR(64) NULL:排序方式(如 relevance/price_asc/…)filters_jsonJSON NULL:结构化筛选条件(原子字段更好;JSON 为起步方案)filters_hashCHAR(32) NULL:filters 归一化后的 hash(便于聚合/去重)results_countINT NOT NULL DEFAULT 0:总命中数returned_countINT NULL:本页返回数量(page size)latency_msINT NULL:搜索耗时is_zero_resultTINYINT NOT NULL DEFAULT 0:是否零结果(可由 results_count=0 派生)is_errorTINYINT NOT NULL DEFAULT 0:是否错误error_codeVARCHAR(64) NULL:错误码(如超时/限流等)page_typeVARCHAR(64) NULL:发生搜索的页面(home/search/pdp/collection…)referrerTEXT NULL:来源页device_typeVARCHAR(32) NULL:pc/mobile(可从 UA 解析)countryVARCHAR(8) NULL:国家(建议由 IP->Geo 派生;避免落全量 IP)created_atDATETIME(3) NOT NULL:事件时间(毫秒级可选)
dwd_search_impression_item:搜索结果“曝光明细”(必须补,否则无法算 position CTR、训练负样本)- 用途:曝光 PV/UV、位置 CTR、召回/排序诊断、训练样本(曝光未点为负样本)
- 主键建议:
(tenant_id, trace_id, item_id, position)(或(tenant_id, request_id, item_id, position),二选一但全局统一) - 核心索引建议:
(tenant_id, trace_id)(tenant_id, request_id)(tenant_id, item_id, exposed_at)
- 字段建议
tenant_idVARCHAR(64) NOT NULLrequest_idVARCHAR(64) NOT NULLtrace_idVARCHAR(64) NOT NULLsearch_session_idVARCHAR(64) NULLuser_keyVARCHAR(64) NOT NULLquery_normalizedVARCHAR(512) NOT NULLitem_idVARCHAR(64) NOT NULL:spu 或 sku,需统一positionINT NOT NULL:在搜索结果中的排名(1-based)scoreDOUBLE NULL:排序分(如 ES score/learning-to-rank score)priceDECIMAL(18,2) NULL:曝光时价格快照(可选但强烈建议)currencyVARCHAR(8) NULLin_stockTINYINT NULL:曝光时库存可售快照(可选但强烈建议)exposed_atDATETIME(3) NOT NULL:曝光时间
dwd_search_click_item:搜索结果点击明细(点击必须能回链到曝光/请求)- 用途:click UV、CTR(曝光口径/会话口径)、位置点击分布、归因触点
- 主键建议:
(tenant_id, click_id)(若无 click_id,可用(tenant_id, trace_id, item_id, clicked_at)近似) - 核心索引建议:
(tenant_id, trace_id)(tenant_id, request_id)(tenant_id, item_id, clicked_at)
- 字段建议
tenant_idVARCHAR(64) NOT NULLclick_idVARCHAR(64) NULL:点击事件唯一 ID(推荐补)request_idVARCHAR(64) NOT NULLtrace_idVARCHAR(64) NOT NULLsearch_session_idVARCHAR(64) NULLuser_keyVARCHAR(64) NOT NULLquery_normalizedVARCHAR(512) NOT NULLitem_idVARCHAR(64) NOT NULLpositionINT NULL:点击时该商品所在排名(无法取到则置空)clicked_atDATETIME(3) NOT NULLtarget_urlTEXT NULL:点击跳转 URL(可选)
推荐域
dwd_rec_request:一次推荐请求/刷新主表(一次刷新 = 一个 trace_id)- 用途:推荐 PV/UV、分推荐位指标、实验/版本拆解、延迟与稳定性
- 主键建议:
(tenant_id, request_id) - 核心索引建议:
(tenant_id, created_at)(tenant_id, placement, created_at)(tenant_id, trace_id)(tenant_id, algo_id, model_version, created_at)
- 字段建议
tenant_idVARCHAR(64) NOT NULLrequest_idVARCHAR(64) NOT NULLtrace_idVARCHAR(64) NOT NULLsession_idVARCHAR(64) NULLuser_keyVARCHAR(64) NOT NULLplacementVARCHAR(64) NOT NULL:推荐位(home/pdp/cart/checkout/search…)module_idVARCHAR(128) NULL:页面内模块标识(一个页面多个推荐模块)trigger_item_idVARCHAR(64) NULL:触发商品(PDP/Cart 场景常用)candidates_countINT NULL:候选数量returned_countINT NULL:返回条数algo_idVARCHAR(64) NULL:策略/算法标识(rule/i2i/embedding/…)model_versionVARCHAR(64) NULL:模型版本experiment_idVARCHAR(64) NULL:实验 ID(如有)variant_idVARCHAR(64) NULL:分桶/分组(如有)latency_msINT NULLis_errorTINYINT NOT NULL DEFAULT 0error_codeVARCHAR(64) NULLcreated_atDATETIME(3) NOT NULL
dwd_rec_impression_item:推荐曝光明细- 主键建议:
(tenant_id, trace_id, item_id, position) - 核心索引建议:
(tenant_id, trace_id)、(tenant_id, item_id, exposed_at) - 字段建议
tenant_idVARCHAR(64) NOT NULLrequest_idVARCHAR(64) NOT NULLtrace_idVARCHAR(64) NOT NULLuser_keyVARCHAR(64) NOT NULLplacementVARCHAR(64) NOT NULLmodule_idVARCHAR(128) NULLtrigger_item_idVARCHAR(64) NULLitem_idVARCHAR(64) NOT NULLpositionINT NOT NULLscoreDOUBLE NULLpriceDECIMAL(18,2) NULLcurrencyVARCHAR(8) NULLin_stockTINYINT NULLexposed_atDATETIME(3) NOT NULL
dwd_rec_click_item:推荐点击明细- 主键建议:
(tenant_id, click_id)(或(tenant_id, trace_id, item_id, clicked_at)) - 核心索引建议:
(tenant_id, trace_id)、(tenant_id, item_id, clicked_at) - 字段建议
tenant_idVARCHAR(64) NOT NULLclick_idVARCHAR(64) NULLrequest_idVARCHAR(64) NOT NULLtrace_idVARCHAR(64) NOT NULLuser_keyVARCHAR(64) NOT NULLplacementVARCHAR(64) NOT NULLmodule_idVARCHAR(128) NULLtrigger_item_idVARCHAR(64) NULLitem_idVARCHAR(64) NOT NULLpositionINT NULLclicked_atDATETIME(3) NOT NULL
转化域(全站通用)
dwd_view_item:商品详情页浏览(可承接 search/rec 的 trace,用于链路与兴趣序列)- 主键建议:
(tenant_id, view_id)(或(tenant_id, user_key, item_id, viewed_at)) - 核心索引建议:
(tenant_id, user_key, viewed_at)、(tenant_id, item_id, viewed_at) - 字段建议
tenant_idVARCHAR(64) NOT NULLview_idVARCHAR(64) NULLsession_idVARCHAR(64) NULLuser_keyVARCHAR(64) NOT NULLitem_idVARCHAR(64) NOT NULLsource_typeVARCHAR(32) NULL:来源(search/rec/direct/ads/…)source_trace_idVARCHAR(64) NULL:来源 trace(若来自 search/rec)dwell_time_msINT NULLviewed_atDATETIME(3) NOT NULL
dwd_add_to_cart:加购事实表(全站统一)- 主键建议:
(tenant_id, atc_id)(或(tenant_id, user_key, item_id, added_at)) - 核心索引建议:
(tenant_id, user_key, added_at)、(tenant_id, item_id, added_at) - 字段建议
tenant_idVARCHAR(64) NOT NULLatc_idVARCHAR(64) NULLsession_idVARCHAR(64) NULLcart_idVARCHAR(64) NULLuser_keyVARCHAR(64) NOT NULLitem_idVARCHAR(64) NOT NULLquantityINT NOT NULL DEFAULT 1priceDECIMAL(18,2) NULLcurrencyVARCHAR(8) NULLsource_typeVARCHAR(32) NULL:来源(search/rec/direct/…)source_trace_idVARCHAR(64) NULL:来源 trace(若来自 search/rec)added_atDATETIME(3) NOT NULLcart_snapshot_jsonJSON NULL:购物车快照(可选,体积大时可下沉到独立表)
dwd_purchase:订单事实表(全站统一)- 主键建议:
(tenant_id, order_id) - 核心索引建议:
(tenant_id, user_key, paid_at)、(tenant_id, paid_at) - 字段建议
tenant_idVARCHAR(64) NOT NULLorder_idVARCHAR(64) NOT NULLuser_keyVARCHAR(64) NOT NULLcurrencyVARCHAR(8) NULLtotal_amountDECIMAL(18,2) NULLpaid_atDATETIME(3) NOT NULL
dwd_purchase_item:订单明细事实表(全站统一)- 主键建议:
(tenant_id, order_id, item_id)(若一单同商品可多行,则增加line_id) - 核心索引建议:
(tenant_id, item_id, paid_at)、(tenant_id, order_id) - 字段建议
tenant_idVARCHAR(64) NOT NULLorder_idVARCHAR(64) NOT NULLline_idVARCHAR(64) NULLuser_keyVARCHAR(64) NOT NULLitem_idVARCHAR(64) NOT NULLquantityINT NOT NULL DEFAULT 1priceDECIMAL(18,2) NULLpaid_atDATETIME(3) NOT NULL
- 落库/分区建议(MySQL)
- 量小:按
tenant_id+created_at索引即可 - 量大:建议按月分表/分区(
*_202601),或落到 ClickHouse/ES/湖仓;DWD 保持明细可追溯,DWS/ADS 做聚合提速
归因桥(可选但强烈建议)
dwd_attribution_touch- order_id, item_id, last_touch_trace_id, touch_type(search/rec), touch_time, window(如 7d click/1d view)
- 这样 BI 里的“搜索带来收入/推荐带来收入”不会口径混乱。
2.4 DWS/ADS:面向看板的汇总层(高性能)
dws_search_kpi_daily(tenant_id, date, page_type/device/geo/new_vs_returning…)- search_pv, search_uv, sessions_with_click, sessions_with_atc, sessions_with_purchase
- click_rate_session, purchase_rate_session, zero_result_rate, no_click_rate, p95_latency
dws_query_daily(tenant_id, date, query_normalized)- searches, exposure_uv, click_uv, purchase_uv, zero_cnt, no_click_cnt
dws_rec_kpi_daily(tenant_id, date, placement, algo_id/model_version)- impressions, clicks, ctr, atc, purchases, revenue, rpi(revenue per impression)
dws_item_daily(tenant_id, date, item_id)- search_impr/click/atc/purchase、rec_impr/click/…(用于“商品天级统计特征”与运营)
3)推荐系统依赖的数据(“能训练、能实时、能解释”)
把特征分成 用户/商品/上下文,再分 静态/快照/统计/序列(实时)。
3.1 用户基础信息(dim)
dim_user:user_key、注册时间、是否会员、国家/语言、获客渠道(如可得)、新老客标签- 合规:只存业务需要的最小信息,敏感字段做脱敏/哈希
3.2 用户实时特征(在线序列 + 聚合)
在线(Redis/Key-Value)建议保留:
- 最近 N 次行为序列(按时间衰减)
- view_item_seq(最近浏览商品)
- search_query_seq(最近搜索词/类目)
- click_seq(点击的商品,区分来自 search/rec)
- cart_seq、purchase_seq
- 实时聚合
- 最近 1h/24h 搜索次数、点击次数、加购次数
- 最近一次偏好类目/品牌/价格带(从序列实时计算)
离线(天/小时级)输出:
- RFM、长期偏好向量(类目/品牌 embedding)、价格敏感度、复购周期等
3.3 商品基础特征(静态)
dim_item(sku/spu)- 类目、品牌、属性(颜色/尺码/材质…)、标题/描述、价格、币种、图片
- 文本/图片 embedding(你们已有 embeddings 模块可复用)
3.4 商品快照(强业务约束,必须可回溯)
item_snapshot(tenant_id, item_id, snapshot_time)- 库存/可售、折扣、上新、发货国家限制、活动标签
- 推荐/搜索曝光明细里最好写入当时的关键快照字段(至少 in_stock、price),避免事后回算失真。
3.5 商品天级统计特征(训练/排序最常用)
- 由
dws_item_daily派生:- 1d/7d/28d:search_ctr、search_cvr、rec_ctr、rec_cvr、atc_rate、purchase_rate
- 热度、趋势(环比/同比)、退货/取消(如可得)
- 分 placement 的表现(同一商品在“购物车推荐”与“首页推荐”差异巨大)
3.6 标签(Label)与训练样本(建议一开始就定口径)
- 曝光→点击(CTR)标签:以
impression_item为样本 - 点击→加购/购买(CVR/GMV)标签:以 click 或 impression 为样本,设定归因窗口
- 负样本:同一 trace_id 下未被点击的曝光 item(更稳定)
4)你提到的两项“未完成工作”,数据层如何补齐
- 商品曝光(搜索/推荐):必须新增
ExposureEvent(建议一屏/一次渲染批量上报 item 列表 + position + module/placement + trace_id) - 链路跟踪:以
trace_id为主串联;若购买跨会话严重,可在 purchase 上补attribution_trace_id或单独落attribution_touch表做归因
如果你愿意,我可以基于你们现有 proto/事件骨架,把“搜索请求/曝光/点击/加购/购买/推荐请求”这几类事件的最小必填字段清单列成一张表(直接给前端埋点和后端 ETL 用),并给出每个指标(CTR/CVR/zero/no-click)的精确定义与 SQL 计算口径。