最近因为几个odps任务节点扣分严重,计算健康度一度堕落至85分的红线以下,上了一次黑榜,立马开始了艰苦的优化之旅。刚刚前几天搞定了两个<code>openmr</code>的<code>列裁剪</code>优化,略作记录。
列裁剪,即针对<code>openmr</code>任务<code>map</code>阶段的输入,如果只使用了其中的某几列,则裁剪掉不需要使用的列,只指定需要使用的列。这样做的好处也就很明显了,减少网络i/o,提升<code>map</code>计算效率等等。其实从使用上来看或许叫做输入列指定更顾名思义一些。
这里贴一个本次重点做了列裁剪的节点,在列裁剪优化之前的<code>logview</code>(由于系统只保留最近几天的日志,所以这里就不贴链接了),这里就贴部分内容吧,具体查看路径为:
在待优化节点右键<code>查看运行日志</code>
找到<code>logview</code>轻点打开
<code>odps tasks</code>-><code>detail</code>
<code>main content</code>-><code>jsonsummary</code>
其中某一路输入的相关统计数据如下:
ok,没错,<code>input_col_total_num</code>和<code>input_col_used_num</code>两个字段的数据就是我们此次优化需要关注的要点。可以看到这张输入表在map阶段总共输入了14896列(这里的意思不是说输入表有1万多列,而是map个数乘以表的列数得到的map阶段总的输入列数),但是实际使用了3724列(同理),利用率非常低,扣分就是从这里开始的。。。
其实回到<code>fuxijobs</code>点击<code>m1_u13</code>,然后在列出的任意一个<code>fuxinstance</code>中点击<code>debug</code>可以看到这个<code>map</code>任务中的统计数据:
从这个<code>map</code>统计数据可以看到,输入表共输入8列,但实际使用了2列,即使用率只有1/4而已,浪费严重。
如果你使用的是<code>java sdk</code>,那么可以在指定输入表的同时指定输入列,如下:
如果你的mr任务输入表确定而不变,则可以参数化进行设置,如下:
第二种方式我没有用过,目前只是实践了第一种方式,而这种方式的灵活性在于,如果你是通过配置文件来表达输入表和输入列时,可以实现<code>只改配置而不改代码</code>。
由于目前提供的sdk不支持使用列索引来做<code>列裁剪</code>(个人觉得应该封装一下),如果因为历史原因,只在输入表的自定义配置文件中指定了输入表的列索引也没关系(如果配置文件比较大,输入表有几十个,我想你也不希望去重新找每张表列索引对应的列名),可以通过获取<code>table</code>的<code>tableschema</code>,然后通过下标来获取到列名,简单看下代码:
这里有一个注意点就是,如果优化前在<code>map</code>任务中是通过输入表的列索引去获取记录值的话,需要同步做相应修改。这是因为指定了输入列之后,实际在<code>map</code>阶段的输入表列数比原输入表要少,你可以理解为在<code>map</code>阶段是一张全新的表,此时列索引应该使用这张经过裁剪后的新表的列索引,因此使用原来的列索引可能会取不到值或者取到错误列的值。建议在使用时需要使用列名来访问或者通过新的列索引来获取记录值。
其实我上面贴的<code>logview</code>中的内容就是因为我在<code>map</code>阶段使用了原输入表的列索引来访问,导致了利用率反而降低了。比如表<code>wc_in</code>一共有0-9共10列,你只需要读索引为7、8、9这三列,但是你在进行了列裁剪优化之后,实际在<code>map</code>阶段的输入表只有0、1、2这三个索引对应的值,此时如果你还是使用原来的列索引去读的话就读不到数据,导致该表的输入列利用率从原来的30%变成了0%。
因此在完成了列裁剪优化之后务必检查<code>map</code>阶段读取<code>record</code>的方式:
如果是使用列索引的务必改成列名,
或者如果不方便获取列名的,在<code>setup</code>阶段读取配置文件建立列索引的映射来解决该问题。