OLAP引擎—ClickHouse常规优化

这篇具有很好参考价值的文章主要介绍了OLAP引擎—ClickHouse常规优化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

OLAP引擎—ClickHouse优化

一、数据一致性问题

即便对数据一致性支持最好的 Mergetree,也只是保证最终一致性。

ReplacingMergeTree

  • 该引擎和 MergeTree 的不同之处在于它会删除排序键值相同的重复项
  • 数据的去重只会在数据合并期间进行。合并会在后台一个不确定的时间进行,因此你无法预先作出计划。
  • 尽管你可以调用 OPTIMIZE 语句发起计划外的合并,但请不要依靠它,因为 OPTIMIZE 语句会引发对数据的大量读写。
    因此,ReplacingMergeTree 适用于在后台清除重复的数据以节省空间,但是它不保证没有重复的数据出现。

在使用 ReplacingMergeTree、SummingMergeTree 这类表引擎的时候,会出现短暂数据不一致的情况。在某些对一致性非常敏感的场景,通常有以下几种解决方案。

1.1 测试表及数据的准备

-- 1、创建表
-- user_id 是数据去重更新的标识;
-- create_time 是版本号字段,每组数据中 create_time 最大的一行表示最新的数据;
-- deleted 是自定的一个标记位,比如 0 代表未删除,1 代表删除数据。

CREATE TABLE test_distinct(
     user_id UInt64,
     score String,
     deleted UInt8 DEFAULT 0,
     create_time DateTime DEFAULT toDateTime(0)
) ENGINE= ReplacingMergeTree(create_time)
  ORDER BY user_id;
  


-- 2、写入 100 万 测试数据
INSERT INTO TABLE test_distinct(user_id,score)
WITH(
 SELECT ['A','B','C','D','E','F','G']
)AS dict
SELECT number AS user_id, dict[number%7+1] FROM numbers(1000000);


-- 3、修改前 5 万 行数据,修改内容包括 name 字段和 create_time 版本号字段
INSERT INTO TABLE test_distinct(user_id,score,create_time)
WITH(
 SELECT ['AA','BB','CC','DD','EE','FF','GG']
)AS dict
SELECT number AS user_id, dict[number%7+1], now() AS create_time FROM 
numbers(50000);

-- 4、还未触发分区合并,所以还未去重
SELECT COUNT() FROM test_distinct;
┌─count()─┐
│ 1050000 │
└─────────┘

1.2 通过 FINAL去重

在查询语句后增加 FINAL 修饰符,这样在查询的过程中将会执行 Merge 的特殊逻辑(例如数据去重,预聚合等)。

注:这种方法在早期版本基本没有人使用,因为在增加 FINAL 之后,我们的查询将会变成一个单线程的执行过程,查询速度非常慢。

在 v20.5.2.7-stable 版本中,FINAL 查询支持多线程执行,并且可以通过 max_final_threads 参数控制单个查询的线程数。

SELECT COUNT() FROM test_distinct final;


┌─count()─┐
│ 1000000 │
└─────────┘

1.3 手动OPTIMIZE(不建议)

-- 在写入数据后,立刻执行 OPTIMIZE 强制触发新写入分区的合并动作。
OPTIMIZE TABLE test_distinct FINAL;

-- 语法如下
OPTIMIZE TABLE [db.]name [ON CLUSTER cluster] [PARTITION partition | 
PARTITION ID 'partition_id'] [FINAL] [DEDUPLICATE [BY expression]]


-- 再次查询,发现已经去重
centos04 :) SELECT COUNT() FROM test_distinct;

┌─count()─┐
│ 1000000 │
└─────────┘

1.4 通过group by进行去重

-- 1、利用group by进行去重
CREATE VIEW view_test_distinct AS
SELECT
 user_id ,
 argMax(score, create_time) AS score,     -- 按照 create_time 的最大值取 score 的值
 argMax(deleted, create_time) AS deleted, -- 按照 create_time 的最大值取 deleted 的值
 max(create_time) AS ctime 
FROM test_distinct 
GROUP BY user_id     -- 对利用group by进行去重
HAVING deleted = 0;  -- 筛选未删除的数据


-- 2、再次插入一条数据(重复数据)
INSERT INTO TABLE test_distinct(user_id,score,create_time) VALUES(0,'AAAA',now());


SELECT 
  *
FROM test_distinct
WHERE user_id = 0;

┌─user_id─┬─score─┬─deleted─┬─────────create_time─┐
│       0 │ AAAA  │       02023-04-06 12:59:03 │
└─────────┴───────┴─────────┴─────────────────────┘
┌─user_id─┬─score─┬─deleted─┬─────────create_time─┐
│       0 │ AA    │       02023-04-06 12:48:26 │
└─────────┴───────┴─────────┴─────────────────────┘

SELECT 
  *
FROM view_test_distinct
WHERE user_id = 0;

┌─user_id─┬─score─┬─deleted─┬───────────────ctime─┐
│       0 │ AAAA  │       02023-04-06 12:59:03 │
└─────────┴───────┴─────────┴─────────────────────┘


-- 3、再次插入一条标记为删除的数据
INSERT INTO TABLE test_distinct(user_id,score,deleted,create_time) 
VALUES(0,'AAAA',1,now());


SELECT 
  *
FROM test_distinct
WHERE user_id = 0;
┌─user_id─┬─score─┬─deleted─┬─────────create_time─┐
│       0 │ AAAA  │       02023-04-06 12:59:03 │
└─────────┴───────┴─────────┴─────────────────────┘
┌─user_id─┬─score─┬─deleted─┬─────────create_time─┐
│       0 │ AAAA  │       12023-04-06 13:02:22 │
└─────────┴───────┴─────────┴─────────────────────┘
┌─user_id─┬─score─┬─deleted─┬─────────create_time─┐
│       0 │ AA    │       02023-04-06 12:48:26 │
└─────────┴───────┴─────────┴─────────────────────┘



SELECT 
  *
FROM view_test_distinct
WHERE user_id = 0;
-- 这行数据并没有被真正的删除,而是被过滤掉了。在一些合适的场景下,可以结合 表级别的 TTL 最终将物理数据删除。

二、查看执行计划

-- 语法如下
EXPLAIN [AST | SYNTAX | PLAN | PIPELINE] [setting = value, ...] 
SELECT ... [FORMAT ...]

➢ PLAN:用于查看执行计划,默认值

​ ◼ header 打印计划中各个步骤的 head 说明,默认关闭,默认值 0;

​ ◼ description 打印计划中各个步骤的描述,默认开启,默认值 1;

​ ◼ actions 打印计划中各个步骤的详细信息,默认关闭,默认值 0。

➢ AST :用于查看语法树;

➢ SYNTAX:用于优化语法;

➢ PIPELINE:用于查看 PIPELINE 计划。

​ ◼ header 打印计划中各个步骤的 head 说明,默认关闭;

​ ◼ graph 用 DOT 图形语言描述管道图,默认关闭,需要查看相关的图形需要配合graphviz 查看;

​ ◼ actions 如果开启了 graph,紧凑打印打,默认开启。

三、建表优化

3.1 字段类型

3.1.1 时间字段的类型

建表时能用数值型或日期时间型表示的字段就不要用字符串。

虽然 ClickHouse 底层将 DateTime 存储为时间戳 Long 类型,但不建议存储 Long 类型,因为 DateTime 不需要经过函数转换处理,执行效率高、可读性好。

create table t_a(
     id UInt32,
     sku_id String,
     total_amount Decimal(16,2) ,
     create_time Int32 
) engine =ReplacingMergeTree(create_time)
 partition by toYYYYMMDD(toDate(create_time)) --需要转换一次,否则报错
 primary key (id)
 order by (id, sku_id);

3.1.2 空值存储类型

官方已经指出 Nullable 类型几乎总是会拖累性能,因为存储 Nullable 列时需要创建一个额外的文件来存储 NULL 的标记,并且 Nullable 列无法被索引。

因此除非极特殊情况,应直接使用字段默认值表示空,或者自行指定一个在业务中无意义的值。

3.2 分区和索引

  • 一般选择按天分区
  • 1亿条数据一般选择30个左右的分区。
  • 必须指定索引列,ClickHouse 中的索引列即排序列,通过 order by 指定。索引:order by(a,b,c) 从左到右优先索引,高频查询的字段放在前面。
  • 基数特别大的不适合做索引列。
    • 基数大的列:该列的数据去重后和数据记录数越接近基数越大。
    • 为什么基数大不适合做索引:基数过大,查找时越需要依次遍历,则失去了索引意义。
……
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID)) -- UserID基数特别大的不适合做索引列,利用intHash32()解决
……

3.3 表参数

  • Index_granularity是用来控制索引粒度的,默认是8192,如非必须不建议调整。
  • 如果表中不是必须保留全量历史数据,建议制定TTL(生存时间值)可以免去手动处理过期历史数据的麻烦,TTL也可以通过Alter table随时修改。

3.4 写入和删除优化

  • 尽量不要执行单条或小批量删除和插入操作,这样会产生小分区文件,给后台Merge任务带来巨大压力;
  • 不要一次写入太多分区,或数据写入太快:
    • 数据写入太快会导致Merge速度跟不上而报错,一般建议每秒钟发起2-3次写入操作,每次操作写入2w-5w条数据

3.5 常见配置项

3.5.1 CPU配置

配置 描述
background_pool_size 后台线程池的大小,merge线程就是在该线程池中执行,该线程池不仅仅是给merge线程使用的,默认值16,允许的前提下建议改成CPU个数的2倍(一核可以虚拟化成两线程)
background_schedule_pool_size 执行后台任务的线程数,默认128,建议改成CPU个数的2倍(线程数)
background_distributed_schedule_pool_size 设置为分布式发送执行后台任务的线程数,默认16,建议改成CPU个数的2倍(线程数)
max_concurrent_queries 最大并发处理请求数(包含select、insert等),默认值100,推荐150(不够再加)~300默认单位为个/每秒
max_threads 设置单个查询所能使用的最大CPU个数,默认是CPU核数

3.5.2 内存配置

配置 描述
max_memory_usage 此参数在users.xml中,表示单词Query占用内存最大值,该值可以设置的比较大,这样可以提升集群查询的上限。保留一点给OS,比如128G内存的机器,设置为100G。
max_bytes_before_external_group_by 一般按照max_memory_usage的一半设置内存,当group使用内存超过阈值后会刷新到磁盘进行。因为Click house聚合分为两个阶段:查询并建立中间数据、合并中间数据,结合上一项,建议50GB。
max_bytes_before_external_sort 当 order by已使用max_bytes_before_external_sort内存就进行溢写磁盘(基于磁盘排序),如果不设置该值,那么当内存不够时直接抛错,设置了该值 order by可以正常完成,但是速度相对存内存来说肯定要慢点(实测慢的非常多,无法接受)。
max_table_size_to_drop 此参数在 config.xml 中,应用于需要删除表或分区的情况,默认是50GB,意思是如果删除50GB以上的分区表会失败。建议修改为0,这样不管多大的分区表都可以删除。

四、查询优化

4.1 单表查询优化

4.1.1 prewhere替代where

prewhere 和where 语句的作用相同,用来过滤数据。不同之处在于 prewhere 只支持MergeTree 族系列引擎的表,首先会读取指定的列数据,来判断数据过滤,等待数据过滤之后再读取 select 声明的列字段来补全其余属性

当查询列明显多于筛选列时使用prewhere可十倍提升查询性能,Prewhere会自动优化执行过滤阶段的数据读取方式,降低io操作。
​ 在某些场合下,prewhere 语句比 where 语句处理的数据量更少性能更高。

4.1.2 列裁剪与分区裁剪

  • 列裁剪:其实就是避免使用select * ,筛选出需要的字段
  • 分区裁剪:避免使用select * ,在where中使用partition by字段,选择分区

4.1.3 order by 结合where、limit

千万以上的数据集进行order by查询时需要搭配where条件和limit语句一起使用。

4.1.4 避免构建虚拟列

如非必须,不要在结果集上构建虚拟列,虚拟列非常消耗资源浪费性能,可以考虑在前端进行处理,或者在表中构造实际字段进行额外存储。

  • 虚拟列:原表中不存在的列,计算出来的列,如:
select
	a,
	b,
	a+b  -- 虚拟列,虚拟列非常消耗资源,浪费性能。
from
	table

4.1.5 uniqCombined替代distinct

  • uniqCombined:近似去重,但是精度不会很低,差别很小

性能可提升10倍以上,uniqCombined底层采用类似HyperLogLog算法实现。

不建议对千万以上的对准确度没有要求的数据进行精确去重,使用近似去重。

-- 反例:
select count(distinct rand()) from hits_v1;

-- 正例:
SELECT uniqCombined(rand()) from datasets.hits_v1

4.2 多表关联

clickhouse 的JOIN:

  • 原理:右表加载到内存,再匹配;
  • 非要使用,如何使用:
    • 能过滤先过滤,特别是右表;
    • 右表放小表;
    • 特殊场景可以考虑使用字典表;
    • 可以替换的话,利用IN替换JOIN
-- 建表的时候,想要复制表结构:
create table XXX as select * from XXXX where 1 = 0; -- 条件不成立,数据永远不会写进来
4.2.1 Join原理

A join B,将B表全部加载到内存中,A表中的数据会逐条匹配内存中的B表。

4.2.2 用IN代替JOIN
  • 当多表联查时,查询的数据仅从其中一张表出时,可以考虑从IN操作而不是JOIN
select table_a.* from table_a where table_a.count_id in (select count_id from table_b);
4.2.3 大小表JOIN

多表 join 时要满足小表在右的原则,右表关联时被加载到内存中与左表进行比较, Click House 中无论是 Left join 、 Right join 还是 Inner join 永远都是拿着右表中的每一条记录到左表中查找该记录是否存在,所以右表必须是小表。

4.2.4 注意谓词下推
  • 尽量在join之前进行过滤

ClickHouse 在join查询时不会主动发起谓词下推的操作,需要每个子查询提前完成过滤操作,需要注意的是,是否执行谓词下推,对性能影响差别很大

4.2.5 分布式表使用GLOBAL
  • 查询放大:两张分布式表进行JOIN的时候,两张表的N各节点互相发起查询,变成N*N次

两张分布式表上的IN和JOIN之前必须加上GLOBAL关键字,右表只会在接收查询请求的那个节点查询一次,并将其分发到其他节点上。如果不加GLOBAL关键字的话,每个节点都会单独发起一次对右表的查询,而右表又是分布式表,就导致右表一共会被查询N²次(N是该分布式表的分片数量),这就是查询放大,会带来很大开销。

4.2.6 使用字典表
  • 可以是自己创建的表,也可以是外部文件。

将一些需要关联分析的业务创建成字典表进行join操作,前提是字典表不宜太大,因为字典表会常驻内存

4.2.7 提前过滤

通过增加逻辑过滤可以减少数据扫描,达到提高执行速度、降低内存消耗的目的。

五、物化视图

5.1 简介

  • 视图:保存的是SQL的操作逻辑。
  • 物化视图:不仅保存SQL的操作逻辑,还保存操作过后的结果,结果根据相应的引擎存到磁盘或内存中

​ ClickHouse 的物化视图是一种查询结果的持久化,它确实是给我们带来了查询效率的提升。用户查起来跟表没有区别,它就是一张表,它也像是一张时刻在预计算的表,创建的过程它是用了一个特殊引擎,加上后来 as select,就是 create一个table as select的写法。

​ "查询结果集"的范围很宽泛,可以是基础表中部分数据的一份简单拷贝,也可以是多表join之后产生的结果或其子集,或者原始数据的聚合指标等等。所以,物化视图不会随着基础表的变化而变化,所以它也称为快照(snapshot)。

优点:查询速度快,要是把物化视图这些规则全部写好,它比原数据查询快了很多,总的行数少了,因为都预计算好了。

缺点:它的本质是一个流式数据的使用场景,是累加式的技术,所以如果要用历史数据做去重、去核这样的分析,在物化视图里面是不太好用的。在某些场景的使用也是有限的。而且如果一张表加了好多物化视图,在写这张表的时候,就会消耗很多机器的资源,比如数据带宽占满、存储一下子增加了很多

5.2 基本语法

创建时会创建一个隐藏的目标表来保存视图数据。也可以TO表明,保存到一张显式的表。没有加TO表名,表名默认就是.inner.物化视图名。文章来源地址https://www.toymoban.com/news/detail-405864.html

CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]table_name [ON CLUSTER] [TO[db.]name] [ENGINE = engine] [POPULATE] AS SELECT ...
  • [POPULATE]:添加之后,在创建视图时会遍历历史数据,会增加服务器负载,如果要历史数据,使用INSERT INTO写入数据
  • 在创建没有TO [db].[table]的物化视图时,您必须指定ENGINE- 用于存储数据的表引擎。
  • 使用TO [db].[table]创建物化视图时,不得使用POPULATE.
  • 物化视图的实现方式如下:向 中指定的表中插入数据时,插入的SELECT部分数据通过该SELECT查询进行转换,并将结果插入到视图中。
  • 查询语句可以包含下面的子句:DISTINCT , GROUP BY , ORDER BY , LIMIT……

5.3 案例详解

-- 1、创建测试数据
create table test_a_test(
	user_id UInt64,
	score String,
	deleted UInt8 DEFAULT 0,
	create_time Date
) ENGINE = MergeTree()
partition by toYYYYMM(create_time)
order by (create_time,intHash32(user_id))
sample by intHash32(user_id)
SETTINGS index_granularity = 8192;


insert into
	test_a_test
select
	*
FROM
	test_a
limit 10000;

-- 2、创建物化视图
create materialized view test_mview
engine = SummingMergeTree
partition by toYYYYMM(create_time)
order by (create_time,intHash32(user_id))
as
SELECT
	user_id,
	create_time,
	count(score),
	sum(deleted)
from
	test_a_test ta
WHERE
	create_time >= toDate(0)
group by user_id,create_time ;

show tables;
>>>结果
.inner_id.5bfba660-812e-49ec-885f-3fa63e16f2f4 -- 默认存储数据的表格
test_a_test
test_mview
-- 3、插入数据
SELECT * from test_mview; -- 第一次查询结果为空

insert  into
	test_a_test
select
	*
FROM
	test_a
limit 10;

SELECT * from test_mview;  -- 插入后在查询有10条新增数据

select * from `.inner_id.068a0cde-c260-4fee-b902-c7f74cc4f194`; -- 自动创建的表中也有数据
-- 4、导入历史数据(重点!!!)
insert into
	test_mview
-- 将物化视图的逻辑再写一遍
SELECT
	user_id,
	create_time,
	count(score),
	sum(deleted)
from
	test_a_test ta
WHERE
	create_time >= toDate(0)
group by user_id,create_time 

到了这里,关于OLAP引擎—ClickHouse常规优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • olap/clickhouse keeper 一致性协调服务

    在https://www.yuque.com/treblez/qksu6c/ahgvn94c2nh1y34w?singleDoc# 《Redis集群:分布式的less is more》中我提到,无论是啥服务,想要达到操作视角的强一致性,要么使用类似TSO/原子钟的方案,要么有一套一致性协调服务。 clickhouse最初是用zookeeper的,在 21.8 版本中开始引入了 ClickHouse-Keeper ,

    2024年01月22日
    浏览(38)
  • OLAP型数据库 ClickHouse的简介 应用场景 优势 不足

    ClickHouse 是一个开源的分布式列式数据库管理系统 (DBMS),专门用于在线分析处理 (OLAP)。它最初由 Yandex 开发,并且在处理大规模数据分析和实时查询方面表现出色。以下是关于 ClickHouse 的简介、应用场景、优势和不足的概述: ClickHouse 是一个高性能的列式数据库管理系统,专

    2024年02月02日
    浏览(42)
  • OLAP系列:三、clickhouse Docker集群部署指南(3分片1副本模式)

    一是为了学习容器,另外也是帮助一些同学解决机器资源紧缺,能够在一台是宿主机部署一套clickhouse集群服务。 本章内容适合开发测试环境使用,生产环境还需要更多细节的处理工作,只能作为参考。 二、部署准备 1、机器准备 172.25.16.108 2、创建容器网络 3、镜像准备 1、下

    2024年02月12日
    浏览(40)
  • OLAP开源引擎对比之历史概述

    OLAP概念诞生于1993年,工具则出现在更早以前,有史可查的第一款OLAP工具是1975年问世的Express,后来走进千家万户的Excel也可归为此类,所以虽然很多数据人可能没听过OLAP,但完全没打过交道的应该很少。 这个概念主要是在大数据圈里流传,而在大数据领域里,目前主流的

    2024年04月27日
    浏览(23)
  • 大数据OLAP查询引擎选型对比

            目前大数据比较常用的OLAP查询引擎包括:Presto、Impala、Druid、Kylin、Doris、Clickhouse、GreenPlum等。         不同引擎特点不尽相同,针对不同场景,可能每个引擎的表现也各有优缺点。下面就以上列举的几个查询引擎做简单介绍。         Presto是 Facebook 推出的一个

    2024年02月08日
    浏览(35)
  • 【大数据OLAP引擎】StarRocks为什么快?

    StarRocks最初主要的优势是性能,当时在单表查询方面与性能标杆ClickHouse不相上下,而join优化特性使其在多表关联查询场景下的性能表现要远远优于ClickHouse,替换ClickHouse自然也就成了StarRocks的第一个目标。 而StarRocks的野心不止于此,后来又进一步发展了联邦查询功能,成为

    2024年02月01日
    浏览(30)
  • 火山引擎ByteHouse:一套方案,让OLAP引擎在精准投放场景更高效

    由于流量红利逐渐消退,越来越多的广告企业和从业者开始探索精细化营销的新路径,取代以往的全流量、粗放式的广告轰炸。精细化营销意味着要在数以亿计的人群中优选出那些最具潜力的目标受众,这无疑对提供基础引擎支持的数据仓库能力,提出了极大的技术挑战。

    2024年02月12日
    浏览(29)
  • 大数据计算分析技术:批处理、流计算、OLAP引擎

    目录 一、批处理的基石:MapReduce 1.工作流程 2.实例分析 二、流计算的代表:storm、spark streaming和flink 1.storm 2.spark streaming 3.flink  4.storm、spark streaming和flink 对比 三、OLAP引擎:Hive、Impala、Presto 1.Hive 1)Hive系统架构 2)Hive和传统数据库的区别 四 离线数据、批量计算、实时计算

    2024年02月16日
    浏览(39)
  • OLAP引擎也能实现高性能向量检索,据说QPS高于milvus!

    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 随着LLM技术应用及落地,数据库需要提高向量分析以及AI支持能力,向量数据库及向量检索等能力“异军突起”,迎来业界持续不断关注。简单来说,向量检索技术以及向量数据库能

    2024年01月16日
    浏览(42)
  • ClickHouse(九):Clickhouse表引擎 - Log系列表引擎

      进入正文前,感谢宝子们订阅专题、点赞、评论、收藏!关注IT贫道,获取高质量博客内容! 🏡个人主页:含各种IT体系技术,IT贫道_Apache Doris,Kerberos安全认证,大数据OLAP体系技术栈-CSDN博客 📌订阅:拥抱独家专题,你的订阅将点燃我的创作热情! 👍点赞:赞同优秀创作

    2024年02月13日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包