注: 文中我们将对象存储和共享存储等价。 [JuiceFS介绍传送门](JuiceFS你应该知道的一些事 ) [MLSQL传送门](A Programming Language Designed For Big Data and AI)
RespectM同学在文章[MLSQL集成JuiceFs](16 - MLSQL集成JuiceFs)中,已经详细的讲解了如何整合两者。丢一个配置文件到SPARK_HOME/conf以及启动时带上SDK Jar就可以完美在MLSQL访问各种对象存储和HDFS了,这非常酷。
但实际上,这仅仅是在API层侧打通了,用户通过HDFS API访问JuiceFS,从而实现对各种对象存储的适配。还有两个地方我们也是需要考虑的:
1. Shuffle临时目录。目前Spark应该只能配置本地盘。尽管可能存在低效,但是我们依然希望对象存储可以作为我们的第二个选择。如果能不修改Spark即可实现,那不要太好。
2. Python对对象存储的操作
第一点比较好理解。今天我们重点谈谈第二点。以MLSQL为例,我们可能会在Executor某个节点的Python Worker里运行Python代码,构建模型,而模型的保存通常是面向“本地磁盘文件系统”的,这不仅仅符合大部分AI工程师的习惯,也符合很多算法库的习惯。但是,如果用户保存在本地磁盘文件,这个磁盘文件就会成为“碎片”并且不可达(甚至也有可能很快会被其他用户写同名目录给覆盖,尽管这个问题对于对象存储而言,可能会更严重)。写完之后,我们就没有任何途径去访问到这个文件了。这个时候juicefs就成为不二选择,我们可以通过CSI插件把对象存储挂载到K8s上,从而给每个容器都挂载上对象存储目录,亦或是without K8s,我们也可以手动通过命令行给每个节点挂载上。MLSQL 还整合了Ray,尽管Ray有自己的存储(Plasma),而且数据都是本集群内被存储(Mem Disk),理论上会更有性能上,但是有一个外置的`Plasma`(JuiceFS挂载的对象存储)也不失为一个选择,尽管内置的Plasma承担了更多的功能。
但是从单机存储到共享存储,其实有个很难处理的问题,就是覆盖问题。比如可能人们都喜欢用`/data/ai_model`这个目录保存自己的模型,在共享存储上,用户之间必然会互相覆盖。同样的,对于很多分布式应用,这也是很严重的问题,比如像ElasticSearch这种自己维持存储管理的集群,通常会在在每个Node节点上配置相同的数据目录地址,如果切到对象存储上,这就很疯狂了,数据显然会相互覆盖导致系统不可用。
所以其实AI工程师这些“没有适配共享存储的系统”一样,可能需要做出改变。尽管如此,我也在思考,能不能尽可能不改变他们的习惯。在MLSQL中,我们有主目录的概念,举个例子,尽管用户写的是保存数据到`/data/ai_model`里,但实际上MLSQL会自动将实际目录转化为 `PREFIX/[username]/data/ai_model`,这样不同用户之间尽管共用一个存储,他们依然互不影响,保证了数据的安全性,不可见性。但是考虑到Python的灵活度,我们似乎很困难(也不排除有一些Python黑魔法,可以让我们拦截并且修改文件路径)的去做到MLSQL所达到的效果。软件先贤们告诉我们:遇到解决不了的问题,不妨多加一层。 所以这个时候我在考虑,能不能把这个功能做到JuiceFs层(亦或是在JuiceFS之上再有一层?)。譬如JuiceFS可以暴露出一些插件能力,允许用户在自己实际使用场景里,通过开发插件完成控制或者修改原有读写文件逻辑的能力。虽然我还没想好,如何将逻辑上的“用户”/"节点名称"/亦或是其他一些信息,传递给JuiceFS(比如在MLSQL中,实际用户是一个虚拟的,并不是启动Python进程用户),假设我们已经解决这个问题,那么JuiceFS会根据插件的配置,在读写时,都对路径改写,从而实现目录隔离。
考虑到(1)深度学习里,一个模型动则几个G,十几个G甚至百G,(2)本地磁盘的“不可达”问题,(3)需要本地文件系统接口,通过引入JuiceFs就变得水到渠成了。但是JuiceFS需要解决目录隔离的功能。