HDFS大目录文件删除方案的实践思考

前言


前面几篇文章笔者讲述了2篇关于文件目录删除的相关文章,也提到了一些相对应的解决方案和思路。不过笔者本文想再谈谈对于这个问题的一些思考,主要关注在HDFS下大目录的删除性能影响方面。不敢说是谈论的是HDFS大目录删除的最佳实践方案,但是在某些点上,在实际环境中还是有一定的可应用性的。本文部分内容会引入笔者前段时间写的两篇相关文章:聊聊HDFS删除Snapshot行为导致的NameNode crash文件系统大目录下的操作性能效率提升

HDFS的大目录删除行为


曾经执行过HDFS大目录rm行为的同学,应该能切身体会到这个操作给集群带来的重大影响,尤其当这个大目录拥有数量庞大的子文件数以及足够深的深度目录树。

因为这个删除操作本身是要池NN FSN锁的,所以这个过程执行的越漫长,对其它正常的RPC请求的处理影响就越大。

那么为什么大目录删除会有这么大的影响呢,总结下来有以下相关三点:

  • 单目录下子entry过多(子目录或子文件),造成delete动作的性能开销。因HDFS目录内用ArrayList做目录孩子文件的信息保存的。当子文件过多的时候,就触发了ArrayList remove操作的性能影响。这点笔者在文件系统大目录下的操作性能效率提升中有相关详细细节的讨论。
  • 目录深度过深且目录下文件总数量规模庞大,需要进行逐一层级的递归目录树的遍历。在这点上,在后面小节中我们再来讨论这个问题。
  • HDFS快照加剧了目录删除的影响。这里的前提是如果集群启用了HDFS Snapshot的情况下,因为Snapshot会保留在Snapshot创建时间点后被删除的那些文件目录数据,达到数据保护的效果。那么这么被“延时”删除的数据会在Snapshot被清理的那一刻, 进行一次性的删除处理。这就好比是一个更大目录删除的叠加效果。

HDFS大目录删除实现方案思考


针对上节提到的三个难题,每一个说实话都不好解决,社区有一些解决方案和思路但是并没有被合入release的分支里面。

第一个问题,单一目录子文件过多触发ArrayList性能问题这点,社区HDFS-7174 JIRA有解决方案,原理是通过拆ArrayList的方式达到优化的效果,笔者也测试了这个JIRA的patch,效果还是相当的明显的,详见文章文件系统大目录下的操作性能效率提升

第二个难点问题,目录树深且待删除目录总文件数庞大。

这里其实有个变相的做法可以绕开这个问题,如果我们不想在HDFS内部删除逻辑这块做优化的情况下,通过改变外部的删除调用时间会是十分简单高效的做法。简单来说,就是让用户不要一下子删除那么目录文件走。取而代之的方式是分小时,分时段一批批的去删除目标的文件目录。因为HDFS Trash机制在后面清理trash目录的时候就会按照时间小时目录依次去清理删除目录,每个小时目录其实删除的量就会小很多,对NN造成的影响也就会小。当然,此方案在HDFS Snapshot目录的case下是起不到作用的。

至于这个难点的另外一个目录树深的问题,这里可以brainstorm一下,比如用线程递归的方式去扫描目录文件数据。这里还不能是纯线程异步的方式,因为这里会涉及到HDFS quota的更新问题,可能可行的方案是多线程启动递归执行,然后在删除目录的call方法里等待线程执行结束。不过社区在这块操作上只是做了removeINode和removeBlock动作的拆分,仅到此为止。removeBlock这步完全是可以分批进行的,所以社区在这里是通过batch的方式分批进行删除。通过改小这个删除block的batch size是可以减轻一些删除操作锁带来的影响的,相关JIRA:HDFS-13831: Make block increment deletion number configurable

第三个难点,HDFS Snapshot模式下怎么减轻删除目录的影响。HDFS Snapshot会hold住实际已经删除了的文件目录数据。如果我们调整自己的删除目录操作时间,也是不起作用的。那么这个时候有什么好的办法呢?答案很简单,同样更改Snapshot的创建时间点,我们将每次的一个大Snapshot变为若干小的Snapshot。这样的话,其实每个小Snapshot所删除的文件目录树就会小很多。

通过建更多小Snapshot的方式,笔者在内部集群上也进行测试过,优化效果只能说一般般。HDFS小Snapshot确实删除的量会变小,但是其中有个getChilrenList的方式有比较大的时间cost。这个getChilrenList的调用主要是通过获取Snapshot的diff来得到那个Snapshot时间点的文件列表数据,然后进行目录的递归删除。

但是getChilrenList在快照很多的情况下需要进行多次Snapshot diff的合并动作,然后再逆序apply到当前的文件目录状态,以此获得Snapshot时间点的状态。

以下面的例子为例,

Diffs: s0­>s1­>s2­>s3­>s4­>s5­>s6­>s7­>s8­>s9­

上面总共10个Snapshot按照时间顺序依次创建而来。假设我们此时准备删除创建实际最早的s0开始,然后我们需要去获取s0时刻Snapshot目录的子文件目录的列表情况,它需要将s0相对s1的diff,比如叫diff01,和后面依次diff12…diff89,和最后的diff9-latest状态的这些diff进行合并。这个过程是会占据一定时间的。

不过社区有解决方案解决上述getChilrenList的操作性能问题,通过SkipList维护多level的Snapshot diff list来达到加速ChilrenList获取的速度。此方案的主要drawback是需要用额外的内存来保存这些diff数据。这里需要我们在具体使用的时候做一定的衡量,此方案相关的JIRA: HDFS-11225: NameNode crashed because deleteSnapshot held FSNamesystem lock too long

SkipList的结构类似如下:

  • level 2: s08------------------------------->s9
  • level 1: S02------->s35-------->s68-------->s9
  • level 0: s0->s1->s2->s3->s4->s5->s6->s7->s8->s9

在上面s0-s9的Snapshot模式下,如果从中间Snapshot开始删,会达到优化的效果吗?实际来说的话,优化效果也比较一般,假设我们从s3删除开始,所说它所需要做diff combine的次数少了,但是它的diff diff34还是需要和diff23进行一次merge作为s2的diff的。其实我们可以看到它的combine diff的总开销其实并没有减少多少。

以上就是本文笔者关于HDFS大目录删除方案的一些想法思考方案,大家感兴趣的可以参考实践实践。

引用


[1].https://issues.apache.org/jira/browse/HDFS-11225
[2].https://issues.apache.org/jira/browse/HDFS-13831
[3].https://blog.csdn.net/Androidlushangderen/article/details/105454638
[4].https://blog.csdn.net/Androidlushangderen/article/details/105256532

猜你喜欢

转载自blog.csdn.net/Androidlushangderen/article/details/105778885