跳到主要内容

内存使用

对于大规模生产场景,需要对 Garnet 的内存使用进行调优,以最佳方式利用机器上可用的内存。在这里,我们讨论内存的组成部分以及如何对其进行调优。配置参数在此处列出。

Garnet 设计为 Tsavorite 的两个存储实例:主存储和对象存储。每个实例的内存都独立配置。如果您只使用原始字符串(例如 GETSET 及其变体)、HYPERLOGLOGBITMAP 命令,则应使用 DisableObjects (--no-obj) 参数禁用对象存储。这将避免为对象存储浪费内存。

主存储

主存储使用的内存由三个部分的和组成

  • 索引大小
  • 日志大小
  • 溢出桶

索引大小

索引大小使用 IndexSize (-i--index) 参数配置。它指定索引在主内存中占用的总大小(以字节为单位)。索引组织为哈希桶,每个桶长 64 字节,即缓存行的大小。桶包含 7 个条目和一个指向溢出桶的指针,如下所述

索引大小的经验法则是:如果您预计缓存存储将容纳 K 个键,则将大小设置为 K * 16 字节。其原因如下:

  • 我们希望桶平均半满,因此每个桶大约有 4 个键
  • 因此,对于 K 个键,我们需要 K / 4 个桶
  • 每个桶占用 64 字节
  • 因此,总大小为 64 * (K / 4) = K * 16 字节

日志大小

上述索引不包含键或值。相反,键和值都存储在称为混合日志的单独结构中。日志占用的内存使用 MemorySize (-m--memory) 配置。

内存组织为页面的循环缓冲区,每个页面的大小使用 PageSize (-p--page) 配置。页面大小控制您可以存储的最大键或值大小,因为记录需要完全适应页面。

Garnet 主存储中的记录由以下部分组成:

  • 一个 8 字节的头,称为 RecordInfo,它包含元数据和记录链中前一个条目的逻辑地址。
  • 键,它由一个 8 字节的头和键字节 (SpanByte) 组成
  • 值,它由一个 8 字节的头和值字节 (SpanByte) 组成

溢出桶

每个哈希桶有 7 个条目(槽),用于存储日志中存储的记录链的根。如果给定键的哈希桶已满,我们会溢出到动态分配的额外桶(称为溢出桶)中。虽然这些桶无法控制或限制,但它们通常非常小,可以忽略。如果您的索引大小设置得太小,它们可能会占用更多空间,为了解决这个问题,您可以动态增长索引,如下所述

自动调整大小的索引

您可以配置 Garnet,使其在索引填满时自动增长索引(每次翻倍)。这通过配置 IndexResizeFrequencySecs (--index-resize-freq) 来指定触发调整大小检查的频率。如果溢出桶的数量超过主哈希桶总数的指定百分比,则会触发索引增长。此阈值使用 IndexResizeThreshold (--index-resize-threshold) 指定。

我们还支持 IndexMaxSize (--index-max-size),它指定索引将增长到的最大大小。目前我们不支持索引大小缩小。

对象存储

索引和溢出桶的管理方式与主存储类似,相应的参数为

  • ObjectStoreIndexSize (--obj-index)
  • ObjectStoreIndexMaxSize (--obj-index-max-size)

但是,日志内存的处理方式不同,如下所述。

日志大小(对象存储)

对于对象存储,混合日志保存键和值(它们是对象)的引用,而不是实际的键和值本身。对象存储日志占用的内存使用 ObjectStoreLogMemorySize (--obj-log-memory) 配置。但是,此参数仅控制对象存储中的记录数量,其中每条记录包含

  • 一个 8 字节的头,称为 RecordInfo,它包含元数据和记录链中前一个条目的逻辑地址。
  • 一个 8 字节的键对象引用,它是堆上的字节数组 (byte[])
  • 一个 8 字节的值对象引用,它是一个 IGarnetObject 实例,可以是不同对象类型,如 SortedSet、Hash、Set 等。

换句话说,当您设置 ObjectStoreLogMemorySize 时,您只控制内存中的记录数量,而不控制对象的总内存使用量。具体来说,由于每条记录长 24 字节,将 ObjectStoreLogMemorySize 设置为 S 仅意味着您最多可以在主内存中保存 S / 24 条记录。

这意味着,当然,我们需要使用不同的机制来跟踪总内存。为此,Garnet 公开了一个名为 ObjectStoreHeapMemorySize (--obj-heap-memory) 的配置,它表示键字节数组和 IGarnetObject 实例在堆内存中占用的字节数。您可以将此参数与 --obj-log-memory 结合使用,以控制对象存储使用的总内存。

总而言之,对象存储占用的总空间是以下各项的总和:

  • 对象存储索引大小(和溢出桶),与以前一样
  • ObjectStoreLogMemorySize (--obj-log-memory),它控制内存中记录的最大数量
  • ObjectStoreHeapMemorySize (--obj-heap-memory),它控制对象占用的总堆大小。

读取缓存

读取缓存有助于将记录从磁盘引入内存中的单独读取缓存区域,而不会增长主日志。这有助于在读取已在磁盘上的记录时避免额外的 I/O。有关读取缓存内部机制的更多详细信息,请参见此处

读取缓存可以为主存储或对象存储或同时为两者独立启用。使用 --readcache 选项为主存储启用它,使用 --obj-readcache 选项为对象存储启用它。

以下配置选项可用于控制读取缓存的内存利用率

  • --readcache-page 有助于控制主存储读取缓存中每个页面的大小。
  • --readcache-memory 控制主存储读取缓存的总大小。
  • --obj-readcache-page 指定对象存储读取缓存中每个页面的大小。
  • --obj-readcache-log-memory 控制对象存储读取缓存日志内存的总大小,这是控制内存中最大记录数量的方式。
  • --obj-readcache-heap-memory 控制读取缓存中对象占用的总堆大小。