Hadoop背后的数学

自从Nathan Marz同学写了那篇著名的How to beat the CAP theorem的Blog,以及Storm发布之后,俨然成为了技术界新偶像。顺着他本人的blog,翻了一下他过去几年的写的技术文章,发现老美的牛人们都爱总结,能够把技术实践提升到理论高度,然后抽象出新的设计和产品,比起我等只能每天苦逼苦逼应对实际需求的人来说,还是强出很多。我自己觉得那么多年,虽然觉得做什么都能做了,但是现在还是只能谈到模仿,做不到提炼总结和超越。

和著名的CAP的那篇Blog,Nathan Marz还有很多其他非常有料值得一读的博客,与其在国内的各种Hadoop大会上听大家泛泛而谈架构,还不如多看看老外们总结下来的技术博呢,比如这篇The mathematics behind Hadoop-based systems,比起大家简单列列机器数字来说,有价值得多。

我曾经写过,数学对于称为一名优秀的程序员是很重要的,但这不一定意味着一定要是多高深的数学。Nathan的这篇Blog用到的数学只有初中难度,但是的确写明白了我们遇到的很多Estimation以及集群数量预估的实际问题,我想一定也有很多人用hadoop但是和我们一样,对于很多机器数量或者workflow的管理是很粗放的,相信这篇blog对很多人都会有用,所以把它翻译成中文,全文如下


基于Hadoop的系统背后的数学

我希望一年之前我就知道这些。现在,根据一些简单的数学,我就可以回答:

  • 为什么在我将处理能力翻倍之后,我的workflow的速度没有翻倍?
  • 为什么10%的任务失败率会导致我的运行时间上升到原来的300%?
  • 如果通过优化了我的workflow的运行时间的30%导致运行时间下降了80%?
  • 我的集群中到底需要多少机器可以满足性能和容错的需要?

所有这些问题都可以通过这样一个简单的等式干净地回答:

运行时间(Runtime) = 额外开销(Overhead) / (1-{处理一小时数据需要的时间})

我们很快就可以推导出这个等式。首先,让我们简单地讨论一下什么是我所说的“基于Hadoop的系统”1。Hadoop的一个常见的用例,就是通过运行一个workflow来处理来连续进入的流数据。这个工作流运行一个”while(true)”的循环,然后每个workflow的迭代,都处理自从上个迭代依赖累积的数据。

下面的这些分析的灵感可以在一个简单的示例中概括出来。假如说你有一个workflow需要运行12小时,然后它会在每个迭代中处理12个小时的数据。然后假如说你加强了这个workflow去做一些额外的分析,然后你估计你在当前的这个workflow中需要多花两个小时来处理。然后麻烦来了。你的workflow增加的运行时间可能远超过两小时。它可能增加了10个小时,100个小时,或者这个workflow可能呈螺旋式上升地在每个迭代中花费越来越多的时间知道无限。

为什么?

问题在于,你将处理12个小时数据的workflow的运行时间增加到了14个小时。这意味着当下一次workflow运行的时候,有14小时的数据需要处理。既然下一个迭代有更多的数据,他要花费更多时间运行。这意味着下一个迭代会有更多的数据,等等。

为了确定什么时候运行时间,让我们做一些简单的算术。首先,让我们写下一个只有一个迭代的workflow的运行时间的等式

运行时间 = 额外开销 + {处理一小时数据的时间} * {多少小时的数据}

额外开销(Overhead)指的是花费在workflow上的任何常数项时间。例如,开始一个job需要的时间会计入额外开销。使用distributed cache来分发文件的时间会计入额外开销。你会把任何独立于数据规模的时间花费放到“额外开销”的分类中去。

“处理一小时数据的时间”指的是workflow中的动态时间。这是你忽视那些额外开销,花费在处理实际数据上的时间。如何一小时的数据会增加你半小时的运行时间上,这个值应该是0.5。如果一小时的数据会增加你一小时的运行时间,这是值就是2。

为了简洁,让我们把上面那个等式通过变量来重新写一下:

T = O + P * H

为了确定workflow的稳定的运行时间,我们需要找到workflow的运行时间在哪个点上正好等于累积了的需要处理的数据量。为了做这个,我们可以简单地插入 T=H 来求解T:

T = O + P * T
T = O / (1 - P)

这就是我之前展示的等式。你可以看到,一个workflow的稳定的运行时间是和workflow中的额外开销呈线性比例的。所以如果你可以将额外开销减少25%,那么你的workflow运行时间就会减少25%。然而,一个workflow的稳定运行时间和动态处理的速率“P”不是呈线性比例的。这背后的隐含含义就是每为集群加入一台机器带来的性能提升是在减少的。

通过这个等式,我们可以回答我在文章开始时候写下的问题了。让我们一起来过一些这些问题:

为什么在我将处理能力翻倍之后,我的workflow的速度没有翻倍?

将你的机器数翻倍,会将你的“处理一小时数据需要的时间”减少50%2。这个的效果和你的运行时间的关系是完全依赖于之前的P值的。让我们用数学来展示一下。假如说你的workflow在集群的机器数量翻倍之前的运行时间是“T1”,翻倍之后是“T2”。这给了我们两个等式:

T1 = O / (1-P)
T2 = O / (1 - P/2)

那么性能加速就会是 T2/T2,新的运行时间和旧的运行时间的比值。这给到我们

T2 / T1 = (1 - P) / (1 - P/2)
T2 / T1 = 1 - P / (2 - P)

对这个式子作图,我们可以得到如下的图,其中原始的P在x轴上,性能加速在y轴3上:

这个图说明了一切。如果你的P非常高,比如说,需要54分钟来处理一小时的数据,那么将你的集群数量翻倍会使得你新的运行时间是原来的18%,足足有82%的性能加速!这是一个非常违背直觉的结论 —— 我强烈推荐读者们仔细思考这种情况出现的背后的机制。

然后,如果你之前的P没有那么高(例如,需要6分钟的动态运行时间来处理1小时的数据),那么将集群数量翻倍对于运行时间几乎没有效果 —— 可能只有类似6%。由于运行时间被额外开销所主导,这是很合乎情理的,动态运行时间在运行时间中占得很小。

为什么10%的任务失败率会导致我的运行时间上升到原来的300%?

这个问题解释了workflow稳定性的属性。在一个大集群中,你总是会有不同的机器故障,所以任务失败率的峰值不会干掉mission critical系统的性能是非常重要的。关于这个问题的分析会和上一个问题看起来非常相似,除了我们会将动态运行时间变差而不是提高它之外。10%的任务失败率意味着我们需要多运行11%的任务来处理完我们的数据4。由于任务依赖于我们所拥有的数据量,这意味着,我们的“处理一小时数据需要的时间”会上升11%。类似于上一个问题,让我们将T1作为没有任务失败所需要的运行时间,T2作为有任务失败的运行时间

T1 = O / (1 - P)
T2 = O / (1 - 1.11*P)
T2 / T1 = (1 - P) / (1 - 1.11*P)

对此作图,我们得到:


(译著: 上图中X轴为P值,Y轴为花费时间增加的比例)

你可以看到,任务失败对于运行时间的影响在你的集群有“额外容量”减少的情况下戏剧性地增长。所以让保持你的P低是非常重要的。我们可以看到你的P越高,由于任务失败率的增加,你就更有可能进入“毁灭循环”的风险5

如果通过优化掉我的workflow的运行时间的30%而导致运行时间下降了80%?

这个问题是使得我真正找出workflow的运行时间的模型。我曾在一个workflow中有一个荒谬的瓶颈,导致了大概10小时的额外开销6,然后整个workflow的运行时间大概是30小时。在我优化了这个瓶颈(大约占据了30%的运行时间)之后,运行时间像石头一样坠落,最终稳定在6小时(减少了80%)。使用我们的模型,我们可以确定为什么这发生了:

30 = O / (1 - P)
6 = (O - 10) / (1 - P)
O = 12.5, P = 0.58

所以那10小时,占据了workflow中的80%的额外开销,这解释了整个的性能提升。

我的集群中到底需要多少机器可以满足性能和容错的需要?

这基本上是一个费效比分析的练习。我们可以看到,通过增加集群中机器数据来提高性能到回报在减少,而一旦P(“处理一小时数据需要的时间”)下降到30分钟以下(0.5),通过增加机器获得的性能提升是次线性的7。我们也同样看到将P保持得低是很重要得,不然任务失败的增加或者其他的任务使用集群,会严重影响你的运行时间。所以,你需要运行一些数据来确定对你的应用最佳的机器数量8

你应该在Twitter上follow

更新:看看本文的后续内容



1 事实上,这个等式适用于任何批量处理连续数据流的系统。
2 这里假设你的处理是完全分布式的,并且没有持有任何中心点,这对于一个基于Hadoop的workflow来说通常是真的。一个可能的例外是,你所有的tasks都通过一个中心的数据库进行通信。事实上,增加更多的机器会在额外开销(更多的机器去处理)以及数据处理速率(mapper需要将数据分发到更多的reducer中去)有少量的不利影响。对于这个分析的目的来说,我们可以忽略这些。
3 该图通过FooPlot生成。
4 比如说我们通过100个task来处理数据,现在,当你运行这些task的时候,其中有10个会失败。当你重新运行这10个的时候,9个会成功,还有1一个会失败。所以你实际运行了111个task而不是100个,这意味着task的数量增加了11%。
5 或者其他的运行在集群上的东西,比如一次性的查询或者其他的workflow。
6 这是由于Berkeley DB在ext3的文件系统上会生成很多碎片。
7 事实上,我没有展示这个,但是你可以通过模型来自己推导。我把这个作为一个练习留给读者。
8 如果你需要的话,你可以让这个模型变得更加全面。比如,这个模型没有区分新进入的数据和已经存在需要被查询的数据(并且在每个迭代中都会增加)。这个变量必然会影响到你的长期扩展需求,记住它是非常重要的。对于那些知识对于这些workflow的性能有获得一个直觉的目的来说,这不是必须的。


新年愿望

去年基本没写Blog,回想一下,去年想干的几件事,基本都没善终,希望今年有所改观

总结下去年的生产力

  • I公司的产品,技术的推动,基本上算是满盘皆输,心理感觉都快和程序员阿士顿的故事差不多了,花了很多时间精力,但是就是没有实际的商业成果。最后11月份放弃换工作了。
  • 手机开发,本来想自己写个iPhone App的,最后也没有抽出时间来写,基本上Objective-C的程序架构都清楚了,debug也行,但是实际除了Example没有写过什么。
  • Ruby和Rails,倒是因为一个项目重新拾起,但是换了工作之后又放下了,不过现在的脚本都用Ruby写了,算勉强保持点功力吧。
  • Lisp,做了点SICP上的题,本来雄心壮志准备去年做完的,但是换了工作,又玩新东西去了,目前停留在第二章,今年怎么也把这本书上的题做完吧
  • 儿子的某大事还算成功,算是了了一半心事。春天的时候去乌镇过了个周末,我非常喜欢那里,准备今年至少再去一次
  • 最后,托HL总的福,换了个新工作,目前很满意,开始做些有追求的大系统,感谢HL总和NH姐姐。当然,也感谢I公司的B总,很愧疚自己还是才具不够,折腾了一年半没能干成啥事儿。

今年的目标也蛮简单,技术上和实际工作基本上完全一致,就是分布式和机器学习了,硬指标基本上是三本书,去年没看完的SICP做完所有得题,分布式看完POSA第四卷,以及机器学习看完PRML,希望做出的系统真的能够对中国互联网有点影响,希望能为Hadoop/HBase/Zookeeper/Mahout/S4这些开源项目贡献点代码。生活上么,去次海南,再去次乌镇,有机会的话,再出趟国,以后能像我毕业前三年那样每年都出去见识见识。对自己的限制么,今年不烧包买大件了,iPad、手机、笔记本电脑都不会更新换代,得开始稍微省点儿钱了。本来还想学门乐器,算算多半也不会有时间,今年就不考虑了,先重新把数学学好吧。还有就是真得锻炼身体了,游泳圈开始初现端倪了……


本周工作总结

  • 不知道是中国人独有还是全世界普遍的,谈判的时候一定要让别人杀掉点价,不然别人会觉得自己的工作很没有成就感,不太愿意和你做生意,即使这次成了,也有碍于长期合作。
  • 对一些事情太乐观了,没有做充足的准备,搞得现在很被动,多小的事都得多做准备,仔细完成,认真杀价。
  • 刚工作的时候总是充满热情,认真学习。两三年过去了,薪水提高了,容易志得意满放松学习混日子,这样会被时代淘汰的。
  • 知识、技能这些东西不如性格、毅力这些东西重要,尤其是要处理的问题慢慢杂驳起来的时候。
  • 无论何时,无论何地,很多事情要做好就是细心、耐心、认真,所谓聪明才智、技能、经验等等都是辅助性道具。
  • 从项目的角度、公司的角度考虑问题,才能做好项目,从自己的角度考虑问题,容易出现偏差。

工作就是解决麻烦啊

看了Mars同学的最近的三篇blog:1,2,3,发现看来同志们的生活都还挺辛苦的,很多时候对很多周遭的事情很不满意,但是回头就告诉自己公司给我发这点工资就是让我来解决这些麻烦的,如果每件事都能美好的平稳发展,也就不需要花钱雇我了。所以,从某种程度上说,越是这样乱糟糟的我才有价值啊。

现在的项目多少还是激起了我一点好好干事的欲望,但是随之而来的就是对别人和项目效率的不满意,有时候还有一点控制不住的愤怒情绪,我问pretty我是不是对有些事情的要求太高了,但是pretty对我说她也是这样的人啊,最近每次向她求教都是这样的结果,果然是和我相似的人啊。

回想起过去的一些日子和工作,不知道应该是对人nice一点还是苛刻一点,好像两个都不能解决问题,不过,本质上,我是个挑剔的家伙。


愤怒了啊

今天晚上彻底愤怒了,好不容易大哥打起精神开始好好干活,忍耐SVN这个愚蠢的SCM工具搞两个partner回来的项目,结果有些人的工作TMD的一点专业精神都没有,浪费了我一个晚上,手工干了无数额外的活,最后TMD他们写的JSP还不能通过编译,没有比这更愚蠢的事情了。我就是天天摸鱼休息到自己最惭愧的时候干的活也没有干成这样的,彻底愤怒了,早知道我还不如直接一开始弊了他回去看哈里波特呢。

要是天天和这样的人工作,这工作就没法干。发现这个世界上没有专业精神的人真是越来越多了。今天还听阿娇同学说她还真遇上了有人要交活的时候来了个“我的code给猫吃了”的笑话,很多人BS印度人,TMD我见过的印度人个个专业NB,不说我们公司了,就算是干低端外包活的也不会拆烂污啊。上回一个项目bid给两家外包,一家印度的一家中国的,中国的愣是把所有的css都写成fix_width_seventy, fix_width_sixty这样的,这TMD的是人干的活么。不会可以,code质量差点也行,要偷懒慢点啥的也就算了,可TMD老是直接想糊弄人还浪费我时间帮你验收,孰不可忍啊。

不明白这世界怎么了,从饭店服务员到IT外包,职业精神强点儿的人怎么就那么少呢?吃个饭有上错菜强插收钱的,打个车有拒载的、半途加塞人的,写个程序居然有编译不过就来交差的,还有代码让狗吃了的,NND。


提高团队的沟通欲望

下午到公司,在ICQ上抓住印度人,搞了30分钟,然后搞定了我折腾了好几天的问题,发现自己之前不去问,一个人在那里瞎折腾,是多么地愚蠢。总结一下对于沟通的想法:

  1. 沟通不只有一个成本问题,还有一个沟通欲望的问题,很多时候,没有或者较差的沟通欲望阻碍了沟通,导致团队效率降低
  2. 以下的情景容易导致大家减少沟通欲望:
    1. 没有回复的邮件,缓慢回应的ICQ等等,通常询问者会觉得自己问了个纯问题或者打扰了别人
    2. 更糟糕的猜想是对方不愿意帮助你解决问题
    3. 没有面对面的交流,双方之间没有建立起信任,不愿意轻易地询问对方
    4. 没有正式地确定双方之间需要分享哪些信息,哪一边在某一具体事务上必须帮助对方
    5. 语言有问题,一两次没听懂,没看懂,又不好意思问,或者性格上有障碍,不愿意沟通
  3. 没有沟通欲望很多时候很难发现,尤其是对于管理者来说,因为每一次沟通都是非常细节具体的事

解决办法对于管理者/协调者来说,有这些:

  1. 让沟通者有面对面交流的机会,当然成本比较高,但是在团队中建立起私人的友谊,有助于大家在工作和业务上的交流
  2. 明确谁有义务帮助别人了解这个项目,可以考虑采取跨国Pair
  3. 作为一个提问者或者翻译器,去pull问题,然后得到回答,人肉加速信息的流转
  4. 永远鼓励大家脸皮要厚,问到答案为止,自己要带头耐心回答问题

对于每个个人来说,要

  1. 脸皮要厚,不要瞎猜,别人没回答你也许是忘了
  2. 如果别人很忙,专门约时间问问题,或者沟通,即时这是个简单的讨论
  3. 尽快回邮件和ICQ,己所不欲,勿施于人
  4. 搞好个人关系,这样问的时候就更有底气了

当然,始终要避免问蠢问题,不过这不在本文讨论范畴之内,可以google提问的艺术