JanusGraph 缓存
缓存
JanusGraph 采用多层数据缓存来促进快速图遍历。这里列出了从 JanusGraph 事务内部访问这些缓存层的顺序。缓存离事务越近,缓存访问速度越快,内存占用和维护开销也越高。
事务级别缓存
在开放事务中,JanusGraph 维护两个缓存
-
顶点缓存:缓存已访问的顶点及其邻接列表(或其子集),以便在同一事务中后续访问速度明显更快。因此,此缓存加快了迭代遍历的速度。
-
索引缓存:缓存索引查询的结果,以便后续的索引调用可以直接从内存中提供,而不是调用索引后端并(通常)等待一次或多次网络往返。
这两个缓存的大小由事务缓存大小决定。事务缓存大小可以通过cache.tx-cache-size配置,或者通过事务构建器graph.buildTransaction()打开事务并使用setVertexCacheSize(int)方法来按事务配置。
顶点缓存
顶点缓存包含顶点以及在特定事务中检索到的其邻接列表(属性和边)的子集。此缓存中维护的最大顶点数等于事务缓存大小。如果事务工作负载是迭代遍历,则顶点缓存将显著加快其速度。如果同一顶点在事务中不再被访问,则事务级别缓存将没有任何区别。
请注意,顶点缓存的堆大小不仅由它可能容纳的顶点数量决定,还由其邻接列表的大小决定。换句话说,具有大型邻接列表(即许多入射边)的顶点将比具有较小列表的顶点在此缓存中占用更多空间。
此外请注意,修改后的顶点会被固定在缓存中,这意味着它们不能被逐出,因为这会导致丢失其更改。因此,包含大量修改的事务最终可能会产生比配置更大的顶点缓存。
假设您的顶点没有从缓存中逐出,或者它从缓存中逐出但您的程序上下文仍然持有对该顶点的引用,那么它的属性和边将与该顶点一起被缓存。这意味着一旦查询了一个属性,任何后续读取都将命中缓存。如果您想强制 JanusGraph 再次从数据存储中读取(前提是您已禁用数据库级别缓存),或者您只是想节省内存,您可以手动清除该顶点的缓存。请注意,此操作不符合 Gremlin 规范,因此您需要将顶点转换为 CacheVertex 类型才能执行刷新操作
// first read automatically caches the property together with v
v.property("prop").value();
// force refresh to clear the cache
((CacheVertex) v).refresh();
// now a subsequent read will look up in JanusGraph's database-level
// cache, and then backend storage read in case of cache miss
v.property("prop").value();
请注意,刷新操作不能保证您的当前事务会从最终一致的后端读取最新数据。您不应该尝试通过刷新操作来实现比较并设置 (CAS),尽管在某些情况下它可能有助于检测事务之间的冲突。
索引缓存
索引缓存包含在此事务上下文中执行的索引查询结果。后续相同的索引调用将从该缓存中提供,因此成本显著降低。如果同一索引调用在同一事务中从未发生两次,则索引缓存没有任何区别。
索引缓存中的每个条目被赋予一个等于2 + 结果集大小的权重,并且缓存的总权重不会超过事务缓存大小的一半。
数据库级别缓存
数据库级别缓存包含顶点及其邻接列表(属性和边)的子集,跨多个事务并超出单个事务的持续时间。数据库级别缓存由跨数据库的所有事务共享。它比事务级别缓存更节省空间,但访问速度也稍慢。与事务级别缓存不同,数据库级别缓存不会在事务关闭后立即过期。因此,数据库级别缓存显著加快了跨事务的读取密集型工作负载的图遍历。读取操作首先在事务级别缓存中查找,然后是数据库级别缓存。
配置参考列出了所有与 JanusGraph 数据库级别缓存相关的配置选项。本页面旨在解释它们的用法。
最重要的是,在 JanusGraph 的当前发布版本中,数据库级别缓存默认是禁用的。要启用它,请设置 cache.db-cache=true。
缓存过期时间
对于性能和查询行为最重要的设置是缓存过期时间,通过 cache.db-cache-time 配置。缓存将最多保留图元素这么多毫秒。如果元素过期,数据将在下次访问时从存储后端重新读取。
如果只有一个 JanusGraph 实例访问存储后端,或者此实例是唯一修改图的实例,则可以将缓存过期时间设置为 0,这将禁用缓存过期。这允许缓存无限期地保留元素(除非它们因空间限制或更新而被逐出),从而提供最佳的缓存性能。由于没有其他 JanusGraph 实例修改图,因此没有保留陈旧数据的危险。
如果存在多个 JanusGraph 实例访问存储后端,则应将时间设置为在**另一个** JanusGraph 实例修改图与此 JanusGraph 实例看到数据之间允许的最大时间。如果任何更改都应立即对所有 JanusGraph 实例可见,则在分布式设置中应禁用数据库级别缓存。然而,对于大多数应用程序来说,特定 JanusGraph 实例以某种延迟看到远程修改是可以接受的。允许的最大延迟越大,缓存性能越好。请注意,给定的 JanusGraph 实例将始终立即看到其自身对图的修改,无论配置的缓存过期时间如何。
缓存大小
配置选项 cache.db-cache-size 控制 JanusGraph 的数据库级别缓存允许消耗多少堆空间。缓存越大,效果越好。但是,过大的缓存可能导致过多的 GC 和较差的性能。
缓存大小可以配置为运行 JanusGraph 的 JVM 可用总堆空间的百分比(表示为 0 到 1 之间的十进制数)或绝对字节数。
请注意,缓存大小是指由缓存独占占用的堆空间量。JanusGraph 的其他数据结构和每个开放的事务都将占用额外的堆空间。如果在同一 JVM 中运行其他软件层,它们也可能占用大量堆空间(例如 Gremlin Server 等)。在进行堆内存估算时要保守。配置过大的缓存可能导致内存不足异常和过多的垃圾回收。
实际上,您可能会观察到 JanusGraph 使用的内存比为数据库级别缓存配置的内存更多。这是一个已知限制,因为反序列化对象的大小难以估算。
清理等待时间
当一个顶点在本地被修改(例如添加了一条边)时,所有与该顶点相关的数据库级别缓存条目都会被标记为过期并最终被逐出。这将导致 JanusGraph 在下次访问时从存储后端刷新顶点数据并重新填充缓存。
然而,当存储后端最终一致时,触发逐出的修改可能尚未可见。通过配置 cache.db-cache-clean-wait,缓存在用从存储后端检索到的条目重新填充缓存之前,将至少等待这么多毫秒。
如果 JanusGraph 在本地运行或针对保证修改立即可见的存储后端运行,此值可以设置为 0。
存储后端缓存
每个存储后端都维护自己的数据缓存层。这些缓存受益于压缩、数据紧凑性、协调过期,并且通常在堆外维护,这意味着可以使用大型缓存而不会遇到垃圾回收问题。虽然这些缓存可能比数据库级别缓存大得多,但它们的访问速度也较慢。
缓存的确切类型及其属性取决于特定的存储后端。请参阅相应的文档以获取有关缓存基础设施以及如何优化它的更多信息。