小肥柴的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 建立的tstimestamp 才是正确的时间序。 - 保留三个子计数 :
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 采用三个应对措施:
- 不做 TopN,输出全矩阵:2500 行全量输出,让下游按自己的样本量门槛筛选。
- 量化均匀性 :专门增加
V08_min_transition = 40、V13_paths_ge50 = 2383两项,量化"路径分布多均匀"。 - 报告必须标注: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 作为"目标页面",得到完整跳转对。
关键理解点:
- 分区内第一行一定是 NULL :
LAG取"上一行",而第一行没有上一行,所以返回 NULL(或指定的 default)。B06 里用WHERE from_page_id IS NOT NULL把每个 session 的第一条行为排除掉。 - 必须有 ORDER BY :
LAG的"上一行"完全依赖ORDER BY。没有ORDER BY,Hive 会报错或给随机结果。 - 分区键决定"不会跨组" :
PARTITION BY session_id保证 session A 的最后一行的"下一行"是 session B 的第一行时,LAG 不会把 B 的值拉到 A 里。 - 和 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 杀掉。修复方案:
-
显式设置 map/reduce 容器内存:
sqlSET mapreduce.map.memory.mb=512; SET mapreduce.map.java.opts=-Xmx384m; SET mapreduce.reduce.memory.mb=768; SET mapreduce.reduce.java.opts=-Xmx512m; -
收紧 io.sort.mb :
SET mapreduce.task.io.sort.mb=32;(默认 100m 太大) -
限制 reducer 数 :
SET hive.exec.reducers.max=1; -
Hive CLI 客户端加堆上限 :
export HADOOP_CLIENT_OPTS="-Xmx512m -Xms256m",写入~/.bashrc
6.2 集群服务被 OOM-killer 杀掉后的恢复流程
jps确认哪些服务挂了。sudo dmesg -T无法获取 OOM 记录时,从jps缺失推断。stop-yarn.sh && stop-dfs.sh后start-dfs.sh && start-yarn.sh。- 单独补启:
hdfs --daemon start datanode、yarn --daemon start nodemanager。 - 冒烟测试:跑一个小的写表语句,确认 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 的真正贡献:不是"一份高转化路径清单",而是:
ads_page_transition(158026 行):session 内相邻两跳事实表,B07 可直接复用。ads_page_single_step_conversion(2500 行):50 × 50 满矩阵,作为页面流量基线。- "均匀"第 6 条证据:B 组故事线从"品类均匀"到"页面均匀"完全闭合。
- 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 组的分析底座。