实时小白一枚,在线求更加强大和方便的工具
需求
对 hudi 进行 upsert 压测,简单的链路为 flink 消费 kafka 直接灌入 hudi 表,需要构造 10 亿条数据,可控制 insert 和 update 的配比
YCSB 调研
引言:熟悉 hudi 的伙伴应该了解到,recordKey 类似于主键是 hudi 的一级公民,通过 recordKey 可以快速定位到需要 update 的数据文件而不用重写所在分区下的所有数据文件。类比于 key-value 的存储,第一时间想到了 HBase,就从 HBase 伙伴了解到了压力测试工具:YCSB
Yahoo! Cloud Serving Benchmark : The goal of the YCSB project is to develop a framework and common set of workloads for evaluating the performance of different “key-value” and “cloud” serving stores
雅虎提供的客户端测试框架,用于评估不同的 key-value 存储和云服务的性能。根据配置文件,自动化构造数据对 db 进行 insert、update、delete、scan、read 压力测试。而本文重点关注构造数据部分
功能速看
两种模式
- load : 数据初始化,所有的 operator 都是 insert
- run : 客户端运行压测,operator 包括 read、scan、insert、update、delete(良好的设计方便后期扩展 kafka db)
关键配置
- writeallfields : 对于 update 操作对应生成的数据是否包含所有字段。根据选择可以灵活配置 hudi 的 PayLoad
- insertorder : 对生成的 key 是以非 hash 方式生成,对生成 update 的 key 非常重要
- requestdistribution : update 数据的 key 的分布方式,后面使用 sequential ,自增 id 的方式
- threadcount : 并发数,启动多少个线程并发生成数据
- fieldcount : 生成的列个数(默认名称 field0, field1...)
- zeropadding : 占位符个数,比如首个 key 是 user1,如果设置个数为5,那么 key 就是 user00001
案例 case
所有测试的基本配置如下 workload:
workload=site.ycsb.workloads.CoreWorkload
readallfields=true
insertorder=ordered
requestdistribution=sequential
threadcount=1
writeallfields=true
fieldcount=7
zeropadding=10
case 1:load 模式
bin/ycsb.sh load basic \ # basic 是默认 db,将结果打印到 终端,可选 redis、mongodb、hbase 等
-P ./workload \ # 指向基本配置文件
-p static_col.dt=20220101 \ # 扩展的字段,暂时不用考虑
-p static_col.ht=02 \ # 扩展的字段,暂时不用考虑
-p insertstart=0 \ # key 的起始 id,同上面的 workload 配置,这里优先级最高
-p insertcount=2 # 总共插入两条
# 结果如下,写入两条数据,分别是 INSERT op,key 为 user0000000000 和 user0000000001,从 0 开始自增 到 1,然后是统计数据,吞吐量/s,耗时等
Loading workload...
Starting test.
***************** properties *****************
"writeallfields"="true"
"fieldcount"="7"
"insertcount"="2"
"threadcount"="1"
"readallfields"="true"
"dotransactions"="false"
"static_col.ht"="02"
"requestdistribution"="sequential"
"workload"="site.ycsb.workloads.CoreWorkload"
"zeropadding"="10"
"insertstart"="0"
"static_col.dt"="20220101"
"insertorder"="ordered"
"db"="site.ycsb.BasicDB"
**********************************************
DBWrapper: report latency for each error is false and specific error codes to track for latency are: []
INSERT usertable user0000000000 [ field1=2-(4]m9+n7G{;[s:C-350=F36@/=Om8@)5O1$A38&z"4Mg+02Rm>'b7Nq;V=85~/.(.Ak>(f'*,524"*&4 z field0=,X%=_m<>&5@1+]=9F)7Qq7'<':49':51b65>.;*85h Es#_q0'd;S7$S)0Fe9+f?3t5[+9&r @i'>n/@q35l6Qy&G/:Jw/N{5Eo? field6=3!>5O-*364,*9R!1T#?6:![o7Vw+E35[w''.;000Go<[?43>?$40Nu7U;$Mc=\k2*f!Ps&W/-N! ),=O)98d:X99&x$M/59z#:~> field3=:E? &x6"($',(2p3Uy-""4;r-Bm>Q1*>9:."H1$_y*+|+2>1Ms+Zy/0,5S#$9<$Bo;;$1?~1M'%D5)#f>Ta/$6 [i5=0<)f3]{*< ?M-05(=Dc?]-,Ue+I7!>f?N5)\y6 field5=;X=%/~7-x&^q.880 t))~.V=0Ri23x;Hc'_a8E1=Ke;;<<9n-Ma2$.(Dw+O-<]!4706?z'J#6K%9@u/2l+-(-841$-7&<-t>*8$'f>F#8\u>Fu3%x2-r#Tk3F/59p"&29B9 N}5_w'Fo>B5S;0 ,C/)*$7V5(A)&0&9=d(#(-0z#+f+G#.=2$Po9%( >f,Bg/=<-+ 0M/0)$8 field6=6Aw2/`0+p>%.6Bo/I{:1.,6`/N7)92-*61P!3"0-_w$E.Sy0/`68z,:l6\7;,2 J!4@s3=8>Aq>=z:Ni/=f4R+4Gk$Ng"[k;".0 field3=,M% Hw%W6U=/\y'D}&?&$T-6<<9;,=*p3]#.Yo6O'3"&1!&=U9=:l Wg&9`%$|;=4)&b>O&9<50>6G}?I5!$n&=f%Q{*^!$W{> field2=9G-)1p'Sg&; 3!h7Fq4Qw:C1'Rw:;$'Gy64,>)~;Is8<2<&`<\y*;l?H5)R{&)85Uq=Nm>%`=[i'Ey9J{+]=:P#%[)-2~&Lc;Q+0 field5=+#40^q'.z?G;+(|'Gs.Wy/Iq;:"( l75> G97 ,4:h:S55T7#9(,%:$9(5V-))z#J+1"j&9x48~7%f%Wo>@-$Py7%z,58-;$$Xo2 field4=?!r*Hk.Lc'^;&F3-[q$2t#<""..(.<2)$,H?:0<$-")Wu2Q93T; _=82v'&~?=&0V!1 l) &
case 2:run 模式
构造 upsert 数据方式,测试的方式是先 load,再 run,比如 case 1 load 了 user0000000000 和 user0000000001 两个 key 的数据,在 run 模式中,重复写入这两条作为更新数据,并且也同时 insert 新 key 的数据从 2 开始
注意:下面的 case 是上自增序列下的时候调研出来的参数含义,并不代表一定适合其它的配置
bin/ycsb.sh run basic \ # 使用 run 模式,包括 insert、update、scan 等
-P ./workload \ # 指向基本配置文件
-p operationcount=4 \ # insert + update + scan 等总共触发 4 次
-p recordcount=2 \ # insert key 的起始 id 为 2
-p insertstart=0 \ # update key 的起始 id 为 0 (为 case 1 已经初始化的范围)
-p insertcount=2 \ # update key 的最大值不超过 2,即为 0 和 1
-p readproportion=0 \ # 没有读操作
-p updateproportion=0.5 \ # 更新操作占 50%
-p scanproportion=0 \ # 没有扫描操作
-p insertproportion=0.5 # 插入操作占 50%
# 结果如下
# insert 和 update 产生的最终条数是近似 1:1,不是严格的
# update 数据的 key 从 user0000000000 ~ user0000000001
# insert 数据的 key 从 user0000000002 ~ user0000000003
Loading workload...
Starting test.
***************** properties *****************
"insertorder"="ordered"
"updateproportion"="0.5"
"scanproportion"="0"
"writeallfields"="true"
"threadcount"="1"
"operationcount"="2"
"zeropadding"="10"
"readallfields"="true"
"requestdistribution"="sequential"
"dotransactions"="true"
"insertproportion"="0.5"
"insertstart"="0"
"workload"="site.ycsb.workloads.CoreWorkload"
"insertcount"="1"
"readproportion"="0"
"fieldcount"="7"
"recordcount"="1"
"db"="site.ycsb.BasicDB"
**********************************************
DBWrapper: report latency for each error is false and specific error codes to track for latency are: []
UPDATE usertable user0000000000 [ field1=(G5)P-.;~#A3%.`"P!/&j7O;?+ )8<42n?Qc3X{2Ws:,:0F3Q5!^a Uk'T9*V}?!vC{57z2@)=3n"L7*_5(0b-1"!De.-n20t,U%5 : Ig'Sm(Rc+E?:(b2B}%P94V$3z.4j8)v:Ke,%2,Uy9M192d-z:969 field6=*>N/44`&F)8;2!7l87b:]'8?>%Es43z"$r!'l+>t!7t+<`6_;000=?t+K &% field2=6T?=_s')b?Ng!4z/V{,$t&H?=?$=S12+$!I19%<97` &8(!|/*j#Ja0O3V1.U)42t:+,! field5=!((8Ak71>0&h!2j>2p&(|"Tk(G)!Ug'I7<#p4/2*+Ec#(H{.,0!"~50d#V):Bg'Ua774,&b)C+51.!(|>8b'&`*;b53 %R"V#$Kw';~6 ]
INSERT usertable user0000000001 [ field1=6^e.Q5/<&1-"3Qg/10$Fe3,.)W19Ho'W{&>t#4b3=p'!~?9l/6* >02_s2R#.3b/T16I;%=8:;d3N14:l>Ym3%$(Jo<5G942(\q6Tw>P?*,&t=-$1S)x#Ia(Eu*9~)8*1 field3==8h*C=>Va2:<1',(Z=2Z99;.5=r**j3Ra"&.'W1)M [a#/4=Z+-Py91x#Yg'8v#S;9S+9Mm3H{3@k9Q?5.,*2*-\9("8$"f,\c? field2=3Gm"7>3;*=Xs>6p*Di':t/Ny.9<0Ca"Sk&%b2O-1Nw<5&0Xq3.6%])7Wy+<6:J{?# 7'&(),?Mi'\g/">8])1<~.Is0Gw73<27n3 field5=$;>=@;71t&]1?Ao#X9C#5)b>@u#]/8920Y/-P=1Xa94:-Lk7A-%Ey3_)$;(. >0$4%!<$9>/I#$Ra:2p;5>6!h#X=>Vg7L9< field4=2*6%,`$^99,.7Q58I98&r"(&4S?52:5%l.^}5F+)"4/R)1X(Xu#Hm>'n*V#5Um67.:Z5):b&%p*3t$S+9/~ 60%.(;<`&Ew(S5$ ]
[OVERALL], RunTime(ms), 9
[OVERALL], Throughput(ops/sec), 222.22222222222223
[TOTAL_GCS_PS_Scavenge], Count, 0
[TOTAL_GC_TIME_PS_Scavenge], Time(ms), 0
[TOTAL_GC_TIME_%_PS_Scavenge], Time(%), 0.0
[TOTAL_GCS_PS_MarkSweep], Count, 0
[TOTAL_GC_TIME_PS_MarkSweep], Time(ms), 0
[TOTAL_GC_TIME_%_PS_MarkSweep], Time(%), 0.0
[TOTAL_GCs], Count, 0
[TOTAL_GC_TIME], Time(ms), 0
[TOTAL_GC_TIME_%], Time(%), 0.0
[CLEANUP], Operations, 1
[CLEANUP], AverageLatency(us), 1.0
[CLEANUP], MinLatency(us), 1
[CLEANUP], MaxLatency(us), 1
[CLEANUP], 95thPercentileLatency(us), 1
[CLEANUP], 99thPercentileLatency(us), 1
[UPDATE], Operations, 1
[UPDATE], AverageLatency(us), 526.0
[UPDATE], MinLatency(us), 526
[UPDATE], MaxLatency(us), 526
[UPDATE], 95thPercentileLatency(us), 526
[UPDATE], 99thPercentileLatency(us), 526
[UPDATE], Return=OK, 1
[INSERT], Operations, 1
[INSERT], AverageLatency(us), 177.0
[INSERT], MinLatency(us), 177
[INSERT], MaxLatency(us), 177
[INSERT], 95thPercentileLatency(us), 177
[INSERT], 99thPercentileLatency(us), 177
[INSERT], Return=OK, 1
总结
功能上,把 insert 操作作为 insert 数据,update 操作用之前写入过的 key 作为主键,就可以达到需要的效果,并且使用自增 id 的方式,可以不用缓存之前写入过的 key。并且可以灵活的设置 insert 和 update 的配比
但是自增 id 也有缺点,在并发条件下,自增 id 的并发问题就会导致其压测能力不会随着线程数增加而等比上升。所以如果没有这种需求,可以设置 hash key 而不是自增 key
完结但不全完结
懒癌患者比较难从 0 到 1 写个测试工具,做完以上调研,数据是有了,下篇,将介绍如何基于 ycsb 扩展 db,也不算 db,将魔爪伸向消息中间件 kafka,回到最初的需求(其实也比较简单)
欢迎关注公众号: