StarRocks 中关于 Hadoop Hedged Read 性能测试

Hedged Read 简介

If a read from a block is slow, start up another parallel, ‘hedged’ read against a different block replica. We then take the result of which ever read returns first (the outstanding read is cancelled). This ‘hedged’ read feature will help rein in the outliers, the odd read that takes a long time because it hit a bad patch on the disc, etc.

This feature is off by default. To enable this feature, set dfs.client.hedged.read.threadpool.size to a positive number. The threadpool size is how many threads to dedicate to the running of these ‘hedged’, concurrent reads in your client.

Then set dfs.client.hedged.read.threshold.millis to the number of milliseconds to wait before starting up a ‘hedged’ read. For example, if you set this property to 10, then if a read has not returned within 10 milliseconds, we will start up a new read against a different block replica.

This feature emits new metrics(几个新指标参数介绍):

  • hedgedReadOps – 发起 hedged read 操作的次数
  • hedgeReadOpsWin – how many times the hedged read ‘beat’ the original read
  • hedgedReadOpsInCurThread – how many times we went to do a hedged read but we had to run it in the current thread because dfs.client.hedged.read.threadpool.size was at a maximum.

hdfs-site.xml 配置参数模版

<configuration> 
  <property>
     <name>dfs.client.hedged.read.threadpool.size</name>
     <value>128</value>
   </property>
   <property>
     <name>dfs.client.hedged.read.threshold.millis</name>
     <value>2000</value>
   </property>
</configuration>  

测试环境

就跑 100G 的 TPCH Parquet 测试,数据存储在 emr 的 hdfs 上面,2 副本,以此避免数据全部都短路读,不会涉及 network io。(EMR 有 3 个 worker 节点)。

StarRocks 配置:1 FE + 2 BE

通过分别调整 dfs.client.hedged.read.threadpool.sizedfs.client.hedged.read.threshold.millis 来对 hedged read 进行测试,

所有 SQL 会跑 5 次。第一次是预热,不算。后面 4 次取平均值。

内网 starrocks ping emr 集群,平均延迟只有 0.6 ms。

实验结果单位为毫秒。

单线程查询测试

关闭 hedged read-1关闭 hedged read-2关闭 hedged read-3关闭 hedged read-4128线程/5ms128线程/50ms128线程/500ms128线程/1000ms128线程/2000ms32线程/3000ms1024线程/2500ms
q147804923483648085582536350715252495551825280
q210331104104810351160105510731081106711501067
q337323936378236704883398137193727382340273867
q419331946193118762237197819591998199719892039
q543874335434443175400475645524465478143484423
q620581990207919942624218220362039205320462040
q745734805451245515634495746244649459048864664
q838663804382937574845437138363890394039003880
q978348068786279629397836079187977793181018004
q1043784570445744724910474046314617460246174636
q11692706696720809704715706724741716
q1221262213219421722633220922102202226423752224
q1336753883370736954346392736593706375539683692
q1424002433239624333538275225212514252224772552
q1523872389238623793372270824802486251524742474
q1614391562143113941569141614181435143415101469
q1722852317229023103070267424372391236124132453
q1879608243804379998394821178687868793382808078
q1924742478249925303567284126392544261025542584
q2034683481346834754415373837023694361037103647
q2165656886660866588484796967886728684770396871
q2211071169113611341247120911261136121511831136
合计7515277241755347534192116821017698277105775297897077796

可以看出开启 hedged read 是会对性能有影响。但是开启 hedged read 后,只要参数设置的不是特别离谱,影响没那么大。

单线程查询 + 慢节点

我使得一个 HDFS 节点成为慢节点,所有请求随机延迟 0~10ms 之间。该 HDFS 节点上面没有 be 运行的。

随机延迟命令

sudo tc qdisc add dev eth0 root netem delay 5ms 5ms 25%

删除延迟

sudo tc qdisc del dev eth0 root netem

关闭 hedged read128线程/1000ms128线程/3000ms
q133747558913238
q231971666116145
q334164819416634
q41932047709585
q533831715213059
q629244492512697
q739533879618245
q847474862820814
q9692171139625086
q10617441013520748
q1122737567613089
q1231417826819600
q1355564596112001
q1440934545413282
q1538234662015216
q161807936957752
q1744604526010740
q18485061452522113
q1938080619014866
q2040122778613278
q21471911534828810
q222498340899058
合计850696165118346056

其实只要一旦存在慢节点,hedged read 一定是正优化的,只是多少的问题。

三并发,BE CPU 全部打满的情况

关闭 hedged read32 线程/2000ms128线程/1000ms128线程/2000ms
q114129144991454614553
q21275138313801332
q39158955396389439
q45112494153005057
q511681118991174811658
q65836600559935948
q712007123991234912308
q810794112021121610464
q919911198141926919397
q1010424105121065510591
q111427151215221496
q125136504853014946
q139357964296299550
q146485687067796863
q156762701771007064
q163369277627072447
q176028636765186466
q1823411234092332524066
q197312751676117537
q207127747678277685
q2118357192851962919313
q221952194019702007
合计197050201065202012200187

从结果上看起来貌似查询并发和 hedged read 之间并没有太大的影响。

总结

线程池的影响

Hedged read 是和 DFSClient 绑定在一起,说明一个 Hdfs client 会有一个 hedged read 线程池。然后那个线程池是 ThreadPoolExecutor

Hadoop FileSystem 里面会自带一个 cache,他的 cache key 是:

  static class Key {
    final String scheme;
    final String authority;
    final UserGroupInformation ugi;
    final long unique;   // an artificial way to make a key unique

    Key(URI uri, Configuration conf) throws IOException {
      this(uri, conf, 0);
    }

    Key(URI uri, Configuration conf, long unique) throws IOException {
      scheme = uri.getScheme()==null ?
          "" : StringUtils.toLowerCase(uri.getScheme());
      authority = uri.getAuthority()==null ?
          "" : StringUtils.toLowerCase(uri.getAuthority());
      this.unique = unique;

      this.ugi = UserGroupInformation.getCurrentUser();
    }

    @Override
    public int hashCode() {
      return (scheme + authority).hashCode() + ugi.hashCode() + (int)unique;
    }

    static boolean isEqual(Object a, Object b) {
      return a == b || (a != null && a.equals(b));
    }

    @Override
    public boolean equals(Object obj) {
      if (obj == this) {
        return true;
      }
      if (obj instanceof Key) {
        Key that = (Key)obj;
        return isEqual(this.scheme, that.scheme)
               && isEqual(this.authority, that.authority)
               && isEqual(this.ugi, that.ugi)
               && (this.unique == that.unique);
      }
      return false;
    }

    @Override
    public String toString() {
      return "("+ugi.toString() + ")@" + scheme + "://" + authority;
    }
  }
}

可以看出来,这个 CacheKey 主要是根据 schemaauthority 进行计算。这意味着在一个 BE 中,所有针对同一个 HDFS 集群的请求,都是用的同一个 DFSClient,也就是用着同一个 hedged read 线程池。

image-20230706200640477

从注释上可以看出,这个线程池是动态扩缩容的,不会存在占着茅坑不拉屎。

image-20230706201001720

创建 hedged read 线程池时候,这个 num 是我们配置的线程池大小,可以看出当线程 idle 超过 60s 后,就会被终止。

我自己也用 jstack 确认过,当没有 query 时,jstack 是打不出 hedged read 的线程。

推荐值

这里推荐给 dfs.client.hedged.read.threshold.millis 设置一个较大的值,比如 3000ms,这样能够有效避免极端情况。然后如果用户的 HDFS 正常的话,也不会发起 hedged read,使得数据多读。

关于 dfs.client.hedged.read.threadpool.size 的大小,实验上看不出 32/128 之间的影响,故这里推荐 128。

原创文章,作者:Smith,如若转载,请注明出处:https://www.inlighting.org/archives/starrocks-hadoop-hedged-read-benchmark

打赏 微信扫一扫 微信扫一扫
SmithSmith
上一篇 2023年4月9日 上午10:45
下一篇 2023年9月6日 下午11:09

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

评论列表(2条)

  • […] Hedged Read 引入 StarRocks 的效果,见 StarRocks 中关于 Hadoop Hedged Read 性能测试 这篇文章。当时得出来的结论是在存在慢节点的情况下,hedged read […]

  • Danny
    Danny 2023年8月8日 下午10:25

    学到了