## 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](https://help.shopify.com/en/manual/online-store/storefront-search/search-and-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_id` VARCHAR(64) NOT NULL:店铺/租户 - `request_id` VARCHAR(64) NOT NULL:该次搜索请求唯一 ID(前端生成或后端返回) - `trace_id` VARCHAR(64) NOT NULL:该次搜索链路 ID(一次结果集 = 一个 trace) - `session_id` VARCHAR(64) NULL:访问会话(30min inactivity) - `search_session_id` VARCHAR(64) NULL:搜索会话(一次“找东西”的连续过程) - `user_id` VARCHAR(64) NULL:登录用户 ID - `anonymous_id` VARCHAR(64) NULL:匿名用户(cookie 级稳定) - `user_key` VARCHAR(64) NOT NULL:归一用户主键(ETL 生成) - `query` TEXT NOT NULL:原始 query - `query_normalized` VARCHAR(512) NOT NULL:归一化 query(聚合用) - `is_suggestion` TINYINT NOT NULL DEFAULT 0:是否由 suggestion 触发 - `suggestion_text` VARCHAR(512) NULL:命中的 suggestion(如有) - `page_number` INT NOT NULL DEFAULT 1:翻页页码(1-based) - `sort` VARCHAR(64) NULL:排序方式(如 relevance/price_asc/…) - `filters_json` JSON NULL:结构化筛选条件(原子字段更好;JSON 为起步方案) - `filters_hash` CHAR(32) NULL:filters 归一化后的 hash(便于聚合/去重) - `results_count` INT NOT NULL DEFAULT 0:总命中数 - `returned_count` INT NULL:本页返回数量(page size) - `latency_ms` INT NULL:搜索耗时 - `is_zero_result` TINYINT NOT NULL DEFAULT 0:是否零结果(可由 results_count=0 派生) - `is_error` TINYINT NOT NULL DEFAULT 0:是否错误 - `error_code` VARCHAR(64) NULL:错误码(如超时/限流等) - `page_type` VARCHAR(64) NULL:发生搜索的页面(home/search/pdp/collection…) - `referrer` TEXT NULL:来源页 - `device_type` VARCHAR(32) NULL:pc/mobile(可从 UA 解析) - `country` VARCHAR(8) NULL:国家(建议由 IP->Geo 派生;避免落全量 IP) - `created_at` DATETIME(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_id` VARCHAR(64) NOT NULL - `request_id` VARCHAR(64) NOT NULL - `trace_id` VARCHAR(64) NOT NULL - `search_session_id` VARCHAR(64) NULL - `user_key` VARCHAR(64) NOT NULL - `query_normalized` VARCHAR(512) NOT NULL - `item_id` VARCHAR(64) NOT NULL:spu 或 sku,需统一 - `position` INT NOT NULL:在搜索结果中的排名(1-based) - `score` DOUBLE NULL:排序分(如 ES score/learning-to-rank score) - `price` DECIMAL(18,2) NULL:曝光时价格快照(可选但强烈建议) - `currency` VARCHAR(8) NULL - `in_stock` TINYINT NULL:曝光时库存可售快照(可选但强烈建议) - `exposed_at` DATETIME(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_id` VARCHAR(64) NOT NULL - `click_id` VARCHAR(64) NULL:点击事件唯一 ID(推荐补) - `request_id` VARCHAR(64) NOT NULL - `trace_id` VARCHAR(64) NOT NULL - `search_session_id` VARCHAR(64) NULL - `user_key` VARCHAR(64) NOT NULL - `query_normalized` VARCHAR(512) NOT NULL - `item_id` VARCHAR(64) NOT NULL - `position` INT NULL:点击时该商品所在排名(无法取到则置空) - `clicked_at` DATETIME(3) NOT NULL - `target_url` TEXT 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_id` VARCHAR(64) NOT NULL - `request_id` VARCHAR(64) NOT NULL - `trace_id` VARCHAR(64) NOT NULL - `session_id` VARCHAR(64) NULL - `user_key` VARCHAR(64) NOT NULL - `placement` VARCHAR(64) NOT NULL:推荐位(home/pdp/cart/checkout/search…) - `module_id` VARCHAR(128) NULL:页面内模块标识(一个页面多个推荐模块) - `trigger_item_id` VARCHAR(64) NULL:触发商品(PDP/Cart 场景常用) - `candidates_count` INT NULL:候选数量 - `returned_count` INT NULL:返回条数 - `algo_id` VARCHAR(64) NULL:策略/算法标识(rule/i2i/embedding/…) - `model_version` VARCHAR(64) NULL:模型版本 - `experiment_id` VARCHAR(64) NULL:实验 ID(如有) - `variant_id` VARCHAR(64) NULL:分桶/分组(如有) - `latency_ms` INT NULL - `is_error` TINYINT NOT NULL DEFAULT 0 - `error_code` VARCHAR(64) NULL - `created_at` DATETIME(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_id` VARCHAR(64) NOT NULL - `request_id` VARCHAR(64) NOT NULL - `trace_id` VARCHAR(64) NOT NULL - `user_key` VARCHAR(64) NOT NULL - `placement` VARCHAR(64) NOT NULL - `module_id` VARCHAR(128) NULL - `trigger_item_id` VARCHAR(64) NULL - `item_id` VARCHAR(64) NOT NULL - `position` INT NOT NULL - `score` DOUBLE NULL - `price` DECIMAL(18,2) NULL - `currency` VARCHAR(8) NULL - `in_stock` TINYINT NULL - `exposed_at` DATETIME(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_id` VARCHAR(64) NOT NULL - `click_id` VARCHAR(64) NULL - `request_id` VARCHAR(64) NOT NULL - `trace_id` VARCHAR(64) NOT NULL - `user_key` VARCHAR(64) NOT NULL - `placement` VARCHAR(64) NOT NULL - `module_id` VARCHAR(128) NULL - `trigger_item_id` VARCHAR(64) NULL - `item_id` VARCHAR(64) NOT NULL - `position` INT NULL - `clicked_at` DATETIME(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_id` VARCHAR(64) NOT NULL - `view_id` VARCHAR(64) NULL - `session_id` VARCHAR(64) NULL - `user_key` VARCHAR(64) NOT NULL - `item_id` VARCHAR(64) NOT NULL - `source_type` VARCHAR(32) NULL:来源(search/rec/direct/ads/…) - `source_trace_id` VARCHAR(64) NULL:来源 trace(若来自 search/rec) - `dwell_time_ms` INT NULL - `viewed_at` DATETIME(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_id` VARCHAR(64) NOT NULL - `atc_id` VARCHAR(64) NULL - `session_id` VARCHAR(64) NULL - `cart_id` VARCHAR(64) NULL - `user_key` VARCHAR(64) NOT NULL - `item_id` VARCHAR(64) NOT NULL - `quantity` INT NOT NULL DEFAULT 1 - `price` DECIMAL(18,2) NULL - `currency` VARCHAR(8) NULL - `source_type` VARCHAR(32) NULL:来源(search/rec/direct/…) - `source_trace_id` VARCHAR(64) NULL:来源 trace(若来自 search/rec) - `added_at` DATETIME(3) NOT NULL - `cart_snapshot_json` JSON NULL:购物车快照(可选,体积大时可下沉到独立表) - `dwd_purchase`:订单事实表(全站统一) - **主键建议**:`(tenant_id, order_id)` - **核心索引建议**:`(tenant_id, user_key, paid_at)`、`(tenant_id, paid_at)` - **字段建议** - `tenant_id` VARCHAR(64) NOT NULL - `order_id` VARCHAR(64) NOT NULL - `user_key` VARCHAR(64) NOT NULL - `currency` VARCHAR(8) NULL - `total_amount` DECIMAL(18,2) NULL - `paid_at` DATETIME(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_id` VARCHAR(64) NOT NULL - `order_id` VARCHAR(64) NOT NULL - `line_id` VARCHAR(64) NULL - `user_key` VARCHAR(64) NOT NULL - `item_id` VARCHAR(64) NOT NULL - `quantity` INT NOT NULL DEFAULT 1 - `price` DECIMAL(18,2) NULL - `paid_at` DATETIME(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 计算口径**。