快速实验篇(B06)页面单跳转化率

小肥柴的Hadoop之旅 快速实验篇(B06)页面单跳转化率

电商用户行为分析实战 · B 组 · 第六阶段

数据源:b_group.dwd_user_behavior

技术栈:Hive on MR(窗口函数 LAG)


前言

B06 是 B 组第六个业务分析项目。原设计的问题是:用户在页面之间如何跳转?哪些页面跳转路径的转化率最高?

B05 已建立"头部结构认知":Top10 席位 65 session 撑 100 席、25 组并列。B06 从"品类"层下钻到"页面"层,本来预期会找到"页面间差异巨大"的信号(真实电商应是漏斗型:少量关键路径贡献大部分转化);但 B06 实测结果又一次与预期相反 :50 个页面、2500 条跳转路径,transition_count 全部集中在 40~91 之间(仅 2.3 倍差) ,与 B03/B04/B05 一样,也是均匀分布。

B06 的真正产出,因此不是"高转化路径 Top10",而是:

  • 50 × 50 页面跳转满矩阵 ads_page_single_step_conversion(2500 行),作为下游 B07/B13/B14 的事实表。
  • "均匀认知"在页面层再次验证:B 组六层认知后,B06 补充第六条证据。
  • 一次真实的 OOM 工程事故记录(见 §6)。

0. 实验全景

text 复制代码
b_group.dwd_user_behavior
   │  排除 07-27:dt BETWEEN '2019-07-17' AND '2019-07-26'
   │  page_id 过滤:非 NULL、非 -1
   │
   │  ① 页面跳转识别(脚本 01,窗口函数 LAG)
   ▼
ads_page_transition       158026 行(中间表,session 内相邻两跳)
   │
   │  ② 单跳转化率(脚本 02,LEFT JOIN 会话转化标记)
   ▼
ads_page_single_step_conversion  2500 行(50×50 满矩阵)
   │
   │  ③ 统一验证(脚本 03)
   ▼
16 项全部通过
   │
   ▼
质量报告:output/b06_quality_report.csv
  • 小结:B05 表达了"头部由 65 session 撑起",B06 则代表"页面跳转完全稠密",两者共同构成"用户在平台上的行为分布高度均匀"的认知。

1. Why:为什么做 B06

1.1 如果不做这一步,后面哪些项目会受影响

B07(Session 会话化与序列)会受影响 :B07 要做 session 内的行为序列分析。B06 的 ads_page_transition 已经是"session 内相邻两跳"的事实表,B07 可直接在其上做更长的序列(3-gram、n-gram)。

B09(城市/区域品类偏好)会受影响:B09 最终要落到"哪个城市的用户偏好哪个品类"。页面跳转是连接"品类偏好"和"浏览路径"的桥梁。

B12(异常流量与刷单识别)会受影响:B06 的页面跳转矩阵,是识别"机器人路径"(固定跳转、无随机性)的基础数据。50×50 满矩阵本身就是"无明显路径偏好"的证据。

B13(ADS 主题宽表)会受影响 :ads_page_transition 是 ADS 层"页面流量主题表"的候选输入。

B14(可视化)会受影响:页面跳转桑基图(Sankey Diagram)、热力图,都需要 B06 的输出。

B15(总结与工程复盘)会受影响:B06 的"均匀"是 B 组故事线的新一环,且 B06 附带一次 OOM 事故,是 B15 工程复盘的重要素材。

1.2 这一步具体做什么

用 Hive 计算两张表:

表 粒度 核心问题 行数
ads_page_transition (session, 相邻两跳) 每次跳转的 from/to 页面 158026
ads_page_single_step_conversion (from_page, to_page) 每对页面的跳转数 / 转化率 2500

排序 / 分区规则:

text 复制代码
窗口分区:PARTITION BY session_id
窗口排序:ORDER BY ts          (用 B02 的 timestamp,不用 action_time)
过滤:from_page_id IS NOT NULL  (排除 session 第一条)
  • 用 ts 排序,不用 action_time :B01 已证实文件顺序有 2.16% 乱序,action_time 是 string。B02 建立的 ts timestamp 才是正确的时间序。
  • 保留三个子计数 :transition_count、order_session_count、pay_session_count 全部保留,下游可按自己的口径算转化率。

1.3 做完怎么验证

共有 16 项统一验证,覆盖:

类别 验证项
中间表规模 transition_rows = 158026, transition_sessions = 8292
页面种类 from_page_cnt = 50, to_page_cnt = 50
转化表规模 conversion_rows = 2500, total_transitions = 156885
跳转次数极值 max_transition = 91, min_transition = 40
转化率极值 order_rate: 0.625~0.9831, pay_rate: 0.5079~0.8939
头部路径 paths_ge50 = 2383
矩阵完整性 matrix_full = 0(0 表示 2500 = 50×50 满矩阵)
会话交叉验证 sessions_with_order = 4977(与 B03 一致)
会话交叉验证 sessions_with_pay = 4111(与 B03 一致)

1.4 与下游什么关系

下游 本步给了什么 不用会怎样
B07 session 内相邻两跳 序列分析要重算
B09 页面 → 品类桥梁 缺中间粒度
B12 满矩阵作为基线 无"正常路径"对照
B13 页面流量主题表 ADS 层缺页面主题
B14 桑基图数据 无法画跳转图
B15 "均匀"第 6 条证据 + OOM 事故 故事线缺一环

2. B05 的硬约束:头部集中 + 排名并列

2.1 B05 实测数据

B05 已实测确认:

  • 100 个 Top10 席位只来自 65 个 session(平均 1.54 席位/session)。
  • 15 个 session 跨 2+ 品类进入 Top10。
  • 25 组(品类 × total_events)出现并列。
  • Top1 最小事件数 = 10,Rank10 最大事件数 = 10,重叠。

2.2 原设计的问题

B06 原设计如果直接做"Top10 高转化路径",会面临同样的问题:

  • 样本量陷阱:2500 条路径中若有大量低样本组合,最高转化率的路径可能只有 1~2 次跳转。
  • 均匀陷阱:即使路径跳转数完全均匀,转化率仍可能因样本分布差异抖动出"假高转化"。

2.3 B06 的应对:全矩阵 + 样本量门槛

B06 采用三个应对措施:

  1. 不做 TopN,输出全矩阵:2500 行全量输出,让下游按自己的样本量门槛筛选。
  2. 量化均匀性 :专门增加 V08_min_transition = 40、V13_paths_ge50 = 2383 两项,量化"路径分布多均匀"。
  3. 报告必须标注:B06 的跳转矩阵是"平台整体跳转基线",不代表"特定用户的跳转偏好"。

3. 脚本逐个讲

3.0 三个脚本的依赖链

text 复制代码
前置:目录 + 基线 + B02 表检查
   │
   ▼
脚本 01:页面跳转识别(LAG 窗口,158026 行)
   │  唯一的"事实源"
   ▼
脚本 02:单跳转化率(2500 行,从 01 读 + 会话转化标记 LEFT JOIN)
   │
   ▼
脚本 03:统一验证(16 项)
   │
   ▼
质量报告 b06_quality_report.csv

设计原则:

  • 01 是唯一事实源:02 从 01 读,不重复聚合。口径只有一处。

  • 02 用 COUNT(DISTINCT session_id):同一 session 多次走同一路径只算一次,避免重复计数。

  • 03 依赖 01/02 全部完成,做统一验收。

  • 重跑规则:01 重跑后,02/03 必须全部重跑。

为什么用"先 CREATE TABLE 再 INSERT",不用 CTAS?

  • Hive 3.1.3 对 CTAS 嵌窗口函数有解析风险。
  • DDL/DML 分离符合 B02 约定:先立结构再灌数,列名类型写错可秒级 DROP 重来。

为什么用 LAG 窗口函数,不用自连接?

LAG 是 Hive 的窗口函数,作用是在当前行取同一分组内、排序后往前第 N 行的某个列值。一句话:把上一行的数据"拉"到当前行。

基本语法:

sql 复制代码
LAG(column, offset, default_value) OVER (
  PARTITION BY partition_key
  ORDER BY order_key
)
参数 含义 默认
column 要取哪一列的历史值 必填
offset 往前第几行(1=上一行,2=上上行) 1
default_value 找不到时返回什么 NULL

最小示例 ,原表 user_actions:

text 复制代码
user_id | action_time | page_id
--------|-------------|--------
1       | 10:00       | A
1       | 10:01       | B
1       | 10:02       | C

执行:

sql 复制代码
SELECT
  user_id,
  action_time,
  page_id,
  LAG(page_id) OVER (PARTITION BY user_id ORDER BY action_time) AS prev_page
FROM user_actions;

结果:

text 复制代码
user_id | action_time | page_id | prev_page
--------|-------------|---------|----------
1       | 10:00       | A       | NULL      ← 第一行,前面没有
1       | 10:01       | B       | A
1       | 10:02       | C       | B

每一行都拿到了"自己 + 上一行"。这正是 B06 需要的:from_page_id = LAG(page_id),to_page_id = 当前 page_id。

B06 里的实际用法:

sql 复制代码
LAG(page_id) OVER (PARTITION BY session_id ORDER BY ts) AS from_page_id

逐字读:

  • PARTITION BY session_id:每个 session 单独算,不会跨 session 串行。
  • ORDER BY ts:按时间排序,用 B02 的 timestamp,不用 string 的 action_time。
  • LAG(page_id):取上一行的 page_id。

结果就是"每次跳转的起始页面",配合当前行的 page_id 作为"目标页面",得到完整跳转对。

关键理解点:

  1. 分区内第一行一定是 NULL :LAG 取"上一行",而第一行没有上一行,所以返回 NULL(或指定的 default)。B06 里用 WHERE from_page_id IS NOT NULL 把每个 session 的第一条行为排除掉。
  2. 必须有 ORDER BY :LAG 的"上一行"完全依赖 ORDER BY。没有 ORDER BY,Hive 会报错或给随机结果。
  3. 分区键决定"不会跨组" :PARTITION BY session_id 保证 session A 的最后一行的"下一行"是 session B 的第一行时,LAG 不会把 B 的值拉到 A 里。
  4. 和 LAG 对应的是 LEAD :LEAD 是往后看(下一行)。B06 用 LAG 得到 from + 当前行 to,等价于用 LEAD 得到当前 from + to。

不用窗口函数也能算相邻两跳,自连接:

sql 复制代码
SELECT a.session_id, a.page_id AS from_page, b.page_id AS to_page
FROM actions a
JOIN actions b
  ON a.session_id = b.session_id
 AND b.ts > a.ts
WHERE b.ts = (SELECT MIN(ts) FROM actions c WHERE c.session_id = a.session_id AND c.ts > a.ts);

问题:

  • 写起来复杂。
  • 子查询相关计算,慢。
  • 一个 session 有 N 条行为,要 N² 比较。

LAG 是一次排序 + 一次扫描,复杂度 O(N log N),性能高一个量级,这是 B06 选窗口函数的原因。

LAG 常见坑:

坑 表现 解法
忘写 ORDER BY 报错或结果随机 必须写
用 string 排序 时间乱序时结果错 用 timestamp 类型
分区键选错 跨 session 串行 PARTITION BY session_id
不处理第一行 from_page 为 NULL 混入结果 WHERE from_page_id IS NOT NULL
内存爆炸 大分区 OOM 加 map/reduce 内存控制(见 §6.1)

3.1 前置:目录、基线、表检查

如果不做这一步,后续哪些项目会难受?

  • 不做基线:后续 SUM(transition_count)=156885 等验证没有对照,验证变成空跑。
  • 不检查 B02 底座:如果 dwd_user_behavior 被误删,01 直接失败。

这一步具体做什么?

bash 复制代码
mkdir -p /home/hadoop/b06/sql /home/hadoop/b06/output
cd /home/hadoop/b06

# 检查 B02 底座列
hive -e "USE b_group; DESCRIBE dwd_user_behavior;" 2>&1 | grep -E 'page_id|action_time|behavior_type|dt'

# 检查行为分布
hive -e "USE b_group;
SELECT behavior_type, COUNT(*) AS cnt
FROM dwd_user_behavior
WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
GROUP BY behavior_type
ORDER BY cnt DESC;" 2>&1 | tail -10

# 记录基线
cat > output/b06_baseline.txt << 'EOF'
source_table,dwd_user_behavior
dt_range,2019-07-17_to_2019-07-26
expected_transition_rows,待执行后回填
expected_conversion_rows,待执行后回填
lag_partition,session_id
lag_order,ts
EOF

实测结果:

  • dwd_user_behavior 含 page_id、action_time、behavior_type、dt 四列,B02 底座完整。
  • 行为分布与 B03 完全一致:
text 复制代码
click   111310
search  37115
order   10887
pay     7458
  • 基线文件 6 行已写入。lag_order 从原设计的 action_time 改为 ts(B02 timestamp 才是正确时间序)。

3.2 脚本 01:页面跳转识别

如果不做这一步,后续哪些项目会难受?

  • 02 无源。
  • B07 若要做页面序列分析,需要重新算。
  • 口径分叉风险。

这一步具体做什么?

  • 从 dwd_user_behavior 读全量行为。
  • 过滤 page_id IS NOT NULL AND page_id != -1。
  • 按 session_id 分区,按 ts 排序,用 LAG 取上一个 page_id 和 ts(见 §3.0 LAG 说明)。
  • 过滤 from_page_id IS NOT NULL(排除 session 首条行为)。
  • 输出 session_id, user_id, from_page_id, to_page_id, from_ts, to_ts, dt。
  • 显式内存控制 :mapreduce.map.memory.mb=512、mapreduce.reduce.memory.mb=768、io.sort.mb=32、hive.exec.reducers.max=1。

完整脚本 sql/01_create_page_transition.hql:

sql 复制代码
USE b_group;

SET hive.exec.mode.local.auto=false;
SET mapreduce.map.memory.mb=512;
SET mapreduce.map.java.opts=-Xmx384m;
SET mapreduce.reduce.memory.mb=768;
SET mapreduce.reduce.java.opts=-Xmx512m;
SET mapreduce.task.io.sort.mb=32;
SET mapreduce.task.io.sort.factor=10;
SET hive.exec.reducers.max=1;
SET hive.exec.compress.intermediate=true;
SET hive.exec.compress.output=true;

DROP TABLE IF EXISTS ads_page_transition;

CREATE TABLE ads_page_transition (
  session_id    string,
  user_id       bigint,
  from_page_id  bigint,
  to_page_id    bigint,
  from_ts       timestamp,
  to_ts         timestamp,
  dt            string
)
STORED AS ORC
TBLPROPERTIES ("orc.compress"="SNAPPY");

INSERT OVERWRITE TABLE ads_page_transition
SELECT
  session_id,
  user_id,
  from_page_id,
  to_page_id,
  from_ts,
  to_ts,
  dt
FROM (
  SELECT
    session_id,
    user_id,
    page_id AS to_page_id,
    ts      AS to_ts,
    dt,
    LAG(page_id) OVER (PARTITION BY session_id ORDER BY ts) AS from_page_id,
    LAG(ts)      OVER (PARTITION BY session_id ORDER BY ts) AS from_ts
  FROM dwd_user_behavior
  WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
    AND page_id IS NOT NULL
    AND page_id != -1
) t
WHERE from_page_id IS NOT NULL;

执行:

bash 复制代码
cd /home/hadoop/b06
export HADOOP_CLIENT_OPTS="-Xmx512m -Xms256m"
hive -f sql/01_create_page_transition.hql 2>&1 | tee output/b06_01_transition.log

实测结果:耗时 96.6 秒,2 个 MR Stage 全部 SUCCESS。

text 复制代码
Stage-Stage-1: Map: 1  Reduce: 1   Cumulative CPU: 17.6 sec   HDFS Read: 1400566 HDFS Write: 1945249 SUCCESS
Stage-Stage-3: Map: 1  Reduce: 1   Cumulative CPU: 6.6 sec   HDFS Read: 17030 HDFS Write: 1976 SUCCESS
OK
Time taken: 96.557 seconds

验证:

bash 复制代码
hive -e "USE b_group;
SELECT
  COUNT(*) AS rows_cnt,
  COUNT(DISTINCT session_id) AS session_cnt,
  COUNT(DISTINCT from_page_id) AS from_page_cnt,
  COUNT(DISTINCT to_page_id) AS to_page_cnt
FROM ads_page_transition;" 2>&1 | tail -5

实测结果:

text 复制代码
158026  8292    50      50

四项全部符合预期:

指标 实际 期望 判定
rows_cnt 158026 约 15.8 万 ✅
session_cnt 8292 约 8744 ✅(部分 session 全是搜索/无 page_id)
from_page_cnt 50 待定 ✅
to_page_cnt 50 待定 ✅ 与 from 一致

全表前 10 行:

text 复制代码
000490eb-a9fe-4f3a-9388-e3d1afd09098    12      27      8       2019-07-18 21:37:47     2019-07-18 21:37:50     2019-07-18
000490eb-a9fe-4f3a-9388-e3d1afd09098    12      8       12      2019-07-18 21:37:50     2019-07-18 21:37:55     2019-07-18
0008d804-2b63-43d3-bd55-f265d1814d31    77      45      50      2019-07-19 14:21:24     2019-07-19 14:21:34     2019-07-19
0008d804-2b63-43d3-bd55-f265d1814d31    77      50      31      2019-07-19 14:21:34     2019-07-19 14:21:38     2019-07-19
0008d804-2b63-43d3-bd55-f265d1814d31    77      31      4       2019-07-19 14:21:38     2019-07-19 14:21:45     2019-07-19
0008d804-2b63-43d3-bd55-f265d1814d31    77      4       29      2019-07-19 14:21:45     2019-07-19 14:21:51     2019-07-19
0008d804-2b63-43d3-bd55-f265d1814d31    77      29      24      2019-07-19 14:21:51     2019-07-19 14:21:57     2019-07-19
0008d804-2b63-43d3-bd55-f265d1814d31    77      24      48      2019-07-19 14:21:57     2019-07-19 14:22:05     2019-07-19
001aab93-7b7c-46b7-b833-a890caa0a6a7    90      38      5       2019-07-24 12:10:59     2019-07-24 12:10:59     2019-07-24
001aab93-7b7c-46b7-b833-a890caa0a6a7    90      5       15      2019-07-24 12:10:59     2019-07-24 12:11:00     2019-07-24

关键观察:

  • session 0008d804-... 在 2019-07-19 有连续的跳转序列:45 → 50 → 31 → 4 → 29 → 24 → 48,是典型的浏览路径。
  • session 000490eb-... 的跳转间隔 3~5 秒,是正常人类行为节奏。

3.3 脚本 02:单跳转化率

如果不做这一步,后续哪些项目会难受?

  • B06 主表缺失。
  • B07/B09/B12/B13/B14 缺少页面跳转基线。
  • B15 缺"均匀"在页面层的证据。

这一步具体做什么?

  • 从 ads_page_transition 读。
  • LEFT JOIN 一个 session 级转化标记子表(从 dwd_user_behavior 聚合 has_order / has_pay)。
  • 按 (from_page_id, to_page_id) 分组。
  • 输出 transition_count、order_session_count、pay_session_count 及两个转化率。
  • 用 COUNT(DISTINCT session_id) 避免同 session 多次跳转重复计数。

完整脚本 sql/02_create_page_single_step_conversion.hql:

sql 复制代码
USE b_group;

SET hive.exec.mode.local.auto=false;
SET mapreduce.map.memory.mb=512;
SET mapreduce.map.java.opts=-Xmx384m;
SET mapreduce.reduce.memory.mb=768;
SET mapreduce.reduce.java.opts=-Xmx512m;
SET mapreduce.task.io.sort.mb=32;
SET mapreduce.task.io.sort.factor=10;
SET hive.exec.reducers.max=1;
SET hive.exec.compress.intermediate=true;
SET hive.exec.compress.output=true;

DROP TABLE IF EXISTS ads_page_single_step_conversion;

CREATE TABLE ads_page_single_step_conversion (
  from_page_id           bigint,
  to_page_id             bigint,
  transition_count       bigint,
  order_session_count    bigint,
  pay_session_count      bigint,
  order_conversion_rate  double,
  pay_conversion_rate    double
)
STORED AS ORC
TBLPROPERTIES ("orc.compress"="SNAPPY");

INSERT OVERWRITE TABLE ads_page_single_step_conversion
SELECT
  t.from_page_id,
  t.to_page_id,
  COUNT(DISTINCT t.session_id) AS transition_count,
  COUNT(DISTINCT CASE WHEN c.has_order = 1 THEN t.session_id END) AS order_session_count,
  COUNT(DISTINCT CASE WHEN c.has_pay   = 1 THEN t.session_id END) AS pay_session_count,
  ROUND(
    COUNT(DISTINCT CASE WHEN c.has_order = 1 THEN t.session_id END)
    / COUNT(DISTINCT t.session_id), 4
  ) AS order_conversion_rate,
  ROUND(
    COUNT(DISTINCT CASE WHEN c.has_pay = 1 THEN t.session_id END)
    / COUNT(DISTINCT t.session_id), 4
  ) AS pay_conversion_rate
FROM ads_page_transition t
LEFT JOIN (
  SELECT
    session_id,
    MAX(CASE WHEN behavior_type = 'order' THEN 1 ELSE 0 END) AS has_order,
    MAX(CASE WHEN behavior_type = 'pay'   THEN 1 ELSE 0 END) AS has_pay
  FROM dwd_user_behavior
  WHERE dt BETWEEN '2019-07-17' AND '2019-07-26'
  GROUP BY session_id
) c ON t.session_id = c.session_id
GROUP BY t.from_page_id, t.to_page_id;

执行:

bash 复制代码
cd /home/hadoop/b06
hive -f sql/02_create_page_single_step_conversion.hql 2>&1 | tee output/b06_02_conversion.log

实测结果:耗时约 2 分钟,3 个 MR Stage,SUCCESS。

验证 V1:总行数与跳转次数

bash 复制代码
hive -e "USE b_group;
SELECT
  COUNT(*) AS rows_cnt,
  SUM(transition_count) AS total_transitions,
  MAX(transition_count) AS max_transition,
  SUM(order_session_count) AS total_order_sessions,
  SUM(pay_session_count) AS total_pay_sessions
FROM ads_page_single_step_conversion;" 2>&1 | tail -5

实测结果:

text 复制代码
2500    156885  91      126924  112646

五项全部符合预期:

指标 实际 说明
rows_cnt 2500 50 × 50 满矩阵
total_transitions 156885 < 158026,去重效果
max_transition 91 最高频跳转的 session 数
total_order_sessions 126924 总和 > B03 的 4977,因为一个 session 可跨多路径
total_pay_sessions 112646 同理

验证 V2:转化率极值

text 复制代码
max_order_rate  0.9831
min_order_rate  0.625
max_pay_rate    0.8939
min_pay_rate    0.5079

order 转化率范围 62.5% ~ 98.3% (差 35.8pp),pay 转化率范围 50.8% ~ 89.4% (差 38.6pp)。这是 B 组第一次出现强区分度指标。

验证 V4:Top10 高转化路径(阈值 50)

text 复制代码
4       30      59      58      0.9831  47      0.7966
1       14      53      51      0.9623  41      0.7736
37      18      56      53      0.9464  40      0.7143
7       2       55      52      0.9455  42      0.7636
26      4       55      52      0.9455  43      0.7818
2       35      68      64      0.9412  51      0.75
7       40      64      60      0.9375  51      0.7969
35      24      63      59      0.9365  45      0.7143
22      24      61      57      0.9344  39      0.6393
42      5       60      56      0.9333  44      0.7333

关键观察:

  • 最高转化路径 4 → 30:59 次跳转中 58 次有下单,转化率 98.31%。
  • 但所有 Top10 路径的样本量都在 53~68 之间,样本量接近。
  • 前 10 名的转化率范围 93.33% ~ 98.31% ,只有 5pp 差。高转化路径之间同样均匀。

验证 V5:样本量分布

text 复制代码
lt5   0
lt10  0
lt30  0
ge50  2383
total 2500

关键观察:

  • 没有任何路径的 transition_count < 30。
  • 2383/2500 = 95.3% 的路径样本量 ≥ 50。
  • 说明 50 个页面的跳转完全稠密,每条路径都走了至少 40 次。

3.4 脚本 03:统一验证

完整脚本 sql/03_verify_b06.hql:

sql 复制代码
USE b_group;

SET hive.exec.mode.local.auto=false;
SET mapreduce.task.io.sort.mb=32;

SELECT 'V01_transition_rows'      AS check_name, CAST(COUNT(*) AS STRING) AS check_value FROM ads_page_transition
UNION ALL
SELECT 'V02_transition_sessions', CAST(COUNT(DISTINCT session_id) AS STRING) FROM ads_page_transition
UNION ALL
SELECT 'V03_from_page_cnt',       CAST(COUNT(DISTINCT from_page_id) AS STRING) FROM ads_page_transition
UNION ALL
SELECT 'V04_to_page_cnt',         CAST(COUNT(DISTINCT to_page_id) AS STRING) FROM ads_page_transition
UNION ALL
SELECT 'V05_conversion_rows',     CAST(COUNT(*) AS STRING) FROM ads_page_single_step_conversion
UNION ALL
SELECT 'V06_total_transitions',   CAST(SUM(transition_count) AS STRING) FROM ads_page_single_step_conversion
UNION ALL
SELECT 'V07_max_transition',      CAST(MAX(transition_count) AS STRING) FROM ads_page_single_step_conversion
UNION ALL
SELECT 'V08_min_transition',      CAST(MIN(transition_count) AS STRING) FROM ads_page_single_step_conversion
UNION ALL
SELECT 'V09_order_rate_max',      CAST(MAX(order_conversion_rate) AS STRING) FROM ads_page_single_step_conversion
UNION ALL
SELECT 'V10_order_rate_min',      CAST(MIN(order_conversion_rate) AS STRING) FROM ads_page_single_step_conversion
UNION ALL
SELECT 'V11_pay_rate_max',        CAST(MAX(pay_conversion_rate) AS STRING) FROM ads_page_single_step_conversion
UNION ALL
SELECT 'V12_pay_rate_min',        CAST(MIN(pay_conversion_rate) AS STRING) FROM ads_page_single_step_conversion
UNION ALL
SELECT 'V13_paths_ge50',          CAST(COUNT(*) AS STRING) FROM ads_page_single_step_conversion WHERE transition_count >= 50
UNION ALL
SELECT 'V14_matrix_full',         CAST((SELECT COUNT(*) FROM ads_page_single_step_conversion) -
                                       (SELECT COUNT(DISTINCT from_page_id) * COUNT(DISTINCT to_page_id)
                                        FROM ads_page_transition) AS STRING)
UNION ALL
SELECT 'V15_sessions_with_order', CAST(COUNT(DISTINCT session_id) AS STRING) FROM dwd_user_behavior
  WHERE dt BETWEEN '2019-07-17' AND '2019-07-26' AND behavior_type = 'order'
UNION ALL
SELECT 'V16_sessions_with_pay',   CAST(COUNT(DISTINCT session_id) AS STRING) FROM dwd_user_behavior
  WHERE dt BETWEEN '2019-07-17' AND '2019-07-26' AND behavior_type = 'pay';

执行:

bash 复制代码
cd /home/hadoop/b06
hive -f sql/03_verify_b06.hql 2>&1 | tee output/b06_03_verify.log

实测结果:耗时 827.3 秒(20 个 MR Stage),16 项全部输出。

16 项实测结果(排序后):

text 复制代码
V01_transition_rows       158026
V02_transition_sessions   8292
V03_from_page_cnt         50
V04_to_page_cnt           50
V05_conversion_rows       2500
V06_total_transitions     156885
V07_max_transition        91
V08_min_transition        40
V09_order_rate_max        0.9831
V10_order_rate_min        0.625
V11_pay_rate_max          0.8939
V12_pay_rate_min          0.5079
V13_paths_ge50            2383
V14_matrix_full           0
V15_sessions_with_order   4977
V16_sessions_with_pay     4111

16 项全部与预期一致。

V15/V16 与 B03 完全一致 。B06 与 B03 交叉验证通过。

grep 输出顺序乱是 MapReduce 多 stage 输出顺序不保证,不是 bug。生成 CSV 时按 V1~V16 排序即可。


4. 三个核心发现

4.1 发现 1:50 × 50 页面跳转满矩阵,路径完全稠密

指标 值 含义
V03_from_page_cnt 50 来源页面种类
V04_to_page_cnt 50 目标页面种类
V05_conversion_rows 2500 = 50 × 50,满矩阵
V14_matrix_full 0 0 表示满矩阵,无缺失组合

业务解释:

  • 50 个页面的两两组合全部出现过,没有任何"未被探索的路径"。
  • 这在真实电商中几乎不可能。真实数据里大部分页面组合是稀疏的(比如"支付成功页 → 首页"可能发生,但"支付成功页 → 商品搜索页"几乎不会)。
  • 说明平台的页面跳转是高度随机的,不是用户意图驱动的。

4.2 发现 2:跳转次数分布均匀,40~91 之间仅 2.3 倍差

指标 值 含义
V07_max_transition 91 最高频路径跳转数
V08_min_transition 40 最低频路径跳转数
极差 91 / 40 ≈ 2.3 仅 2.3 倍差
V13_paths_ge50 2383 95.3% 路径样本 ≥ 50

关键观察:

  • 没有任何路径样本 < 30,最短路径也有 40 次跳转。
  • 这是 B 组六层"均匀认知"(B03 品类、B04 加权、B05 席位、B06 路径)的最强证据。
  • 说明页面级别的行为分布同样均匀,甚至比品类层更均匀。

4.3 发现 3:转化率有区分度,但极值可疑

指标 范围 差
order_conversion_rate 62.50% ~ 98.31% 35.8pp
pay_conversion_rate 50.79% ~ 89.39% 38.6pp

这是 B 组第一次出现强区分度指标。但需要警惕:

  • 最高转化路径 4 → 30:59 次跳转 58 次有下单,转化率 98.31%。
  • 所有 Top10 高转化路径的样本量在 53~68 之间,样本量高度接近。
  • 前 10 名的转化率范围 93.33% ~ 98.31%,只有 5pp 差。

报告必须标注:

B06 的 order/pay 转化率有区分度,但 Top10 高转化路径内部仍然只有 5pp 差。这些"高转化路径"是否有业务含义,需要进一步验证(如:是否存在页面类型差异,而非路径差异)。


5. 对下游的影响

下游 影响
B07 ads_page_transition 是 session 内相邻两跳,可直接做 n-gram 序列分析
B09 页面 → 品类桥梁,可分析"不同城市的跳转偏好"
B12 满矩阵作为"正常路径"基线,任何偏离都是异常信号
B13 ads_page_transition 是 ADS 页面流量主题表候选输入
B14 页面跳转桑基图、热力图数据
B15 "均匀"第 6 条证据 + OOM 事故记录

6. 工程收获

6.1 窗口函数在 2GB 集群上的内存风险

B06 脚本 01 第一次执行触发了 OOM,DataNode + NodeManager 被内核 OOM-killer 杀掉。修复方案:

  1. 显式设置 map/reduce 容器内存:

    sql 复制代码
    SET mapreduce.map.memory.mb=512;
    SET mapreduce.map.java.opts=-Xmx384m;
    SET mapreduce.reduce.memory.mb=768;
    SET mapreduce.reduce.java.opts=-Xmx512m;
  2. 收紧 io.sort.mb :SET mapreduce.task.io.sort.mb=32;(默认 100m 太大)

  3. 限制 reducer 数 :SET hive.exec.reducers.max=1;

  4. Hive CLI 客户端加堆上限 :export HADOOP_CLIENT_OPTS="-Xmx512m -Xms256m",写入 ~/.bashrc

6.2 集群服务被 OOM-killer 杀掉后的恢复流程

  1. jps 确认哪些服务挂了。
  2. sudo dmesg -T 无法获取 OOM 记录时,从 jps 缺失推断。
  3. stop-yarn.sh && stop-dfs.sh 后 start-dfs.sh && start-yarn.sh。
  4. 单独补启:hdfs --daemon start datanode、yarn --daemon start nodemanager。
  5. 冒烟测试:跑一个小的写表语句,确认 YARN 能派容器。

6.3 其他工程细节

  • DDL/DML 分离 :先 CREATE TABLE,再 INSERT OVERWRITE。列名类型写错可秒级 DROP 重来。
  • 窗口函数排序用 ts 不用 action_time:B02 timestamp 才是正确时间序。
  • COUNT(DISTINCT session_id) 而非 COUNT(*):避免同 session 多次跳转重复计数。V06 = 156885 < V01 = 158026,说明去重生效。
  • 验证独立成脚本 :16 项验证收拢到一个 SQL,输出 metric, value 两列,未来可 diff。

7. 方法收获

  • 均匀分布不是 B03 的偶然 :从 B03 品类、B04 加权、B05 席位、到 B06 路径,均匀性一路验证。这是数据集生成方式的问题,不是分析方法的缺陷。
  • 满矩阵是强证据:50 × 50 = 2500 条路径全部出现,说明行为随机性极高。
  • 样本量门槛是转化率分析的前提:B06 的转化率极值(98.3%)看似强区分度,但若没有 V13 的样本量检查,可能被误读为"这条路径特别好"。
  • 交叉验证必不可少:V15/V16 与 B03 完全一致,这是 B06 与 B03 的口径一致性的证明。

8. 遗留问题

  • 页面 50 种是什么:page_id 只有 50 个,但具体代表什么页面未知。可能是"1=首页、2=搜索、3=商品详情...",但无维表。B09 需自建小维表。
  • 满矩阵的业务解释:为什么 2500 条路径都出现了?数据集生成随机性太强,还是真的业务特征?待 B12 分析。
  • 转化率极值的真伪 :Top1 高转化路径 4 → 30(98.3%),但样本量仅 59。是否真的是"关键路径",还是统计噪声?
  • session_cnt 8292 vs 8744:约 450 个 session 无有效页面跳转(全是搜索或 page_id=-1),占比 5.1%。B07 需处理。
  • 07-27 疑截断:B06 全部排除。
  • OOM 风险:B06 之后的窗口函数脚本,必须继承 §3.2 的内存控制参数。

9. 结语

B06 是 B 组第六个业务分析项目。它完成了"页面单跳转化率"的任务,但更重要的是:它把"均匀认知"从品类、session 层延伸到了页面层。

原来的问题是"哪些页面跳转路径的转化率最高"。实测发现:

  • 50 个页面、2500 条路径,完全稠密(满矩阵)。
  • 路径跳转数范围 40~91 ,仅 2.3 倍差。
  • order / pay 转化率有区分度(62.5%~98.3% / 50.8%~89.4%),但 Top10 内部仅 5pp 差。
  • B06 附带一次真实 OOM 事故:DataNode + NodeManager 被 OOM-killer 杀掉,通过显式设置容器内存 + HADOOP_CLIENT_OPTS 修复。

B06 对后续 B07--B15 的真正贡献:不是"一份高转化路径清单",而是:

  1. ads_page_transition(158026 行):session 内相邻两跳事实表,B07 可直接复用。
  2. ads_page_single_step_conversion(2500 行):50 × 50 满矩阵,作为页面流量基线。
  3. "均匀"第 6 条证据:B 组故事线从"品类均匀"到"页面均匀"完全闭合。
  4. OOM 事故的工程记录:为 B07+ 的窗口函数脚本提供内存控制模板。

B01 建立了"数据认知"(11 天、四类互斥、文件顺序≠时间顺序),B02 建立了"结构认知"(分区、ts、seq_in_session),B03 建立了"业务认知"(搜索不是入口、支付无下单是真实业务、品类均匀),B04 建立了"均匀认知"(排名不成立、加权无区分度、只有 otp 可用),B05 建立了"头部结构认知"(65 session 撑 100 席位、15 个跨品类、25 组并列),B06 建立了"页面稠密认知"(2500 满矩阵、2.3 倍差、OOM 事故)。

这六层认知,是 B07--B15 的共同底座。


附录 A:质量报告

output/b06_quality_report.csv:

text 复制代码
check_name,check_value
V01_transition_rows,158026
V02_transition_sessions,8292
V03_from_page_cnt,50
V04_to_page_cnt,50
V05_conversion_rows,2500
V06_total_transitions,156885
V07_max_transition,91
V08_min_transition,40
V09_order_rate_max,0.9831
V10_order_rate_min,0.625
V11_pay_rate_max,0.8939
V12_pay_rate_min,0.5079
V13_paths_ge50,2383
V14_matrix_full,0
V15_sessions_with_order,4977
V16_sessions_with_pay,4111

附录 B:执行环境

项 版本
Hadoop 3.3.6
Hive 3.1.3 (on MR)
集群 1 master + 3 worker,每节点 2GB
Java 8
工作目录 /home/hadoop/b06
Metastore 全局固定 /home/hadoop/hive_metastore/metastore_db
Hive CLI 堆 HADOOP_CLIENT_OPTS="-Xmx512m -Xms256m"(B06 新增)
运行日期 2026-10-02

附录 C:OOM 事故时间线

时间 事件
22:23:44 脚本 01 第一次执行,Stage-1 map=0% 卡死
22:24~22:30 jps 仅剩 NameNode + ResourceManager;DataNode + NodeManager 被 OOM-killer 杀掉
22:35 hdfs --daemon start datanode + yarn --daemon start nodemanager 恢复服务
22:36 冒烟测试通过
22:38 脚本 01 加内存控制参数后重跑,成功

本文是 B06 阶段完整复盘,也是 B07--B15 的页面级分析底座说明。

B01 是"数据认知",B02 是"结构认知",B03 是"业务认知",B04 是"均匀认知",B05 是"头部结构认知",B06 是"页面稠密认知"。六者共同构成 B 组的分析底座。

相关推荐
欢喜躲在眉梢里1 小时前
时序数据库选型指南:从大数据架构视角拆解 Apache IoTDB 的适用边界
大数据·人工智能·ai·架构·时序数据库·模型
IT毕设梦工厂1 小时前
计算机毕业设计选题推荐:基于大数据的气象站地面观测数据分析与可视化|毕业设计选题|计算机毕设|选题推荐|毕设指导|项目定制|源码|高质量项目
大数据·hadoop·信息可视化·数据挖掘·数据分析·课程设计·大数据毕设项目
外参财观1 小时前
咖啡三国杀:瑞幸咖啡碾压局,库迪咖啡贴身战,幸运咖下沉王
大数据
nvd111 小时前
# 从 192 秒到 13 秒:Apache Flink 批处理极速调优实战记录
大数据·flink·apache
weixin_750330236 小时前
AI获客工具选型:从技术架构看中小企业效率提升方案
大数据·人工智能·架构·ai获客
mpp0078 小时前
36氪《2026 中国 AI Agent 行业发展报告》:当 Agent 进入「交付」主战场
大数据·人工智能
johnsong11 小时前
当决策成为免费商品:思维成本崩溃背后的治理真空
大数据·人工智能
数字新视界11 小时前
动环监控系统优化数据中心管理,提升环境监测与安全效率
大数据·人工智能·数据中心·微模块机房·模块化机房
zhenaibo52112 小时前
如何向导师请教问题,才能得到有效建议?
大数据·人工智能·深度学习