跳至主要内容

·阅读时间:6 分钟

此示例演示了如何将属于不同 AD 安全组的 AD 用户授予 ClickHouse 中的角色访问权限。它还演示了如何将用户添加到多个 AD 用户组,以便他们可以获得多个角色提供的访问权限。

在此环境中,我们拥有以下内容

  • Windows Active Directory 域:marsnet2.local
  • 一个 ClickHouse 集群,cluster_1S_3R,在 1 分片 3 副本的集群配置上具有 3 个节点
  • 3 个 AD 用户
AD 用户描述
clickhouse_ad_adminClickHouse 管理员用户
clickhouse_db1_user具有对 db1.table1 访问权限的用户
clickhouse_db2_user具有对 db2.table1 访问权限的用户
ch_db1_db2_user具有对 db1.table1 和 db2.table1 访问权限的用户
  • 3 个 AD 安全组
AD 组描述
clickhouse_ad_adminsClickHouse 管理员组
clickhouse_ad_db1_users与对 db1.table1 的访问权限进行映射的组
clickhouse_ad_db2_users与对 db2.table1 的访问权限进行映射的组
  • 示例 AD 环境和 UO 结构

Example_AD_Env_and_UO_structure

  • 示例 AD 安全组配置

Example_AD_Group_clickhouse_ad_db1_users

  • 示例 AD 用户配置

Example_AD_user_clickhouse_db1_user

  1. 在 Windows AD 用户和组中,将每个用户添加到他们各自的组中,他们将被映射到 ClickHouse 角色(下一步中的示例)。
AD 安全组ClickHouse 角色
clickhouse_ad_adminclickhouse_ad_admins
clickhouse_db1_userclickhouse_ad_db1_users
clickhouse_db2_userclickhouse_ad_db2_users
ch_db1_db2_userclickhouse_ad_db1_users 和 clickhouse_ad_db2_users
  • 示例用户组成员资格

Example_AD_user_to_group

  1. 在 ClickHouse config.xml 中,将 ldap_servers 配置添加到每个 ClickHouse 节点。
<ldap_servers>
<marsnet2_ad>
<host>marsdc1.marsnet2.local</host>
<port>389</port>
<bind_dn>{user_name}@marsnet2.local</bind_dn>
<user_dn_detection>
<base_dn>OU=Users,OU=ClickHouse,DC=marsnet2,DC=local</base_dn>
<search_filter>(&amp;(objectClass=user)(sAMAccountName={user_name}))</search_filter>
</user_dn_detection>
<enable_tls>no</enable_tls>
</marsnet2_ad>
</ldap_servers>
xml 标签描述示例值
ldap_servers用于定义 ClickHouse 将使用的 ldap 服务器的标签NA
marsnet_ad此标签是任意的,只是用于在 <user_directories> 部分中标识服务器的标签NA
hostActive Directory 服务器或域的 FQDN 或 IP 地址marsdc1.marsnet2.local
portActive Directory 端口,通常对于非 SSL 为 389,对于 SSL 为 636389
bind_dn将用于创建 AD 绑定的用户,如果普通用户不允许,则可以是专用用户{user_name}@marsnet2.local
user_dn_detection有关 ClickHouse 如何查找 AD 用户的设置NA
base_dnAD OU 路径,用于开始搜索用户OU=Users,OU=ClickHouse,DC=marsnet2,DC=local
search_filterldap 搜索过滤器,用于查找 AD 用户(&(objectClass=user)(sAMAccountName={user_name}))

有关完整选项集,请参阅文档:https://clickhouse.ac.cn/docs/en/operations/external-authenticators/ldap#ldap-server-definition

  1. 在 ClickHouse config.xml 中,使用 <ldap> 条目添加 <user_directories> 配置,并将其添加到每个 ClickHouse 节点。
<user_directories>
<users_xml>
<path>users.xml</path>
</users_xml>
<local_directory>
<path>/var/lib/clickhouse/access/</path>
</local_directory>
<ldap>
<server>marsnet2_ad</server>
<role_mapping>
<base_dn>OU=Groups,OU=ClickHouse,DC=marsnet2,DC=local</base_dn>
<search_filter>(&amp;(objectClass=group)(member={user_dn}))</search_filter>
<attribute>CN</attribute>
<scope>subtree</scope>
<prefix>clickhouse_</prefix>
</role_mapping>
</ldap>
</user_directories>
xml 标签描述示例值
user_directories定义将使用哪些身份验证器NA
ldap包含将使用的 ldap 服务器的设置,在此 AD 中将被使用NA
server这是在 <ldap_servers> 部分中定义的标签marsnet2_ad
role_mapping关于如何将身份验证的用户在 AD 组和 ClickHouse 角色之间进行映射的定义NA
base_dn系统将使用 AD 路径来开始搜索 AD 组OU=Groups,OU=ClickHouse,DC=marsnet2,DC=local
search_filterldap 搜索过滤器,用于查找 AD 组(&(objectClass=group)(member={user_dn}))
attribute应使用哪个 AD 属性字段来标识用户CN
scope系统应在基本 DN 的哪些级别中搜索组subtree
prefixAD 中组名称的前缀,此前缀将被删除以在 ClickHouse 中查找角色clickhouse_

有关所有选项的完整说明,请参考文档:https://clickhouse.ac.cn/docs/en/operations/external-authenticators/ldap#ldap-external-user-directory

注意::: 由于示例中的 AD 安全组带有前缀(例如:clickhouse_ad_db1_users),因此当系统检索它们时,会删除前缀,系统将查找名为 ad_db1_users 的 ClickHouse 角色来映射到 clickhouse_ad_db1_users:::

  1. 创建示例数据库。
create database db1 on cluster 'cluster_1S_3R';
create database db2 on cluster 'cluster_1S_3R';
  1. 创建示例表。
create table db1.table1 on cluster 'cluster_1S_3R'
(
id Int32,
column1 String
)
engine = MergeTree()
order by id;

create table db2.table1 on cluster 'cluster_1S_3R
(
id Int32,
column1 String
)
engine = MergeTree()
order by id;
  1. 插入示例数据。
insert into db1.table1
values
(1, 'a');

insert into db2.table1
values
(2, 'b');
  1. 创建 ClickHouse 角色。
create role ad_admins on cluster 'cluster_1S_3R';
create role ad_db1_users on cluster 'cluster_1S_3R';
create role ad_db2_users on cluster 'cluster_1S_3R';
  1. 授予角色权限。
GRANT SHOW, SELECT, INSERT, ALTER, CREATE, DROP, UNDROP TABLE, TRUNCATE, OPTIMIZE, BACKUP, KILL QUERY, KILL TRANSACTION, MOVE PARTITION BETWEEN SHARDS, ACCESS MANAGEMENT, SYSTEM, dictGet, displaySecretsInShowAndSelect, INTROSPECTION, SOURCES, CLUSTER ON *.* on cluster 'cluster_1S_3R' TO ad_admins WITH GRANT OPTION;

GRANT SELECT ON db1.table1 on cluster 'cluster_1S_3R' TO ad_db1_users;

GRANT SELECT ON db2.table1 on cluster 'cluster_1S_3R' TO ad_db2_users;
  1. 测试限制的 db1 用户的访问权限。例如
root@chnode1:/etc/clickhouse-server# clickhouse-client --user clickhouse_db1_user --password MyPassword123  --secure --port 9440 --host chnode1.marsnet.local
ClickHouse client version 24.1.3.31 (official build).
Connecting to chnode1.marsnet.local:9440 as user clickhouse_db1_user.
Connected to ClickHouse server version 24.1.3.


clickhouse :) select * from db1.table1;

SELECT *
FROM db1.table1

Query id: b04b92d6-5b8b-40a2-a92a-f06f15774930

┌─id─┬─column1─┐
│ 1 │ a │
└────┴─────────┘

1 row in set. Elapsed: 0.004 sec.

clickhouse :) select * from db2.table1;

SELECT *
FROM db2.table1

Query id: 7f7eaa44-7b47-4184-807a-6968a56057ad


Elapsed: 0.115 sec.

Received exception from server (version 24.1.3):
Code: 497. DB::Exception: Received from chnode1.marsnet.local:9440. DB::Exception: clickhouse_db1_user: Not enough privileges. To execute this query, it's necessary to have the grant SELECT(id, column1) ON db2.table1. (ACCESS_DENIED)
  1. 测试具有访问 db1 和 db2 两个数据库权限的用户的访问权限。例如
root@chnode1:/etc/clickhouse-server# clickhouse-client --user ch_db1_db2_user --password MyPassword123  --secure --port 9440 --host chnode1.marsnet.local
ClickHouse client version 24.1.3.31 (official build).
Connecting to chnode1.marsnet.local:9440 as user ch_db1_db2_user.
Connected to ClickHouse server version 24.1.3.

clickhouse :) select * from db1.table1;

SELECT *
FROM db1.table1

Query id: 23084744-08c2-48bd-8635-a23438812026

┌─id─┬─column1─┐
│ 1 │ a │
└────┴─────────┘

1 row in set. Elapsed: 0.005 sec.

clickhouse :) select * from db2.table1;

SELECT *
FROM db2.table1

Query id: f9954ec4-d8d9-4b5a-9f68-a7aa79a1bb4a

┌─id─┬─column1─┐
│ 2 │ b │
└────┴─────────┘

1 row in set. Elapsed: 0.004 sec.
  1. 测试管理员用户的访问权限。例如
root@chnode1:/etc/clickhouse-server# clickhouse-client --user clickhouse_ad_admin --password MyPassword123  --secure --port 9440 --host chnode1.marsnet.local
ClickHouse client version 24.1.3.31 (official build).
Connecting to chnode1.marsnet.local:9440 as user clickhouse_ad_admin.
Connected to ClickHouse server version 24.1.3.

clickhouse :) create table db1.table2 on cluster 'cluster_1S_3R'
(
id Int32,
column1 String
)
engine = MergeTree()
order by id;

CREATE TABLE db1.table2 ON CLUSTER cluster_1S_3R
(
`id` Int32,
`column1` String
)
ENGINE = MergeTree
ORDER BY id

Query id: 6041fd32-4294-44bd-b442-3fdd41333e6f

┌─host──────────────────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐
│ chnode1.marsnet.local │ 9440 │ 0 │ │ 2 │ 2 │
└───────────────────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘
┌─host──────────────────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐
│ chnode2.marsnet.local │ 9440 │ 0 │ │ 1 │ 1 │
└───────────────────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘
┌─host──────────────────┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐
│ chnode3.marsnet.local │ 9440 │ 0 │ │ 0 │ 0 │
└───────────────────────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘

·阅读时间:6 分钟

概述: 本文将引导您完成将数据从 Kafka 主题发送到 ClickHouse 表的过程。我们将使用维基最近更改供稿,它提供了一个 事件流,代表对各种维基媒体属性的更改。步骤包括

  1. 如何在 Ubuntu 上设置 Kafka
  2. 将数据流导入 Kafka 主题
  3. 创建一个订阅该主题的 ClickHouse 表

1. 在 Ubuntu 上设置 Kafka

  1. 创建 Ubuntu ec2 实例并通过 SSH 连接到它
ssh -i ~/training.pem [email protected]
  1. 安装 Kafka(根据此处提供的说明:https://www.linode.com/docs/guides/how-to-install-apache-kafka-on-ubuntu/)
sudo apt update
sudo apt install openjdk-11-jdk

mkdir /home/ubuntu/kafka
cd /home/ubuntu/kafka/

wget https://downloads.apache.org/kafka/3.7.0/kafka_2.13-3.7.0.tgz

tar -zxvf kafka_2.13-3.7.0.tgz
  1. 启动 ZooKeeper
cd kafka_2.13-3.7.0
bin/zookeeper-server-start.sh config/zookeeper.properties
  1. 打开一个新的控制台并启动 Kafka
ssh -i ~/training.pem [email protected]
cd kafka/kafka_2.13-3.7.0/
bin/kafka-server-start.sh config/server.properties
  1. 打开第三个控制台并创建一个名为 wikimedia 的主题
ssh -i ~/training.pem [email protected]
cd kafka/kafka_2.13-3.7.0/

bin/kafka-topics.sh --create --topic wikimedia --bootstrap-server localhost:9092
  1. 您可以通过以下方式验证它是否已成功创建:
bin/kafka-topics.sh --list --bootstrap-server localhost:9092

2. 将维基媒体流导入 Kafka

  1. 我们首先需要一些实用程序
sudo apt-get install librdkafka-dev libyajl-dev
sudo apt-get install kafkacat
  1. 数据使用巧妙的 curl 命令发送到 Kafka,该命令获取最新的维基媒体事件,解析 JSON 数据并将数据发送到 Kafka 主题
curl -N https://stream.wikimedia.org/v2/stream/recentchange  | awk '/^data: /{gsub(/^data: /, ""); print}' | kafkacat -P -b localhost:9092 -t wikimedia
  1. 您可以“描述”主题
bin/kafka-topics.sh --describe --topic wikimedia --bootstrap-server localhost:9092
  1. 让我们通过消费一些事件来验证一切正常
bin/kafka-console-consumer.sh --topic wikimedia --from-beginning --bootstrap-server localhost:9092
  1. Ctrl+c 结束上一个命令。

3. 将数据导入 ClickHouse

  1. 以下是传入数据的示例
{
"$schema": "/mediawiki/recentchange/1.0.0",
"meta": {
"uri": "https://www.wikidata.org/wiki/Q45791749",
"request_id": "f64cfb17-04ba-4d09-8935-38ec6f0001c2",
"id": "9d7d2b5a-b79b-45ea-b72c-69c3b69ae931",
"dt": "2024-04-18T13:21:21Z",
"domain": "www.wikidata.org",
"stream": "mediawiki.recentchange",
"topic": "eqiad.mediawiki.recentchange",
"partition": 0,
"offset": 5032636513
},
"id": 2196113017,
"type": "edit",
"namespace": 0,
"title": "Q45791749",
"title_url": "https://www.wikidata.org/wiki/Q45791749",
"comment": "/* wbsetqualifier-add:1| */ [[Property:P1545]]: 20, Modify PubMed ID: 7292984 citation data from NCBI, Europe PMC and CrossRef",
"timestamp": 1713446481,
"user": "Cewbot",
"bot": true,
"notify_url": "https://www.wikidata.org/w/index.php?diff=2131981357&oldid=2131981341&rcid=2196113017",
"minor": false,
"patrolled": true,
"length": {
"old": 75618,
"new": 75896
},
"revision": {
"old": 2131981341,
"new": 2131981357
},
"server_url": "https://www.wikidata.org",
"server_name": "www.wikidata.org",
"server_script_path": "/w",
"wiki": "wikidatawiki",
"parsedcomment": "<span dir=\"auto\"><span class=\"autocomment\">Added qualifier: </span></span> <a href=\"/wiki/Property:P1545\" title=\"series ordinal | position of an item in its parent series (most frequently a 1-based index), generally to be used as a qualifier (different from &quot;rank&quot; defined as a class, and from &quot;ranking&quot; defined as a property for evaluating a quality).\"><span class=\"wb-itemlink\"><span class=\"wb-itemlink-label\" lang=\"en\" dir=\"ltr\">series ordinal</span> <span class=\"wb-itemlink-id\">(P1545)</span></span></a>: 20, Modify PubMed ID: 7292984 citation data from NCBI, Europe PMC and CrossRef"
}
  1. 我们将需要 Kafka 表引擎从 Kafka 主题中提取数据
CREATE OR REPLACE TABLE wikiQueue
(
`id` UInt32,
`type` String,
`title` String,
`title_url` String,
`comment` String,
`timestamp` UInt64,
`user` String,
`bot` Bool,
`server_url` String,
`server_name` String,
`wiki` String,
`meta` Tuple(uri String, id String, stream String, topic String, domain String)
)
ENGINE = Kafka(
'ec2.compute.amazonaws.com:9092',
'wikimedia',
'consumer-group-wiki',
'JSONEachRow'
);
  1. 出于某种原因,Kafka 表引擎似乎会获取公共 ec2 URL 并将其转换为私有 DNS 名称,因此我不得不将其添加到本地 /etc/hosts 文件中
52.14.154.92  ip.us-east-2.compute.internal
  1. 您可以从 Kafka 表中读取数据,只需启用一个设置
SELECT *
FROM wikiQueue
LIMIT 20
FORMAT Vertical
SETTINGS stream_like_engine_allow_direct_select = 1;

这些行应根据 wikiQueue 表中定义的列进行良好的解析

id:          2473996741
type: edit
title: File:Père-Lachaise - Division 6 - Cassereau 05.jpg
title_url: https://commons.wikimedia.org/wiki/File:P%C3%A8re-Lachaise_-_Division_6_-_Cassereau_05.jpg
comment: /* wbcreateclaim-create:1| */ [[d:Special:EntityPage/P921]]: [[d:Special:EntityPage/Q112327116]], [[:toollabs:quickstatements/#/batch/228454|batch #228454]]
timestamp: 1713457283
user: Ameisenigel
bot: false
server_url: https://commons.wikimedia.org
server_name: commons.wikimedia.org
wiki: commonswiki
meta: ('https://commons.wikimedia.org/wiki/File:P%C3%A8re-Lachaise_-_Division_6_-_Cassereau_05.jpg','01a832e2-24c5-4ccb-bd93-8e2c0e429418','mediawiki.recentchange','eqiad.mediawiki.recentchange','commons.wikimedia.org')
  1. 我们需要一个 MergeTree 表来存储这些传入事件
CREATE TABLE rawEvents (
id UInt64,
type LowCardinality(String),
comment String,
timestamp DateTime64(3, 'UTC'),
title_url String,
topic LowCardinality(String),
user String
)
ENGINE = MergeTree
ORDER BY (type, timestamp);
  1. 让我们定义一个物化视图,该视图在 Kafka 表上发生插入时触发,并将数据发送到我们的 rawEvents
CREATE MATERIALIZED VIEW rawEvents_mv TO rawEvents
AS
SELECT
id,
type,
comment,
toDateTime(timestamp) AS timestamp,
title_url,
tupleElement(meta, 'topic') AS topic,
user
FROM wikiQueue
WHERE title_url <> '';
  1. 您应该几乎立即看到数据进入 rawEvents
SELECT count()
FROM rawEvents;
  1. 让我们查看一些行
SELECT *
FROM rawEvents
LIMIT 5
FORMAT Vertical
Row 1:
──────
id: 124842852
type: 142
comment: Pere prlpz commented on "Plantilles Enciclopèdia Catalana" (Diria que no cal fer res als articles. Es pot actualitzar els enllaços que es facin servir a les referències (tot i que l'antic encara ha...)
timestamp: 2024-04-18 16:22:29.000
title_url: https://ca.wikipedia.org/wiki/Tema:Wu36d6vfsiuu4jsi
topic: eqiad.mediawiki.recentchange
user: Pere prlpz

Row 2:
──────
id: 2473996748
type: categorize
comment: [[:File:Ruïne van een poortgebouw, RP-T-1976-29-6(R).jpg]] removed from category
timestamp: 2024-04-18 16:21:20.000
title_url: https://commons.wikimedia.org/wiki/Category:Pieter_Moninckx
topic: eqiad.mediawiki.recentchange
user: Warburg1866

Row 3:
──────
id: 311828596
type: categorize
comment: [[:Cujo (película)]] añadida a la categoría
timestamp: 2024-04-18 16:21:21.000
title_url: https://es.wikipedia.org/wiki/Categor%C3%ADa:Pel%C3%ADculas_basadas_en_obras_de_Stephen_King
topic: eqiad.mediawiki.recentchange
user: Beta15

Row 4:
──────
id: 311828597
type: categorize
comment: [[:Cujo (película)]] eliminada de la categoría
timestamp: 2024-04-18 16:21:21.000
title_url: https://es.wikipedia.org/wiki/Categor%C3%ADa:Trabajos_basados_en_obras_de_Stephen_King
topic: eqiad.mediawiki.recentchange
user: Beta15

Row 5:
──────
id: 48494536
type: categorize
comment: [[:braiteremmo]] ajoutée à la catégorie
timestamp: 2024-04-18 16:21:21.000
title_url: https://fr.wiktionary.org/wiki/Cat%C3%A9gorie:Wiktionnaire:Exemples_manquants_en_italien
topic: eqiad.mediawiki.recentchange
user: Àncilu bot
  1. 让我们看看有哪些类型的事件正在传入
SELECT
type,
count()
FROM rawEvents
GROUP BY type
   ┌─type───────┬─count()─┐
1. │ 142 │ 1 │
2. │ new │ 1003 │
3. │ categorize │ 12228 │
4. │ log │ 1799 │
5. │ edit │ 17142 │
└────────────┴─────────┘

让我们定义一个连接到当前物化视图的物化视图。我们将跟踪每分钟的一些汇总统计信息

CREATE TABLE byMinute
(
`dateTime` DateTime64(3, 'UTC') NOT NULL,
`users` AggregateFunction(uniq, String),
`pages` AggregateFunction(uniq, String),
`updates` AggregateFunction(sum, UInt32)
)
ENGINE = AggregatingMergeTree
ORDER BY dateTime;

CREATE MATERIALIZED VIEW byMinute_mv TO byMinute
AS SELECT
toStartOfMinute(timestamp) AS dateTime,
uniqState(user) AS users,
uniqState(title_url) AS pages,
sumState(toUInt32(1)) AS updates
FROM rawEvents
GROUP BY dateTime;
  1. 我们将需要 -Merge 函数来查看结果
SELECT
dateTime AS dateTime,
uniqMerge(users) AS users,
uniqMerge(pages) AS pages,
sumMerge(updates) AS updates
FROM byMinute
GROUP BY dateTime
ORDER BY dateTime DESC
LIMIT 10;

·1 分钟阅读

为了构建和为 ClickHouse 做出贡献,您必须使用 LLVMClang.

以下是构建 Linux 上最新版本的 LLVM 和 Clang 的命令

git clone [email protected]:llvm/llvm-project.git
mkdir llvm-build
cd llvm-build
cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DLLVM_ENABLE_PROJECTS=all -DLLVM_TARGETS_TO_BUILD=all ../llvm-project/llvm
time ninja
sudo ninja install

·3 分钟阅读

当存在具有相似命名约定和相似列但未复制的表时,这非常有用。例如,在系统数据库中搜索查询日志表中的条目。

query_log 表未复制,并且仅执行特定节点上的查询才会记录。数据也可能滚动到不同的表。例如,数据可能会插入到 query_log_0query_log_1 等中。由于一个节点可能比其他节点更早滚动,因此在查找我们正在查找的数据时,尝试查找不在同一名称的表中很有用。

本质上,我们需要做类似的事情,但使用 ClickHouse 语法

SELECT column1, column2 FROM my_db.my_table_*

为此,我们可以使用 clusterAllReplicas() 搜索所有节点,并使用 merge() 表函数来使用正则表达式模式搜索多个表。

以下示例显示如何查询所有以 query_log 为前缀的表

clickhouse-cloud :) SELECT 
`event_time`,
`query_id`,
`query`,
`type`
FROM
clusterAllReplicas(default,merge('system', '^query_log*'))
WHERE
query ilike '%db1.table1%' and event_time > now() - toIntervalMinute(5);

SELECT
event_time,
query_id,
query,
type
FROM clusterAllReplicas(default, merge('system', '^query_log*'))
WHERE (query ILIKE '%db1.table1%') AND (event_time > (now() - toIntervalMinute(5)))

Query id: de95c13e-5759-436e-90d9-a12c1327889e

┌──────────event_time─┬─query_id─────────────────────────────┬─query──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─type────────┐
│ 2024-02-08 00:15:20 │ d1dd0d6a-4337-4e58-bdd1-c2c827b6dfe2 │ /* ddl_entry=query-0000000428 */ CREATE TABLE db1.table1 UUID '781f25db-3cd1-47c6-a76e-701945a67485' (`id` Int32, `string_column` String) ENGINE = ReplicatedMergeTree ORDER BY id │ QueryStart │
│ 2024-02-08 00:15:20 │ d1dd0d6a-4337-4e58-bdd1-c2c827b6dfe2 │ /* ddl_entry=query-0000000428 */ CREATE TABLE db1.table1 UUID '781f25db-3cd1-47c6-a76e-701945a67485' (`id` Int32, `string_column` String) ENGINE = ReplicatedMergeTree ORDER BY id │ QueryFinish │
└─────────────────────┴──────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────┘
┌──────────event_time─┬─query_id─────────────────────────────┬─query──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─type────────┐
│ 2024-02-08 00:15:20 │ f0ca43b2-544e-4b94-a21d-0f05e777fa96 │ /* ddl_entry=query-0000000428 */ CREATE TABLE db1.table1 UUID '781f25db-3cd1-47c6-a76e-701945a67485' (`id` Int32, `string_column` String) ENGINE = ReplicatedMergeTree ORDER BY id │ QueryStart │
│ 2024-02-08 00:15:20 │ f0ca43b2-544e-4b94-a21d-0f05e777fa96 │ /* ddl_entry=query-0000000428 */ CREATE TABLE db1.table1 UUID '781f25db-3cd1-47c6-a76e-701945a67485' (`id` Int32, `string_column` String) ENGINE = ReplicatedMergeTree ORDER BY id │ QueryFinish │
└─────────────────────┴──────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────┘
┌──────────event_time─┬─query_id─────────────────────────────┬─query──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─type────────┐
│ 2024-02-08 00:15:20 │ 5cc0a508-7f64-460b-a5be-949ef1d1f2ca │ /* ddl_entry=query-0000000428 */ CREATE TABLE db1.table1 UUID '781f25db-3cd1-47c6-a76e-701945a67485' (`id` Int32, `string_column` String) ENGINE = ReplicatedMergeTree ORDER BY id │ QueryStart │
│ 2024-02-08 00:15:20 │ 5cc0a508-7f64-460b-a5be-949ef1d1f2ca │ /* ddl_entry=query-0000000428 */ CREATE TABLE db1.table1 UUID '781f25db-3cd1-47c6-a76e-701945a67485' (`id` Int32, `string_column` String) ENGINE = ReplicatedMergeTree ORDER BY id │ QueryFinish │
│ 2024-02-08 00:15:20 │ d1e01cb0-a27c-44b2-829c-90fb2596c9c0 │ create table db1.table1
(
id Int32,
string_column String
)
engine = MergeTree
order by id │ QueryStart │
│ 2024-02-08 00:15:20 │ d1e01cb0-a27c-44b2-829c-90fb2596c9c0 │ create table db1.table1
(
id Int32,
string_column String
)
engine = MergeTree
order by id │ QueryFinish │
│ 2024-02-08 00:15:27 │ 6c2c6c3f-173e-464f-bfa0-643089ca085e │ insert into db1.table1
values
│ QueryStart │
│ 2024-02-08 00:15:27 │ 6c2c6c3f-173e-464f-bfa0-643089ca085e │ insert into db1.table1
values
│ QueryFinish │
└─────────────────────┴──────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────┘

10 rows in set. Elapsed: 0.046 sec. Processed 317.27 thousand rows, 33.57 MB (6.89 million rows/s., 729.43 MB/s.)
Peak memory usage: 67.04 MiB.

请注意,您选择的列必须存在于要查询的每个表中,否则您可能会遇到错误,例如

Received exception from server (version 24.0.2):
Code: 47. DB::Exception: Received from abc123.us-west-2.aws.clickhouse.cloud:9440. DB::Exception: Missing columns: 'hostname' while processing query: 'WITH 'query_log_0' AS _table

·2 分钟阅读

在 ClickHouse Cloud 中创建的字典在初始创建阶段可能会出现不一致。这意味着您可能在创建后立即在字典中看不到任何数据。但是,在多次重试后,创建查询可能会落在不同的副本上,并且数据将可见。

这有时会发生,因为字典是在分区到达服务器之前创建的。例如

2024-01-25 13:38:25.615837 - CREATE DICTIONARY received
2024-01-25 13:38:25.626468 - CREATE DICTIONARY finished
2024-01-25 13:38:25.733008 - Part all_0_0_0 downloaded

如您所见,分区仅在字典创建后到达。如果您使用 LIFETIME(MIN 0 MAX 0),这可能会是一个更大的问题,因为这意味着字典永远不会自动刷新。因此,字典将保持为空,直到执行 RELOAD DICTIONARIES 命令。

解决此问题的方法是在创建字典时使用 SELECT 查询,而不是指定源表,并启用设置 select_sequential_consistency=1

不要指定源表

SOURCE(CLICKHOUSE(
table 'test.temp_title_table_1706189903924'
user default password 'PASSWORD'))

使用带有 select_sequential_consistency=1SELECT 查询

SOURCE(CLICKHOUSE(QUERY
'SELECT songTitle, mappedTitle
FROM test.temp_title_table_1706189903924
SETTINGS select_sequential_consistency=1' USER default PASSWORD ''))

为什么会出现此问题?

当您插入数据,然后创建或重新加载字典时,DDL 可能会在数据(或新数据)之前到达副本。这会导致副本之间的字典不一致。然后,根据接收查询的副本,您可能会得到不同的结果。

请注意,当您插入数据并在其后立即从表中读取数据时也会发生同样的情况。如果您从尚未复制数据的副本中读取,您将看不到新插入的数据。当您需要顺序一致性时,以性能为代价(这就是通常不建议使用它的原因),您可以启用 select_sequential_consistency

字典的情况有点棘手,因为字典不使用查询中的设置,而是使用服务器中的设置。因此,在将数据加载到字典中时,即使您 SET select_sequential_consistency=1,数据也可能会在副本之间不一致地加载。在字典源查询中指定 select_sequential_consistency=1 允许字典即使未在全局范围内作为服务器设置启用,也能遵守此设置。

·3 分钟阅读

问题

如何备份 ClickHouse 中的特定分区?

答案

请参阅以下示例,它使用我们 docker compose 示例 页面中列出的 S3(Minio) 磁盘 配置

注意

这并不适用于 ClickHouse Cloud

创建表

ch_minio_s3 :) CREATE TABLE my_table
(
`event_time` DateTime,
`field_foo` String,
`field_bar` String,
`number` UInt256
)
ENGINE = MergeTree
PARTITION BY number % 2
ORDER BY tuple()

CREATE TABLE my_table
(
`event_time` DateTime,
`field_foo` String,
`field_bar` String,
`number` UInt256
)
ENGINE = MergeTree
PARTITION BY number % 2
ORDER BY tuple()

Query id: a1a54a5a-eac0-477c-b847-b40acaa62780

Ok.

0 rows in set. Elapsed: 0.016 sec.

添加一些数据,这些数据将平均填充两个分区

ch_minio_s3 :) INSERT INTO my_table SELECT
toDateTime(now() + number) AS event_time,
randomPrintableASCII(10) AS field_foo,
randomPrintableASCII(20) AS field_bar,
number
FROM numbers(1000000)

INSERT INTO my_table SELECT
toDateTime(now() + number) AS event_time,
randomPrintableASCII(10) AS field_foo,
randomPrintableASCII(20) AS field_bar,
number
FROM numbers(1000000)

Query id: bf6ef803-5747-4ea1-ad00-a17967e349b6

Ok.

0 rows in set. Elapsed: 0.282 sec. Processed 1.00 million rows, 8.00 MB (3.55 million rows/s., 28.39 MB/s.)

验证数据

ch_minio_s3 :) SELECT
_partition_id AS partition_id,
cityHash64(sum(number)) AS hash,
count() AS count
FROM my_table
GROUP BY partition_id

SELECT
_partition_id AS partition_id,
cityHash64(sum(number)) AS hash,
count() AS count
FROM my_table
GROUP BY partition_id

Query id: d8febfb0-5339-4f97-aefa-ef0003128526

┌─partition_id─┬─cityHash64(sum(number))─┬──count─┐
015460940821314360342500000
111827822647069388611500000
└──────────────┴─────────────────────────┴────────┘

2 rows in set. Elapsed: 0.025 sec. Processed 1.00 million rows, 32.00 MB (39.97 million rows/s., 1.28 GB/s.)

将 ID 为 1 的分区备份到配置的 s3 磁盘

ch_minio_s3 :) BACKUP TABLE my_table PARTITION 1 TO Disk('s3','backups/');

BACKUP TABLE my_table PARTITION 1 TO Disk('s3', 'backups/')

Query id: 810f6144-e282-42e2-99d0-9a80c75a927d

┌─id───────────────────────────────────┬─status─────────┐
4d1da197-c4c9-4b6e-966c-76202eadbd53 │ BACKUP_CREATED │
└──────────────────────────────────────┴────────────────┘

1 row in set. Elapsed: 0.095 sec.

删除表

ch_minio_s3 :) DROP TABLE my_table

DROP TABLE my_table

Query id: c3456044-4689-406e-82ac-8d08b8b618fe

Ok.

0 rows in set. Elapsed: 0.007 sec.

从备份中恢复 ID 为 1 的分区

ch_minio_s3 :) RESTORE TABLE my_table PARTITION 1 FROM Disk('s3','backups/');

RESTORE TABLE my_table PARTITION 1 FROM Disk('s3', 'backups/')

Query id: ea306c73-83c5-479f-9c0c-391594facc69

┌─id───────────────────────────────────┬─status───┐
│ ec6841a8-0607-465e-bc4d-d446f960d40a │ RESTORED │
└──────────────────────────────────────┴──────────┘

1 row in set. Elapsed: 0.065 sec.

验证恢复的数据

ch_minio_s3 :) SELECT
_partition_id AS partition_id,
cityHash64(sum(number)) AS hash,
count() AS count
FROM my_table
GROUP BY partition_id

SELECT
_partition_id AS partition_id,
cityHash64(sum(number)) AS hash,
count() AS count
FROM my_table
GROUP BY partition_id

Query id: a916176d-6a6e-47fc-ba7d-79bb33b152d8

┌─partition_id─┬─────────────────hash─┬──count─┐
111827822647069388611500000
└──────────────┴──────────────────────┴────────┘

1 row in set. Elapsed: 0.012 sec. Processed 500.00 thousand rows, 16.00 MB (41.00 million rows/s., 1.31 GB/s.)

·2 分钟阅读

问题

我将数据写入 ClickHouse cloud,并且需要在读取数据时能够保证我获取的是最新的完整信息。

答案

连接到同一节点

如果您使用的是本机协议或会话来进行写入/读取操作,那么您应该连接到同一个副本:在这种情况下,您直接从写入数据的节点读取,那么您的读取将始终一致。

连接到随机节点

如果您无法保证连接到同一节点(例如,通过 HTTPS 调用连接到节点,这些调用通过负载均衡器进行洗牌),您可以选择以下两种方式:

A)

  1. 写入您的数据
  2. 连接到一个新的副本
  3. 运行 SYSTEM SYNC REPLICA db.table_name LIGHTWEIGHT
  4. 读取最新数据

请参阅 SYSTEM 命令 参考

或者

B) 随时使用顺序一致性进行读取

SELECT 
...
SETTINGS select_sequential_consistency = 1

请注意,当使用 ClickHouse Cloud 及其默认 SharedMergeTree 引擎时,不需要使用 insert_quorum_parallel(这是给定的)

使用 SYSTEM SYNC REPLICASselect_sequential_consistency 会增加 ClickHouse Keeper 的负载,并且可能会降低性能,具体取决于服务上的负载。

建议的方法是使用相同的会话或本机协议(粘性连接)来进行写入/读取操作。

·2 分钟阅读

如果您使用 brew 安装 ClickHouse,您可能会遇到来自 MacOS 的错误。默认情况下,MacOS 不会运行由无法验证的开发者创建的应用程序或工具。当尝试运行任何 clickhouse 命令时,您可能会看到以下错误

MacOS showing a developer verification error.

要解决此验证错误,您需要从 MacOS 的隔离 bin 中删除该应用程序,方法是找到 系统设置窗口 中的相应设置,或 使用终端

系统设置过程

从隔离 bin 中删除 clickhouse 可执行文件的最简单方法是:

  1. 打开 系统设置

  2. 导航到 隐私和安全性

  3. 滚动到窗口底部,找到一条消息,内容为“_clickhouse-macos-aarch64_ 被阻止使用,因为它不是来自已识别开发者的”。

  4. 点击 仍然允许

  5. 输入您的 MacOS 用户密码。

您现在应该可以在终端中运行 clickhouse 命令。

终端过程

您可以使用命令行执行此过程

首先找到 Homebrew 安装 clickhouse 可执行文件的位置

which clickhouse

这将输出类似以下内容:

/opt/homebrew/bin/clickhouse

通过运行 xattr -d com.apple.quarantine,然后跟随上一个命令中的路径,从隔离 bin 中删除 clickhouse

xattr -d com.apple.quarantine /opt/homebrew/bin/clickhouse

您现在应该可以运行 clickhouse 可执行文件

clickhouse

这将输出类似以下内容:

Use one of the following commands:
clickhouse local [args]
clickhouse client [args]
clickhouse benchmark [args]
...

·2 分钟阅读

这是一个基本的代码片段文件 main.ts

Package.json(将其放在 ./ 下)

{
"name": "a simple clickhouse client example",
"version": "1.0.0",
"main": "main.js",
"license": "MIT",
"devDependencies": {
"typescript": "^5.3.2"
},
"dependencies": {
"@clickhouse/client": "^0.2.6"
}
}

Main.ts(将其放在 ./src 下)

import { ClickHouseClient, createClient } from '@clickhouse/client'; // or '@clickhouse/client-web'

interface ClickHouseResultSet<T> {
meta: Meta[];
data: T[];
rows: number;
statistics: Statistics;
}

interface Statistics {
elapsed: number;
rows_read: number;
bytes_read: number;
}

interface Meta {
name: string;
type: string;
}

interface Count {
c: number;
}

//Please replace client connection parameters like`host`
//`username`, `passowrd`, `database` as needed.

const initClickHouseClient = async (): Promise<ClickHouseClient> => {
const client = createClient({
host: 'https://FQDN.aws.clickhouse.cloud',
username: 'default',
password: 'password',
database: 'default',
application: `pingpong`,
});

console.log('ClickHouse ping');
if (!(await client.ping())) {
throw new Error('failed to ping clickhouse!');
}
console.log('ClickHouse pong!');
return client;
};

const main = async () => {
console.log('Initialising clickhouse client');
const client = await initClickHouseClient();

const row = await client.query({
query: `SELECT count() AS c FROM system.tables WHERE database='system'`,
});

const jsonRow: ClickHouseResultSet<Count> = await row.json();

console.log(`I have found ${jsonRow.data[0].c} system tables!`);

await client.close();
console.log(`👋`);
};

main();

要安装软件包,请从 ./ 运行 yarn

$ yarn
yarn install v1.22.19
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...
✨ Done in 0.14s.

使用以下命令从 ./ 执行 main.ts 中的代码:

$ npx ts-node src/main.ts

将输出

Initialising clickhouse client
ClickHouse ping
ClickHouse pong!
I have found 120 system tables!
👋

·2 分钟阅读

背景

如果您不喜欢 clickhouse client 在您的终端窗口中显示的提示,您可以通过创建一个 XML 文件来更改它。本文将解释如何将提示更改为您想要的任何内容。

默认提示是您的本地计算机名称后跟 :)

但是,您可以根据需要编辑提示

步骤

要编辑提示,请按照以下步骤操作

  1. 找到您 clickhouse 可执行文件存储的位置,并在同一目录中创建一个名为 custom-config.xml 的文件

    ./
    ├── clickhouse
    ├── custom-config.xml
    ...
    ├── user_scripts
    └── uuid
  2. custom-config.xml 中粘贴以下代码

    <?xml version="1.0" ?>
    <clickhouse>
    <prompt_by_server_display_name>
    <default>CUSTOM_PROMPT_HERE</default>
    </prompt_by_server_display_name>
    </clickhouse>
  3. CUSTOM_PROMPT_HERE 替换为您想要的提示。您必须将提示保持在打开和关闭 <default> 标签之间的单行

    <?xml version="1.0" ?>
    <clickhouse>
    <prompt_by_server_display_name>
    <default>local_clickhouse_client $> </default>
    </prompt_by_server_display_name>
    </clickhouse>
  4. 保存 custom-config.xml 文件。

  5. 如果 Clickhouse 服务器尚未运行,请启动它。

    ./clickhouse server
  6. 在新的终端窗口中,使用 --config-file=custom-config.xml 参数启动 Clickhouse 客户端。

    ./clickhouse client --config-file="custom-config.xml"
  7. Clickhouse 客户端应打开并显示您的自定义提示。