内存使用
对于大规模生产场景,需要对 Garnet 的内存使用进行调优,以最佳方式利用机器上可用的内存。在这里,我们讨论内存的组成部分以及如何对其进行调优。配置参数在此处列出。
Garnet 设计为 Tsavorite 的两个存储实例:主存储和对象存储。每个实例的内存都独立配置。如果您只使用原始字符串(例如 GET
、SET
及其变体)、HYPERLOGLOG
和 BITMAP
命令,则应使用 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
控制读取缓存中对象占用的总堆大小。