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

在基于 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 数据库

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mysql的整体架构

服务层-引擎层-硬盘层 一条语句执行的整体过程: 先建立连接(mysql -h -p 密码)–预处理-词法分析-语法分析-优化器选择用什么索引表如何连接等-执行器 到这里都是属于server层,大多数功能包括视图,存储过程,触发器都是…

WIZnet W5500-EVB-Pico DHCP 配置教程(三)

DHCP协议介绍 什么是DHCP? 动态主机配置协议DHCP(Dynamic Host Configuration Protocol)是一种网络管理协议,用于集中对用户IP地址进行动态管理和配置。 DHCP于1993年10月成为标准协议,其前身是BOOTP协议。DHCP协议由R…

AD21 PCB设计的高级应用(六)极坐标的应用

(六)极坐标的应用 在 PCB 设计过程中,特别是 LED圆形灯板的 PCB 设计,需要对 LED灯珠进行圆形等间距排列,如果每个元件都计算清楚其坐标再进行放置会非常烦琐。要实现如图 所示的元件布局效果,在 Altium Designer 软件里可以使用极坐标的方法。 (1)打开…

自己实现MyBatis 底层机制--抽丝剥茧(上)

😀前言 本篇博文是学习过程中的笔记和对于MyBatis底层机制的分析思路,希望能够给您带来帮助😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到…

深入理解 SQL:从基本查询到高级聚合

目录 背景理论知识示例1211. 查询结果的质量和占比(Round group by)1204. 最后一个能进入巴士的人 (Having limit order by)1193. 每月交易 I(if group by)1179. 重新格式化部门表1174. 即时食物配送 II&am…

jenkins 配置git

在linux 中输入 保证git 安装成功 git --version使用查看git 安装目录(非源码安装直接用yum 安装的) which gitjenkins 中到 系统管理–>全局工具配置–> Git installations 新建一个项目 选择自由风格 源码管理选择 git 如果使用的是码云&a…

【机器学习】Overfitting and Regularization

Overfitting and Regularization 1. 过拟合添加正则化2. 具有正则化的损失函数2.1 正则化线性回归的损失函数2.2 正则化逻辑回归的损失函数 3. 具有正则化的梯度下降3.1 使用正则化计算梯度(线性回归 / 逻辑回归)3.2 正则化线性回归的梯度函数3.3 正则化…

论文笔记--GloVe: Global Vectors for Word Representation

论文笔记--GloVe: Global Vectors for Word Representation 1. 文章简介2. 文章概括3 文章重点技术3.1 两种常用的单词向量训练方法3.2 GloVe3.3 模型的复杂度 4. 文章亮点5. 原文传送门6. References 1. 文章简介 标题:GloVe: Global Vectors for Word Representa…

计算机图形学笔记2-Viewing 观测

观测主要解决的问题是如何把物体的三维“模型”变成我们在屏幕所看到的二维“图片”,我们在计算机看到实体模型可以分成这样几步: 相机变换(camera transformation)或眼变换(eye transformation):想象把相机放在任意一个位置来观测物体&#…

OSPF协议RIP协议+OSPF实验(eNSP)

本篇博客主要讲解单区域的ospf,多区域的仅作了解。 目录 一、OSPF路由协议概述 1.内部网关协议和外部网关协议 二、OSPF的应用环境 1.从以下几方面考虑OSPF的使用 2.OSPF的特点 三、OSPF重要基本概念 3.1,辨析邻居和邻接关系以及七种邻居状态 3…

MySQL中的函数

系列文章目录 MySQL常见的几种约束 文章目录 系列文章目录前言一、单行函数1.字符串函数 (String StringBuilder)2.数值函数 (Math)3.日期与时间函数4.流程函数( IF SWITCH)5.JSON函数6.其他函数 二、多行…

shopee,lazada,etsy店群如何高效安全的管理

对于电商卖家来说,要经营多个店铺,管理多个账号是非常常见的操作。为了避免账号关联被平台识别出来,需要使用防关联的浏览器来进行操作 ​1、支持多平台 支持同时管理多个电商平台店铺,Shopee、Lazada、etsy、poshmark、vinted等&…

Pytorch 最全入门介绍,Pytorch入门看这一篇就够了

本文通过详细且实践性的方式介绍了 PyTorch 的使用,包括环境安装、基础知识、张量操作、自动求导机制、神经网络创建、数据处理、模型训练、测试以及模型的保存和加载。 1. Pytorch简介 在这一部分,我们将会对Pytorch做一个简单的介绍,包括它…

MFC第二十四天 使用GDI对象画笔和画刷来开发控件(分页控件选择态的算法分析、使用CToolTipCtrl开发动静态提示)

文章目录 GDI对象画笔和画刷来开发控件梯形边框的按钮控件CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp 矩形边框的三态按钮控件 CToolTipCtrl开发动静态提示CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp: 实现文件 矩形边框的三态按钮控件 CToolTipCtrl开发动…

华为、阿里巴巴、字节跳动 100+ Python 面试问题总结(六)

系列文章目录 个人简介:机电专业在读研究生,CSDN内容合伙人,博主个人首页 Python面试专栏:《Python面试》此专栏面向准备面试的2024届毕业生。欢迎阅读,一起进步!🌟🌟🌟 …

echart折线图,调节折线点和y轴的间距(亲测可用)

options代码: options {tooltip: {trigger: axis, //坐标轴触发,主要在柱状图,折线图等会使用类目轴的图表中使用。},xAxis: {type: category,//类目轴,适用于离散的类目数据,为该类型时必须通过 data 设置类目数据。…

抓紧收藏,Selenium无法定位元素的几种解决方案

01、frame/iframe表单嵌套 WebDriver只能在一个页面上对元素识别与定位,对于frame/iframe表单内嵌的页面元素无法直接定位。 解决方法: driver.switch_to.frame(id/name/obj) switch_to.frame()默认可以直接取表单的id或name属性。如果没有可用的id和…

自适应大屏

可视化大屏适配/自适应现状 可视化大屏的适配是一个老生常谈的话题了,现在其实不乏一些大佬开源的自适应插件、工具但是我为什么还要重复造轮子呢?因为目前市面上适配工具每一个都无法做到完美的效果,做出来的东西都差不多,最终实…

ETHERNET/IP 转ETHERCAT连接ethercat总线伺服如何控制

捷米JM-EIP-ECAT网关连接到ETHERNET/IP总线中做为从站使用,连接到ETHERCAT总线中做为从站使用,可以同时满足多种工业生产的需求。支持广泛的设备类型,可以和多种不同的设备进行通讯。 技术参数 ETHERNET/IP 技术参数 网关做为 ETHERNET/IP …

CentOS下 Docker、Docker Compose 的安装教程

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。 Docker Compose是用于定义…