缓存

为了提高性能,JuiceFS 实现了多种缓存机制来降低访问的时延和提高吞吐量,包括元数据缓存、数据缓存,以及多个客户端之间的缓存共享。

元数据缓存

JuiceFS 支持在内核和客户端内存中缓存元数据以提升元数据的访问性能。

内核元数据缓存

在内核中可以缓存三种元数据:属性(attribute)、文件项(entry)和目录项(direntry),它们可以通过如下三个参数控制缓存时间:

  1. --attrcacheto=ATTRCACHETO
  2. 属性缓存时间,默认 1
  3. --entrycacheto=ENTRYCACHETO
  4. 文件项缓存时间,默认 1
  5. --direntrycacheto=DIRENTRYCACHETO
  6. 目录项缓存时间,默认 1

默认会缓存属性、文件项和目录项,保留 1 秒,以提高 lookup 和 getattr 的性能。

客户端内存元数据缓存

为了减少客户端和元数据服务之间频繁的列表和查询操作,客户端可以把经常访问的目录完整地缓存在客户端内存中,可以通过如下的参数开启:

  1. --metacache 在客户端中缓存元数据

开启后,被列表或者频繁查询的目录会被在客户端内存中缓存 5 分钟,对缓存目录的所有修改都会使缓存失效以保障一致性。 lookup、getattr、access、open 都能有效地使用这些缓存来提升性能。

此外,客户端还会缓存符号链接的内容,因为符合链接是不会被修改(覆盖已有符号链接时是创建新的文件),是一直有效的。

一致性保证

当只有一个客户端访问时,这些缓存的元数据能够根据当前客户端的访问操作自动失效,不会影响数据一致性。

当多个客户端同时使用时,内核中缓存的元数据只能通过时间失效,客户端中缓存的元数据会根据所有客户端的修改自动失效,但是是异步的, 极端情况下可能出现在 A 机器做了修改操作,再去 B 机器访问时,B 机器还未能看到更新的情况。

建议

当对元数据的访问很频繁,并且不要求多机的实时一致性时,可以启用元数据缓存提高性能,详细的缓存参数请参照 缓存相关参数 的使用方法及注意事项。

数据缓存

JuiceFS 对数据也提供多种缓存机制来提高性能,包括内核中的页缓存和客户端所在机器的本地缓存。

内核中数据缓存

对于已经读过的文件,内核会把它的内容自动缓存下来,下次再打开的时候,如果文件没有被更新,就可以直接从内核中的缓存读获得最好的性能。

在 JuiceFS 的元数据服务器中,会跟踪所有最近被打开的文件,同一个客户端要再次打开相同文件时, 它会根据该文件是否被修改了告诉该客户端是否可以使用内核中缓存的数据,以保证客户端能够读到最新的数据。

当重复读 JuiceFS 中的同一个文件时,速度会非常快,延时可低至低至微秒,吞吐量可以到每秒几 GB。

当前的 JuiceFS 客户端还未启用内核的写入缓存功能,所有来自应用的写操作(write)会直接通过 FUSE 传递到客户端。 建议应用层做适当的写入缓存以避免过于频繁的写操作影响性能。

客户端读缓存

客户端会根据应用读数据的模式,自动做预读和缓存操作以提高顺序读的性能。

客户端会把最近的少量数据缓存到客户端内存中(32MB),同时把更多的数据缓存到本地文件系统中,可以是基于硬盘、SSD 或者内存的任意本地文件系统。 本地缓存可以通过以下两个参数来调整:

  1. --cache-dir=CACHEDIR
  2. 缓存目录,默认为 /var/jfsCache
  3. --cache-size=CACHESIZE
  4. 缓存空间大小,默认为 1GB

JuiceFS 客户端会尽可能快地把从对象存储下载的数据(包括新上传的数据)写入到缓存目录中,不做压缩和加密。 因为 JuiceFS 会为所有写入对象存储的数据生成唯一的名字,而且所有对象不会被修改,因此不用担心缓存的数据的失效问题。 缓存在使用空间到达上限时(或者磁盘空间快满时)会自动进行清理,目前的规则是根据写入时间和大小,优先清理更大和更老的文件。

数据的本地缓存可以有效地提高随机读的性能,建议使用更快的存储介质和更大的缓存空间来提升对随机读性能要求高的应用的性能,比如 MySQL、Parity 等。

客户端写缓存

客户端会把应用写的数据缓存在内存中,当一个块被写满,或者应用强制写入(close 或者 fsync),或者一定时间之后再写入到对象存储中。 当应用调用 fsync() 或者 close()时,客户端会等数据写入到对象存储并且通知元数据服务后才返回,以确保数据安全。 在某些情况下,如果本地存储是可靠的,可以通过启用异步上传到对象的方式来提高性能,此时 close() 不会等待数据写入到对象存储, 而是写入到本地缓存目录就返回。

异步上传模式可以通过下面的参数启用:

  1. --writeback 将写文件先写入缓存再异步上传

这种模式下,它会把小文件异步上传,而大文件仍然是同步并行上传。 此时还可以把多个小文件合并之后再上传到对象存储,以减少 PUT 请求的调用数量和缩小数据碎片。

当需要短时间写入大量小文件时,建议使用 --writeback 参数挂载以提高写入性能,写入完成之后再去掉它重新挂载。 或者在有大量随机写时 (比如应用 MySQL 的增量备份时),也建议启用 --writeback

这个选项尤其推荐使用跨公网的对象存储时使用,可以大大提高性能。

警告

--writeback 开启时,千万不能删除 /var/jfsCache/<fs-name>/rawstaging/ 中的内容,否则会导致数据丢失。

开启 --writeback 时,缓存本身的可靠性与数据写入的可靠性直接相关,对此要求高的场景应谨慎使用。

默认情况下 --writeback 不开启。

客户端缓存数据共享

当同一个集群的客户端需要反复访问同一个数据集时(比如机器学习时需要用同一个数据集反复训练), JuiceFS 提供了缓存共享功能可以有效地提升这个场景下的性能。它可以通过下面的命令启用:

  1. --cache-group=CACHEGROUP
  2. 相同组的客户端之间可以相互共享缓存的数据

对于同一个局域网内挂载了同一个文件系统的客户端,如果使用了相同的缓存组名, 它们会把监听在内网 IP 的随机端口汇报给元数据服务器,进而发现其他的客户端,并通过内网通信。

当一个客户端需要访问某个数据块时,它会询问某个负责这个数据块的节点,从对方的缓存读(或者从对象存储读并写入缓存)。 这些相同缓存组的客户端组成了一个一致性哈希(Consistent Hash)的环,类似于 memcached 的用法。 在这个组内新加或者减少客户端时,只影响到少量数据块的缓存命中率。

缓存共享功能非常适合使用 GPU 集群进行深度学习训练的场景,通过把训练数据集缓存到集群所有节点的内存中, 可以给提供非常高性能的访问,让 GPU 不会因为数据读取太慢而闲置。

常见问题

为什么我设置了缓存容量为 50 GiB,但实际占用了 60 GiB 的空间?

对同样一批缓存数据,很难精确计算它们在不同的本地文件系统上所占用的存储空间, 目前是通过累加所有被缓存对象的大小并附加固定的开销(4KiB)来估算得到的, 与 du 得到的数值并不完全一致。

当缓存目录所在文件系统空间不足时(少于 256MiB),客户端会尽量减少缓存使用量来防止缓存盘被写满。