Presto System load过高问题调研

背景:

我们Presto有个集群,每6.5天会出现System load过高问题,这个集群有个特点,只服务于一个业务方,且SQL基本相似。如图所示:Sys load很高(20-40%),严重影响查询性能

业务SQL查询时间表现为:

ScanFilterAndProjectOperator(Source Stage)阶段有机器有明显的长尾现象,比如20台机器,正常这个Operator执行时间只需要1S,但是有几台机器会耗时几分钟。而重启服务后,查询恢复正常。

先jstack看下Presto在干啥:

如图所示,Presto通过ScanFilterAndProjectOperator类执行filter过滤,其中这个filter方法是Codegen生成。

pstack查看下线程栈:

看到程序主要CPU浪费在Deoptimization::uncommon_trap里了。为什么呢?再说这个问题之前,我们先说下JIT。

JIT:

为了提高热点代码(Hot Spot Code)的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(JIT)。

“热点代码”两类:

  • 被多次调用的方法

  • 被多次执行的循环体 – 尽管编译动作是由循环体所触发的,但编译器依然会以整个方法(而不是单独的循环体)作为编译对象。编译器根据概率选择一些大多数时候都能提升运行速度的优化手段,当优化的假设不成立,出现“罕见陷阱”(Uncommon Trap)时可以通过逆优化(Deoptimization)退回到解释状态继续执行。

如:

 
  1. static void test(Object input) {

  2. if (input == null) {

  3. return;

  4. }

  5. // do something

  6. }

如果input一直不为空,执行1W次时,那么上述代码将优化为(类似GCC的 -O2优化算法):

 
  1. static void test(Object input) {

  2. // do something

  3. }

但是如果之后出现input为空,那么将会触发Uncommon Trap,通过逆优化(Deoptimization)退回到解释状态继续执行。所以触发逆优化是正常的,但是我们的问题是程序一直在做Uncommon Trap,做了很多无用功,这是不正常的。

查阅JVM源码 https://github.com/MuniyappanV/jdk-source-code/blob/master/jdk6u21_src/hotspot/src/share/vm/opto/compile.cpp#L2694,发现有参数可以控制可编译次数:

 
  1. bool Compile::too_many_recompiles(ciMethod* method,

  2. int bci,

  3. Deoptimization::DeoptReason reason) {

  4.  

  5. // Pick a cutoff point well within PerBytecodeRecompilationCutoff.

  6. uint bc_cutoff = (uint) PerBytecodeRecompilationCutoff / 8;

  7. uint m_cutoff = (uint) PerMethodRecompilationCutoff / 2 + 1; // not zero

  8. Deoptimization::DeoptReason per_bc_reason

  9. = Deoptimization::reason_recorded_per_bytecode_if_any(reason);

  10.  

  11. if ((per_bc_reason == Deoptimization::Reason_none

  12. || md->has_trap_at(bci, reason) != 0)

  13. // The trap frequency measure we care about is the recompile count:

  14. && md->trap_recompiled_at(bci)

  15. && md->overflow_recompile_count() >= bc_cutoff) {

  16. // Do not emit a trap here if it has already caused recompilations.

  17. // Also, if there are multiple reasons, or if there is no per-BCI record,

  18. // assume the worst.

  19. if (log())

  20. log()->elem("observe trap='%s recompiled' count='%d' recompiles2='%d'",

  21. Deoptimization::trap_reason_name(reason),

  22. md->trap_count(reason),

  23. md->overflow_recompile_count());

  24. return true;

  25. } else if (trap_count(reason) != 0

  26. && decompile_count() >= m_cutoff) {

  27. // Too many recompiles globally, and we have seen this sort of trap.

  28. // Use cumulative decompile_count, not just md->decompile_count.

  29.  

  30. if (log())

  31. log()->elem("observe trap='%s' count='%d' mcount='%d' decompiles='%d' mdecompiles='%d'",

  32. Deoptimization::trap_reason_name(reason),

  33. md->trap_count(reason), trap_count(reason),

  34. md->decompile_count(), decompile_count());

  35.  

  36. return true;

  37. } else {

  38. // The coast is clear.

  39. return false;

  40. }

  41. }

PerMethodRecompilationCutoff 可以控制recompiles次数,触发uncommontrap时,且too_many_recompiles为true时,其行为为Deoptimization::Action_none。https://github.com/MuniyappanV/jdk-source-code/blob/master/jdk6u21_src/hotspot/src/share/vm/opto/graphKit.cpp#L1807

 
  1. void GraphKit::uncommon_trap(int trap_request,

  2. ciKlass* klass, const char* comment,

  3. bool must_throw,

  4. bool keep_exact_action) {

  5. Deoptimization::DeoptReason reason = Deoptimization::trap_request_reason(trap_request);

  6. Deoptimization::DeoptAction action = Deoptimization::trap_request_action(trap_request);

  7.  

  8. switch (action) {

  9. case Deoptimization::Action_maybe_recompile:

  10. case Deoptimization::Action_reinterpret:

  11. // Temporary fix for 6529811 to allow virtual calls to be sure they

  12. // get the chance to go from mono->bi->mega

  13. if (!keep_exact_action &&

  14. Deoptimization::trap_request_index(trap_request) < 0 &&

  15. too_many_recompiles(reason)) {

  16. // This BCI is causing too many recompilations.

  17. action = Deoptimization::Action_none;

  18. trap_request = Deoptimization::make_trap_request(reason, action);

  19. } else {

  20. C->set_trap_can_recompile(true);

  21. }

  22. break;

  23.  

  24. case Deoptimization::Action_make_not_entrant:

  25. C->set_trap_can_recompile(true);

  26. break;

我们写个测试程序,测试PerMethodRecompilationCutoff 参数,参数超过recompile次数时性能表现:

 
  1. public class DeoptimizationTest

  2. {

  3. private DeoptimizationTest() {}

  4. static int someComplicatedFunction(double x)

  5. {

  6. return (int) (Math.pow(x, x) + Math.log(x) + Math.sqrt(x));

  7. }

  8. static void hotMethod(int iteration)

  9. {

  10. if (iteration < 20) {

  11. someComplicatedFunction(1.23);

  12. }

  13. else if (iteration < 40) {

  14. someComplicatedFunction(1.23);

  15. }

  16. else if (iteration < 60) {

  17. someComplicatedFunction(1.23);

  18. }

  19. else if (iteration < 80) {

  20. someComplicatedFunction(1.23);

  21. }

  22. else {

  23. someComplicatedFunction(1.23);

  24. }

  25. }

  26. static void hotMethodWrapper(int iteration)

  27. {

  28. int count = 100_000;

  29. for (int i = 0; i < count; i++) {

  30. hotMethod(iteration);

  31. }

  32. }

  33. public static void main(String[] args)

  34. {

  35. for (int k = 0; k < 100; k++) {

  36. long start = System.nanoTime();

  37. hotMethodWrapper(k + 1);

  38. System.out.println("iteration " + k + ": " + (System.nanoTime() - start) / 1_000_000 + "ms");

  39. }

  40. }

  41. }

执行代码:javac DeoptimizationTest.java && java -XX:PerMethodRecompilationCutoff=3 DeoptimizationTest

指定PerMethodRecompilationCutoff为3,意思是PerMethodRecompilationCutoff/2 + 1 = 2,那么第三个分支将触发大量Deoptimization::Action_none操作,如上图所示,发现第59次迭代时,查询变慢了接近600倍。

火焰图见下图,可以看到耗时与我们Presto遇到的问题非常相似,耗时主要在Deoptimization::uncommon_trap

解决方案:

这个问题是个JVM Bug导致的,既然当达到阈值会触发Deoptimization::Action_none,那么最简单的解决方法是不让他达到阈值就好了。修改JVM参数:

-XX:PerMethodRecompilationCutoff -XX:PerBytecodeRecompilationCutoff

这2个值尽量调大一些,比如2000和500以上。

线上我找了一台机器测试了一周,CPU表现为:

因为这个问题,应该是个JVM Bug,社区找了下关键词,发现JDK 11.0.6 好像解了这个问题,但是这个版本还没有release,无法验证,等版本发布后再验证下。

https://bugs.java.com/bugdatabase/viewbug.do?bugid=8227523

参考链接

  • https://www.slideshare.net/dougqh/jvm-mechanics-when-does-the

  • 社区wsmithril网友提供的资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值