Skip to main content

在内存中存储字典

有多种方法可以将字典存储在内存中。

我们建议 , 散列complex_key_hashed. 其提供最佳的处理速度。

不建议使用缓存,因为性能可能较差,并且难以选择最佳参数。 阅读更多的部分 “缓存”.

有几种方法可以提高字典性能:

  • 调用该函数以使用后的字典 GROUP BY.
  • 将要提取的属性标记为"注射"。 如果不同的属性值对应于不同的键,则称为注射属性。 所以当 GROUP BY 使用由键获取属性值的函数,此函数会自动取出 GROUP BY.

ClickHouse为字典中的错误生成异常。 错误示例:

  • 无法加载正在访问的字典。
  • 查询错误 cached 字典

您可以查看外部字典的列表及其状态 system.dictionaries 桌子

配置如下所示:

<clickhouse>
<dictionary>
...
<layout>
<layout_type>
<!-- layout settings -->
</layout_type>
</layout>
...
</dictionary>
</clickhouse>

相应的 DDL-查询:

CREATE DICTIONARY (...)
...
LAYOUT(LAYOUT_TYPE(param value)) -- layout settings
...

在内存中存储字典的方法

字典以平面数组的形式完全存储在内存中。 字典使用多少内存? 量与最大键的大小(在使用的空间中)成正比。

字典键具有 UInt64 类型和值限制为500,000。 如果在创建字典时发现较大的键,ClickHouse将引发异常,不会创建字典。

支持所有类型的来源。 更新时,数据(来自文件或表)将完整读取。

此方法在存储字典的所有可用方法中提供了最佳性能。

配置示例:

<layout>
<flat />
</layout>

LAYOUT(FLAT())

散列

该字典以哈希表的形式完全存储在内存中。 字典中可以包含任意数量的带有任意标识符的元素,在实践中,键的数量可以达到数千万项。

支持所有类型的来源。 更新时,数据(来自文件或表)将完整读取。

配置示例:

<layout>
<hashed />
</layout>

LAYOUT(HASHED())

sparse_hashed

类似于 hashed,但使用更少的内存,有利于更多的CPU使用率。

配置示例:

<layout>
<sparse_hashed />
</layout>
LAYOUT(SPARSE_HASHED())

complex_key_hashed

这种类型的存储是用于复合 . 类似于 hashed.

配置示例:

<layout>
<complex_key_hashed />
</layout>
LAYOUT(COMPLEX_KEY_HASHED())

range_hashed

字典以哈希表的形式存储在内存中,其中包含有序范围及其相应值的数组。

此存储方法的工作方式与散列方式相同,除了键之外,还允许使用日期/时间(任意数字类型)范围。

示例:该表格包含每个广告客户的折扣,格式为:

+---------|-------------|-------------|------+
| advertiser id | discount start date | discount end date | amount |
+===============+=====================+===================+========+
| 123 | 2015-01-01 | 2015-01-15 | 0.15 |
+---------|-------------|-------------|------+
| 123 | 2015-01-16 | 2015-01-31 | 0.25 |
+---------|-------------|-------------|------+
| 456 | 2015-01-01 | 2015-01-15 | 0.05 |
+---------|-------------|-------------|------+

要对日期范围使用示例,请定义 range_minrange_max 中的元素 结构. 这些元素必须包含元素 nametype (如果 type 如果没有指定,则默认类型将使用-Date)。 type 可以是任何数字类型(Date/DateTime/UInt64/Int32/others)。

示例:

<structure>
<id>
<name>Id</name>
</id>
<range_min>
<name>first</name>
<type>Date</type>
</range_min>
<range_max>
<name>last</name>
<type>Date</type>
</range_max>
...

CREATE DICTIONARY somedict (
id UInt64,
first Date,
last Date
)
PRIMARY KEY id
LAYOUT(RANGE_HASHED())
RANGE(MIN first MAX last)

要使用这些字典,您需要将附加参数传递给 dictGetT 函数,为其选择一个范围:

dictGetT('dict_name', 'attr_name', id, date)

此函数返回指定的值 ids和包含传递日期的日期范围。

算法的详细信息:

  • 如果 id 未找到或范围未找到 id,它返回字典的默认值。
  • 如果存在重叠范围,则可以使用任意范围。
  • 如果范围分隔符是 NULL 或无效日期(如1900-01-01或2039-01-01),范围保持打开状态。 范围可以在两侧打开。

配置示例:

<clickhouse>
<dictionary>

...

<layout>
<range_hashed />
</layout>

<structure>
<id>
<name>Abcdef</name>
</id>
<range_min>
<name>StartTimeStamp</name>
<type>UInt64</type>
</range_min>
<range_max>
<name>EndTimeStamp</name>
<type>UInt64</type>
</range_max>
<attribute>
<name>XXXType</name>
<type>String</type>
<null_value />
</attribute>
</structure>

</dictionary>
</clickhouse>

CREATE DICTIONARY somedict(
Abcdef UInt64,
StartTimeStamp UInt64,
EndTimeStamp UInt64,
XXXType String DEFAULT ''
)
PRIMARY KEY Abcdef
RANGE(MIN StartTimeStamp MAX EndTimeStamp)

缓存

字典存储在具有固定数量的单元格的缓存中。 这些单元格包含经常使用的元素。

搜索字典时,首先搜索缓存。 对于每个数据块,所有在缓存中找不到或过期的密钥都从源请求,使用 SELECT attrs... FROM db.table WHERE id IN (k1, k2, ...). 然后将接收到的数据写入高速缓存。

对于缓存字典,过期 使用寿命 可以设置高速缓存中的数据。 如果更多的时间比 lifetime 自从在单元格中加载数据以来,单元格的值不被使用,并且在下次需要使用时重新请求它。 这是存储字典的所有方法中最不有效的。 缓存的速度在很大程度上取决于正确的设置和使用场景。 缓存类型字典只有在命中率足够高(推荐99%或更高)时才能表现良好。 您可以查看平均命中率 system.dictionaries 桌子

要提高缓存性能,请使用以下子查询 LIMIT,并从外部调用字典函数。

支持 来源:MySQL的,ClickHouse的,可执行文件,HTTP.

设置示例:

<layout>
<cache>
<!-- The size of the cache, in number of cells. Rounded up to a power of two. -->
<size_in_cells>1000000000</size_in_cells>
</cache>
</layout>

LAYOUT(CACHE(SIZE_IN_CELLS 1000000000))

设置足够大的缓存大小。 你需要尝试选择细胞的数量:

  1. 设置一些值。
  2. 运行查询,直到缓存完全满。
  3. 使用评估内存消耗 system.dictionaries 桌子
  4. 增加或减少单元数,直到达到所需的内存消耗。

!!! warning "警告" 不要使用ClickHouse作为源,因为处理随机读取的查询速度很慢。

complex_key_cache

这种类型的存储是用于复合 . 类似于 cache.

直接

字典不存储在内存中,并且在处理请求期间直接转到源。

字典键具有 UInt64 类型。

所有类型的 来源,除了本地文件,支持。

配置示例:

<layout>
<direct />
</layout>

LAYOUT(DIRECT())

ip_trie

这种类型的存储用于将网络前缀(IP地址)映射到ASN等元数据。

示例:该表包含网络前缀及其对应的AS号码和国家代码:

  +-----------|-----|------+
| prefix | asn | cca2 |
+=================+=======+========+
| 202.79.32.0/20 | 17501 | NP |
+-----------|-----|------+
| 2620:0:870::/48 | 3856 | US |
+-----------|-----|------+
| 2a02:6b8:1::/48 | 13238 | RU |
+-----------|-----|------+
| 2001:db8::/32 | 65536 | ZZ |
+-----------|-----|------+

使用此类布局时,结构必须具有复合键。

示例:

<structure>
<key>
<attribute>
<name>prefix</name>
<type>String</type>
</attribute>
</key>
<attribute>
<name>asn</name>
<type>UInt32</type>
<null_value />
</attribute>
<attribute>
<name>cca2</name>
<type>String</type>
<null_value>??</null_value>
</attribute>
...
</structure>
<layout>
<ip_trie>
<access_to_key_from_attributes>true</access_to_key_from_attributes>
</ip_trie>
</layout>

CREATE DICTIONARY somedict (
prefix String,
asn UInt32,
cca2 String DEFAULT '??'
)
PRIMARY KEY prefix

该键必须只有一个包含允许的IP前缀的字符串类型属性。 还不支持其他类型。

对于查询,必须使用相同的函数 (dictGetT 与元组)至于具有复合键的字典:

dictGetT('dict_name', 'attr_name', tuple(ip))

该函数采用任一 UInt32 对于IPv4,或 FixedString(16) 碌莽禄Ipv6拢IPv6:

dictGetString('prefix', 'asn', tuple(IPv6StringToNum('2001:db8::1')))

还不支持其他类型。 该函数返回与此IP地址对应的前缀的属性。 如果有重叠的前缀,则返回最具体的前缀。

数据存储在一个 trie. 它必须完全适合RAM。