ClickHouse 使用与表设计简介
与 PostgreSQL 的区别
intro 页面很好地概述了 ClickHouse。
ClickHouse 与传统的 OLTP(在线事务处理)数据库(如 PostgreSQL)有很多不同。底层架构有些差异,处理过程比传统数据库更依赖 CPU。 ClickHouse 是一个以日志为中心的数据库,不可变性是其关键组成部分。这种方法的优点已有充分记录;更多信息请参见 不可变数据存储的兴起。然而,这也使得更新变得更加困难。有关提供 UPDATE/DELETE 支持的操作,请参见 ClickHouse 文档。值得注意的是,这些操作应该是非频繁的。
在表设计时,这种区别很重要。要么:
- 不需要更新(最佳情况)
- 如果需要更新,则不应在查询执行期间运行。
ACID 兼容性
ClickHouse 对事务支持有稍微不同的概述,其中保证仅适用于插入到特定表的数据块。详细信息请参见 事务(ACID)支持 文档。
应避免在单个写入中进行多次插入,因为跨多个表的事务支持仅在物化视图中得到覆盖。
ClickHouse 致力于为分析查询提供一流的支持。像聚合这样的操作非常快速,并且有几个功能可以增强这些能力。 ClickHouse 有一些很好的博客文章涵盖了 聚合的详细信息。
主索引、排序索引和字典
强烈建议阅读 “ClickHouse 主索引的实用介绍” 以了解 ClickHouse 中的索引。
特别是 ClickHouse 中的数据库索引设计如何与像 PostgreSQL 这样的事务数据库中的索引 不同。
主索引设计在查询性能中扮演着非常重要的角色,应该仔细设计。几乎所有查询都应该依赖主索引,因为全数据扫描注定会花费更长时间。
阅读 查询中的主键和索引 文档,了解索引如何影响 MergeTree 表引擎(ClickHouse 的默认表引擎)中的查询性能。
ClickHouse 中的二级索引与其他系统中的索引不同。它们也称为数据跳过索引,因为它们用于跳过数据块。有关数据跳过索引的文档,请参见 文档。
ClickHouse 还提供 “字典”,可以用作外部索引。字典从内存加载,可用于在查询运行时查找值。
数据类型与分区
ClickHouse 提供 SQL 兼容的 数据类型 和一些专门的数据类型,如:
LowCardinality- UUID
- Maps
- Nested 很有意思,因为它模拟了列中的表。
在表设计时出现的一个关键设计方面是分区键。分区可以是任意表达式,但通常是时间间隔,如月、日或周。ClickHouse 采用最佳努力方法,通过使用最小的分区集来最小化读取的数据。
建议阅读:
分片与复制
分片是一种允许将数据拆分到多个 ClickHouse 节点以增加吞吐量和减少延迟的功能。分片功能使用由本地表支持的分布式引擎。分布式引擎是一个不存储任何数据的"虚拟"表。它用作插入和查询数据的接口。
参见 ClickHouse 文档 和关于 复制与分片 的这一部分。ClickHouse 可以使用 Zookeeper 或通过称为 ClickHouse Keeper 的组件使用自己的兼容 API 来维护共识。
节点设置完成后,它们对客户端可能不可见,读写查询可以发送到任何节点。
在大多数情况下,集群通常从固定数量的节点(~分片)开始。重新平衡分片 操作繁重,需要严格测试。
MergeTree 表引擎支持复制,有关如何定义复制的详细信息,请参见文档中的 复制部分。 ClickHouse 依赖分布式协调组件(Zookeeper 或 ClickHouse Keeper)来跟踪仲裁中的参与节点。复制是异步的且多主架构。插入可以发送到任何节点,它们可能会在其他节点上以一些延迟出现。如果需要,可以使用粘性到特定节点来确保读取观察到最新写入的数据。
物化视图
ClickHouse 的一个标志性特征是物化视图。功能上,它们类似于 ClickHouse 的插入触发器。
我们建议阅读官方文档中的 视图 部分,以更好地了解它们的工作原理。
引用 文档:
ClickHouse 中的物化视图实现更像插入触发器。 如果视图查询中有某些聚合,它只应用于新插入的数据批次。 源表现有数据的任何更改(如更新、删除、删除分区等)都不会改变物化视图。
安全且合理的默认设置
ClickHouse 实例应遵循以下安全建议:
用户
文件:users.xml 和 config.xml。
| 主题 | 安全要求 | 原因 |
|---|---|---|
user_name/password |
用户名不得为空。密码必须使用 password_sha256_hex 且不得为空。 |
plaintext 和 password_double_sha1_hex 不安全。如果未指定用户名,则使用 default 且无密码。 |
access_management |
使用服务器 配置文件 users.xml 和 config.xml。避免 SQL 驱动的工作流。 |
SQL 驱动的工作流意味着至少有一个用户拥有 access_management,这可以通过配置文件避免。考虑到 “您不能同时通过两种配置方法管理同一个访问实体。”,这些文件也更容易审计和监控。 |
user_name/networks |
必须设置至少一个 <ip>、<host> 或 <host_regexp>。不要使用 <ip>::/0</ip> 来开放任何网络的访问。 |
网络控制。(谨慎信任 原则) |
user_name/profile |
使用配置文件在多个用户上设置相似属性并设置限制(从用户界面)。 | 最小权限 原则和限制。 |
user_name/quota |
尽可能为用户设置配额。 | 限制一段时间内的资源使用或跟踪资源使用情况。 |
user_name/databases |
限制数据访问,避免拥有完全访问权限的用户。 | 最小权限 原则。 |
网络
文件:config.xml
| 主题 | 安全要求 | 原因 |
|---|---|---|
mysql_port |
除非绝对必要,否则禁用 MySQL 访问:<!-- <mysql_port>9004</mysql_port> -->。 |
关闭不必要的端口和功能暴露。(纵深防御 原则) |
postgresql_port |
除非绝对必要,否则禁用 PostgreSQL 访问:<!-- <mysql_port>9005</mysql_port> --> |
关闭不必要的端口和功能暴露。(纵深防御 原则) |
http_port/https_port & tcp_port/tcp_port_secure |
配置 SSL-TLS,并禁用非 SSL 端口:<!-- <http_port>8123</http_port> --><!-- <tcp_port>9000</tcp_port> -->并启用安全端口: <https_port>8443</https_port><tcp_port_secure>9440</tcp_port_secure> |
加密传输中的数据。(纵深防御 原则) |
interserver_http_host |
如果 ClickHouse 配置为集群,则禁用 interserver_http_host,改用 interserver_https_host (<interserver_https_port>9010</interserver_https_port>)。 |
加密传输中的数据。(纵深防御 原则) |
存储
| 主题 | 安全要求 | 原因 |
|---|---|---|
| 权限 | ClickHouse 默认以 clickhouse 用户运行。永远不需要以 root 运行。对文件夹使用最小权限原则:/etc/clickhouse-server、/var/lib/clickhouse、/var/log/clickhouse-server。这些文件夹必须属于 clickhouse 用户和组,并且没有其他系统用户可以访问它们。 |
默认密码、端口和规则是"开放门"。(安全失败并使用安全默认值 原则) |
| 加密 | 如果处理 RED 数据,请对日志和数据使用加密存储。在 Kubernetes 上,使用的 StorageClass 必须加密。GKE 和 EKS 已经对所有静态数据进行了加密。在这种情况下,使用自己的密钥是最佳选择,但不是必需的。 | 加密静态数据。(纵深防御) |
日志
| 主题 | 安全要求 | 原因 |
|---|---|---|
logger |
Log 和 errorlog 必须由 clickhouse 定义并可写入。 |
确保日志被存储。 |
| SIEM | 如果托管在 GitLab.com 上,ClickHouse 实例或集群必须将 日志报告到我们的 SIEM(内部链接)。 | GitLab 记录关键信息系统活动。 |
| 日志敏感数据 | 如果可能记录敏感数据,则必须使用查询屏蔽规则。参见 示例屏蔽规则。 | 列级加密 可能会在日志中泄露敏感数据(密钥)。 |
示例屏蔽规则
<query_masking_rules>
<rule>
<name>隐藏 SSN</name>
<regexp>(^|\D)\d{3}-\d{2}-\d{4}($|\D)</regexp>
<replace>000-00-0000</replace>
</rule>
<rule>
<name>隐藏加密/解密参数</name>
<regexp>
((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\'|.)+'|.*?)\s*\)
</regexp>
<replace>(???)</replace>
</rule>
</query_masking_rules>