当前位置: 首页 > news >正文

Debezium日常分享系列之:提升Debezium性能

Debezium日常分享系列之:提升Debezium性能

  • 测试环境搭建
  • PostgreSQL
  • 运行测试
  • 分析结果
  • 提出修复方案
  • JMH基准测试
  • 在测试应用中进行验证
  • 结论

定期评估整个项目(或至少其关键部分)的性能表现十分必要,尤其是在新增功能或进行重大代码重构时。不过,性能检查也可以临时开展,或更理想地——形成定期机制。

本文将通过一个案例,演示如何快速识别和分析Debezium中的特定性能问题。内容涵盖完整闭环:搭建轻量级性能测试、分析结果、提出优化方案并验证效果。

建立完善的性能测试通常充满挑战,许多细节可能导致结果失真。本文介绍的方法旨在快速暴露潜在瓶颈——通常是那些通过简单测试就能发现的"低垂果实"。当然,这并不意味着常规性能测试不重要。恰恰相反,全面且周期性的测试(尤其是使用高级负载生成工具的端到端测试)至关重要,许多性能问题只有在复杂环境中才会显现。

测试环境搭建

Debezium应用程序

我们首先基于Debezium嵌入式引擎和PostgreSQL连接器创建一个简单应用。该应用采用极简配置,主要依赖默认设置。特别说明的是,它使用decoderbufs插件从PostgreSQL数据库获取数据。

应用本身不处理获取的数据——这是为了刻意避免任何记录后处理(例如单消息转换)。这种设计确保应用耗时集中在核心数据获取路径上,而非外部处理逻辑。换言之,我们的重点是评估通过decoderbufs插件从PostgreSQL检索数据的效率,并定位该路径中的潜在性能瓶颈。

public class DebeziumEngniePostgres {public static void main(String[] args) {final Properties props = new Properties();props.setProperty("name", "engine");props.setProperty("connector.class", "io.debezium.connector.postgresql.PostgresConnector");props.setProperty("database.hostname", "127.0.0.1");props.setProperty("database.port", "5432");props.setProperty("database.user", "postgres");props.setProperty("database.password", "postgres");props.setProperty("database.dbname", "postgres");props.setProperty("topic.prefix", "perf");props.setProperty("table.include.list", "public.pgbench_.*");props.setProperty("snapshot.mode", "no_data");props.setProperty("offset.storage", "org.apache.kafka.connect.storage.FileOffsetBackingStore");props.setProperty("offset.storage.file.filename", "./data/offsets.dat");props.setProperty("offset.flush.interval.ms", "60000");props.setProperty("schema.history.internal", "io.debezium.storage.file.history.FileSchemaHistory");props.setProperty("schema.history.internal.file.filename", "./data/schemahistory.dat");try (DebeziumEngine<ChangeEvent<SourceRecord, SourceRecord>> engine = DebeziumEngine.create(Connect.class).using(props).notifying(record -> {}).build()) {ExecutorService executor = Executors.newSingleThreadExecutor();executor.execute(engine);System.out.println("Debezium started");Thread.sleep(600000);}catch (IOException|InterruptedException e) {System.out.println("Failed with " + e);}System.out.println("Debezium stopped");}
}

源码仓库地址

您可以像往常一样使用Maven构建和运行应用程序。只需首先创建数据目录,因为Debezium引擎会尝试将偏移量和模式历史文件存储在那里。

PostgreSQL

由于我们在这个示例中选择了PostgreSQL连接器,因此需要设置一个测试用的PostgreSQL数据库。为了简化操作,我们可以使用Debezium项目提供的容器镜像——它已经包含了所有必要的数据库配置,开箱即用:

podman run --rm --name postgres -it -e POSTGRES_PASSWORD=postgres -p 5432:5432 quay.io/debezium/example-postgres:3.2

接下来,我们需要一个工具在测试期间为数据库生成负载。PostgreSQL自带这样的工具:pgbench。在大多数Linux发行版中,它与核心数据库软件包是分开提供的。例如在Fedora系统上,您需要额外安装postgresql-contrib包。

建立常规性能测试时,关键环节之一是选择合适的负载生成工具。像pgbench这类工具可能存在诸如协调遗漏问题等缺陷,导致生成的数据不真实,或引入其他偏差从而产生误导性结果。如前所述,本文暂不探讨这些复杂问题——这是本示例中采用的简化处理方式之一,特此说明。

在使用pgbench之前,需要先初始化它将操作的测试表:

PGPASSWORD=postgres pgbench -h 127.0.0.1 -U postgres -i postgres --scale=10

该命令会在数据库中创建若干以pgbench_开头的表。您可能已经注意到,我们的应用程序已配置为捕获这些pgbench_*表的变更。

运行测试

现在一切准备就绪,让我们使用pgbench对数据库施加负载:

PGPASSWORD=postgres pgbench -h 127.0.0.1 -U postgres --scale=10 -b simple-update --jobs=20 --client=20 -T 120 postgres

同时,我们将运行启用 Java Flight Recorder 的测试应用程序:

java -XX:+FlightRecorder -XX:StartFlightRecording=delay=30s,duration=60s,filename=dbz-flight.jfr,settings=profile -jar dbz-app/target/debezium-quick-perf-1.0-SNAPSHOT.jar

要捕获Flight Recorder事件,请确保在启动应用程序时包含-XX:+FlightRecorder参数。您可以通过性能分析工具手动开始记录,或通过命令行参数配置延迟时间、持续时间、输出文件和性能分析设置:-XX:StartFlightRecording=delay=30s,duration=60s,filename=dbz-flight.jfr,settings=profile.

当记录完成后,您可以同时停止应用程序和pgbench(如果它仍在运行)。也可以停止并删除数据库容器。

如需重复测试,建议从干净的数据库开始,并删除数据目录中的offset文件以重置连接器的读取位置。

分析结果

获取Flight Recorder文件后,我们可以用任何支持Java Flight Recorder (JFR)的工具打开它。这可以是您的IDE,但我更推荐使用专用工具:Java Mission Control (JMC)。

不过需要注意,JMC对Wayland的支持存在缺陷。这可能导致某些关键视图(如我们需要的火焰图)显示为空。详情可参阅JMC-8247问题记录。

火焰图能直观展示应用程序的时间消耗分布:
在这里插入图片描述

有一件事立即引人注目,那就是在 schemaChanged() 方法中花费了大量的时间,特别是在正则表达式匹配中:
在这里插入图片描述
同样的问题在方法分析视图中也很明显:
在这里插入图片描述
这里,正则表达式匹配是最耗时的操作之一。您还可以检查采样事件的调用堆栈,精确定位这个计算的来源。

问题的根源在于PostgreSQL不会发出专门的模式变更事件。相反,模式元数据被嵌入到第一个使用更新后模式的记录中。因此,Debezium必须几乎在处理的每条记录中检查可能的模式变更。具体实现取决于所使用的插件。对于默认的decoderbufs插件,Debezium会将记录的类型和其他修饰符与已有的信息进行比较。这些类型修饰符以字符串形式提供,并且每条记录都需要使用正则表达式进行解析。随着时间的推移,这会累积成很大的开销。

既然我们已经发现了一个潜在的性能瓶颈,最好将我们的发现报告给开发团队。针对这个具体问题,我提交了DBZ-9093。

提出修复方案

在上一步中,我们发现了PostgreSQL连接器在使用decoderbufs插件时存在的性能问题。幸运的是,当使用pgoutput插件时不存在这个问题。不过考虑到decoderbufs仍受支持且被设为默认选项,让我们探讨如何修复这个问题。

JMH基准测试

为了衡量修复效果,我们可以使用专为此目的设计的Java微基准测试工具(JMH)。一个简单的JMH基准测试示例如下:

@State(Scope.Benchmark)
public class PostgresTypeMetadataPerf {private static final int OP_COUNT = 10;private static final int MOD_COUNT = 10;private static final String[] MODIFIERS = {"text","character varying(255)","numeric(12,3)","geometry(MultiPolygon,4326)","timestamp (12) with time zone","int[]","myschema.geometry","float[10]","date","bytea"};private ReplicationMessage.Column[] columns = new ReplicationMessage.Column[OP_COUNT];private ReplicationMessage.Column createColumn (int modifierIndex) {String columnName = "test";PostgresType columnType = PostgresType.UNKNOWN;String typeWithModifiers = MODIFIERS[modifierIndex];boolean optional = true;return new AbstractReplicationMessageColumn(columnName, columnType, typeWithModifiers, optional) {@Overridepublic Object getValue(PostgresStreamingChangeEventSource.PgConnectionSupplier connection,boolean includeUnknownDatatypes) {return null;}};}@Setup(Level.Invocation)public void setup() {Random random = new Random(1234);for (int i = 0; i < OP_COUNT; i++) {columns[i] = createColumn(random.nextInt(MOD_COUNT));}}@Benchmark@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.MICROSECONDS)@Fork(value = 1)@OperationsPerInvocation(OP_COUNT)public void columnMetadata(Blackhole bh) {for (int i = 0; i < OP_COUNT; i++) {bh.consume(columns[i].getTypeMetadata());}}
}

虽然 JMH 和微基准测试通常存在许多需要注意的事项,但这个基准测试至少为我们提供了一些基准。

以下是在我的机器上使用未打补丁的代码得出的结果:

Iteration   1: 0.768 us/op
Iteration   2: 0.761 us/op
Iteration   3: 0.780 us/op
Iteration   4: 0.780 us/op
Iteration   5: 0.750 us/opBenchmark                                Mode  Cnt  Score   Error  Units
PostgresTypeMetadataPerf.columnMetadata  avgt    5  0.768 ? 0.049  us/op

鉴于类型修饰符通常会被复用,一种简单的优化方法是将已解析的修饰符缓存到 map 中。实现此缓存逻辑后,JMH 基准测试给出了以下结果:

Iteration   1: 0.278 us/op
Iteration   2: 0.278 us/op
Iteration   3: 0.284 us/op
Iteration   4: 0.288 us/op
Iteration   5: 0.291 us/opBenchmark                                Mode  Cnt  Score   Error  Units
PostgresTypeMetadataPerf.columnMetadata  avgt    5  0.284 ? 0.023  us/op

虽然这是一个简单的基准测试(可能存在某些问题),但性能提升幅度已经足以表明实际应用中的收益。

在测试应用中进行验证

作为最终测试,我们可以重新运行简单的性能测试,检查schemaChanged()方法的时间消耗。从更新后的火焰图可以明显看出改进效果:
在这里插入图片描述
当您对修复方案有信心后,可以提交一个拉取请求——最好包含用于验证的JMH基准测试。

要高度确信该修复的有效性,需要建立完整的端到端性能测试流程。不过在如此复杂的环境中,实际效果可能微乎其微。与我们的受控测试或JMH基准测试不同,真实场景包含序列化、I/O开销等诸多因素,这些都可能稀释可见的收益。孤立测试中显著的性能提升,在整体上可能只带来微小改进——但积少成多总是好的。

结论

本文探索了一种轻量级方法来识别Debezium(使用PostgreSQL连接器的嵌入式引擎)中的性能瓶颈。通过简单的测试设置、Flight Recorder和火焰图分析,我们定位到了decoderbufs插件中正则表达式处理的高成本问题。

虽然这个设置有意简化了现实场景的许多方面,也存在其他潜在缺陷,但它对于发现容易解决的性能问题仍然有效。开发者可以直接在本地运行轻量级基准测试,有助于在开发早期发现并解决低效问题。这些优化能提升整体吞吐量并减少不必要的CPU使用,尤其在高吞吐场景中。虽然这种方法不能替代全面的端到端性能测试,但它提供了一种快速实用的方式,在开发过程中发现并解决性能退步或低效问题。在投入时间建立更复杂的基准测试流程之前,这也是一个很好的第一步。

http://www.dtcms.com/a/279321.html

相关文章:

  • 制造业实战:数字化集采如何保障千种备件“不断供、不积压”?
  • 16.避免使用裸 except
  • MFC扩展库BCGControlBar Pro v36.2新版亮点:可视化设计器升级
  • 计算机毕业设计Java轩辕购物商城管理系统 基于 SpringBoot 的轩辕电商商城管理系统 Java 轩辕购物平台管理系统设计与实现
  • 面向对象的设计模式
  • 【数据结构】树(堆)·上
  • js的局部变量和全局变量
  • 测试驱动开发(TDD)实战:在 Spring 框架实现中践行 “红 - 绿 - 重构“ 循环
  • Bash vs PowerShell | 从 CMD 到跨平台工具:Bash 与 PowerShell 的全方位对比
  • vue3 服务端渲染时请求接口没有等到数据,但是客户端渲染是请求接口又可以得到数据
  • 7.14 map | 内存 | 二维dp | 二维前缀和
  • python+Request提取cookie
  • 电脑升级Experience
  • python transformers笔记(Trainer类)
  • 代码随想录算法训练营第三十五天|416. 分割等和子集
  • LLM表征工程还有哪些值得做的地方
  • 内部文件审计:企业文件服务器审计对网络安全提升有哪些帮助?
  • 防火墙技术概述
  • Qt轮廓分析设计+算法+避坑
  • Redis技术笔记-主从复制、哨兵与持久化实战指南
  • 第五章 uniapp实现兼容多端的树状族谱关系图,剩余组件
  • 学习C++、QT---25(QT中实现QCombobox库的介绍和用QCombobox设置编码和使用编码的讲解)
  • SQL ORM映射框架深度剖析:从原理到实战优化
  • 【Unity】MiniGame编辑器小游戏(十三)最强射手【Shooter】(下)
  • ElasticSearch重置密码
  • 嵌入式 Linux开发环境构建之Source Insight 的安装和使用
  • c++算法二
  • 不同Linux版本下安装Synopsys工具的差异与选择建议
  • PyTorch 数据加载全攻略:从自定义数据集到模型训练
  • [Pytorch]深度学习-part1