关于数据库:ClickHouse-冷热分离存储在得物的实践

4次阅读

共计 10726 个字符,预计需要花费 27 分钟才能阅读完成。

1. 业务背景

得物上一代日志平台的存储次要依赖于 ES。随着公司业务的高速倒退,日志场景逐渐产生了一些新需要,次要体现在:利用数量逐渐增多,研发须要打印更多的日志定位业务问题,平安合规须要保留更长时间的日志。随着 Clickhouse 的利用宽泛,咱们理解到行业局部出名公司曾经将日志平台逐渐由 ES 迁徙至 Clickhouse,以此来获取更好的写入性能与高压缩比。因而咱们与日志平台研发团队开始进行日志平台新存储的选型评估,本文会介绍咱们如何通过 Clickhouse 的冷热拆散存储代替 ES 的实施方案。

2. 前置介绍

2.1 ClickHouse 简介

ClickHouse 是一个用于联机剖析 (OLAP) 的列式数据库管理系统(DBMS)。列式数据库更适宜于 OLAP 场景(对于大多数查问而言,处理速度至多进步了 100 倍),上面通过图片更有利于直观了解:

  • 行式

    图片起源:https://ClickHouse.com/docs/a…

  • 列式

    图片起源:https://ClickHouse.com/docs/a…

同时 ClickHouse 基于列存,还提供了很高的压缩比。压缩算法 lz4 能够到 1:4,zstd 能够到 1:10。本文次要是介绍咱们在 ClickHouse 下面的实际,对于 ClickHouse 自身的个性及相干介绍就不在这里赘述了。

2.2 ClickHouse 存储策略

ClickHouse 创立表时是反对表级数据 TTL 策略的,TTL 策略能够反对数据过期主动合并 (Compaction) 后删除,当然也反对主动合并后挪动到其余 Disk 或 Volumn。日志平台的多级存储就是利用了存储策略,但因为踩了 TTL 的一个坑,咱们最终放弃表级 TTL 设置,改成搬迁表 part 的任务调度实现 TTL 策略,前面会说到。

  • 配置存储策略

    <path>/data1/ClickHouse/data/</path>  -- 为了便于查找,咱们倡议在默认的存储门路下方增加存储策略
      <storage_configuration>
              <disks>
                  <hot>
                      <path>/data1/ClickHouse/hot/</path>  -- 这里留神,应用存储策略后,倡议任何数据门路之间不要有子集关系
                  </hot>
                  <cold>
                      <path>/data2/ClickHouse/cold/</path>
                  </cold>
              </disks>
              <policies>
                  <ttl>
                      <volumes>
                          <hot>
                             <disk>hot</disk>
                          </hot>
                          <cold>
                             <disk>cold</disk>
                          </cold>
                      </volumes>
                  </ttl>
              </policies>
      </storage_configuration>
    • <path> 为 ClickHouse 默认的存储门路,找到该标签后在下方增加存储策略标签 <storage_configuration>。
    • <storage_configuration>:固定标签,定义存储策略。
    • <dicks> 固定标签,上面会定义磁盘名称,以及磁盘绝对路径。
    • <hot>、<cold>:自定义标签,用来标记该门路,可依照此名称定义便于辨别。
    • <policies>:固定标签,定义具体存储策略名称。
    • <ttl>:自定义标签,定义具体存储策略的名称,用于表级 TTL,可依照此名称定义便于辨别。
    • <volumes>:固定标签,定义卷组。
    • <hot>、<cold>:卷组名称,每个卷组下能够包含一个或多个 disk 标签,disk 标签取值为 < disks > 标签下定义的磁盘名称。
  • 创立表

示例:

CREATE TABLE db_rdsauditlog_local ON CLUSTER auditlog
(
   `check_rows` Int64,
   `client_ip` String,
   `db` String,
   `fail` Int64,
   `instance_id` String,
   `latency` Int64,
   `origin_time` DateTime('Asia/Shanghai'),
   `return_rows` Int64,
   `sql` String,
   `thread_id` Int64,
   `update_rows` Int64,
   `user` String,
   `tables` Array(String),
   `sqlhash` String,
   `sqlfingerprint` String,
   `sqltype` String,
   INDEX instance_sqltype (instance_id, sqltype) TYPE set(100) GRANULARITY 5,
   INDEX origintime_sqlhash (instance_id,sqlhash) TYPE set(100) GRANULARITY 5,
   INDEX origintime_instance_clientip(instance_id, client_ip) TYPE set(100) GRANULARITY 5
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(origin_time)
ORDER BY origin_time
TTL origin_time + INTERVAL 6 hour TO DISK 'cold'
SETTINGS storage_policy = 'ttl',index_granularity = 8192;

Create table db_rdsauditlog on cluster auditlog as db_rdsauditlog_local ENGINE=Distributed(auditlog, default, db_rdsauditlog_local, rand());

创立表级 TTL,要想应用分级冷热存储,必须要指定存储策略,能力应用到 Disk,为什么要指定存储策略,我的了解是 TTL 能够指定到 Disk 和 Volumn 级,然而 Volumn 级别只能定义在存储策略里。

具体浏览:https://ClickHouse.com/docs/e…

2.3 分区策略

PARTITION BY– 分区键,表级可选的参数。在大多数状况下,其实不须要分区键,同时即使应用了分区键,不太倡议应用比月更细粒度的分区键,分区不会减速查问(与 ORDER BY 表达式相同)。你永远不应该应用太细化的分区。不要按客户端标识符或名称对数据进行分区,而是将客户端标识符或名称作为 ORDER BY 表达式中的第一列(官网文档这句话还是要好好牢记,个别咱们只倡议工夫字段做分区)。

以上是官网文档的原话,然而理论中咱们须要依据具体情况是能够创立甚至到小时级别粒度的分区,还是那句话,看需要,用不好会有副作用,前面会提到。

3. 业务需要

业务背景中除了提到须要很高的写入能力,很高的压缩比,理论需要调研中,基础架构同学还提出各个业务域都有本人的日志工夫查问范畴需要(ES 作为存储时,研发甚至须要可能提供天级的日志保留工夫),比方 7 天,30 天,90 天,甚至更长等不同范畴,因为冷数据查问频次较低,能够应用更低廉的存储介质,另外有一些数据保留一段时间之后不会被查但须要满足合规要求,再长的日志就能够删除。基础架构心愿 DBA 可能帮忙在 ClickHouse 的存储上对数据保留工夫提供一些倡议,尽最大可能升高存储老本。

总结一下就是如下需要:

  • 如何能尽可能满足各个业务域的天级保留策略?
  • 如何将数据可能依据日期寄存在不同的存储介质中?
  • 多级存储策略选用什么样的存储介质可能尽最大可能升高存储老本?

依据这些需要,DBA 对这些需要提供了如下计划:

  • 天级保留策略,咱们应用了表分区策略,并躲避了一些坑。
  • 多级存储次要应用了三级存储策略,即 Hot+Cold+Arch(Archive 后续均已 Arch 代替)。
  • Hot 盘选用 ESSD PL1 盘挂载宿主机,磁盘名称为 hot;Cold 选用 ESSD PL0 盘挂载宿主机,磁盘名称为 cold;Arch 咱们最终抉择间接挂载 OSS 文件系统(因为两头咱们还调研过 JuiceFS,但最终还是放弃了),磁盘名称为 arch。

上面一一解说。

4. 方案设计

如业务需要,咱们须要解决三个问题:

4.1 如何尽可能满足各个业务域的天级保留策略?

  • 计划 1:
    分区策略 PARTITION BY (application,environment,toYYYYMMDD(log_time))

最后咱们冀望应用分区策略,用利用名称 + 环境 + 按天分区的组合分区策略来满足要求,即 PARTITION BY (application,environment,toYYYYMMDD(log_time))。这样每个利用每个环境有独立的分区,业务研发还能够很灵便的随便批改天级的日志保留工夫,这样每个分区能够依据保留策略独立的挪动到不同的磁盘。现实很饱满,事实很骨感。尽管这个计划能够很好的满足需要,然而真正写入的时候,咱们遇到了问题,因为线上利用在较多,且还在呈上升趋势,每个利用还可能有多个环境,即使每个利用每天只写入到一个天分区,理论测试过程中,咱们发现写入性能严重不足,写入很慢,同时碰到多个参数值不够报错的问题。

  • max_partitions_per_insert_block,默认值是 100。

意思是一次插入的数据块波及到的分区数量。因为日志是生产的 kafka 一个某个 topic,一个 topic 可能有几百个利用,每个利用还有多个环境,即使写入天级分区,分区是物理上隔离成不同的目录,一次写入也会被 ClickHouse 拆分成几千个不同 partition 中的 part,一次 insert 波及到的 partition 数爆炸,提醒插入数据的 part 数量超过该参数值(too many partitions for single insert blocks),远超参数设置,100 基本不够,调整到 1w 还是报错。写入量不是很大的状况下,是能够适当调整该参数,但测试环境日志产生的切实太碎,上调该参数很快就有 too many parts 的报错,too many parts 的呈现就是 ClickHouse 合并跟不上写入,间接回绝写入数据,这就是上面的参数 max_parts_in_total 值不够。

  • max_parts_in_total,默认值 10w。

测试环境中因为有一些利用数据较少,导致攒批数据比拟难,加上若代码在批次写入数据的中央解决不好,很容易呈现频繁写入较少数据行的 part,加上利用数,环境较多,导致写入的数据较碎,一个表内 active 的 part 数据非常容易超过 10w(能够查看 system.parts 表中状态为 active 的数据个数),屡次报错 too many parts in total 的谬误。这里能够适当进步 background_pool_size 的值晋升合并速度,然而对于大量较碎的 part 也是无济于事,不能解决基本问题。最终咱们放弃计划 1,抉择计划 2。

  • 计划 2:
    分区策略 PARTITION BY (toDate(log_time), log_save_time, oss_save_time)

计划 1 的问题最次要还是分区字段设置的问题,咱们决定在满足写入能力的前提下提供肯定能力的日志保留工夫。最终将分区字段去掉利用名称 application 字段(为了保障查问速度,咱们将 application 字段放入到 order by 的第一个字段。

因为三级保留策略,最开始想到用多个表即咱们定义日志保留工夫范畴的固定选项(7d,30d,90d,180d),日志保留工夫须要研发做一些斗争,不能随便批改保留工夫,依据这几个选项创立对应的表,这样能施展写入的最佳性能。然而会有一个问题,就是当研发提工单批改利用的保留工夫后,工夫调整会导致日志落入不同的表,这样代码中查问语句不变的状况下会呈现变更保留工夫后查不到历史数据的问题,尽管能够代码做肯定的路由规定,但很繁琐,不够敌对,放弃该计划。

如何解决?通过咱们探讨,咱们想到一个办法,表中增加两个字段 log_save_time,oss_save_time,这两个字段是 int 类型,将分区字段调整为 PARTITION BY (toDate(log_time), log_save_time, oss_save_time)。log_save_time 为每条日志在 hot 盘中保留的工夫,超出会被工作挪动到 cold 盘;oss_save_time 为每条日志在 cold 盘中保留的工夫,超出会被挪动到 arch 盘 (这里的挪动工作会在上面介绍到)。工作每天会查问 system.parts 表,查看分区字段中三个字段比照,即 toDate(log_time) 和以后日期比拟,差值大于分区中 log_save_time 的值,则挪动该分区到 cold 盘,差值大于 oss_save_time 则挪动到 arch 盘。这样如果利用日志保留策略的元数据信息批改,新产生的日志数据这两个字段也会写入新的保留策略值,新数据会落到不同的分区中。那么如何实现?进入问题 2。

4.2 如何满足依据日期寄存在不同的存储介质中?

解决了第一个问题,那么日志怎么依据分区设定做过期挪动?

  • 计划 1:
    应用表级 TTL 设置

最开始咱们想到在表的创立语句中应用表级 TTL 设置:

PARTITION BY (toDate(log_time), log_save_time, oss_save_time)
ORDER BY (application, environment, log_time, ip, file_offset)
TTL origin_time + INTERVAL 24 hour TO DISK 'cold' -- 相似这样,SETTINGS allow_nullable_key = 1, storage_policy = 'ttl', index_granularity = 8192

长处是利用 ClickHouse 本身的能力做数据的搬迁,实现过期数据迁徙到冷存储介质。

但会有一个问题,就是会导致后面提到的会依据多个可选工夫范畴创立多个对应的表。

另外还有一个坑就是,表级 TTL 一旦要批改 TTL 的保留工夫,ClickHouse 会 reload 表的所有 part 目录,导致 IO Util 打满,集群无奈响应,这个很坑,目前还没有好的方法解决,所以放弃该计划。

  • 计划 2:
    开发调度工作,手动挪动分区数据

联合问题 1,最终咱们抉择这样创立表构造(只作为 Demo 参考,并非业务真实情况),如下:

CREATE TABLE dw_log.tb_logs_local
(
    `application` String,
    `environment` String,
    `ip` String,
    `filename` String,
    `keys` Array(Nullable(String)),
    `values_string` Array(Nullable(String)),
    `values_number` Array(Nullable(Float64)),
    `file_offset` Nullable(UInt32),
    `message` String CODEC(ZSTD(1)),
    `log_type` String,
    `log_time` DateTime64(3),
    `log_level` String,
    `trace_id` String,
    `pid` Int64,
    `endpoint` String,
    `log_save_time` Int32,
    `oss_save_time` Int32,
    `meta_size` Int64,
    INDEX meta_size meta_size TYPE SET(100) GRANULARITY 2,
    INDEX application application TYPE SET(100) GRANULARITY 2,
    INDEX environment environment TYPE SET(100) GRANULARITY 2,
    INDEX ip ip TYPE SET(100) GRANULARITY 2,
    INDEX idx_message message TYPE tokenbf_v1(512, 2, 0) GRANULARITY 2,
    INDEX trace_id trace_id TYPE SET(100) GRANULARITY 2,
    INDEX log_level log_level TYPE SET(10) GRANULARITY 2,
    INDEX pid pid TYPE SET(100) GRANULARITY 2,
    INDEX idx_endpoint endpoint TYPE tokenbf_v1(512, 2, 0) GRANULARITY 2,
    INDEX logtime log_time TYPE minmax GRANULARITY 2
)
ENGINE = MergeTree
PARTITION BY (toDate(log_time), log_save_time, oss_save_time)
ORDER BY (application, environment, log_time, ip, file_offset)
SETTINGS allow_nullable_key = 1, storage_policy = 'ttl', index_granularity = 8192

日志平台会起一个调度工作,同时保护利用和 (log_save_time,oss_save_time) 的对应关系。每天依据该表的信息做利用对应日志分区的搬迁动作。

alter table dw_log.tb_logs_local on cluster default MOVE PARTITION XXX to disk 'cold'

但这里也有一个小问题就是当研发须要批改日志保留工夫时,比方保留工夫调大,则新的数据会落到新的分区里,这样之前的分区会因为匹配规定起因被提前删除,比方 7 天调整到 30 天,那么因为之前保留工夫在分区中还是 7 这个值,到了第 7 天,之前的分区曾经满足删除策略会被删除。尽管改成 30 天保留策略,仍然会呈现有 7 天日志查不到的状况,当然工夫往后推移 7 天后还是能查到残缺的 30 天日志。反之保留工夫调小,比方从 30 调整到 7,会呈现最长有 23 天的日志被继续保留,空间没有及时开释。时间推移 23 天后空间开释。这些问题不要紧,能够容忍,比照多个表的计划,该计划利大于弊,最终抉择该计划。

4.3 抉择何种介质满足归档数据的海量存储?

解决了过期策略,表构造的设计后,后面提到的 arch 磁盘来存储根本不查的数据,应用低存储老本介质来降低成本,咱们首先想到的就是能不能应用 OSS?答案是能够,同时咱们查看过费用,等同容量的 OSS 老本仅是 ESSD PL0 的三分之一,这无疑能够大幅升高存储费用。但怎么用最优,须要调研 + 测试。

  • 计划 1:
    ClickHouse +  JuiceFS + OSS

JuiceFS 次要性能就是将 S3 转成文件系统挂载应用,且在多家出名互联网公司都有上线案例(某海内电商平台的公开技术分享文章就提到了基于 JuiceFS 实现的 ClickHouse 冷热拆散存储),因而咱们基于这些信息开始调研这个计划的可行性。

JuiceFS 介绍和架构图如下:

JuiceFS 是一款面向云原生设计的高性能共享文件系统,在 Apache 2.0 开源协定下公布。提供齐备的 POSIX 兼容性,可将简直所有对象存储接入本地作为海量本地磁盘应用,亦可同时在跨平台、跨地区的不同主机上挂载读写。

JuiceFS 采纳「数据」与「元数据」拆散存储的架构,从而实现文件系统的分布式设计。应用 JuiceFS 存储数据,数据自身会被长久化在对象存储(例如,Amazon S3),绝对应的元数据能够按需长久化在 Redis、MySQL、TiKV、SQLite 等多种数据库中。

JuiceFS 提供了丰盛的 API,实用于各种模式数据的治理、剖析、归档、备份,能够在不批改代码的前提下无缝对接大数据、机器学习、人工智能等利用平台,为其提供海量、弹性、高价的高性能存储。

图片起源:https://www.juicefs.com/docs/…

过后思考 JuiceFS 的另外一个起因是它的读缓存机制,当拜访 JuiceFS 中的文件时,会有多级缓存给常常拜访的数据提供更好的性能,读申请会顺次尝试内核分页缓存、JuiceFS 过程的预读缓冲区、本地磁盘缓存,当缓存中没找到对应数据时才会从对象存储读取,并且会异步写入各级缓存保障下一次拜访的性能。

图片起源:https://www.juicefs.com/docs/…

可见 JuiceFS 反对将对象存储挂载到 ECS 上,通过文件系统做本地盘拜访,同时反对读缓存来减速文件读取,咱们也做了测试,的确读写性能还不错。

本地读写测试:

将 JuiceFS 的文件系统挂载到 ClickHouse 存储策略下,进行 SSBM 测试后果如下:

QPS(ESSD PL0) QPS(JuiceFS)
Q1.1 30.919 28.642
Q1.2 210.061 191.958
Q1.3 395.616 350.915
Q2.1 5.344 5.053
Q2.2 5.786 6.029
Q2.3 6.173 6.446
Q3.1 3.297 2.618
Q3.2 4.484 4.657
Q3.3 5.427 5.748
Q3.4 243.571 216.825
Q4.1 2.978 3.011
Q4.2 9.057 8.845
Q4.3 14.022 14.155

从 ClickHouse 角度看,查问性能和 PL0 相当,甚至略好一些。(PL0 相比 OSS 老本要高不少)

然而,最终咱们没有应用 JuiceFS 起因如下:

  • 整个 JuiceFS 架构引入第三方存储介质用来保留文件的元数据。
  • 元数据存储应用 Redis 不能保障元数据的一致性,若 Redis 产生切换,元数据失落则 ClickHouse 存在失落数据的危险;
  • 元数据存储应用 MySQL、QPS 和 RT 均不能满足 ClickHouse 高速文件拜访对元数据的拜访频次;
  • 元数据存储应用 TiKV,会减少运维复杂性。

基于以上问题,咱们为了防止引入其余技术栈带来额定的运维老本,当然这里并不是说 JuiceFS 有什么弊病,而是在咱们这个场景下须要的是尽可能少引入其余组件。恰好咱们有现成的对象存储服务。于是持续测试了原生挂载 OSS 的计划(也就是上面要讲到的计划)。这次调研也让咱们深刻理解了 JuiceFS 的架构和劣势,为后续在其余场景的利用奠定了根底。

  • 计划 2:
    ClickHouse + OSS(咱们最终抉择的计划)

ClickHouse 原生的 MergeTree 自身就反对间接挂载 S3 作为数据盘应用,咱们基于这个个性做了测试,后果合乎预期。尽管官网的 demo 是 S3,因为 OSS 也是反对 S3 协定,所以也是同样能够应用的。须要留神的是 endpoint 这里必须 http 结尾,否则无奈失常挂载。

存储策略配置如下:

<storage_configuration>
            <disks>
                <hot>
                    <path>/data1/ClickHouse/hot/data/</path>
                    <move_factor>0.1</move_factor>
                </hot>
                <cold>
                    <path>/data2/ClickHouse/cold/data/</path>
                </cold>
                <arch>
                   <type>s3</type>
                   <endpoint>http://log-sh.oss-cn-xx-internal.xxx.com/xxxxxxxxxx/</endpoint>
                   <access_key_id>xxxxxxxx</access_key_id>
                   <secret_access_key>xxxxxx</secret_access_key>
                   <metadata_path>/data1/ClickHouse/disks/s3/</metadata_path>
                   <cache_enabled>true</cache_enabled>
                   <data_cache_enabled>true</data_cache_enabled>
                   <cache_path>/data1/ClickHouse/disks/s3/cache/</cache_path>
                </arch>
            </disks>
            <policies>
                <ttl>
                    <volumes>
                        <hot>
                           <disk>hot</disk>
                        </hot>
                        <cold>
                           <disk>cold</disk>
                        </cold>
                        <arch>
                           <disk>arch</disk>
                        </arch>
                    </volumes>
                </ttl>
            </policies>
    </storage_configuration>

测试状况:

a. 写测试,move 数据从 hot 盘到 OSS。

OSS 写入能够到 7Gb/s。

b. 重启集群也能很快启动。

该计划架构简略牢靠,便于运维,最终咱们抉择了间接挂载 OSS。抉择 OSS 后,对于归档的数据量,费用能够比 ESSD PL0 还能节俭 66%。而且咱们给每个集群独自申请一个 OSS Bucket,降低成本的同时还能满足足够的写性能。日志平台目前曾经陆续有日志满足归档策略开始迁徙,置信会节俭很大的一笔费用。

5. 存储架构

基于下面讲的计划,咱们的日志平台存储架构如下:

该架构充分利用了云上的基础设施带来的劣势,比方:

  1. 基于 ESSD 云盘实现了单正本的存储架构 (云盘曾经实现了多正本存储),若 ClickHouse 节点故障会主动迁徙到新的 ClickHouse 节点,ESSD 磁盘会一起迁徙。能够实现热(内存 copy) 迁徙或者自行触发迁徙;
  2. 基于 ESSD 的本地扩大能力,能够无损实现磁盘空间的垂直扩容,服务不受影响;比方后期存储空间占用不多,能够先给估算的 50%,随着数据量逐渐回升再扩容;
  3. 基于 ECS 的扩缩容能力,实现疾速的节点能力扩容或降配缩减老本。因为 ClickHouse 自身不具备自动化 reblance 的能力,咱们能够先给较多低配节点,前期随着业务量回升进行 ECS 的垂直升配即可;
  4. 基于低成本对象存储 OSS,存储不拜访或者访问量极少的归档数据。如果本人通过 HDD 搭建的话,是须要一次性投入。云上的 OSS 能够按量免费,绝对比拟划算。

6. 总结

这篇文章次要解说了前一段时间 DBA 团队在日志平台的业务革新中参加的一部分事项,如表字段索引设计倡议、过期策略的计划制订、SQL 编写和优化倡议、提供老本升高计划等输入。最终通过与日志平台研发同学的致力,咱们将日志平台存储由 ES 迁徙到了 ClickHouse,岂但取得了高性能的写入能力,同时也额定节约存储老本 50% 以上。

援用:

  • https://ClickHouse.com/docs/e…
  • https://www.juicefs.com/docs/…
  • https://www.juicefs.com/docs/…

文 /Chen

关注得物技术,每周一三五晚 18:30 更新技术干货
要是感觉文章对你有帮忙的话,欢送评论转发点赞~

正文完
 0