导读 TCHouse-C 是腾讯云基于 ClickHouse 内核的云端全托管服务,目前已服务众多企业客户,包括腾讯内部超过 90% 的 ClickHouse 业务。
主要内容包括以下几个部分:
1. 实时数据更新
2. SCHEMA LESS
3. Q&A
分享嘉宾|彭健 腾讯 高级工程师
编辑整理|张俊光
内容校对|李瑶
出品社区|DataFun
01
实时数据更新
1. 实时数据更新场景
在当前大数据分析的背景下,对于实时数据更新的需求日益凸显,特别集中在两类关键场景上。
- 第一类是对数据进行高频的增删改查。尽管在 ClickHouse 中执行这类操作通常被认为是较为繁重的,且我们通常建议客户避免频繁进行,但在实际的分析业务中,这类场景却是频繁出现的。例如,实时看板、IOT 设备数据监控、用户行为跟踪以及电商交易等场景,都需要对实时数据进行频繁地更新与更正。
- 这些场景往往要求数据更新非常频繁,可能达到几万甚至几十万次每秒(QPS),同时要求低延迟,即数据更新后应立即可见。然而,在当前 ClickHouse 的架构和设计下,已难以满足这样的实时性和高并发需求。
- 第二类是利用部分列更新能力构建大宽表。我们通常建议在业务层面构建大宽表,以充分利用 ClickHouse 的查询和分析性能。然而,社区提供的构建大宽表的方法在实际应用中并不太易用,且性能不尽如人意。
- 在 ClickHouse 支持 UPSERT 功能后,客户在构建大宽表时变得更为简单和高效。通过利用部分列的更新能力,上游不同的业务数据可以仅更新其对应的列,从而极大地简化了数据集成工作。这是目前客户在构建大宽表时面临的两类主要场景之一。
2. ClickHouse 现有的数据更新方案
我们来看一下 ClickHouse 现有的数据更新方案。在社区版本中,它提供了 ALTER TABLE ... UPDATE/DELETE 操作。然而,这些功能实际上无法满足实时数据更新的要求。主要原因在于,当提交更新操作后,ClickHouse 会在后台同步或异步地重写数据,即对这些数据分区(parts)进行重写。这种重写的代价相对较大,且在重写完成之前,更新的数据效果是不可见的,即无法做到实时更新。
这个方案存在两个明显的不足之处。首先,它是基于 mutation 机制来完成数据更新的,这在大数据量下可能会影响业务的正常运行,特别是在高峰期进行这样的操作更可能导致问题。因此,我们通常不建议客户在业务高峰期进行这样的操作。其次,它的更新是非实时的,这意味着在数据更新后,用户不能立即看到更新后的数据状态。
3. ClickHouse Cloud 提供 lightweight-update/delete 功能
通过公开资料,我们了解到 ClickHouse Cloud 提供了 lightweight-update/delete 功能,这是一个引人注目的特性,因为它能够实现数据更新的实时可见性。该功能并非简单地基于传统的 mutation 机制,而是采用了更为灵活和高效的方法。
具体来说,lightweight-update/delete 功能会将待更新的表达式存储在内存的 keeper 中。当执行查询时,系统会首先检查是否有相关的更新表达式。如果对应的数据分区(parts)已经完成重写,则直接返回底层的数据给客户端;如果重写尚未完成,系统会将更新的表达式应用到底层的数据上,并在查询时计算出更新后的结果。这种机制确保了数据更新的实时可见性。
然而,这个功能也存在一些潜在的问题。首先,它依然依赖于 mutation 机制,这意味着在更新非常频繁且规模很大的情况下,可能会对查询性能产生影响。因为系统需要在查询时应用这些更新表达式,这会增加查询的复杂性和计算负担。其次,由于更新表达式存储在内存中,如果更新的数据量非常大,可能会消耗大量的内存资源,需要仔细管理和监控。
总的来说,ClickHouse 的 lightweight-update/delete 功能为实时数据更新提供了一种新的解决方案,但也需要根据具体的业务场景和需求来评估其适用性和性能影响。
4. 数仓领域实时数据更新业界现有方案
(1)Copy-On-Write
Copy-on-Write(写时复制)策略。在这种方案中,当进行数据更新时,如果存在与更新操作重叠或冲突的存量数据,系统会对这部分数据进行重写(rewrite)。这意味着在数据写入的过程中,系统就已经完成了数据的更新和重写工作。更新后的数据是一个已经处于正确状态的数据,即包含了所有必要的更新。
这种方案的一个显著特点是写入代价较大。由于它类似于传统的 mutation 机制,但将这类操作放在了数据写入的流程中,因此会增加写入的复杂性和资源消耗。然而,它的优势在于查询代价较小。因为更新后的数据已经是正确的状态,所以当客户进行查询时,可以直接使用这些数据,无需再进行额外的计算和转换。这是 Copy-on-Write 技术的核心优势。
(2)Merge-On-Read
Merge-on-Read(读时合并)方案相较于 Copy-on-Write,主要解决了写入代价较大的问题。具体实现方式是,在数据写入时,系统不会立即解决数据冲突问题,而是直接将更新的数据追加写入。这意味着写入操作变得相对简单和高效,因为系统不需要立即处理数据之间的冲突和重叠。
然而,数据冲突的问题被推迟到了查询阶段解决。当执行查询时,如果后台的数据还没有进行合并,系统就需要在查询过程中解决数据冲突,进行合并和去重操作,然后返回正确的数据结果。因此,Merge-on-Read 方案的特点是写入代价较小,但查询代价相对较大。这是因为查询时可能需要处理更多的数据和逻辑,以确保返回的结果是正确的。
(3)Dleta-Store
Delta-Store 相较于前两个方案引入了主键索引这一关键特性。Delta-Store 在写入更新的数据时,通过主键索引能够迅速定位到与更新操作存在冲突的数据。对于冲突的数据,Delta-Store 会进行增量的更新,即将更新的数据附加在冲突数据的后面,而对于新增的数据则直接进行追加写入。
尽管 Delta-Store 也面临数据冲突的问题,但它解决冲突的方式相比 Merge-on-Read 更加高效。在写入过程中,Delta-Store 会识别冲突并进行处理,这虽然牺牲了一定的写入性能,但在查询时却能够显著提升性能。由于写入时已经对冲突数据进行了处理,查询时合并的代价就相对较小,从而提高了查询的响应速度和效率。
Delta-Store 的特点在于它在写入过程中就处理了数据冲突,避免了在查询时进行大量的合并和去重操作。这种设计使得 Delta-Store 在处理大量更新和查询请求时更加高效和稳定。
(4)Delete-Insert
Delete-Insert 方案在特定场景下能够极大地提升查询性能,其实现方式是通过引入主键索引来优化数据的更新和查询过程。
在 Delete-Insert 方案中,当新数据需要写入时,系统首先通过主键索引来检查是否存在与新数据冲突的存量数据。如果找到冲突数据,系统不会直接修改这些数据,而是通过一个标记的方式将这些冲突数据标记为删除状态(即逻辑删除)。然后,新的数据会被追加写入到存量数据的后面。
在查询过程中,系统会读取所有的数据,但会根据之前设置的删除标记来过滤掉那些已经被标记为删除(即过期)的数据。通过这种方式,查询能够直接跳过那些不再需要的数据,从而显著提高查询效率。
在 OLAP(联机分析处理)的向量化引擎中,Delete-Insert 方案能够充分发挥其优势。由于 OLAP 系统通常处理的是大规模数据集,而向量化引擎能够同时处理多个数据项,因此 Delete-Insert 方案中的过滤操作可以非常高效地进行,从而进一步提升查询性能。
此外,Delete-Insert 方案也符合列式存储的数据处理特点。在列式存储中,数据是按列式存储的,而 Delete-Insert 方案中的删除标记可以单独作为一个列来处理。这样,在查询时,系统可以只读取那些未被标记为删除的数据列,进一步减少 I/O 操作和数据处理时间。
总的来说,Delete-Insert 方案通过引入主键索引和逻辑删除机制,实现了对数据的高效更新和查询,特别是在 OLAP 场景中能够发挥显著优势。
5. TCHouse-C 实时数据更新方案:Delete-Insert
(1)整体方案
在数仓领域,我们面临多种成熟的技术方案选择。在技术选型过程中,我们决定采用 Delete-Insert 方案来实现数据的管理。具体来说,我们在 SQL 接入层支持标准的 Upsert(即合并插入/更新)语义,这使得我们能够精准地处理数据更新、删除和插入操作。无论是基于条件的删除,还是按字段的更新(包括指定字段的更新),我们都能够轻松应对,这完全符合标准的 Upsert 操作要求。
在存储引擎层,我们引入了表级别的行级索引。这个行级索引的主要作用是在数据写入时,帮助我们高效地定位潜在的冲突数据,并将这些冲突数据标记为待删除状态。在具体的数据部分(data part),我们采用了 bitmap 来存储这些删除标记(delete mark),这样的设计有助于我们高效地进行数据管理和查询优化。
此外,我们还支持了 ClickHouse 的多副本同步机制。这一机制为我们提供了更高的数据可用性和容错能力。通过多副本同步,可以确保数据在多个节点上的冗余存储,即使某个节点出现故障,也能通过其他节点快速恢复数据,保证业务的连续性。
(2)数据写入与去重
我们主要依赖行级索引来解决去重问题。当新的数据部分(part)被写入时,我们会针对其中的每一个主键(primary key)进行判断,以快速确定这些数据在现有存量中是否已存在。
如果存在存量数据,会在对应存量数据的数据部分(data parts)的 bitmap 上打上已删除标记。这个过程完成后,新的数据部分会提交整个 bitmap 的标记。通常,这个提交过程会在较短时间约 15 秒内完成,确保数据的一致性和及时性。
在查询数据时,我们引入了一个虚拟列 exists_row。这个虚拟列是基于数据部分的 bitmap 构建的。在大部分情况下,如果某个数据部分不存在对应的 bitmap(因为大部分场景下 bitmap 是不存在的),那么这个虚拟列在查询过程中几乎不涉及任何计算,查询速度非常快。
然而,如果某个数据部分存在 bitmap,查询时会根据这个 bitmap 去查找,判断行级索引是否存在。这样,通过引入行级索引和 bitmap 机制,我们能够在保证数据准确性的同时,提高查询效率,有效解决去重问题。
(3)多副本数据
首先,我们解决了多副本复制数据同步的问题。我们沿用了 ClickHouse 的多副本顺序同步机制,通过在 ZooKeeper(ZK)上维护一个 log entry 队列来实现不同副本之间的数据同步,每个副本都会根据 log entry 来同步自己的数据。
在工程实现上,我们引入了行级的版本机制来解决数据更新时的冲突问题。考虑到在数据更新时,业务层可能会对同一个 key 进行并发的更新操作,这些更新操作到达服务端(server)时,其时序关系可能会因网络延迟等因素而错位。为了解决这个问题,我们引入了版本机制,允许业务层,显式地指定时序,从而解决数据更新时的冲突问题。
其次,为了同步行级删除操作,我们引入了“墓碑”机制。通过“墓碑”的存在,我们可以将删除状态同步到各个副本上,确保所有副本都知晓哪些数据已被删除。
最后,我们解决了持久化删除的机制。在 ClickHouse 的默认实现中,删除数据通常是通过直接删除并同步新的数据部分(part)到副本节点来完成的。然而,在我们的场景中,需要明确告知每个副本节点哪些数据已被删除。为此,我们引入了一类特殊的数据部分(data part),用于记录并同步这些删除事件。
综上所述,我们通过上述三种方式解决了多副本复制数据同步、数据更新冲突以及持久化删除机制等问题。
6. TCHouse-C 实时数据更新方案的性能测试
我们通过测试对比了 TCHouse-C 项目中引入的 UniqueMergeTree 表引擎与社区标准版的 ReplacingMergeTree 表引擎。测试在三个不同场景下展开,所有测试均在同一台高性能机器上进行,该机器配置了 64 个 CPU 核心、256GB 内存,并配备了四块 3.5TB 的 SSD 硬盘。
第一个场景:数据导入。
使用了标准的 SSB(Star Schema Benchmark)数据集,特别是其中的 lineorder_fat 表,构造了包含 6 亿行数据的测试集。这一测试旨在评估两种表引擎在大量数据快速导入时的性能表现。
第二个场景:并发数据更新。
同样采用 SSB 数据集,数据集规模也维持在 6 亿行。该测试模拟了高并发环境下的数据更新操作,以检验 UniqueMergeTree 和 ReplacingMergeTree 在处理大量并发更新请求时的效率和稳定性。
第三个场景:特定数据集更新。
使用了来自官网的 New York Taxi Data 数据集,尽管原数据集仅包含 2000 万行数据,但为了保持测试的一致性,我们对其进行了修改,特别是调整了 order ID,最终也构造出了 6 亿行的测试集。这一场景主要考察两种表引擎在处理具有特定数据分布和更新模式的真实世界数据集时的性能差异。
通过这三个场景的测试,我们可以更全面地了解 UniqueMergeTree 表引擎相对于 ReplacingMergeTree 表引擎在数据导入、并发更新以及处理特定数据集时的性能优势和特点。
(1)数据导入
在数据导入的性能评估中,我们直接对比了社区中的两大表引擎:ReplacingMergeTree 和普通的 MergeTree,同时还加入了我们的 Upsert Table,即 UniqueMergeTree。通过精确记录并统计导入时间(时间越短,性能越优),我们得出了以下结论:
在实际操作中,UniqueMergeTree 在数据导入方面展现出了相对于 ReplacingMergeTree 显著的性能提升。这一提升主要归因于 UniqueMergeTree 在处理唯一性约束上的高效机制,即在写入数据的同时利用内部的表级或行级索引进行冲突检测和去重,从而减少了不必要的后续处理,加速了数据导入过程。
然而,与追求极致写入性能的 MergeTree 相比,UniqueMergeTree 的导入性能虽然出色,但仍稍显逊色。这是因为 MergeTree 作为 ClickHouse 的基础表引擎,其设计初衷就是最大化写入速度,没有像 UniqueMergeTree 那样额外引入索引机制来处理唯一性需求。因此,在没有唯一性约束的场景下,MergeTree 通常能提供更高的写入效率。
综上所述,UniqueMergeTree 在保证数据唯一性的前提下,提供了卓越的数据导入性能,是处理具有唯一性要求的大规模数据集时的优选方案。其内部的索引机制虽然在一定程度上增加了写入时的开销,但换来了数据一致性和准确性的显著提升。
(2)数据查询
第二个场景聚焦于数据查询性能。首先是针对单次查询,在 lineorder 表上执行了 SSD 上的 13 个查询操作。为了更全面地评估性能,我们针对 ReplacingMergeTree 表引擎测试了两个不同的场景:一是查询时加上了 FINAL 关键词,二是未加 FINAL 关键词。
对于使用 FINAL 关键词的查询,我们观察到其延迟明显较高,这完全符合 ReplacingMergeTree 表引擎的预期行为。因为 FINAL 会强制表引擎在查询前完成所有必要的合并操作,以确保数据的最终一致性,但这一过程增加了额外的计算负担,从而导致了查询延迟的增加。
在未使用 FINAL 关键词的查询中,UniqueMergeTree 展现出了相对于 ReplacingMergeTree 的明显性能优势,实际上达到了数倍的性能提升。这一结果充分证明了 UniqueMergeTree 在处理查询时的高效性,特别是在不需要额外合并操作来确保数据一致性的场景下,其性能优势尤为突出。
综上所述,UniqueMergeTree 在单次查询性能方面表现优异,特别是在未使用 FINAL 关键词的情况下,能够显著提升查询效率,为用户提供更加流畅的数据访问体验。
在关于并发查询的性能测试中,我们随机生成了 100 个主键(Primary Key),并在这两个表引擎(ReplacingMergeTree 和 UniqueMergeTree)中并发地查询这些主键对应的行数据。测试采用了 32 个并发线程来模拟高并发访问场景。
测试结果显示,UniqueMergeTree 在并发查询方面的性能远远优于 ReplacingMergeTree。这一显著的性能优势主要归功于 UniqueMergeTree 是内部的主键索引机制。由于 UniqueMergeTree 在数据插入时就已经根据主键建立了索引,因此在查询时能够迅速定位到目标数据,极大地提高了查询效率。特别是在并发场景下,主键索引的优势更加凸显,使得 UniqueMergeTree 能够更快速响应多个并发查询请求。
综上所述,UniqueMergeTree 通过内置的主键索引机制,在并发查询性能方面实现了显著的性能提升,为处理高并发查询请求提供了强有力的支持。
(3)数据更新/删除
第三个场景聚焦于数据更新和删除的性能评估。首先,我们构建了一个包含 6 亿行数据的测试表,并在此基础上进行了单次更新和删除操作的测试。测试覆盖了不同规模的数据更新量,包括1000 万行、500 行以及 100 万行,以全面评估两种表引擎(ReplacingMergeTree 和 UniqueMergeTree)在数据更新和删除方面的性能表现。
在单次删除操作的测试中,我们观察到更新(或删除)操作的延迟与所处理的数据量紧密相关,数据量越小,操作延迟通常越低,性能表现越好。特别值得注意的是,在更新数据量相对较小的场景下,如 100 万行或更少时,UniqueMergeTree 展现出了非常快的更新速度,其性能优势尤为明显。这主要得益于 UniqueMergeTree 内部的优化机制,使其能够高效处理小规模数据更新,而无需对整个表进行大规模的合并或重构。
综上所述,UniqueMergeTree 在数据更新和删除方面,特别是在处理小规模数据更新时,表现出了显著的性能优势。这一优势使得 UniqueMergeTree 成为处理需要频繁数据更新和删除操作的应用场景中的理想选择。
接下来看并发更新/删除的性能。我们使用了 32 个并发线程来模拟高负载下的删除操作,以评估 ReplacingMergeTree 和 UniqueMergeTree 在并发环境下的表现。
测试结果显示,UniqueMergeTree 在 32 并发删除操作中展现出了极低的延迟。这一优异的性能表现主要归功于 UniqueMergeTree 内部的高效索引机制和数据管理策略,使得它能够在并发环境下快速定位并删除目标数据,而无需对整个表进行大规模的重构或合并。
相比之下,ReplacingMergeTree 在并发删除时表现出了较高的延迟。这主要是因为 ReplacingMergeTree 依赖于后台的 mutation 机制来处理数据的更新和删除。在并发环境下,多个 mutation 可能会同时触发,导致资源争用和锁竞争,从而增加了操作的延迟。
综上所述,UniqueMergeTree 在并发删除性能上明显优于 ReplacingMergeTree,这主要得益于其内部的高效索引和数据管理机制。这一优势使得 UniqueMergeTree 在处理高并发删除请求时能够保持低延迟,为用户提供更好的数据操作体验。
7. TCHouse-C 实时数据更新规划
关于实时数据更新的后续规划,我们当前在内存中采用哈希索引,仍有较大的优化空间。鉴于支持 Upsert 语义,我们将优化更新、删除等 SQL 操作的执行计划,实现更轻量级的数据版本控制(已部分实现)。此外,利用现有索引技术,我们将增强对特定数据(如行情数据)的点查能力。
为提升性能与扩展性,我们将优化查询引擎以减轻查询负担,并计划从全量数据索引扩展至对冷数据也进行索引,从而支持更大规模的数据集和更广泛的应用场景。这些规划旨在进一步提升实时数据更新的效率与灵活性。
02
SCHEMA LESS
1. 半结构化数据
接下来将探讨半结构化数据的处理策略。半结构化数据在业务与生产中极为常见,如 JSON、监控数据、日志等,来源广泛且价值潜力巨大,而当前社区尚缺乏高效处理半结构化数据的解决方案。通过数据分析、挖掘,我们能获得深刻洞察与预测能力,其灵活性使得数据生产代价低,样式多变,可适应不同业务场景。同时,其多样性和不规则性也为表达复杂数据提供了可能。
2. ClickHouse 处理半结构化数据优势
ClickHouse 在处理类似日志数据的半结构化数据时,展现出了显著优势。首先,成本效益显著,其高压缩比相较于 ES 等系统能够大幅降低存储需求,同时向量化检索能力确保了卓越的性能,从而在降低成本的同时提升了查询效率。这意味着在相同工作节点下,所需的机器配置可相应降低。
其次,ClickHouse 在性能上同样出类拔萃,表现为高写入吞吐量和低查询延迟,这对业务应用极为友好,满足了实时数据处理的需求。
再者,运维简便也是其一大亮点。ClickHouse 的运维工作相对简单,系统无外部依赖,稳定性高。若采用腾讯云等云服务,运维负担进一步减轻,用户可更专注于核心业务逻辑。
3. 业界广泛使用 ClickHouse 处理半结构化数据
在业界,ClickHouse 广泛应用于处理半结构化数据,如小红书、B 站、携程、Uber 等知名企业均利用其处理日志及 APM 数据,实现了显著的性能提升。例如,小红书通过采用 Schema-less 模式替代传统的 Object 或 JSON 字符串存储,性能提升近 20 倍,成本降低超过 50%。B 站也分享了类似的显著性能与成本优化成果。携程和 Uber 同样报告了性能的大幅提升,尽管 Uber 未提及具体成本数据。此外,京东、唯品会、快手等也在探索 ClickHouse 处理半结构化数据的潜力,尽管当前应用尚存挑战,但业界对其优势普遍认可,并仍在持续探索中。
4. ClickHouse 处理半结构化数据的痛点
ClickHouse 处理结构化数据时面临一些痛点。首先,若将数据以字符串形式存储并尝试使用分析函数,会导致 CPU 消耗高、压缩比低,且缺失 Schema 信息,从而无法充分利用 ClickHouse 的加速性能。其次,采用社区早期的 Object 方案,即将 JSON 存储为 Object 列,同样面临压缩比低、CPU 资源占用高的问题,且无法利用二级索引、物化视图及 Projection 等高级功能进行性能优化。尽管存在这些挑战,社区仍在不断努力改进并探索更高效的处理方案。
5. TCHouse-C 处理半结构化新方案 SCHEMA-LESS
(1)整体方案
在腾讯云 ClickHouse 服务中,我们创新性地实现了 Schema-less 方案,旨在简化业务数据写入 ClickHouse 的过程。用户在创建表时,仅需指定主键(PrimaryKey)、分区键(Partition Key)及排序键(Sorting Key),无需详细定义表的 Schema。业务数据写入时,只需包含这些关键字段及一个 JSON 结构(无论是字符串还是结构化形式),即可被系统接受。
服务端接收到此类请求后,会自动解析 JSON 内容,构建树状结构,并根据从根节点到叶节点的遍历结果,解析数据类型,最终将数据转换为 ClickHouse 内部可识别的 Block 格式。这一过程中,若遇到新的字段,系统将自动进行 Schema 扩展,确保数据的完整性和一致性。
值得注意的是,Schema 扩展与数据分区(Part)的提交是在同一事务内完成的,一旦事务失败,系统将自动回滚,保证数据的内部状态不会因部分写入而受损。
为了支持分布式查询,我们实现了 Schema 的快速同步机制。借助 Keeper 等组件,新的 Schema 信息能够迅速被集群中的其他节点订阅或主动分发,确保数据查询的一致性和准确性。
在查询层面,我们对 SQL 进行了优化和重写,支持按字段名或路径的模糊查询等高级功能。这一特性充分利用了 JSON 数据的灵活性,允许用户在不完全了解数据结构的情况下,通过前缀匹配等方式进行高效查询,极大提升了用户体验。
综上所述,TCHouse-C 的 Schema-less 方案为业务数据的高效写入与灵活查询提供了强有力的支持,是处理半结构化数据的理想选择。
(2)方案细节
TCHouse-C 处理半结构化数据的方案细节重点有以下四个方面:
- 解析半结构化数据:首先将接收到的半结构化数据(如 JSON)解析为 ClickHouse 内核能够识别的 Block 格式。这一过程涉及从 JSON 结构中提取数据,并根据数据类型和结构构建 ClickHouse 内部的数据块,以便后续处理。
- 查询重写:为了支持对半结构化数据中字段的灵活查询,我们需要对 SQL 查询进行重写。这包括处理按字段名或路径的模糊查询,确保用户能够利用 JSON 的灵活性,即使在不完全了解数据结构的情况下,也能进行有效的数据检索。
- 动态扩展Schema:随着数据的不断写入,可能会引入新的字段。因此,我们实现了动态 Schema 扩展机制,能够在不中断服务的情况下,自动更新表的 Schema 以包含新字段。这一机制确保了数据的完整性和系统的可扩展性。
- 集群内部 Schema 信息同步:为了支持分布式查询,需要确保集群内所有节点都拥有最新的 Schema 信息。因此,我们实现了 Schema 信息的快速同步机制,利用 Keeper 等工具确保新 Schema 能够迅速被集群中的其他节点所订阅或分发,从而保证查询的一致性和准确性。
(3)案例
TCHouse-C 处理半结构化数据的新方案通过实际案例展现了其强大功能。假设我们创建了一个 ClickHouse 表,指定了分区键和排序键,并启用了动态 Schema(即Schema-less)功能。在数据写入时,除了必要的分区键和排序键外,还包含了一个 JSON 字符串字段。这个 JSON 字符串可能来源于 S3 等存储服务,经过下载后作为数据的一部分被写入 ClickHouse。
由于启用了 Schema-less 功能,该表在 ClickHouse 内部实际上拥有了一个非固定的 Schema 结构,能够根据 JSON 字段动态地存储数据。用户无需事先定义所有可能的字段,即可开始数据写入过程。
一旦数据被成功写入,用户便可以利用 ClickHouse 提供的丰富功能对 JSON 字段进行深入分析。他们可以直接在查询中使用 JSON 字段,也可以为这些字段创建索引以提高查询效率。此外,用户还可以基于 JSON 字段创建 Projection,以优化特定查询的性能。更进一步,通过物化视图,用户可以将基于 JSON 字段的数据同步到另一个表中,实现数据的灵活处理和转换。
在整个过程中,用户无需关心字段的变更或调整。TCHouse-C 的 Schema-less 方案自动处理这些底层细节,确保数据的完整性和一致性,同时为用户提供了一种简单而强大的方式来处理和分析半结构化数据。
(4)效果
TCHouse-C 处理半结构化数据的新方案在性能和成本方面带来了显著效果。首先,该方案极大地简化了数据的写入流程,对业务接入极其友好。无论是新业务还是存量业务,都无需频繁与平台服务方核对 Schema,业务方只需清楚自己要写入哪些数据及其 Schema,而服务方则专注于提供稳定的存储和计算服务。这种去中心化的数据管理方式显著降低了沟通成本和时间消耗。
其次,由于 JSON 数据在内部被展开并按字段存储,该方案实现了更高的性价比和压缩比。常见的压缩比可达到 5-10 倍,这不仅减少了存储空间的需求,还提高了数据的查询性能。用户可以根据具体字段进行查询,而无需扫描整个 JSON 对象,从而进一步提升了查询效率。
此外,TCHouse-C 的新方案还提供了更多的优化手段。包括对字段的二级索引、物化视图(Projection)的支持,以及数据管理能力,如删除不需要或错误的字段。这些功能在之前的方案中往往难以实现或成本高昂,但现在却变得轻松可行。同时,按字段或按表的 TTL(Time-To-Live)机制也为数据管理提供了更多灵活性。
在实际应用中,这些改进带来了显著的性能提升和成本降低。据我们在真实客户生产环境中的测试显示,该方案实现了至少 20 倍的性能提升,并降低了 50% 的成本。这表明,TCHouse-C 的 Schema-less 方案在 ClickHouse 平台上是非常适合处理日志数据等半结构化数据的工具。它不仅提高了数据处理和查询的效率,还降低了整体运营成本,为企业的数字化转型提供了有力支持。
03
Q&A
Q1:TCHOUSE-C 主键索引是单机级别还是集群级别的?
A1:TCHOUSE-C 的主键索引是设计为单机级别的。在数据写入过程中,如果希望实现特定的数据分布策略,即按照某种规则(比如特定的 KEY)来分布数据写入,那么仍然需要显式地指定这个 KEY 来指导数据的分配。也就是说,尽管主键索引本身是单机级别的,但可以通过选择适当的 KEY 来影响数据在集群中的分布,从而优化查询效率和数据管理。
Q2:如果社区的使用 Copy-On-Write,为什么不是写入慢查询更快?因为查询的时候不用管墓位标记。
A2:社区 ClickHouse 使用 Merge-On-Read 的异步合并机制和 final 关键字的同步查询功能,在数据写入效率和查询结果一致性之间取得了良好的平衡。
Q3:增加行级索引存储会增加多少?数据结构是什么样子?构建索引是在写入时候实时构建的,还是异步去构建的?
A3:在 TCHOUSE-C 项目中,当我们讨论为 ClickHouse 增加“行级索引”(尽管 ClickHouse 本身不直接支持传统意义上的行级索引,但这里可以理解为通过主键优化查询性能的方式)时,确实需要注意到内存占用量与主键字段长度的紧密联系。我们避免了直接对主键进行哈希处理,主要是出于避免哈希冲突、保持数据一致性和查询准确性的考虑。相反,我们直接利用主键字段来构建索引,这意味着索引的内存占用量会随主键内容的大小而变化。
在整体设计合理的前提下,主键的设置能够有效控制索引的存储大小。正如您在我们的测试场景中所观察到的,即使数据量高达 600 多 GB,主键索引的内存占用量也控制在了 30-40GB 的范围内,这表明主键字段的选择和索引策略是恰当的。
关于索引的实现细节,目前我们采用的是表级全量索引的方式,这意味着索引覆盖了表中的所有数据。我们确实使用了哈希索引的某些特性或原理来优化查询效率,但需要注意的是,这里的“哈希索引”并非 ClickHouse 原生支持的类型,而是我们在实现层面上采用的一种技术手段或策略。
索引的构建过程分为两个阶段:首先,在服务启动时,系统会加载数据并构建初始索引;其次,随着新数据的写入,系统会实时地为这些新数据构建索引。这种动态索引更新的机制确保了数据的实时性和查询的高效性。
然而,也需要注意到,如果索引过大,确实可能会给 ClickHouse 的内存管理带来压力。因此,在设计索引策略时,需要综合考虑数据的特性、查询的需求以及系统的资源限制,以寻求最佳的平衡点。
Q4:如果关于索引过大,给内存管理带来压力,TCHouse-C 是考虑基于什么方案解决这个问题的呢?
A4:在生产业务环境中,并不是所有场景都需要对全表的数据进行实时的逻辑更新。通过限制更新的范围,特别是针对那些被认为是“冷数据”的分区,可以有效降低对内存和计算资源的需求。
具体来说,您可以考虑将主键索引构建在分区级别上,而不是整个表级别。这样,每个分区内部的数据可以保证主键的唯一性和不重复性,而不需要在全局范围内进行严格的同步和检查。这种策略在大多数情况下都是可接受的,因为它允许您在保持关键数据(即最新数据)一致性的同时,对旧数据或冷数据进行更为灵活的处理。
当然,这种优化也涉及到一个权衡取舍的过程。一方面,它降低了系统的复杂性和资源消耗;另一方面,它也可能在某些场景下牺牲了一定的数据一致性和完整性保证。因此,在实施这种策略时,需要充分理解业务需求和数据特性,确保所做出的取舍是符合实际需求的。
Q5:基于 Schema Free 的 json 优化方向,日志存入 ClickHouse 进行分析,ClickHouse 的并发支持不太行,多个人同时进行日志分析时,会不会经常失败?
A5:确实,ClickHouse 的存储机制和数据模型对于性能优化至关重要。当数据以类似于“schema-less”的方式存储在 ClickHouse 中时,实际上它内部仍然会遵循严格的列式存储结构,这种结构对于大规模数据分析具有显著的性能优势。ClickHouse 作为一款专为在线分析处理(OLAP)设计的列式数据库管理系统,拥有众多优化工具和机制,如分区、索引、压缩等,这些都能在大宽表模式下得到充分利用,从而提供极高的查询性能。
关于并发操作和潜在的失败问题,这确实取决于多种因素,包括系统的资源配置、查询的复杂度、数据更新的频率等。在资源充足且使用方式正确的情况下,ClickHouse 通常能够很好地处理并发请求,减少失败情况的发生。此外,ClickHouse 的分布式架构也提供了良好的容错性和可扩展性,有助于进一步提升系统的稳定性和可靠性。
您提到的在 B 站使用 ClickHouse 存储日志的实践经验非常有价值。从实际情况来看,即使 B 站这样的高流量平台,ClickHouse 也能够轻松应对日志数据的存储和查询需求。这得益于 ClickHouse 的高效性能和稳定性,使得它成为处理大规模日志数据的理想选择。同时,这也证明了在正确配置和使用的前提下,ClickHouse 能够胜任各种复杂的业务场景。
Q6:ClickHouse 如何处理 JSON 字段类型变化,及其对查询性能和正确性的影响?具体来说,当 JSON字段的数据类型在存储过程中从数字变为字符串时,ClickHouse 如何识别并存储这些变化的数据类型?这种类型变化是否会影响后续查询的正确性或性能?
A6:在 ClickHouse 中处理 JSON 字段的类型变化确实涉及一些细节考量。首先,关于数值类型的推导,ClickHouse 会根据数据的实际大小尝试使用最小可能的存储空间来存储,例如从字节到整数类型的逐步推导。然而,这种自动推导机制在面临数据类型后续变化时可能引发问题,尤其是当数值超出初始推导类型的范围时。
为了解决这一问题,我们采取了预防措施:对于数值类型,我们统一采用 64 位整数(如 Int64)或双精度浮点数(如 Double)来存储,无论实际数据大小如何。这种策略确保了即使数据类型在后续发生变化,也能被正确存储,避免了因类型推导错误导致的数据溢出或精度损失。
对于 JSON 字段的易变形特性,ClickHouse 在发现字段类型与已定义类型不符时,会采取一系列措施。最直接的做法是抛出异常,通知业务层进行处理。业务层可以通过 ALTER TABLE 语句来修改表结构,以适应新的数据类型,或者删除问题字段并引入新字段。这种方法虽然直接,但要求业务层具备足够的灵活性和响应能力。
另外,我们也在探索一种更智能的处理方式:在数据写入时,尝试将字符串类型的字段转换为预期的数值类型(如 long)。如果转换成功,则按新类型存储;如果转换失败(例如,字符串包含非数字字符),则向用户报告错误。这种方法在一定程度上提高了系统的自动化处理能力,减少了人工干预的需求。
还有,对于数值类型,统一采用最高精度的数据类型(如 64 位整数和双精度浮点数),以确保数据的完整性和准确性。而对于字符串到数值的转换,采取尝试转换并报错的策略,也是一种有效的错误处理方式。这样的设计既保证了系统的健壮性,又提高了用户体验。
以上就是本次分享的内容,谢谢大家。