SPL 计算性能系列测试:漏斗分析

一、 测试任务

电商业务中漏斗分析是常见的统计需求。用户使用智能设备购物时,系统会建立连接形成会话 session。每个会话又包含很多个操作事件 event,比如:访问网站,浏览产品页,下单购买等等。每个用户的操作事件有一定的先后顺序,事件顺序越靠后,完成该事件的用户数量越少,就像是一个漏斗。漏斗转化分析先要统计各步骤操作事件的去重用户数量,在此基础上再做转化率等进一步的计算。

现将电商系统的数据结构简化如下:

sessions 表

列名

数据类型

说明

id

long

session 编号,主键

userid

long

用户号

devicetype

int

设备类型

events:

列名

数据类型

说明

id

long

event 编号,主键

sessionid

long

session 编号

userid

long

用户号

eventtime

datetime

发生时间(精确到毫秒)

eventtype

int

事件类型

eventmessage

string

事件消息(200 字符)

eventtype:

列名

数据类型

说明

id

int

序号,主键

name

string

名称

devicetype

列名

数据类型

说明

id

int

序号,主键

name

string

名称

表间关系说明:

sessions 表(简称 s)和 events 表(简称 e)是一对多关系,通过 s 的 userid、id 和 e 的 userid、sessionid 关联。

不同的 userid 对应 session 的 id 不会重复,不同的 session 对应 event 的 id 不会重复。

events 表和 eventtype 表(简称 et)是多对一关系,通过 e 的 eventtype 和 et 的 id 关联。

sessions 表和 devicetype 表(简称 dt)是多对一关系,通过 s 的 devicetype 和 dt 的 id 关联。

本测试任务的输入参数是指定日期,例如:2022-07-21。测试任务是对指定日期前 30 天内的 events 计算三步和七步漏斗转化率。

以三步漏斗为例,在 2022-06-21 零点到 2022-07-21 零点之间,分别计算出:

做过 visit 类型事件的去重用户数量,记为 step1_count;

在一天内依次做过 visit,view 类型事件的去重用户数量,记为 step2_count;

在一天内依次做过 visit,view,confirm 类型事件的去重用户数量,记为 step3_count。

在此基础上,按照这些事件对应 sessions 的设备类型 devicetype,对这些去重用户数量分组求总数。最后用 step3_count 除以 step1_count,求出三步漏斗的转化率。

三步漏斗计算结果大致是这样的:

..

七步漏斗计算过程与三步漏斗大致相同,只是 events 类型有七个,依次是:visit、view、detail、login、cart、confirm、pay。七步漏斗计算结果大致是这样的:

..

本次测试还按照是否包含字符串过滤条件分为 A 类和 B 类两类测试。B 类测试是在上述 A 类测试的基础上,每一步都加上包含子串的过滤条件。比如第一步漏斗,对 events 还要加上一个条件:eventmessage 包含字符子串“check_point”。每一步漏斗过滤条件中,eventmessage 字段的子串都是不同的。

二、 对标技术

本次测试采用了 SPL 企业版(2023 年 5 月版本),并在同样环境下采用 StarRocks、Oracle 进行漏斗转化率计算,对比计算性能。

1、StarRocks, 宣称最快的 SQL 型数据库,测试版本 3.0 和 2.5.3。

2、Oracle,使用很广泛,常常作为数据库性能测试的对比标杆,测试版本 19c。

Oracle 不是专业 OLAP 数据库,性能指标通常会较弱,仅供参考。

三、 测试环境

单台物理服务器,配置:

2 颗 Intel3014,主频 1.7G,共 12 核 CPU

64G 内存

SSD 固态硬盘

为了能测试出这些产品的外存计算能力以及对内存的敏感性,我们使用虚拟机来限制内存。设计了几套内存不同的测试环境,CPU 都是 8 核:

VM1:32G 内存;VM2:16G 内存;VM3:8G 内存;VM4:6G 内存;VM5:4G 内存

StarRocks 至少要安装两个节点 BE 和 FE,将承担计算任务的 BE 安装在虚拟机上,管理节点 FE 安装在物理机上,这样不会影响测试效果。

SPL 和 Oracle 都只要在虚拟机下安装就可以了。

SPL、StarRocks 在能跑出结果的虚拟机上都测试了,Oracle 因为仅作为参考,只在 VM1 测试了。

四、 测试数据

全部数据的日期范围是 2022-06-28 到 07-28

sessions:350 万行

events:2.8 亿行

eventtype:678 行

devicetype:8 行

点击这里下载测试数据。(如果点击后没有开始下载,请复制 http://d.raqsoft.com.cn:6888/funnel.zip 到浏览器地址栏下载。)

下载后,解压缩到一个目录中,在集算器中将这个目录配置成主目录,执行 dataInit.splx 生成文本文件。

用于测试的数据存放在 4 个 tab 分隔的文本文件(无字段名称):

sessions.txt:数据按照 userid、id 有序(升序,下同)。

events.txt:数据按照 userid、sessionid、eventtime、id 有序。events 表对于 B 类测试中的字符子串(比如“check_point”)的过滤条件,判断结果都是“真”。

eventtype.txt:数据按照 id 有序。

devicetype.txt:数据按照 id 有序。

字段先后顺序和上述表结构的字段顺序相同。

以 devicetype.txt 为例,文件内容是:

1 phone

2 other

3 unkown

4 laptop

5 pad

6 PC

7 TV

8 spider

五、 SQL 测试

Oracle 在 VM1 上测试了 A 类的三步和七步漏斗。

StarRocks 在所有能跑出结果的虚拟机上测试了三步和七步漏斗,包括 A 类和 B 类。

测试过程中发现,StarRocks 计算两步及以上的 step_count 时会报错:

..

所以测试中,每次只能计算一个步骤的 step_count。

例如 3 步骤漏斗中如果计算 step1_count、step2_count、step3_count 中的任意两个或者三个,都会报这个错误。所以测试的时候只能计算三个中的一个。

最新版本 3.0 和稳定版本 2.5.3 都有这个错误。

Oracle 的 SQL 语句(A 类三步漏斗):

WITH e1 AS (
SELECT
userid,
eventtime AS step1_time,
MIN(sessionid) AS sessionid,
1 AS step1
  FROM events e1
  JOIN eventtype ON eventtype.id = e1.eventtype
  WHERE eventtime>= to_date('2022-07-21','yyyy-mm-dd')-30
    AND eventtime< to_date('2022-07-21','yyyy-mm-dd') 
    AND (eventtype.name = 'visit')
  GROUP BY userid,eventtime
), e2 AS (
  SELECT
e2.userid,
MIN(e2.sessionid) AS sessionid,
1 AS step2,
MIN(eventtime) AS step2_time,
MIN(e1.step1_time) AS step1_time
  FROM events e2
  JOIN e1 ON e1.sessionid = e2.sessionid  AND eventtime > step1_time
  JOIN eventtype ON eventtype.id = e2.eventtype
  WHERE eventtime < step1_time +1
    AND (eventtype.name = 'view')
  GROUP BY  e2.userid
), e3 AS (
  SELECT
e3.userid,
MIN(e3.sessionid) AS sessionid,
1 AS step3,
MIN(eventtime) AS step3_time,
MIN(e2.step1_time) AS step1_time
  FROM events e3
  JOIN e2 ON e2.sessionid = e3.sessionid  AND eventtime > step2_time
  JOIN eventtype ON eventtype.id = e3.eventtype
  WHERE eventtime < step1_time+1
    AND (eventtype.name = 'detail')
  GROUP BY  e3.userid
)
SELECT
dt.name AS devicetype,
  COUNT(DISTINCT CASE WHEN funnel_conversions.step1 IS NOT NULL THEN funnel_conversions.step1_userid  ELSE NULL END) AS step1_count,
  COUNT(DISTINCT CASE WHEN funnel_conversions.step2 IS NOT NULL THEN funnel_conversions.step2_userid  ELSE NULL END) AS step2_count,
  COUNT(DISTINCT CASE WHEN funnel_conversions.step3 IS NOT NULL THEN funnel_conversions.step3_userid  ELSE NULL END) AS step3_count 
  COUNT(DISTINCT CASE WHEN funnel_conversions.step3 IS NOT NULL THEN funnel_conversions.step3_userid  ELSE NULL END) 
/ COUNT(DISTINCT CASE WHEN funnel_conversions.step1 IS NOT NULL THEN funnel_conversions.step1_userid  ELSE NULL END) AS step3_rate
FROM (
  SELECT
e1.step1_time AS step1_time,
e1.userid AS userid,
e1.userid AS step1_userid,
e2.userid AS step2_userid,  
e3.userid AS step3_userid,
e1.sessionid AS step1_sessionid,
step1, step2, step3
  FROM e1
  LEFT JOIN e2 ON e1.userid=e2.userid
  LEFT JOIN e3 ON e2.userid=e3.userid
  ) funnel_conversions
LEFT JOIN sessions s ON funnel_conversions.step1_sessionid = s.id 
left join devicetype dt on s.devicetype=dt.id 
GROUP BY dt.name

其中:

1、子查询 e1 是找出 to_date('2022-07-21','yyyy-mm-dd') 到 to_date('2022-07-21','yyyy-mm-dd')-30 期间,eventtype 是 visit 的 event。按照用户 userid 和时间 eventtime 分组,每组取 sessionid 的最小值作为 e1 的 sessionid,eventtime 作为 e1 的 step1_time。常数 1 是结果集 e2 的 step1 字段。

2、子查询 e2 是用 e1 和 events 表内连接。连接的条件是 e1 的 sessionid 和 events 表的 sessionid 相等,且 events 的时间要大约 e1.step1_time。连接的结果在做过滤,条件是 events 的时间要小于 e1.step1_time+1 天,且 events 的 eventtype 是 view。再按照 events 的用户 userid 分组,sessionid、eventtime、step1_time 都取最小值,分别作为结果集 e2 的 sessionid、step2_time、step1_time。常数 1 是结果集 e2 的 step2 字段。

3、子查询 e3 和 e2 大致相同,只是 eventtype 不同。

4、子查询 funnel_conversions 用 e1 左连接 e2、e3,连接条件是 userid 相同。结果中有 e1 到 e3 的 userid,有 step1 到 step3。

5、最外层查询用 funnel_conversions 的 sessionid 连接 sessions 表的 id,用 sessions 表的 devicetype 连接 devicetype 表的 id。再用 devicetype 表的 name 分组,统计每一类设备 device、每一步骤的去重用户数。且用第三步的结果除以第一步的结果, 计算总体转化率。

StarRocks 的 SQL 语句(B 类七步漏斗):

WITH e1 AS (
  SELECT
userid,
eventtime AS step1_time,
MIN(sessionid) AS sessionid,
1 AS step1
  FROM events e1
  JOIN eventtype ON eventtype.id = e1.eventtype
  WHERE  eventtime>= DATE_ADD(str_to_date('2022-07-21','%Y-%m-%d') ,INTERVAL -30 day) AND eventtime< str_to_date('2022-07-21','%Y-%m-%d') 
    AND (eventtype.name = 'visit')
	AND eventmessage like '%check%'
  GROUP BY userid,eventtime
), e2 AS (
  SELECT
e2.userid,
MIN(e2.sessionid) AS sessionid,
1 AS step2,
MIN(eventtime) AS step2_time,
MIN(e1.step1_time) AS step1_time
  FROM events e2
  JOIN e1 ON e1.sessionid = e2.sessionid  AND eventtime > step1_time
  JOIN eventtype ON eventtype.id = e2.eventtype
  WHERE eventtime < DATE_ADD(step1_time ,INTERVAL +1 day)
    AND (eventtype.name = 'view')
	AND eventmessage like '%point%'
  GROUP BY  e2.userid
), e3 AS (
  SELECT
e3.userid,
MIN(e3.sessionid) AS sessionid,
1 AS step3,
MIN(eventtime) AS step3_time,
MIN(e2.step1_time) AS step1_time
  FROM events e3
  JOIN e2 ON e2.sessionid = e3.sessionid  AND eventtime > step2_time
  JOIN eventtype ON eventtype.id = e3.eventtype
  WHERE eventtime < DATE_ADD(step1_time ,INTERVAL +1 day)
    AND (eventtype.name = 'detail')
	AND eventmessage like '%check_point%'
  GROUP BY  e3.userid
), e4 AS (
  SELECT
e4.userid,
MIN(e4.sessionid) AS sessionid,
1 AS step4,
MIN(eventtime) AS step4_time,
MIN(e3.step1_time) AS step1_time
  FROM events e4
  JOIN e3 ON e3.sessionid = e4.sessionid  AND eventtime > step3_time
  JOIN eventtype ON eventtype.id = e4.eventtype
  WHERE eventtime < DATE_ADD(step1_time ,INTERVAL +1 day)
    AND (eventtype.name = 'login')
	AND eventmessage like '%check_p%'
  GROUP BY  e4.userid
), e5 AS (
  SELECT
e5.userid,
MIN(e5.sessionid) AS sessionid,
1 AS step5,
MIN(eventtime) AS step5_time,
MIN(e4.step1_time) AS step1_time
  FROM events e5
  JOIN e4 ON e4.sessionid = e5.sessionid  AND eventtime > step4_time
  JOIN eventtype ON eventtype.id = e5.eventtype
  WHERE eventtime < DATE_ADD(step1_time ,INTERVAL +1 day)
    AND (eventtype.name = 'cart')
	AND eventmessage like '%k_point%'
  GROUP BY  e5.userid
), e6 AS (
  SELECT
e6.userid,
MIN(e6.sessionid) AS sessionid,
1 AS step6,
MIN(eventtime) AS step6_time,
MIN(e5.step1_time) AS step1_time
  FROM events e6
  JOIN e5 ON e5.sessionid = e6.sessionid  AND eventtime > step5_time
  JOIN eventtype ON eventtype.id = e6.eventtype
  WHERE eventtime < DATE_ADD(step1_time ,INTERVAL +1 day)
    AND (eventtype.name = 'confirm')
	AND eventmessage like '%ck_point%'
  GROUP BY  e6.userid
), e7 AS (
  SELECT
e7.userid,
MIN(e7.sessionid) AS sessionid,
1 AS step7,
MIN(eventtime) AS step7_time,
MIN(e6.step1_time) AS step1_time
  FROM events e7
  JOIN e6 ON e6.sessionid = e7.sessionid  AND eventtime > step6_time
  JOIN eventtype ON eventtype.id = e7.eventtype
  WHERE eventtime < DATE_ADD(step1_time ,INTERVAL +1 day)
    AND (eventtype.name = 'pay')
	AND eventmessage like '%check_poi%'
  GROUP BY  e7.userid
)
SELECT
dt.name AS devicetype,
  COUNT(DISTINCT funnel_conversions.step7_userid) AS step7_count
FROM (
  SELECT
    e1.step1_time AS step1_time,
    e1.userid AS userid,
    e1.userid AS step1_userid,
    e2.userid AS step2_userid,  
    e3.userid AS step3_userid,
    e4.userid AS step4_userid,
    e5.userid AS step5_userid,
    e6.userid AS step6_userid,
    e7.userid AS step7_userid,
    e1.sessionid AS step1_sessionid,
    step1,
    step2,
    step3,
	step4,
	step5,
	step6,
	step7
  FROM e1
  LEFT JOIN e2 ON e1.userid=e2.userid
  LEFT JOIN e3 ON e2.userid=e3.userid
  LEFT JOIN e4 ON e3.userid=e4.userid
  LEFT JOIN e5 ON e4.userid=e5.userid
  LEFT JOIN e6 ON e5.userid=e6.userid
  LEFT JOIN e7 ON e6.userid=e7.userid
  ) funnel_conversions
LEFT JOIN sessions s ON funnel_conversions.step1_sessionid = s.id 
left join devicetype dt on s.devicetype=dt.id 
GROUP BY dt.name

StarRocks 的 SQL 和 Oracle 大致相同。由于 StartRocks 会出现前面说的错误,所以最外层只计算了七步中的一步:COUNT(DISTINCT funnel_conversions.step7_userid) AS step7_count。

进行 A 类测试的时候,只需要将各个步骤中的 eventmessage 过滤条件去掉即可。

六、 SPL 测试

SPL 代码:

1、 数据转换代码

将 devicetype 表和 eventtype 表存为集文件。SPL 代码很简单:


A

1

=file("devicetype.txt").import().new(_1:id,_2:name)

2

=file("devicetype.btx").export@b(A1)

3

=file("eventtype.txt").import().new(_1:id,_2:name)

4

=file("eventtype.btx").export@b(A3)

对于 sessions 表,将数据存成列存组表,按照第一个字段分段。

代码大致如下:


A

1

=file("sessions.txt").cursor().new(_2:userid,_1:id,_3:devicetype)

2

=file("sessions.ctx").create@p(#userid,#id,devicetype)

3

=A2.append@i(A1)

对于 events 表,将时间 eventtime 转为长整数,存成列存组表,按照第一个字段分段。

代码大致如下:


A

1

=file("events.txt").cursor().new(_1:userid,_2:sessionid,_3:eventtime,_4:id,_5:eventtype,_6:eventmessage)

2

=file("eventtype.btx").import@b().keys@i(id)

3

=A1.new(userid,sessionid,long(eventtime):eventtime,id,eventtype,eventmessage)

4

=file("events.ctx").create@p(#userid,#sessionid,#eventtime,#id,eventtype,eventmessage)

5

=A4.append@i(A3)

2、 漏斗计算代码

以 B 类七步漏斗为例,代码大致是这样的:


A

1

=eventtypes=file("eventtype.btx").import@b()

2

=devicetypes=file("devicetype.btx").import@b()

3

=long(elapse(arg_date,-30))

4

=long(arg_date)

5

=long(arg_date+1)

6

=A1.(case(name,"visit":1,"view":2,"detail":3,"login":4,"cart":5,"confirm":6,"pay":7;null))

7

=file("events.ctx").open()

8

=A7.cursor@mv(userid,sessionid,eventtime,eventtype,eventmessage;eventtime>=A3 && eventtime<A5,eventtype:A6:#)

9

=sessions=file("sessions.ctx").open().cursor@v(userid,id,devicetype;;A8)

10

=A8.joinx@m(userid:sessionid,A9:userid:id,devicetype)

11

=A10.group(userid)

12

=A11.new(~.align@a(7,eventtype):e,e(1).select(eventtime<A4 && pos(eventmessage,"check")).group@u1(eventtime):e1,e(2).select(pos(eventmessage,"point")).group@o(sessionid):e2,e(3).select(pos(eventmessage,"check_point")):e3,e(4).select(pos(eventmessage,"check_p")):e4,e(5).select(pos(eventmessage,"k_point")):e5,e(6).select(pos(eventmessage,"ck_point")):e6,e(7).select(pos(eventmessage,"check_poi")):e7)

13

=A12.derive(join@m(e1:sub_e1,sessionid;e2:sub_e2,sessionid).derive@o(sub_e2.select(eventtime>sub_e1.eventtime && eventtime<sub_e1.eventtime+86400000).min(eventtime):sub_e2_min_time).select(sub_e2_min_time) :e1_join_e2 )

14

=A13.new(e1.id(devicetype):e1_id_devicetypeno,e1_join_e2.min(sub_e1.eventtime):e1_min_time,e1_join_e2.min(sub_e2_min_time):e2_min_time,e1_join_e2.min(sub_e1.sessionid):e1_min_sessionid,e3.select(sessionid==e1_min_sessionid && eventtime>e2_min_time && eventtime<e1_min_time+86400000):e3_1,e3_1.min(eventtime):e3_min_time,e4.select(sessionid==e3_1.min(sessionid) && eventtime>e3_min_time && eventtime<e1_min_time+86400000):e4_1,e4_1.min(eventtime):e4_min_time,e5.select(sessionid==e4_1.min(sessionid) && eventtime>e4_min_time && eventtime<e1_min_time+86400000):e5_1,e5_1.min(eventtime):e5_min_time,e6.select(sessionid==e5_1.min(sessionid) && eventtime>e5_min_time && eventtime<e1_min_time+86400000):e6_1,e6_1.min(eventtime):e6_min_time,e7.select(sessionid==e6_1.min(sessionid) && eventtime>e6_min_time && eventtime<e1_min_time+86400000):e7_1,e7_1.min(eventtime):e7_min_time)

15

=A14.news(e1_id_devicetypeno;~:devicetype,e2_min_time,e3_min_time,e4_min_time,e5_min_time,e6_min_time,e7_min_time)

16

=A15.groups(devicetype;count(1):STEP1_COUNT,count(e2_min_time):STEP2_COUNT,count(e3_min_time):STEP3_COUNT,count(e4_min_time):STEP4_COUNT,count(e5_min_time):STEP5_COUNT,count(e6_min_time):STEP6_COUNT,count(e7_min_time):STEP7_COUNT)

17

=A16.new(devicetypes(devicetype).name:DEVICETYPE,STEP1_COUNT,STEP2_COUNT,STEP3_COUNT,STEP4_COUNT,STEP5_COUNT,STEP6_COUNT,STEP7_COUNT,STEP7_COUNT/STEP1_COUNT:STEP7_RATE)

传入参数 arg_date 是日期,例如 2023-03-21。

A3-A5:对参数 arg_date 求前 30 天、1 天后的日期,三者都转为长整数 long。

A6:把序表 eventtypes 中,计算用到的七种类型依次设置为 1-7,其他都是 null。

A8:events 过滤出七种类型的事件,日期要多取出一天。建立多路的列式游标。

A10-A11:event 和 session 两个列式游标有序归并,按照 userid 分组。

A12:每个 userid 分组内,eventtype 按照 1,2,3,…,7 对齐分组,分别依次对应 7 个事件类型。e1 到 e7 中的过滤条件中都有包含字符子串的判断。

A13:e1 和 e2 按照 sessionid 有序归并,结果计算出满足条件的 eventtime 最小值。过滤出最小值不为空的。

A14:e1 对 devicetype 去重,且将 e1 和 e2 归并的结果求 eventtime 最小值和 sessionid 最小值。从 e3 开始过滤出符合 sessionid 和时间条件的记录,也就是 e3 到 e7 中符合条件的 event。

A15:在 A14 游标基础上计算去重后的 devicetype,求 e2 到 e7 的时间最小值,合并到原游标。

A16:对 A15 做小结果集分组汇总,分组字段是 devicetype,每组求各个步骤的计数值。

A17:将 A16 中的序表中的 devicetype 从序号转化为名称。

进行 A 类测试的时候,只要将 A12 中 eventmessage 的过滤条件去掉即可。

七、 测试结果

一、A 类测试(无 eventmessage 条件):

三步漏斗

VM1:32G

VM2:16G

VM3:8G

VM4:6G

VM5:4G

SPL

27

26

28

26

28

StarRocks

53

51

62

64

溢出

Oracle

418

-

-

-

-


七步漏斗

VM1:32G

VM2:16G

VM3:8G

VM4:6G

VM5:4G

SPL

30

32

28

33

34

StarRocks

122

114

152

155

溢出

Oracle

1827

-

-

-

-

二、B 类测试(有 eventmessage 条件):

三步漏斗

VM1:32G

VM2:16G

VM3:8G

VM4:6G

SPL

35

39

39

52

StarRocks

210

210

192

溢出


七步漏斗

VM1:32G

VM2:16G

VM3:8G

VM4:6G

SPL

46

61

52

92

StarRocks

510

520

溢出

溢出

三、说明:

1、 表中数值是计算所用时间,单位是秒。

2、Oracle 计算时间较长,所以仅测试了 A 类的 VM1。

3、StarRocks 在计算两步以上的时候会出现错误,所以只能计算一步。

八、 结果点评

1、 SPL 计算漏斗统计的性能最好,StarRocks 较差,Oracle 最慢。SPL 计算速度比 StarRocks 快 2 到 5 倍,比 Oracle 快 15 到 60 倍。

2、 A 类测试中漏斗计算的步骤从 3 步变 7 步时,StarRocks 计算速度降低了近 2 倍。SPL 变化则不明显。这是因为 SQL 前后步骤之间要按照 sessionid 或 userid 做 JOIN 计算,漏斗步数越多 JOIN 计算的次数越多,性能自然会相应的下降。SPL 则是将相同的 userid 和 sessionid 放在一起算,避免了反复的 JOIN。

3、 其他条件相同时,B 类计算普遍要比 A 类慢。但相比之下,SPL 差距小,StarRocks 差距大。StarRocks 的向量式计算能利用 CPU 特性提速,但只对 int/long 这种简单数据类型有效,对字符串这种复杂数据类型无效,涉及到字符串计算时性能会较大幅度下降。

4、 StarRocks 限制了内存上限(物理内存的 80%),在虚拟机内存减少时会出现内存溢出错误,无法计算出结果。因为 SQL 的 JOIN 是内存计算,需要消耗更多的内存。特别是 B 类测试有字符串参与计算,内存占用更大,所以更容易出现溢出错误。而 SPL 避免了反复 JOIN,可以用很少的内存正常运算出结果。

5、 测试结论:

SPL 可以利用有序存储,非常适合用于漏斗统计这种用户行为分析计算:总数据量很大,但每个 userid 的数据量并不大。按照 userid 和 sessionid 有序存储数据,不仅可以获得极致性能,而且代码更简洁。

漏斗统计相当复杂,勉强用 SQL 写出来,也只能用反复 JOIN 的办法,用老牌数据库 Oracle 还能得到正确结果,但是性能很差。StarRocks 出现时间较短,面对这样的复杂 SQL 直接报错了,仅计算一步的时候可以跑,但性能仍然远远不如 SPL,如果计算多步,计算速度会更慢了。