ClickHouse 实现存算分离:与 Amazon S3 结合使用的三种方式

简介

为了极致的查询速度,ClickHouse 在架构上利用本地磁盘进行数据存储。但是在现代化的云架构中,对象存储依然是最重要的存储组成部分。

使用对象存储可以给数据分析系统带来诸多优势:

  1. 数据备份。 对象存储提供了无限扩展性的存储能力,使其成为数据备份的理想选择。

  2. 冷热分层。 对于数据分析系统来说,只有部分数据被频繁查询,而大多数历史数据很少被访问。因此,可以将冷数据存储在对象存储(如S3)中。

  3. 存算分离。 利用对象存储提供的高可靠性和高度灵活的弹性伸缩能力,可以将对象存储作为底层存储,实现存算分离的能力。

Amazon S3(Simple Storage Service) 是AWS 正式推出的第一个云服务,目前Amazon S3 已经成为事实上的云对象存储标准。

本文将详细探讨 ClickHouse 与 Amazon S3 结合使用的三种主要方式。

  1. 表函数。通过使用表函数(Table function), 我们能够将S3文件直接映射为ClickHouse中的虚拟数据表,从而可以直接从S3的数据文件中进行即席查询。同时,利用表函数还可以实现 S3 数据的导入导出。
  2. 表引擎 。表函数更像是一个临时视图,必须在每次查询时规定 bucket,access_key 以及 table strcuture 等信息。利用表引擎( Table Engine 功能,可以建立表到 S3 文件的持久映射,从而一劳永逸。
  3. S3直接作为磁盘。 通过将S3作为磁盘,ClickHouse 可以直接与S3进行交互,将原本存储在本地磁盘上的文件迁移至S3中。这一做法不仅节省了本地存储资源,还能够充分利用Amazon S3的弹性存储特性,算是 ClickHouse 当前架构下存算分离的一种实现。

文章内容较长,如果对冷热分层、存算分离等部分感兴趣,可以直接跳转 DiskS3 使用方法部分。

表函数使用

表函数使用的语法如下:

SQL 复制代码
s3(path, [aws_access_key_id, aws_secret_access_key,] format, structure, [compression])

为了展示表函数的相关用法,首先我们建立一个本地表(people),然后使用表函数将本地表的数据导出到 S3。

然后,我们可以使用表函数直接对导出到 S3 的数据进行查询。

最后,我们再将导出到 S3 的数据重新导入到本地。

本地表 people 建表语句:

SQL 复制代码
CREATE DATABASE my_db;
CREATE TABLE my_db.people (`name` String, `country` String) ENGINE = MergeTree() PARTITION BY `country` PRIMARY KEY `name` ORDER BY `name`;
INSERT INTO my_db.people values ('Jack', 'Singapore');
INSERT INTO my_db.people values ('Wang', 'China');
INSERT INTO my_db.people values ('Mike', 'America');

导出、查询示例

示例中,S3 使用了 MinIO 进行模拟,因此访问的是本地 端口

接下里的 SQL 将导出 people 表的所有数据到 s3,这里必须显式规定 FUNCTION 关键字,否则会报错。

SQL 复制代码
INSERT INTO FUNCTION
   s3(
       'http://127.0.0.1:19000/test-s3/test_file.csv',
       'minio', 'minio123',
       'CSV',
       '`name` String, `country` String'
    )
SELECT *
FROM my_db.people;

导出后检查 test-s3 bucket 下新增 test_file.csv 文件,说明导出成功了。

SHELL 复制代码
➜  ClickHouse git:(23.7) ✗ as3 ls s3://test-s3
2023-08-15 15:50:17         51 test_file.csv

为了检查导出数据是否正确,我们直接使用表函数对 test_file.csv 进行查询。

SQL 复制代码
SELECT *
FROM s3(
       'http://127.0.0.1:19000/test-s3/test_file.csv',
       'minio', 'minio123',
       'CSV',
       '`name` String, `country` String'
        );

显然结果没有问题。

SQL 复制代码
SELECT *
FROM s3('http://127.0.0.1:19000/test-s3/test_file.csv', 'minio', 'minio123', 'CSV', '`name` String, `country` String')

Query id: 56cc1a7c-d8f8-49a6-9efe-5e78b982de95

┌─name─┬─country───┐
│ Jack │ Singapore │
│ Mike │ America   │
│ Wang │ China     │
└──────┴───────────┘

3 rows in set. Elapsed: 0.008 sec.

导入示例

我们给出的示例 Schema 较为简单,在 Schema 较为复杂的情况下,每次都输入 s3 function 显得较为繁琐。因此,我们可以将 s3 中的数据导入到 ClickHouse 中。

这里我们新建一个表 people_backup,用于存储从 S3 导入的数据。

SQL 复制代码
INSERT INTO my_db.people_backup
SELECT *
FROM s3(
       'http://127.0.0.1:19000/test-s3/test_file.csv',
       'minio', 'minio123',
       'CSV',
       '`name` String, `country` String'
    );

这里对 people_backup 中导入的数据进行查询,验证正确性。

SQL 复制代码
SELECT *
FROM my_db.people_backup

Query id: 99192976-0c85-4d41-8a1a-8860c2b9c8b7

┌─name─┬─country─┐
│ Wang │ China   │
└──────┴─────────┘
┌─name─┬─country─┐
│ Mike │ America │
└──────┴─────────┘
┌─name─┬─country───┐
│ Jack │ Singapore │
└──────┴───────────┘

3 rows in set. Elapsed: 0.002 sec.

分区导出

在数据导出到 S3 时,可以使用 PARTITION BY 关键字进行分区导出,避免导出单个大文件。此时,对于每个分区值,都会写入一个单独的文件。

SQL 复制代码
INSERT INTO FUNCTION
   s3(
       'http://127.0.0.1:19000/test-s3/test_file.csv',
       'minio', 'minio123',
       'CSV',
       '`name` String, `country` String'
    )
    PARTITION BY name
SELECT *
FROM my_db.people;

小结

表函数方案的优势在于对于已有的数据湖中的数据,比如各种开放数据格式CSV、Parquet等,都可以通过ClickHouse进行查询,无需作出额外的改动,趋于LakeHouse这样的新架构。

表引擎使用

每次使用表函数时,都不得不规定 bucket,table strcuture 以及 access_key 等信息,导致表函数的使用较为繁琐。因此,ClickHouse 为 S3 引入了专门的表引擎 Table Engine

通过使用表引擎,可以建立 S3 中的数据文件到 ClickHouse 中数据表的映射,之后就可以直接对 S3 表进行多次查询,而无需再声明 bucket 等信息。

表引擎使用的语法如下:

SQL 复制代码
CREATE TABLE s3_engine_table (name String, value UInt32)
    ENGINE = S3(path, [aws_access_key_id, aws_secret_access_key,] format, [compression])
    [SETTINGS ...]

建表示例

我们依然使用上文中导出的 S3 文件进行建表。从本地表导出 S3 文件的过程如下。

SQL 复制代码
CREATE DATABASE my_db;
CREATE TABLE my_db.people (`name` String, `country` String) ENGINE = MergeTree() PARTITION BY `country` PRIMARY KEY `name` ORDER BY `name`;
INSERT INTO my_db.people values ('Jack', 'Singapore');
INSERT INTO my_db.people values ('Wang', 'China');
INSERT INTO my_db.people values ('Mike', 'America');

INSERT INTO FUNCTION
   s3('http://127.0.0.1:19000/test-s3/test_file.csv', 'minio', 'minio123', 'CSV', '`name` String, `country` String')
SELECT *
FROM my_db.people;

根据 test_file.csv 建表的操作如下所示:

SQL 复制代码
--创建表
CREATE TABLE my_db.s3_people
(
    `name` String,
    `country` String
)
ENGINE = S3(
       'http://127.0.0.1:19000/test-s3/test_file.csv',
       'minio', 'minio123',
       'CSV')

查询、写入示例

在建表完成后,即可像普通表一样对 S3 表进行查询。

SQL 复制代码
SELECT *
FROM my_db.s3_people

Query id: b3609152-c92b-4334-859a-babdb826a21d

┌─name─┬─country───┐
│ Jack │ Singapore │
│ Mike │ America   │
│ Wang │ China     │
└──────┴───────────┘

3 rows in set. Elapsed: 0.005 sec.

由于 S3 等对象存储不支持追加写,因此向 S3 表中的写入,都会覆盖原数据文件。( 如果表是使用通配符的方式进行定义(如*.CSV),那么会阻塞该写入)。

例如,我们截断数据的前两行,将其插入到 S3 表中。

SQL 复制代码
INSERT INTO my_db.s3_people
   SELECT *
   FROM my_db.s3_people
   LIMIT 2;

通过查询 S3 表我们可以得知,这是一个覆盖,而不是一个追加的过程。

SQL 复制代码
SELECT *
FROM my_db.s3_people

Query id: b4e673e0-e719-4dbe-9190-fc522c22b906

┌─name─┬─country───┐
│ Jack │ Singapore │
│ Mike │ America   │
└──────┴───────────┘

2 rows in set. Elapsed: 0.005 sec.

通过访问原数据文件,也可以得到这一结论。

SQL 复制代码
SELECT *
FROM s3('http://127.0.0.1:19000/test-s3/test_file.csv', 'minio', 'minio123', 'CSV', '`name` String, `country` String')

Query id: 7190f4df-4008-4bde-8e19-8908b5e71019

┌─name─┬─country───┐
│ Jack │ Singapore │
│ Mike │ America   │
└──────┴───────────┘

2 rows in set. Elapsed: 0.005 sec.

建议使用这种方法时,只去查询S3中的数据。

小结

该方案的优势在于对于已有的数据湖中的数据,比如各种开放数据格式CSV、Parquet等,都可以通过ClickHouse进行查询,无需作出额外的改动,趋于LakeHouse这样的新架构。

同时,通过使用表引擎,可以建立 S3 中的数据文件到 ClickHouse 中数据表的映射,之后就可以直接对 S3 表进行多次查询,而无需再声明 bucket 等信息。

值得注意的是,删除 S3 表不会影响 S3 中存储的数据文件。

DiskS3 使用

接下来,我们将直接使用 S3 作为磁盘存储数据。该方案的优点是:

  1. 使用 ClickHouse 原生的数据存储格式,支持主索引,主持缓存,性能相对 S3 表引擎较好。
  2. 可以较为简便的实现冷热分离,即将"热"数据划分到高速存储(NVMe SSD)对应的数据表,而"冷"数据划分到 S3 对象存储对应的数据表。
  3. 可以将 S3 作为主磁盘,从而实现存算分离。

该方案的缺点因为使用 ClickHouse 专有的数据存储格式(可以参考ClickHouse 数据存储结构 ),所以没有办法通过其他数据分析工具进行处理。

存储层(Storage Tiers)

这里先对 ClickHouse 存储层的概念进行介绍。

官方文档版:

ClickHouse storage volumes allow physical disks to be abstracted from the MergeTree table engine. Any single volume can be composed of an ordered set of disks. Whilst principally allowing multiple block devices to be potentially used for data storage, this abstraction also allows other storage types, including S3. ClickHouse data parts can be moved between volumes and fill rates according to storage policies, thus creating the concept of storage tiers.

信达雅翻译版:

ClickHouse存储卷(volume)是MergeTree表引擎使用的物理磁盘的抽象表示。每个存储卷由一系列磁盘(Disk)组成。 在这里,磁盘(Disk)既可以是不同的物理磁盘,也可以是不同类型的 存储介质 ,例如S3。 存储卷还可以根据存储策略(Storage policy)来管理数据的迁移和平衡。 根据存储策略的设置,数据可以在不同的 disk 之间进行移动,以实现负载均衡和数据的优化存储(如冷热分离)。

配置

为了使用 S3 作为磁盘存储,首先需要在ClickHouse的配置文件中进行相应的声明和配置。

配置包括两部分,首先在配置文件storage_configuration下增加一个 disk 选项,命名为 s3这个选项用于定义S3作为磁盘的配置和参数。

其次,还需要增加一个策略(policy)选项,用于声明 disk s3 可以在存储卷(volume)中使用。这个策略选项指定了磁盘的使用规则和行为。 在示例中,我们假设 s3 是存储卷中唯一的磁盘,即所有数据都将写入 S3。

XML 复制代码
<clickhouse>
    <storage_configuration>
        ...
        <!-- 1. Add disk s3 --> 
        <disks>
            <s3>
                <type>s3</type>
                <endpoint>http://127.0.0.1:19000/test_bucket/data/</endpoint>
                <access_key_id>minio</access_key_id>
                <secret_access_key>minio</secret_access_key>
                <region></region>
                <!-- 如果使用 IAM roles,可以将 use_environment_credentials 设为 true
                <use_environment_credentials>true</use_environment_credentials>
                -->
            </s3>
        </disks>
        ...
        <!-- 2. Add policy using disk s3 --> 
        <policies>
            <s3_main>
                <volumes>
                    <main>
                        <disk>s3</disk>
                    </main>
                </volumes>
            </s3_main>
        </policies>
    </storage_configuration>
</clickhouse>

这里我使用 MinIO 模拟的 S3 环境,因此访问的是本地端口,且使用的是 http 协议。

如果需要使用 https 协议,可能还需要修改openSSL中的client配置,可以参考这里

如果 endpoint 是普通的 path style,则路径格式为

https://s3.Region.amazonaws.com/bucket-name/key/ 如果使用的是 virtual-hosted style,则路径格式为

https://bucket-name.s3.Region.amazonaws.com/key/

注意 key 之后的 / 不可省略, ClickHouse 会将这个 Key 作为 Data 目录。

建表

配置完成后,我们建立 s3_disk_people 表,并声明其 storage_policys3_main

SQL 复制代码
CREATE TABLE my_db.s3_disk_people
(
    `name` String,
    `country` String
)
ENGINE = MergeTree
PARTITION BY `country` 
PRIMARY KEY `name` 
ORDER BY `name`
SETTINGS storage_policy = 's3_main';

接下来,我们可以像普通表一样向其中写入数据。

SQL 复制代码
INSERT INTO my_db.s3_disk_people values ('Jack', 'Singapore');
INSERT INTO my_db.s3_disk_people values ('Wang', 'China');
INSERT INTO my_db.s3_disk_people values ('Mike', 'America');

像普通表一样查询,验证数据是否插入成功。

SQL 复制代码
SELECT *
FROM my_db.s3_disk_people

Query id: ae1b6500-e018-42e8-bd0e-3654bb558e9d

┌─name─┬─country─┐
│ Wang │ China   │
└──────┴─────────┘
┌─name─┬─country───┐
│ Jack │ Singapore │
└──────┴───────────┘
┌─name─┬─country─┐
│ Mike │ America │
└──────┴─────────┘

3 rows in set. Elapsed: 0.004 sec.

数据存储结构

在磁盘上,ClickHouse 保留了 MergeTree 表引擎下的数据目录,包括数据 bin 文件、分区信息、索引等内容。但是其中并没有保存真实的数据,而是存储了 S3 的链接。

对于MergeTree 数据存储结构不熟悉的同学,可以参考:ClickHouse 存储引擎解析:磁盘上的数据组织

查看 S3 中的数据文件,是长这个样子的,这里用了随机哈希命名,估计是为了随机化 S3 对象前缀从而增加并行访问速度。

yaml 复制代码
➜  ~ as3 ls s3://test-s3/data/
2023-08-15 21:49:24         34 bdvazwvdkwmfwaiguudrnvrrkohmkadk
2023-08-15 21:49:28         68 bknrbwzgnlzidqbriiasefufptzkulyu
2023-08-15 21:49:28          8 bxagkcmyoyrlvzejehmgvzkdbqsayolg
2023-08-15 21:49:28        282 ctholyzbamayoziagskjidbogeiytujd
2023-08-15 21:47:11          1 erreybamhveeghifebdxcsuulwibmfcq
2023-08-15 21:49:19         20 ewumvskhfzrlmebxyewrfwshhagcehfk
2023-08-15 21:49:24        282 fcjyvmielivighhwxcpmrnimnopcvjrb
2023-08-15 21:49:28         16 fmvxcnajadryhdjhtejasukzoyxorozg
2023-08-15 21:49:19         68 hgawjkybxicnlwiwlpwlkdeoyijzcwca
2023-08-15 21:49:19         10 igjybcedzouqtegbjoykffideyezaujd
2023-08-15 21:49:24         63 jgcynwnupjuvdvyvcagfpipqrtprjsfm
2023-08-15 21:49:19         80 kyngjpbzzyosbbkbirwwuufncmbpyyja
2023-08-15 21:49:28         10 ltskmcyxfmhfgixwqfwnhrpxqembeyql
2023-08-15 21:49:19         10 mdnpdxnozforfiqzubnuvxrqdtopduzd
2023-08-15 21:49:24          6 nblmcntwztjcbmtdlwawiwqbsjvxfuvr
2023-08-15 21:49:19         34 nkkqrypovgpcfklgzntgyztanzwwmvem

冷热分离

在前面的部分,我们介绍了存储层的概念,其中一个数据卷(volume)可以包含多个磁盘。因此,我们可以在卷中定义多个磁盘,并通过定义存储比例来实现冷热数据的分离

在示例中,我们定义新的存储策略 s3_tiered。该策略重用(reuse)了卷 main,并引入一个新的卷 hot。卷 hot 使用了默认磁盘(默认磁盘仅包含一个磁盘,由参数 <path> 配置)

在新的存储策略下,新插入的数据将一直存储在默认磁盘上,直到达到 move_factor 乘以磁盘大小的阈值。一旦达到这个阈值,数据将被重新定位到 S3 中。

通过这样的设置,我们可以实现数据的冷热分离。热数据将存储在默认磁盘上,以便快速访问和处理。而冷数据将被移动到S3中,以节省存储空间并降低成本。

XML 复制代码
<policies>
   <s3_main>
       ...
   </s3_main>
   <s3_tiered>
       <volumes>
           <hot>
               <disk>default</disk>
           </hot>
           <main>
               <disk>s3</disk>
           </main>
       </volumes>
       <move_factor>0.2</move_factor>
   </s3_tiered>
</policies>

在某些情况下,用户可能需要修改特定表的存储策略。比如,你想要将之前的存储策略 s3_main 修改为 s3_tiered。此时,需要注意新的目标策略必须包含前一个策略的所有磁盘和卷 ,也就是说不会迁移之前的数据来满足策略更改 。由于 s3_tiered 包含了 s3_main 的所有磁盘和卷,因此示例的修改是没问题的。

小结

  1. 直接使用 S3 作为 Disk 存储数据, 能够实现对 S3 的无感使用,和本地存储没有什么区别,但是由于网络通信的延迟,读写速度会慢不少。
  2. 可以通过定义存储策略,将本地磁盘作为热存,S3作为冷存,从而实现冷热分离。
  3. 缺点:数据在 S3 上使用 ClickHouse 专有的数据存储格式存储,因此无法通过其他数据分析工具进行处理。

参考

clickhouse.com/docs/zh/sql...

clickhouse.com/docs/en/eng...

www.jianshu.com/p/e14590825...

aws.amazon.com/cn/blogs/ch...

clickhouse.com/docs/en/int...

clickhouse.com/docs/en/int...

clickhouse.com/docs/en/int...

yanbin.blog/aws-s3-key-...

相关推荐
胡小禾11 分钟前
mongoDB-1
数据库·mongodb
天冬忘忧17 分钟前
MongoDB在Linux系统中的安装与配置指南
数据库·mongodb·datax
睡不醒的小泽2 小时前
VSCode环境下连接 MySQL 8.0 数据库 (C++)
数据库·windows·vscode
藓类少女4 小时前
正则表达式
数据库·python·mysql·正则表达式
魏 无羡5 小时前
pgsql 分组查询方法
java·服务器·数据库
szcsd1234567895 小时前
简单有效关于msvcp140.dll丢失的解决方法,msvcp140.dll修复的方法原理及步骤
数据库·dll文件·dll修复工具·dll修复·dll丢失
江凡心5 小时前
Qt 每日面试题 -1
服务器·数据库·qt
好记忆不如烂笔头abc5 小时前
db2恢复数据库
数据库
Counter-Strike大牛6 小时前
MySQL迁移达梦报错,DMException: 第1 行附近出现错误: 无效的表或视图名[ACT_GE_PROPERTY]
java·数据库
小诸葛的博客8 小时前
pg入门18—如何使用pg gis
数据库