clickhouse分布式查询降级为本地查询

news2024/9/23 7:26:27

在基于 clickhouse 做类数仓建模时通常的做法是在本地创建物化视图,然后使用分布式表做代理对外提供服务。我们知道 clickhouse 对于 DQL 内部实现了分布式,而对于 DDL 则需要我们自动实现比如:

drop table table_name on cluster cluster_name;

来实现分布式 DDL,但对于

select count() from distributed_table_name;

此类分布式表的查询会自动执行分布式查询并在查询入口节点汇总最终数据,即 MPP 架构。

image-20230727094657201

在 clickhouse 中本地表通常以 local 结尾,而代理它们的分布式表通常以 all 结尾(只是规范)

但在一些特殊情况下,上述的分布式查询会降级为本地查询,此时对分布式表的查询会随机路由到一张代理的本地表中导致该 SQL 的返回结果在反复横跳即为诡异

image-20230727094948720

一、问题复现

1.1 知识点复习

这里先复习一下本文所涉及到的知识点或概念:副本和分片

在 clickhouse 中用户可以将所有节点按照自己的需求组成不同应用场景的集群,可以将所有节点组成一个单一集群也可以划分成多个小集群。副本和分片的区别在于:

  1. 从功能层面说:副本是防止数据丢失,增加数据冗余;分片是实现数据的水平切分,提高查询效率
  2. 从数据层面说:副本之间的数据是完全相同的;分片之间的数据是完全不同的

clickhouse 目前支持副本的表引擎只有 MergeTree Family,其格式为 Replicated*MergeTree,同时副本表需要依赖 zookeeper 实现个节点数据同步的协同工作,这里配置集群、zookeeper以及复制表是如何工作的就不做过多介绍。集群的信息通过下面的命令查看

select * from system.clusters;

结果如下

image-20230727102133634

创建复制表需要在原 MergeTree 表的基础上额外指定 zookeeper 路径(分片信息)和副本信息

engine = ReplicatedMergeTree(path:String, replica:String [, columns:any])

path一致表示它们是同一个分片,后面的 replica 标记为不同的副本,通常 replica 填写本机 ip 或主机名

下面创建一个 3 分片 2 副本的 MergeTree 表作为本次问题复现的 ODS 表

create table event_local on cluster cluster
(
    ldtime Datetime,
    ip     IPv4
) engine = ReplicatedMergeTree('/clickhouse/tables/default/{shard}/event_local', '{replica}')
      order by ldtime
      partition by toYYYYMMDD(ldtime);

说明:

{shard}{replica}是 clickhouse 的宏,在配置文件中指定的可以通过下面的 sql 查看

select *
from system.macros;

结果如下

image-20230727105208951

同时 path 也存在一定的约束,通常是/clickhouse/tables/数据库名称/分片编号/表名

on cluster cluster是标记该 DDL 为分布式 DDL 且最后的 cluster 为集群名称上面截图所示,因此该 DDL 语句会被发送到 cluster 的集群中所有节点去执行,省去了我们手动去各个节点执行的步骤。同时该分布式 DDL 也会展示所有节点的响应,执行 SQL 的入口节点会等待所有节点创建完将信息返回用户(默认最大等待时间为 180s,超过后会转入后台进行)

当然也可以使用 remote 查询所有节点宏信息

select *
from remote('chi-settings-01-cluster-0-0', 'system', 'macros', 'username', 'password')
where macro in ('replica', 'shard')
union all
select *
from remote('chi-settings-01-cluster-0-1', 'system', 'macros', 'username', 'password')
where macro in ('replica', 'shard')
union all
select *
from remote('chi-settings-01-cluster-1-0', 'system', 'macros', 'username', 'password')
where macro in ('replica', 'shard')
union all
select *
from remote('chi-settings-01-cluster-1-1', 'system', 'macros', 'username', 'password')
where macro in ('replica', 'shard')
union all
select *
from remote('chi-settings-01-cluster-2-0', 'system', 'macros', 'username', 'password')
where macro in ('replica', 'shard')
union all
select *
from remote('chi-settings-01-cluster-2-1', 'system', 'macros', 'username', 'password')
where macro in ('replica', 'shard');

随机向三个分片中写入一定量的数据,可以观察副本数据的同步

# chi-settings-01-cluster-0-0
insert into event_local
values (toDateTime('2023-07-01 00:00:00'), '192.168.0.1'),
       (toDateTime('2023-07-01 00:00:00'), '192.168.0.1'),
       (toDateTime('2023-07-01 00:00:00'), '192.168.0.1'),
       (toDateTime('2023-07-02 00:00:00'), '192.168.0.2');
# chi-settings-01-cluster-1-1
insert into event_local
values (toDateTime('2023-07-03 00:00:00'), '192.168.0.3'),
       (toDateTime('2023-07-03 00:00:00'), '192.168.0.3'),
       (toDateTime('2023-07-04 00:00:00'), '192.168.0.4');
# chi-settings-01-cluster-2-0
insert into event_local
values (toDateTime('2023-07-05 00:00:00'), '192.168.0.5'),
       (toDateTime('2023-07-06 00:00:00'), '192.168.0.6');

根据分片的特性 event 全量数据应该是各个分片的 compact 因此我们需要一张分布式表来代理所有的副本分片表

create table event_all
(
    ldtime Datetime,
    ip     IPv4
) engine = Distributed('{cluster}', 'default', 'event_local', rand());

rand() 为分片健,当数据通过分布式表写入时,会根据分片健将数据写入不同的分片中。分片健要求是一个整型数值,可以是表字段或返回整型的函数。

注:生产中分布式表通常只做查请求,不做写请求。因为如果对分布式表执行写请求每条数据都需要在 clickhouse 中计算所属分片效率不高,建议的做法是业务测做”分库分表“将数据直接写入对应的本地表不让 clickhouse 来做自动分片。

1.2 复现问题

对 event_all 查询结果如下

select * from event_all order by ldtime;

2023-07-01 00:00:00,192.168.0.1
2023-07-01 00:00:00,192.168.0.1
2023-07-01 00:00:00,192.168.0.1
2023-07-02 00:00:00,192.168.0.2
2023-07-03 00:00:00,192.168.0.3
2023-07-03 00:00:00,192.168.0.3
2023-07-04 00:00:00,192.168.0.4
2023-07-05 00:00:00,192.168.0.5
2023-07-06 00:00:00,192.168.0.6

该 SQL 是执行了分布式查询,对于副本表 clickhouse 会按照一定策略读取其中一个副本读取数据,具体的副本表读写流程不是这篇文章重点。下面开始复现问题

简化一下需求,实时统计每个 ip 出现的个数。正常数仓可能会这么做

create materialized view mv_ip_count
            engine = SummingMergeTree(num)
                order by ip
            populate
as
select ip, count() as num
from event_all
group by ip;

192.168.0.1,3
192.168.0.2,1
192.168.0.3,2
192.168.0.4,1
192.168.0.5,1
192.168.0.6,1

这样的结果是没有问题的,但是生产中不是所有的需求都可以这么做。因为之所以创建分布式表是因为数据量很大,需要做分片。那么对于处理后的数据量依然很大的需求我们就不能通过查询分布式表来创建物化视图,因为这样做该物化视图所有的数据都会存储在该节点上

为了解决这个问题就需要各个节点去统计自己存储在本地的数据,然后再创建一张分布式表对外提供统一的服务,sql 如下

create materialized view mv_ip_count_local on cluster cluster
            engine = SummingMergeTree(num)
                order by ip
            populate
as
select ip, count() as num
from event_local
group by ip;

create table mv_ip_count_all
(
    ip  IPv4,
    num UInt64
) engine = Distributed('{cluster}', 'default', 'mv_ip_count_local', rand());

当我们用 mv_ip_count_all 进行二次聚合时

select sum(num) from mv_ip_count_all;

其值在 4、3、2 间反复横跳,正确答案应该是 9。这就很纳闷了

二、问题解决

这种情况定位出来就是触发了本地查询,反复横跳的数据是各个节点本地查询结果。产生这个问题的原因是下面这个 sql

create materialized view mv_ip_count_local on cluster cluster
            engine = SummingMergeTree(num)
                order by ip
            populate
as
select ip, count() as num
from event_local
group by ip;

首先这个 sql 会被分布式执行,但是 event_local 它是一个副本表,该 sql 执行结束后每个节点都会有一张毫无关联的 SummingMergeTree 表,此时我们又创建了分布式表来代理,造成的后果就是分布式表的数据量暴增 n 倍,n 为副本个数。

当我们基于分布式表做二次聚合时,clickhouse 或许也发现了这个问题,如果按照正常的 mpp 架构来执行这个 sql 得到的结果就是正确个数的 n 倍,显然这样错误的结果是 clickhouse 无法接受的,因此它将分布式 sql 降级为本地 sql 如文章开头的图二流程。

或许 clickhouse 觉得这样的错误结果比 mpp 流程得到的结果要更容易接受吧,虽然这两个结果都存在问题

分析到这里解决方案也就浮出水面了,就是让 SummingMergeTree 保持和 event_local 一样的副本分片关系即可,即创建具有副本功能的 SummingMergeTree 表

create materialized view mv_ip_count_local
            on cluster cluster
            engine = ReplicatedSummingMergeTree('/clickhouse/tables/default/{shard}/mv_ip_count_local', '{replica}', num)
                order by ip
as
select ip, count() as num
from event_local
group by ip;

分布式表正常创建即可,最终问题解决。

三、总结

出现这个问题本质上是对 clickhouse 的副本分片表、分布式表不熟练,同时 clickhouse 在赋予用户极高自由度的同时也给用户带来了很多心智负担。clickhouse 分布式表还存在很多错误的使用方式,例如基于分布式表做聚合或连接操作时存在大坑,这点后面有时间可以单独出一篇博客来说明。虽然在使用过程中产生过很多匪夷所思的结果,但当分析完原因并成功解决后又会觉得 clickhouse 这么做很合理,它依然是一款极其优秀且强大的 olap 数据库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/798930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【6】toLocaleString、toLocaleDateString、toLocaleTimeString等toLocale系列方法的使用及案例

一、介绍 在当今前端开发的领域里,快速、高效的项目构建工具以及使用最新技术栈是非常关键的。ViteVue3 组合为一体的项目实战示例专栏将带领你深入了解和掌握这一最新的前端开发工具和框架。 作为下一代前端构建工具,Vite 在开发中的启动速度和热重载…

医学多模态综述

原文链接:https://arxiv.org/abs/2307.07362 一个医学多模态综述,本人搞分割的重点看了一下分割的,其余任务没时间细看我就截了个模型汇总图,想详细了解的去喵一下上面这个论文就行 数据集汇总 Report generation 报告生成 报告…

效率与质量兼备的6个设计工具!

今天本文为大家推荐的这6个设计工具,将帮助设计师实现高效工作,同时也更好地展示自己的创作力,一起来看看吧! 1、即时设计 即时设计是一款国内的设计工具,它为设计师提供了非常多实用的设计功能和精致的设计素材&…

变分自编码器VAE代码

一,Auto-Encoder(AE) 自编码器的目的是自己训练自己,他的输入和输出是一样的。比如28*28的黑白手写数字图片(单通道),如果使用矩阵形式进行表达,真正有作用的特征是哪些数值为1的地方,以及他们在矩阵空间的位置。而大部…

Android13导入OpenCV4.8流程

网上看了很多流程都不能正常导入OpenCV,有一堆报错,而且还有限制,我记一下我的处理方式 首先这个博客主要解决以下问题 1、不需要项目选择native C,使用最新的OpenCV库 2、不用复制文件,不需要手动改动项目自身的bu…

亲测解决Git inflate: data stream error (incorrect data check)

Git inflate: data stream error (incorrect data check) error: unable to unpack… 前提是你的repository在github等服务器或者其他路径有过历史备份/副本,不要求是最新版本的,只要有就可能恢复你做的所有工作。 执行git fsck --full检查损坏的文件 在…

享元模式-提供统一实现对象的复用

下围棋时,分为黑白棋子。棋子都一样,这是出现的位置不同而已。如果将每个棋子都作为一个独立的对象存储在内存中,将导致内存空间消耗较大。我们可以将其中不变的部分抽取出来,只存储它的位置信息来实现节约内存。 图 围棋 1 享元模…

Linux-DHCP安装配置流程

DHCP 介绍 DHCP(Dynamic Host Configuration Protocol)是一种网络协议,用于在局域网(LAN)中自动分配IP地址和其他网络配置信息给计算机设备。DHCP旨在简化网络管理,允许设备自动获取IP地址,无需手动配置,…

哈工大计算机网络课程局域网详解之:无线局域网

哈工大计算机网络课程局域网详解之:无线局域网 文章目录 哈工大计算机网络课程局域网详解之:无线局域网IEEE 802.11无线局域网802.11体系结构802.11:信道与AP关联 本节介绍一下平时经常使用的一个无线局域网技术,也就是通常我们使…

《机器学习公式推导与代码实现》chapter22-EM算法

《机器学习公式推导与代码实现》学习笔记,记录一下自己的学习过程,详细的内容请大家购买作者的书籍查阅。 EM算法 作为一种迭代算法,EM算法(expectation maximization,期望极大值算法)用于包含隐变量的概率模型参数的极大似然估…

devops(后端)

1.前言 该devpos架构为gitlabjenkinsharbork8s,项目是java项目,流程为从gitlab拉取项目代码到jenkins,jenkins通过maven将项目代码打成jar包,通过dockerfile构建jdk环境的镜像并把jar包放到镜像中启动,构建好的镜像通…

springboot运行报错Failed to load ApplicationContext for xxx

Failed to load ApplicationContext for报错解决方法 报错Failed to load ApplicationContext for 报错Failed to load ApplicationContext for 网上找了一堆方法都尝试了还是没用 包括添加mapperScan,添加配置类 配置pom文件 [外链图片转存失败,源站可能有防盗链机…

com.android.ide.common.signing.KeytoolException:

签名没问题但是提示Execution failed for task :app:packageDebug. > A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable > com.android.ide.common.signing.KeytoolException: Failed to read ke…

21.2:象棋走马问题

请同学们自行搜索或者想象一个象棋的棋盘, 然后把整个棋盘放入第一象限,棋盘的最左下角是(0,0)位置 那么整个棋盘就是横坐标上9条线、纵坐标上10条线的区域 给你三个 参数 x,y,k 返回“马”从(0,0)位置出发,必须走k步 …

数据结构—串

4.1串 4.1.1串的定义 串(String)——零个或多个任意字符组成的有限序列 S"a1 a2...an"串的定义——几个术语 子串:串中任意个连续字符组成的子序列称为该串的子串 例如,“abcde”的子串有: “ ”、“a”、…

【C++】【自用】选择题 刷题总结

文章目录 【类和对象】1. 构造、拷贝构造的调用2. 静态成员变量3. 初始化列表4. 成员函数:运算符重载5. 友元函数、友元类55. 特殊类设计 【细节题】1. 构造 析构 new \ deletet、new[] \ delete[] 【类和对象】 1. 构造、拷贝构造的调用 #include using namespace…

大数据面试题:超详细版MapReduce工作原理

面试题来源: 《大数据面试题 V4.0》 大数据面试题V3.0,523道题,679页,46w字 参考答案: MapReduce详细流程: 1、准备待处理文件(200M) 2、submit()对原始文件进行切片分析&#…

热点活动-秒杀功能设计

一、需求描述 秒杀活动是电子商务兴起后出现的一种新型的购物方式,通过网上APP、小程序等平台推出一些低于市场价格的商品,提升购买率的营销活动,所有买家在同一时间网上抢购的一种销售方式。对比其他的营销活动,秒杀限时性更强&…

地平线J5芯片部署参考算法(2023.07.27)

本文主要是记录地平线官方提供的可在J5芯片上部署的参考算法。 参考算法数据集FPSPointPillarsKITTI116 (双核)CenterPointNuscenes98.72(双核)FCOS3DNuscenes589 (双核)GANetCULane2431(双核)Swin TransformerImageNet133&#…

网络加速技巧

某APP限制网速,可以这么做: (1)把网络禁用 (2)在APP的设置里面,把优化速率打开 (3)启用网络 2023年7月27日亲测有用,开启优化速率之前是100k/s,开…