内存配置
Neo4j服务的内存划分为很多部分,以下是其中的一部分:
neo4j 内存管理
- 「JVM堆」
JVM堆是一个单独的动态内存分配,Neo4j使用它来存储实例化对象。Java对象的内存由垃圾收集器自动管理,更多垃圾回收器的调整可以参考垃圾回收器的内容。
堆内存的大小由参数
dbms.memory.heap.initial_size
和dbms.memory.heap.max_size
配置,建议把这两个参数配置成相同的值,避免不必要的垃圾回收。为了提高性能,你可以配置足够大的内存来保证并发。 - 「事务」
在执行事务时,Neo4j将尚未提交的数据、结果、和查询的中间状态保存在内存中。所以,如果长时间运行复杂的查询可能需要更多的内存,配置参数:
dbms.memory.transaction.global_max_size
- 「页面缓存」
页面缓存用于缓存磁盘中的Neo4j数据,把图数据和索引缓存到内存中有助于避免代价高昂的磁盘读写。页面缓存配置参数:
dbms.memory.pagecache.size
容量规划
为了更好的管理服务,最好显示的去设置页面缓存和堆大小参数,否则Neo4j在启动时将根据可用资源计算默认值。
页面缓存规划
使用bin/neo4j-admin memrec
命令估算页面缓存大小
neo4j-home> bin/neo4j-admin memrec
...
Total size of lucene indexes in all databases: 6690m
Total size of data and native indexes in all databases: 35050m
可以看到数据量和索引大小公共占用35GB左右,你估计数据量会有20%的增长,那么
代码语言:javascript复制dbms.memory.pagecache.size= 1.2 * (35GB) = 42GB
你可以配置成42GB。
事务内存规划
通过dbms.memory.transaction.global_max_size
设置所有事务使用的最大内存,合理配置该值,以免高事务负载期间发生OutOfMemory
。
为了提高多租户的稳定性,也可以设置每个事务的内存使用
dbms.memory.transaction.database_max_size
: 限制每个数据库的内存总量。dbms.memory.transaction.max_size
: 约束每个事务。
当达到内存使用总量限制时,事务将被终止,以保证数据库的整体运行状况。
你可以使用以下命令来查看当前的使用情况:
代码语言:javascript复制CALL dbms.listPools()
CALL dbms.listTransactions()
CALL dbms.listQueries()
索引配置
在 Neo4j 中有四种不同的索引类型:b-tree、full-text、 text和token lookup。
四种类型的索引都可以使用 Cypher 创建和删除,它们也都可以用于索引节点和关系。token lookup索引是数据库中默认存在的唯一索引。
B 树、文本和全文索引提供从属性值到实体(节点或关系)的映射。token lookup索引提供从标签到节点或从关系类型到关系的映射,而不是属性和实体之间的映射。
用户不需要知道各种索引之间的区别来使用它们,因为 Cypher 的查询计划器决定在什么情况下使用哪个索引。
垃圾收集器的优化
堆分为老年代和年轻代。新对象在年轻代中分配,然后如果它们保持活动(使用)足够长的时间,则稍后移动到老年代。当一代填满时,垃圾收集器会执行一次收集,在此期间进程中的所有其他线程都将暂停。由于暂停时间与对象的活动集相关,因此年轻代很快就会被收集起来。在老年代,暂停时间与堆的大小大致相关。出于这个原因,理想情况下,堆的大小和调整应该使事务和查询状态永远不会到达老年代。
堆大小使用neo4j.conf文件dbms.memory.heap.max_size
中的(以 MB 为单位)设置进行配置。堆的初始大小由设置或标志指定,或者如果未指定,则由 JVM 本身启发式选择。JVM 将根据需要自动增大堆,直至达到最大大小。堆的增长需要一个完整的垃圾回收周期。建议将初始堆大小和最大堆大小设置为相同的值。这样可以避免垃圾收集器增加堆时发生的暂停。dbms.memory.heap.initial_size``-Xms???m
如果新生代太小,短寿命的对象可能会过早地移动到老年代。这称为过早提升,会通过增加老年代垃圾回收周期的频率来减慢数据库速度。如果新生代太大,垃圾收集器可能会认为老年代没有足够的空间来容纳它希望从新代提升到老年代的所有对象。这将新一代垃圾回收周期转换为老一代垃圾回收周期,再次减慢数据库速度。运行更多的并发线程意味着在给定的时间跨度内可以进行更多的分配,这反过来又增加了特别是新一代的压力。
为了获得良好的性能,这些是首先要研究的事情:
- 确保 JVM 没有花费太多时间来执行垃圾收集。目标是拥有足够大的堆,以确保重载/峰值负载不会导致所谓的 GC-trashing。当 GC-trashing 发生时,性能可能会下降两个数量级。堆太大也可能会损害性能,因此您可能必须尝试一些不同的堆大小。
- Neo4j 需要足够的堆内存来处理事务状态和查询处理,还要为垃圾收集器留出一些空间。由于堆内存需求如此依赖于工作负载,因此堆内存配置通常从 1 GB 到 32 GB。
属性名称 | 含义 |
---|---|
dbms.memory.heap.initial_size | 初始堆大小(以 MB 为单位) |
dbms.memory.heap.max_size | 最大堆大小(以 MB 为单位) |
dbms.jvm.additional | 附加文字 JVM 参数 |
Bolt 线程池配置
Bolt 线程池具有最小和最大容量。它从可用线程的最小数量开始,然后根据工作负载增加到最大数量。空闲时间超过指定时间段的线程将停止并从池中删除,以释放资源。但是,池的大小永远不会低于最小值。
每个正在建立的连接都分配给连接器的线程池。空闲连接不会消耗服务器端的任何资源,并且会针对来自客户端的消息进行监视。到达连接的每条消息都会触发线程池中可用线程上的连接调度。如果所有可用线程都忙,并且仍有空间增长,则创建一个新线程并将连接移交给它进行处理。如果池容量已满,并且没有线程可用于处理,则拒绝作业提交并生成失败消息以通知客户端问题。
以下配置选项可用于配置 Bolt 线程池:
选项名称 | 默认 | 描述 |
---|---|---|
dbms.connector.bolt.thread_pool_min_size | 5 | 即使它们处于空闲状态,也将始终处于运行状态的最小线程数。 |
dbms.connector.bolt.thread_pool_max_size | 400 | 线程池将创建的最大线程数。 |
dbms.connector.bolt.thread_pool_keep_alive | 5m | 线程池在从池中杀死空闲线程之前将等待的持续时间。但是,线程数永远不会低于dbms.connector.bolt.thread_pool_min_size. |
Linux文件系统优化
数据库在查询数据时通常会产生许多小的随机读取,而在提交更改时会产生很少的顺序写入。为获得最佳性能,建议将数据库和事务日志存储在单独的物理设备上。
通常,推荐的做法是禁用文件和目录访问时间更新。这样,文件系统就不必发出更新此元数据的写入,从而提高写入性能。
由于数据库可以长时间对存储系统施加高且一致的负载,因此建议使用具有良好老化特性的文件系统。EXT4 和 XFS 文件系统均受支持。
随着时间的推移,高读写 I/O 负载也会降低 SSD 的性能。防止 SSD 磨损的第一道防线是确保工作数据集适合 RAM。但是,具有高写入工作负载的数据库仍会导致 SSD 磨损。解决这个问题的最简单方法是过度供应。使用比您严格要求的尺寸至少大 20% 的 SSD。
「Neo4j 不推荐也不支持使用 NFS 或 NAS 作为数据库存储。」
磁盘、内存 和 其他事项
磁盘
您的存储解决方案需要考虑许多性能特征。性能可以在数量级上有很大差异。通常,将所有数据保存在 RAM 中可以实现最佳性能。
如果您有多个可用的磁盘或持久性介质,最好将存储文件和事务日志划分到这些磁盘上。将存储文件保存在具有低寻道时间的磁盘上可以为读取操作创造奇迹。
dstat
当您的应用程序运行时,使用或之类的工具vmstat
来收集信息。如果交换或分页数很高,则表明数据库不太适合内存。在这种情况下,数据库访问可能会有很高的延迟。
「为了获得最大性能,建议为 Neo4j 提供尽可能多的 RAM 以避免磁盘读写」。
页面缓存
Neo4j 启动时,它的页面缓存是空的,需要预热。页面及其图形数据内容在查询需要时按需加载到内存中。这可能需要一段时间,尤其是对于大型商店。从驱动器读取许多块的时间很长以及 IO 等待时间较长的情况并不少见。这将在页面缓存指标中显示为页面错误的初始峰值。页面错误峰值之后是页面错误活动的逐渐下降,因为查询需要尚未在内存中的页面的可能性下降。
「Neo4j 企业版有一个称为活动页面缓存预热的功能,默认情况下通过dbms.memory.pagecache.warmup.enable
配置设置启用。」