代码改变世界

ClickHouse 的 Mutations

2024-10-15 10:45  abce  阅读(137)  评论(0编辑  收藏  举报

mutations

ALTER 查询是通过一种称为 "mutations" 的机制实现的,其中最常见的是 ALTER TABLE ... DELETE 和 ALTER TABLE ... UPDATE。它们是在后台异步进行的,类似于 MergeTree 表中的合并。

 

对于 *MergeTree 表,mutations 是通过重写所有的 part 来执行的。mutations 不具有原子性。

 

mutations 完全是按照 part 的创建顺序进行应用的。会部分按照 INSERT INTO 查询的顺序:mutations 之前插入表中的数据将被应用,而在此之后插入的数据将不会被应用。请注意,mutations 不会以任何方式阻止插入。

 

添加 mutations 条目后,mutations 查询会立即返回。mutations 本身使用系统配置文件设置异步执行。可以使用 system.mutations 表跟踪进度。即使重新启动 ClickHouse 服务器,已成功提交的 mutations 仍将继续执行。mutations 一旦提交,就无法回滚,但如果因某种原因卡住,可以使用 KILL MUTATION 命令来取消。

 

已完成的 mutations 条目不会立即删除(保留条目的数量由 finished_mutations_to_keep 存储引擎参数决定)。较旧的突变条目会被删除。

 

ClickHouse 支持 mutations 频率较高的操作,并提供多种功能来帮助用户管理这些操作。例如,推荐的用于时间序列数据的 MergeTree 引擎内置了重复数据删除和版本控制功能。这意味着你可以在不覆盖现有数据的情况下插入新数据,避免 mutations 并保留历史数据。此外,ClickHouse 还提供版本管理功能,允许你保留同一行的多个版本,这样你就可以在不覆盖现有数据的情况下插入新数据。

ALTER 查询的同步性

对于非复制表,所有 ALTER 查询都是同步执行的。对于复制表,查询只是向 ZooKeeper 添加相应操作的指令,操作本身会尽快执行。不过,ALTER 查询可以等待这些操作在所有复制表上完成。

 

对于创建 mutations的 ALTER 查询(例如:包括但不限于 UPDATE、DELETE、MATERIALIZE INDEX、MATERIALIZE PROJECTION、MATERIALIZE COLUMN、APPLY DELETED MASK、CLEAR STATISTIC、MATERIALIZE STATISTIC),同步性由 mutations_sync 设置来定义。

 

对于其他只修改元数据的 ALTER 查询,可以使用 alter_sync 设置来设置等待。

 

通过 replication_wait_for_inactive_replica_timeout 设置,可以指定等待非活动副本执行所有 ALTER 查询的时间(以秒为单位)。

 

注意

对于所有 ALTER 查询,如果 alter_sync = 2,且某些副本未活跃的时间超过 replication_wait_for_inactive_replica_timeout 设置中指定的时间,则会出现 UNFINISHED 异常。

 

表的mutations 是并行还是串行

ClickHouse 按顺序运行 mutations,但它可以将多个 mutations 合并为一个,并在一次合并中应用所有mutations。有时,当 ClickHouse 需要执行的合并表达式变得非常大时,这可能会导致问题(如果 ClickHouse 将数千个 mutations 合并在一个表达式中)。

 

由于 ClickHouse 将数据存储在独立的 part 中,因此 ClickHouse 能够为每个 part 独立并行运行 mutations 合并。如果你在 mutations 中使用 x IN (SELECT ... FROM big_table)语句,这也会导致资源利用很高,尤其是内存使用率,因为每次合并都会运行并在内存中保留其自己的 HashSet。如果在这种mutations中使用字典方法,就可以避免这个问题。

 

并行控制的参数设置:

SELECT name,value,description
FROM system.merge_tree_settings
WHERE name LIKE '%mutation%'

┌─name──────────────────────────────────────────────────────┌─value──────┬─description────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ max_replicated_mutations_in_queue                         │ 8          │ How many tasks of mutating parts are allowed simultaneously in ReplicatedMergeTree queue.                                                                                                      │
│ number_of_free_entries_in_pool_to_execute_mutation        │ 20         │ When there is less than specified number of free entries in pool, do not execute part mutations. This is to leave free threads for regular merges and avoid "Too many parts"                   │
│ max_number_of_mutations_for_replica                       │ 0          │ Limit the number of part mutations per replica to the specified amount. Zero means no limit on the number of mutations per replica (the execution can still be constrained by other settings). │
│ replicated_max_mutations_in_one_entry                     │ 10000      │ Max number of mutation commands that can be merged together and executed in one MUTATE_PART entry (0 means unlimited)                                                                          │
│ number_of_mutations_to_delay                              │ 500        │ If table has at least that many unfinished mutations, artificially slow down mutations of table. Disabled if set to 0                                                                          │
│ number_of_mutations_to_throw                              │ 1000       │ If table has at least that many unfinished mutations, throw 'Too many mutations' exception. Disabled if set to 0                                                                               │
│ finished_mutations_to_keep                                │ 100        │ How many records about mutations that are done to keep. If zero, then keep all of them.                                                                                                        │
│ zero_copy_merge_mutation_min_parts_size_sleep_before_lock │ 1073741824 │ If zero copy replication is enabled sleep random amount of time before trying to lock depending on parts size for merge or mutation                                                            │
└───────────────────────────────────────────────────────────└────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

 

监控mutations

整个过程是异步的,有可能会花费很长的时间。也可能会遇到错误,并阻塞后面的 mutations。因此,很有必要监控它,从而决定是取消还是重新应用。尤其是表上有很多 ALTER、UPDATE的时候。

 

监控是通过查询表 system.mutations 实现的。每个mutation 会在表中产生一条记录。

 

比如,如果有大量的 mutations 在执行的时候,可以通过以下语句查看数量:

SELECT
   hostname() AS host,
   count()
   FROM clusterAllReplicas('default', 'system.mutations') WHERE not is_done
   GROUP BY host;

查看是否有错误发生:

SELECT
    hostname() AS host,
    count()
FROM clusterAllReplicas('default', 'system.mutations')
WHERE (latest_fail_reason <> '' OR toUInt64(latest_fail_time) <> 0)
GROUP BY host

查看正在运行的 mutations:

SELECT
    hostname() AS host,
    count()
FROM clusterAllReplicas('default', 'system.mutations')
WHERE is_done = 0 AND create_time < now() - INTERVAL 24 HOUR
GROUP BY host

以上查询都是假设,你的集群名称是 default。如果不是,需要修改成自己的集群名称。如果要想确认是否所有的节点都已成功结束,需要在每个节点单独执行查询。因为表 system.mutations 是本地表。

 

如果没有集群,可以使用以下的查询:

SELECT
   hostname() AS host,
   count()
   FROM system.mutations WHERE not is_done
   GROUP BY host;

 

如何避免 mutations

1.使用适当的数据模型: 确保有一个设计良好的数据模型,将数据分隔成适当的表和列。这将有助于防止数据意外 mutations。

2.使用只读用户: 如果需要只允许某些用户读取数据,可以使用只读用户来避免意外 mutations。该用户只能选择数据,但不能更新、删除或插入数据。

3.使用 modify_date_column_name 设置: 在执行可能会更新数据的查询时,可以使用 modify_date_column_name 查询显式设置时间戳列,该列将以当前时间更新。

4.使用 FINAL 关键字:在创建表时,可以使用 FINAL 关键字来阻止对表定义的任何进一步修改,这将有助于避免任何意外 mutations。

5.使用 MergeTree 引擎:

在处理时间序列数据或需要避免 mutations 时,建议使用 MergeTree 引擎,该引擎内置重复数据删除和版本控制功能,这意味着插入新数据时无需覆盖现有数据。

6.使用版本管理功能: ClickHouse 具有版本控制功能,允许你保留同一行的多个版本,这意味着你可以在不覆盖现有数据的情况下插入新数据,这将帮助你避免意外mutations。

7.使用只读属性:如果想防止特定表发生意外 mutations,可以使用 readonly 属性将表设置为只读,这将防止对该表进行任何更新、删除或插入操作。

8.使用数据库触发器:可以使用数据库触发器来防止某些 mutations 的发生,例如,可以使用触发器来防止对特定表进行删除操作。