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 + 结果集大小,且缓存的总权重不会超过
事务缓存大小的一半。
数据库级别缓存
数据库级别缓存包含顶点及其邻接列表的子集(属性和边),跨多个事务且超出单个事务的持续时间。数据库级别缓存由数据库中的所有事务共享。它比事务级别缓存更节省空间,但访问速度稍慢。与事务级别缓存不同,数据库级别缓存在关闭事务后不会立即过期。因此,数据库级别缓存显著加速了跨事务的读取密集型工作负载的图遍历。读取操作首先在事务级别缓存中查找,然后在数据库级别缓存中查找。
Configuration Reference 列出了所有与JanusGraph数据库级别缓存相关的配置选项。本页面旨在解释这些选项的使用方法。
最重要的是,在当前版本的JanusGraph中,数据库级缓存默认是禁用的。要启用它,请设置cache.db-cache=true。
缓存过期时间
对于性能和查询行为最重要的设置是通过cache.db-cache-time配置的缓存过期时间。缓存最多将保持图元素这么多毫秒。如果一个元素过期,数据将在下次访问时从存储后端重新读取。
如果只有一个JanusGraph实例访问存储后端,或者该实例是唯一修改图的实例,可以将缓存过期时间设置为0以禁用缓存过期。这使得缓存可以无限期地保留元素(除非由于空间限制或更新而被驱逐),从而提供最佳的缓存性能。由于没有其他JanusGraph实例修改图,因此不存在持有陈旧数据的风险。
如果有多个JanusGraph实例访问存储后端,时间应设置为另一个JanusGraph实例修改图数据后,到当前实例看到数据之间所允许的最大时间间隔。若要求所有变更必须立即对所有JanusGraph实例可见,则应在分布式设置中禁用数据库级缓存。不过对于大多数应用而言,特定JanusGraph实例延迟感知远程变更是可接受的。允许的最大延迟越长,缓存性能就越好。请注意,无论配置的缓存过期时间如何,给定JanusGraph实例始终会立即看到自身对图的修改。
缓存大小
配置选项 cache.db-cache-size 控制 JanusGraph 的数据库级别缓存允许消耗多少堆空间。缓存越大,效果越好。然而,过大的缓存大小可能导致过多的垃圾回收和性能下降。
缓存大小可以配置为运行JanusGraph的JVM可用总堆空间的一个百分比(表示为0到1之间的小数),或者配置为绝对的字节数。
注意,缓存大小指的是专门由缓存占用的堆空间量。JanusGraph的其他数据结构 以及每个打开的事务都会占用额外的堆空间。如果 同一JVM中运行了额外的软件层,这些层也可能占用 大量的堆空间(例如Gremlin服务器等)。 在堆内存估算时要保守。配置过大的缓存 可能导致内存不足异常和过多的垃圾回收。
在实践中,您可能会观察到JanusGraph使用的内存比配置的数据库级别缓存更多。这是一个已知的限制,由于难以估计反序列化对象的大小。
清理等待时间
当一个顶点在本地被修改(例如添加了一条边)时,该顶点的所有相关数据库级缓存条目将被标记为过期并最终被驱逐。这将导致JanusGraph在下次访问时从存储后端刷新该顶点的数据并重新填充缓存。
然而,当存储后端是最终一致性时,触发驱逐的修改可能尚未可见。通过配置cache.db-cache-clean-wait,缓存将在从存储后端检索条目重新填充缓存之前,至少等待这么多毫秒。
如果JanusGraph在本地运行或针对保证修改即时可见的存储后端运行,此值可设置为0。
存储后端缓存
每个存储后端都维护自己的数据缓存层。这些缓存 受益于压缩、数据紧凑性、协调过期,并且 通常维护在堆外,这意味着可以使用大型缓存 而不会遇到垃圾回收问题。虽然这些缓存可以 显著大于数据库级别的缓存,但它们的访问速度也较慢。
确切的缓存类型及其属性取决于特定的 存储后端。请参阅相应的 文档以获取有关缓存基础设施以及 如何优化它的更多信息。