上篇中提到了很多 Alluxio 为了减速读取数据做的各种各样的优化,那么对于用户来说还有一个十分重要的问题——在机器学习训练中应用 Alluxio 读数据到底有多快?
比如说数据是贮存在云上的,那从 Alluxio 读会不会比间接从云上读更快,具体能快多少?有了比拟咱们能力判断出应用 Alluxio 到底能不能取得训练性能晋升,能晋升多少。
在 Alluxio 中有一个本人的性能测试工具,叫 StressBench,它能够通过在肯定工夫内进行一系列操作,比方读、写或者元数据操作,来测试不同环境下 Alluxio 各个局部的处理速度。它的长处在于不依赖内部组件,只须要一个运行的 Alluxio 集群,这个集群能够是只有一个节点,一个 master,一个 worker,也能够是一个更大的集群,有更多的节点。
在 StressBench 中退出了 fuse StressBench,就是一个对于 fuse 的测试, 用来测试通过 POSIX 接口,从 Alluxio 中读取文件的速度。那么 fuse StressBench 除了须要一个 Alluxio 集群之外,还须要额定在每一个 worker 节点上都将 Alluxio 通过 fuse 挂载到本地文件系统。
既然要测试性能,最次要的一个目标就是能够量化读性能,在有这个测试工具之前,只能说从 Alluxio POSIX 接口读文件要比从云上读快很多,然而具体有多快,快多少,用户并没有方法测量,当初有了这个工具,就能够拿出数据证实的确快,也能够展现有多快。
最简略状况下,就是 Alluxio 集群只有一个节点,一个 worker 和一个读文件的挂载点,在测试中第一步咱们将测试文件写入 Alluxio worker,第二步通过 POSIX API 去读它们,测量在一段时间内读了多少字节,就能够返回在这段时间内读的速度。整个过程只须要跑两个命令,就能够看到单机状况下的读性能了。
相比单机测试,集群测试更贴近理论生产中的机器学习场景。其中有多个节点运行了 Alluxio worker,每个节点都通过 fuse 挂载了 Alluxio,每个节点上都有本人从 Alluxio 中读取文件的工作,当然理论生产环境中训练还有计算的局部,咱们这里 fuse StressBench 只是测试读取数据的行为。
咱们利用了 Alluxio 的 job service 来模仿这个场景,能够了解为每台机器都有一个程序在读文件,就像理论训练中每个节点上的训练任务,每个节点上的 fuse StressBench 各自计算肯定工夫内本人读了多少字节,从而就能够晓得每个节点读的速度,进而晓得整个集群的速度。同样,整个过程只须要两个命令就能够实现。
这里用单节点状况做了一个简略的例子,有一个单节点的 Alluxio 集群,一个 worker 和一个本地用于读的挂载点,通过 bin/alluxio runClass 来跑 StressBench 测试工具,能够看到还同时提供了写的挂载点,这一步用来将测试文件写进 Alluxio 的门路,而后指定操作为写文件。咱们还提供了线程的数量,一共将测试文件写进多少个文件夹,每个文件夹有多少文件,以及每个文件的大小,期待命令返回之后,这些文件就会全副贮存到了 Alluxio worker 外面。
文件写好后进行读操作,在读测试文件时,用雷同的这个 bin/alluxio runClass 来启动测试。接下来指定读文件的挂载门路、读的形式、线程数量、测试工夫,以及和方才生成文件是一样的,文件夹数量,每个文件夹有多少个文件和文件大小。可能有人发现咱们这个测试中读和写的本地门路不一样,是因为在生成测试文件的时候 fuse 就会有一些缓存,如果再去同一个文件夹读的话,这些缓存会影响测试读性能的准确性,所以用了不同的挂载点来防止这个问题。也就是在开始测试前挂载的时候,将 Alluxio 挂载到两个不同的门路,一个负责写,一个负责读。这样的话写文件时产生的缓存,就不会影响到读文件了。
这是读测试返回后生成的局部后果。能够看到这个单节点达到了每秒 183.1 兆的读取速度。咱们方才指定了一些参数,包含门路、文件大小等等,以及如果在测试中呈现了任何谬误,也会在这里显示。
目前这个 fuse StressBench 的测试工具有两个次要的限度,第一个是它只反对读性能的测试,临时还不反对写数据或者元数据操作的测试。之前的写操作只是为了测试读性能生成的数据,它本人并不能作为一个测试。另外一个就是只反对读用它自身生成的文件,不能应用任意文件来进行测试,否则会报错,次要起因是因为如果用任意文件测试的话,很难保障资源平均分配,可能有些线程很早就完结了它的工作,然而有些线程前面又读了很久,那这样的话这个测试后果就不精确了。
想理解更多对于 fuse StressBench 内容能够查看官网文档中对于 fuse StressBench 的局部,其中有对如何应用这个工具更为具体的介绍。接下来更为具体的介绍一下如何在 Alluxio POSIX API 中 debug 以及性能剖析指南。
首先故障排除,是 fuse 有个很大的问题,在于具体的故障起因不会显示在咱们的命令输入中,这次要是 fuse 自身自带的一个限度,咱们只能确定错误码是什么,但这个错误码可能翻译给用户之后,它并没有蕴含那么多的信息。比如说咱们这个命令可能 ls 一个 Alluxio fuse 中的文件,而后会呈现一个 I / O 报错,但无奈具体晓得到底是什么样的问题,而具体的问题显示在日志文档中,就包含如果独自起 fuse 过程的话,你须要查看 fuse.log,而如果 fuse 起在 worker 过程中的话,你查看 worker.log,它外面会显示具体的起因,有可能是因为没有方法去连贯 master,导致读不到数据之类的种种原因。
能够对日志进行等级批改,批改等级为 debug 之后,咱们能够取得更加具体的信息。批改日志等级次要有两种形式,一种是通过批改 Alluxio 的 conf/log4j.properties 间接批改日志等级,咱们能够对任意的包,把它批改成 debug 这种日志等级。
如果 fuse 起在 worker 过程中,能够动静地通过一个 Alluxio 的 CLI 的命令,而后开启 debug 级别的日志输入。比如说 logLevel 这个命令, 它能够把 worker 中所有 Alluxio.fuse 包的所有日志变成一个 debug 级别的日志输入,就能够不便咱们更好去排查呈现的问题。这些日志通过批改为 debug,能够看到 fuse 各种操作执行程序以及消耗工夫,有助于发现到底是哪一些 fuse 操作花了比拟多的工夫,以及是否有一些非预期的执行程序。已经有过一些故障就来源于一些非预期的执行程序。
当 fuse 应用了 Alluxio POSIX API 之后,发现可能性能不如预期,那性能不如预期这个问题到底呈现在哪一个方面?
有几个层面:一个是利用,一个是 fuse, 还有 Alluxio。性能不如预期的状况下,就要回过头来看这一整套货色。先理解一下整个 fuse 的工作流程,当用户收回了一个申请,比如说 ls 这个申请,它会把这个申请发送到 fusekernel 端,而后 Kernel 端进行肯定的解决之后,再返回到利用端,发送给 Libfuse,而后 Libfuse 把这些命令变成一个能够自定义的模式,通过实现 Libfuse,来实现咱们本人的文件系统。
然而 Libfuse 是写在 C ++ 上的,而 Alluxio 是写在 Java 外面的,就引入了另外一层形象叫 JNIFuse,通过 JNIFuse 之后,Alluxio fuse 再定义每个操作具体要执行什么样的具体的内容。比如说 Alluxio fuse 接到底层 fuse 的一个命令说 ok,这个用户做了一个 ls 操作,曾经拿到了一系列的 ls 的指令相干内容,比如说你要读哪个文件的一些信息,而后 Alluxio fuse 要决定如何去应答这些操作,咱们拿到 ls 操作之后,就会去 Alluxio 集群内去问,这个用户要读文件 a,能不能给我文件 a 的信息,而 Alluxio fuse 拿到这部分信息之后,再通过一系列的 fuse 返回给用户。
所以这其中其实蕴含了三个方面,一方面是应用层,利用是与 fuse 如何进行交互的;另一方面就是 fuse 层,包含 fuse,kernel,Libfuse 以及 JNIFuse,这些都统称为 fuse 层,就是 fuse 是如何把一个应用层的命令转化给 Alluxio 去具体地实现。最初一层就是 Alluxio 层,包含 Alluxio 是如何实现 fuse 的,以及 Alluxio 整个零碎。
首先须要明确性能瓶颈是不是来源于 Alluxio POSIX API,因为训练性能不佳,可能是来源于各种各样的起因,包含资源有余,包含算法和训练逻辑比拟迟缓,以及包含利用的元数据延时长或者数据吞吐低。而咱们就要剖析这个性能不佳起源是不是跟数据或者元数据相干,如果与其无关可能性能瓶颈并就不在 Alluxio POSIX API 这一层,那钻研它也就杯水车薪。
所以,须要先剖析性能瓶颈是否来源于 Alluxio POSIX API,咱们能够比照 Alluxio POSIX API 与文件系统以及其余数据计划的性能差别,而且能够移除训练逻辑只留下数据相干操作,看看性能如何。
当咱们发现可能性能瓶颈来源于 Alluxio POSIX API 之后,咱们须要更多的去理解利用与 Alluxio POSIX API 之间的互动,比如说这个利用跑了哪些与 POSIX API 相干的指令,利用是如何呼叫这些指令的,它的呼叫程序是怎么样的,呼叫的并发度是怎么样的,这些信息都是须要提前理解的。
剖析到底是 fuse 还是 Alluxio 是性能瓶颈,咱们先应用了一个 stack fuse 来剖析这部分性能。
Stack fuse 是跟 Alluxio fuse 应用了雷同的 fuse 组件,它也包含 fuse kernel,Libfuse 和 JNIFuse,然而与 Alluxio fuse 不同的在于,它是间接对本地文件系统进行读写与以及元数据操作,不波及任何的 Alluxio 组件。
能够通过一个命令能把本地文件系统 mount 在另外一个文件夹,而后对这个 mount 文件夹进行一系列的操作来比照性能,通过比照本地文件夹以及 stack fuse,能够理解 fuse 这个层的性能损耗,通过比照 stack fuse 以及 Alluxio fuse,能够得出 Alluxio 的性能损耗。
之所以要理解是 fuse 还是 Alluxio 的性能瓶颈,也是因为如果是 fuse 的性能损耗,其实在 Alluxio 这端是做不了过多的性能晋升的,就是任何一个 fuse 的实现,包含 s3 fuse 之类的其余实现,它都会带有 fuse 原生的性能损耗,而 fuse 这种操作实现形式是很多年前就曾经实现了的,近期也没有什么大的扭转,也不会因为要把 fuse 利用在咱们的训练中,进行一个大版本的降级。而如果发现是 Alluxio 的性能损耗,就能够进一步去剖析到底是 Alluxio 中哪一部分出了问题,哪一部分能够进一步的优化来晋升训练性能。
Alluxio fuse 的剖析包含几个方面,首先能够通过看一些指标,比如说 Alluxio fuse 的相干指标,发现有多少个文件正在并发读取,多少个文件正在并发写入,性能不好是不是因为有超高量的并发读取和写入的操作,而这个超高量的 fuse 方面的读取跟写入,可能并没有被 Alluxio 的线程池或者存储系统很好地撑持起来,这是第一步发现到底是什么性能问题的一个指标。
也能够剖析 fuse 相干操作的频繁度以及耗时。对于每个 fuse 操作都会记录相应的指标,比如说 fuse 每个读命令都会记录这个读命令被呼叫了多少次,均匀的实现工夫是多少?超过 5% 的读操作超过了多少工夫?从这些信息中咱们能够看出哪个 fuse 利用被呼叫的最多,而哪个 fuse 利用消耗了最长的工夫,是均匀工夫长还是偶然工夫长,这些信息都对进一步剖析性能瓶颈有十分重要的帮忙。比方发现 fuse 的读操作破费工夫最长,那咱们可能就专一于在读操作上,看看是哪一步的性能有肯定的损耗。如果是均匀工夫特地长的话,是不是波及到配置或者波及到存储系统的连贯的问题?那如果是均匀工夫十分短,然而有 1% 或者 5% 的操作花了十分长的工夫,是不是就波及到资源的抢夺?就能够进一步的去剖析性能瓶颈在哪里了。
还有一些更高级的追踪性能瓶颈的形式,能够通过指标零碎进行工夫追踪,比方 fuse 读这个操作十分的慢,就能够在 fuse 读操作外面加上一些 timer metrics,而后一步一步的追踪上来,看看到底在哪里花了最长的工夫,最初针对那个方面进行性能的晋升。
除工夫追踪之外,还能够进行 CPU 资源、内存资源以及锁资源的追踪,有个利用叫 Async profiler,它能够帮忙进行 CPU memory 以及锁的瓶颈的剖析,能够让咱们发现哪一个 Alluxio 的 Java class 花了最多的 CPU 资源或者最多的内存和锁资源,再或者能够对 RPC 指标进行追踪,目前反对 gRPC 和 s3。
能够发现,比如说这些 RPC 操作十分的耗时,那到底是在哪里耗时呢?咱们的 client 对 worker 发送了一个申请,十分耗时,那到底是这个申请发送途中很耗时还是 worker 解决这个申请很耗时呢?就能够发现到底是 worker 解决慢,还是可能存在资源竞争的问题。这一整套剖析都是比拟有难度的,这里也放了一个性能剖析的案例,也就是咱们最近在一个百万小文件的场景下,通过性能剖析,把最高的吞吐从 2400 小文件每秒晋升到超过 13,000 小文件每秒。能够通过这个案例来具体看一下,咱们是如何做整一套性能剖析的,大家谢谢。