高效DEBUG事务正确性BUG

作者:张旭 MO研发工程师

目录

Part 1. 事务回顾

Part 2. 事务的正确性问题

Part 3. 如何高效 DEBUG

熟悉数据库分布式事务的读者,应该能够理解DEBUG分布式事务正确性问题的BUG是一件非常有挑战的事情。本文主要是给大家介绍一下,MatrixOne在研发过程中,是如何DEBUG事务正确性问题的。

1. 事务回顾

我们首先来回顾一下,在 MatrixOne 中,事务是如何实现的。

1.1 数据存在哪儿

MatrixOne 是一个云原生的数据库,数据库中大部分的数据都存储在对象存储(可以是任何S3兼容的对象存储)中,这部分数据是不变的。数据不可变带来了非常多的好处,比如不需要考虑这些数据的一致性问题。

除了存储在对象存储中的数据外,还有非常少的数据存储在LogService中,LogService是一个使用 Raft 实现的高性能的 WAL 服务。这部分数据在 MatrixOne 中被称为 LogTail。这部分数据是变化的,可以认为是 MatrixOne 集群最新的 Commit 数据。

对象存储 + LogTail组成了 MatrixOne 的全量数据。

1.2 事务隔离级别

MatrixOne 的事务支持RCSI的隔离级别,默认是RC

1.3 事务模式

MatrixOne 的事务支持悲观乐观的模式,默认是悲观

1.4 事务并发控制

MatrixOne 使用 MVCC 来实现事务并发控制。并且使用 HLC 来实现事务时钟。

1.5 事务读操作

对于事务的读操作而言,首先需要确定的就是哪些数据对于事务是可见的。在任何一个时刻,对于事务可见的数据包括:

  1. 对象存储中所有CommitTimestamp < txn.SnapshotTimestamp的数据
  2. LogTail中所有CommitTimestamp < txn.SnapshotTimestamp的数据
  3. 事务的Workspace中所有的Uncommitted的数据

在我们明确哪些数据对于事务是可见的时候,就需要确定满足条件的数据在读发生的时候,是否完整。

Workspace中的数据,任何时刻对于事务都是完整的。需要保证的就是对象存储LogTail的数据了。由于LogTail是整个 MatrixOne 集群最新的数据写入,所以只要保证LogTail的数据完整了,那么对象存储对应的数据对于事务也就完整了。

现在的问题就是如何保证LogTail的数据对于事务是完整的。

MatrixOne 的事务在CN节点创建,事务创建后,就会明确一个事务的SnapshotTimestamp(这个时间戳对于SI是整个事务生命周期不变的,对于RC是每个Statement的生命周期内是不变的)。

LogTail的数据在TN节点产生,并且写入LogService。CN使用订阅的方式来获得最新的LogTail数据,并且把这些LogTail中的数据Apply到CN的内存中。

CN在内存中维护了一个最大的ApplyCommitTimestamp,可以根据这个时间戳的水位和事务的SnapshotTimestamp来确保,LogTail的数据对于事务是完整的。

1.6 事务的写操作

MatrixOne 事务的 Uncommitted 的数据,都是写入 Workspace,这个 Workspace 在CN的内存中。

一个事务写入的数据越多,这个Workspace占用的内存越大,知道OOM发生。MatrixOne 为了解决这个问题,对于Workspace的内存大小有一个阈值(默认是1MB),当发现Workspace的内存超过这个阈值,就会把Workspace中的数据写入到对象存储,在Workspace的数据会被替换成对象存储上临时文件名

事务在没有Commit之前,不会和TN交互,在Commit的时候,会把Workspace的数据发送给TN节点做Commit处理。

2. 事务的正确性问题

2.1 什么是正确性问题

上面的章节我们回顾了事务,现在需要说明一下,什么事事务正确性的问题。在 MatrixOne 的研发过程中遇到事务正确性的BUG主要有以下几种:

  • RC模式下的Lost Update
  • 悲观事务锁服务失效
  • Workspace数据问题

这几个问题都会产生事务正确性问题。这些问题,都会产生事务读了错误的数据,或者提交了错误的数据。

2.2 如何测试

MatrixOne 很多的测试,来帮助我们发现这些事务正确问题,这些测试包括:

  • 单元测试
  • 集成测试
  • PR Merge之前的CI测试
  • PR Merge之后的性能基准测试
  • 7*24小时执行的稳定性测试
  • daily的各种测试
  • chaos 测试

这些测试都会帮助我们发现事务正确性问题。

2.3 常规分析问题的手段

对于研发来说,我们常规的分析问题的手段一半是这几种:

  • 断点Debug
  • 分析日志
  • Metrics
  • Tracing

对于分析分布式集群的事务问题,其中最有用的就是日志,其余手段几乎没有用。断点DEBUG只能分析必现的问题,Metrics只能观测到系统的一个大概情况,不能定位数据问题。Tracing一般是指调用链监控,可以用来分析性能问题,但是对于数据错误的问题,无法提供帮助。

总结来看,分析正确性问题几乎只有日志可用。

2.3.1 需要哪些日志

事务正确性问题,归根结底是对于一行记录,读到了错误的数据或者写入错误的数据。这个问题的本身,是数据的内容错了。数据的内容错了,问题可能出现在这行数据所有发生读写的地方。

如果我们需要分析事务正确性问题,那么就需要分析问题出在哪一个事务发生读写的地方。并且还有一个条件,就是能够根据出问题的事务,出问题的行,把这些地方的日志信息全部串联起来,就可以找到问题所在。

但是问题是复杂的,由于悲观事务的模式,多个并发事务会相互影响,所以还需要串联起来有冲突的事务的所有相关信息一起分析。

2.3.2 日志的问题

我们分析问题需要那些信息,上文我们分析了,这些信息需要都记录到日志中。这些日志不可能运行在Info的日志级别,只能在DEBUG级别。这样就带来了一些问题:

  • 在一些测试中无法打开DEBUG级别的日志

    在性能测试中,是无法打开DEBUG日志的。如果错误出现在性能测试中出现,几乎无法分析。

  • DEBUG日志难以重现问题

    事务正确性问题,有时候非常难以重现,可能需要满足特定的并发时序。如果是在不能打开DEBUG的测试中出现,需要打开DEBUG日志级别,跑一样的负载,DEBUG日志过多,改变了系统运行的时序,问题会更加难以复现。

  • 日志难以分析

    当具备完备的DEBUG的日志的时候,这个日志的规模可能非常非常大,并且是一个在分布式环境中,产生的各自节点和进程的日志,分析的难度也是异常艰难的。

3. 如何高效DEBUG

在 MatrixOne 的研发过程中,Fix 事务正确性的BUG,一直是痛苦的经历。MatrixOne 还处于快速发展阶段,系统中有非常多的优化还没有去处理,这些修改,都有可能带来新的事务正确性的BUG。所以我们需要一个高效DEBUG事务正确性BUG的工具和方法。

3.1 设计目标

使用日志去分析的问题弊端,我们已经有扩深刻的经历。现在需要达成的设计目标有3点:

  • 在任何测试场景中,只要出现BUG,不需要重新复现,就有足够的信息分析问题
  • 不能对于性能测试有太大的影响,10%以内的性能影响是可以接受的
  • 要提供非常丰富的分析信息的方式和手段

3.2 设计挑战

在测试中会产生非常巨大的需要分析的数据。这些数据如何存储,如何提供丰富的分析查询能力。因为分析问题的时候,需要根据各种各样的条件去分析查询信息。

3.2.1 如何提供分析查询能力

首先我们不考虑数据如何存储,先来看如何提供数据查询分析能力,解决了这个问题,可能数据如何存储的问题就解决了。

目前为止,没有比以SQL的方式来提供这些数据的查询更方便的,语意更丰富能力的分析查询方式了。如果我们可以提供一SQL的方式来提供DEBUG数据的分析查询能力,这个好处是显而易见的,并且DEBUG的效率也是提升巨大的。

所以我们决定以SQL的方式提供DEBUG数据的查询分析。

3.2.2 数据如何存储

存储方式就显而易见了,因为提供SQL的方式来提供查询能力,那么数据就需要存储的数据库中。所以我们需要一个能够提供强大AP能力的数据库来存储这些DEBUG数据。

结论显而易见了,MatrixOne 自己就是一个支持高性能AP查询的数据库。

3.2.3 数据如何写入

我们有一个设计目标是,开启收集DEBUG信息的时候,对于性能不能有超过10%的性能影响。我们需要对数据写入到数据库有一些特殊的设计:

  • 异步以Load的方式写入数据到数据库
  • DEBUG数据的写入可以skip掉事务中一些耗时的操作(去重,冲突检测)
  • 尽可能的减少不必要的信息

3.3 Trace框架设计

从 MatrixOne 1.2版本开始,MatrixOne 提供一个mo_debug的内置数据库,并且根据之前分析日志的经验,对分析事务问题需要的数据进行了抽象,提供了一些表来存储数据。

并且提供了一些专门的语句来动态的打开和关闭Trace的功能。

由于篇幅问题,本文不会描述这些表的具体设计含义,只会简单介绍一下,主要目的还是给分享一下思路。

3.3.1 数据表
sql 复制代码
create table trace_event_txn (
    ts 			      bigint       not null,
    txn_id            varchar(50)  not null,
    cn                varchar(100) not null,
    event_type        varchar(50)  not null,
    txn_status	      varchar(10),
    snapshot_ts       varchar(50),
    commit_ts         varchar(50),
    info              varchar(1000)
)

create table trace_event_data (
    ts 			      bigint          not null,
    cn                varchar(100)    not null,
    event_type        varchar(50)     not null,
    entry_type		  varchar(50)     not null,
    table_id 	      bigint UNSIGNED not null,
    txn_id            varchar(50),
    row_data          varchar(500)    not null, 
    committed_ts      varchar(50),
    snapshot_ts       varchar(50)
)

create table trace_event_txn_action (
    ts 			      bigint          not null,
    txn_id            varchar(50)     not null,
    cn                varchar(50)     not null,
    table_id          bigint UNSIGNED,
    action            varchar(100)    not null,
    action_sequence   bigint UNSIGNED not null,
    value             bigint,
    unit              varchar(10),
    err               varchar(100) 
)

create table trace_event_error (
    ts 			      bigint          not null,
    txn_id            varchar(50)     not null,
    error_info        varchar(1000)   not null
)

create table trace_statement (
    ts 		   bigint          not null,
    txn_id     varchar(50)     not null,
    sql        varchar(1000)   not null,
    cost_us    bigint          not null 
)

这些数据表主要记录了,在执行过程中所有数据发生的写入,读取,以及事务的元数据变更,执行的SQL,并发冲突等等关键信息。

3.3.2 Filter表
sql 复制代码
create table trace_table_filters (
    id              bigint UNSIGNED primary key auto_increment,
    table_id	    bigint UNSIGNED not null,
    table_name      varchar(50)     not null,
    columns         varchar(200)
);

create table trace_txn_filters (
    id             bigint UNSIGNED primary key auto_increment,
    method         varchar(50)     not null,
    value          varchar(500)    not null
);

create table trace_statement_filters (
    id             bigint UNSIGNED primary key auto_increment,
    method         varchar(50)     not null,
    value          varchar(500)    not null
);

这些Filter表,用来做过滤,尽可能的减少需要记录的数据量。

3.4 效果

打开Trace后,对于性能的影响在5%左右。依靠 MatrixOne 提供能的高性能的AP查询服务能力,研发人员可以根据SQL来查询DEBUG问题,查询执行期间所有需要的数据变更,事务元数据变更等等所有的对于DEBUG问题有帮助的信息。

这样极大的提高了效率,提升了 FIX 事务正确性的BUG的速度。

About MatrixOne

MatrixOne 是一款基于云原生技术,可同时在公有云和私有云部署的多模数据库。该产品使用存算分离、读写分离、冷热分离的原创技术架构,能够在一套存储和计算系统下同时支持事务、分析、流、时序和向量等多种负载,并能够实时、按需的隔离或共享存储和计算资源。 云原生数据库MatrixOne能够帮助用户大幅简化日益复杂的IT架构,提供极简、极灵活、高性价比和高性能的数据服务。

MatrixOne企业版和MatrixOne云服务自发布以来,已经在互联网、金融、能源、制造、教育、医疗等多个行业得到应用。得益于其独特的架构设计,用户可以降低多达70%的硬件和运维成本,增加3-5倍的开发效率,同时更加灵活的响应市场需求变化和更加高效的抓住创新机会。在相同硬件投入时,MatrixOne可获得数倍以上的性能提升。

关键词:超融合数据库、多模数据库、云原生数据库、国产数据库。

相关推荐
编程修仙30 分钟前
MySQL外连接
数据库·mysql
Edward-tan36 分钟前
【全栈开发】----用pymysql库连接MySQL,批量存入
数据库·mysql·pymysql
mxbb.40 分钟前
单点Redis所面临的问题及解决方法
java·数据库·redis·缓存
大霸王龙1 小时前
在 Django 中使用 SMTP 发送邮件是一个常见的需求
数据库·django·sqlite
Hello Dam2 小时前
面向微服务的Spring Cloud Gateway的集成解决方案:用户登录认证与访问控制
spring cloud·微服务·云原生·架构·gateway·登录验证·单点登录
纪伊路上盛名在2 小时前
爬虫1:uniprot蛋白质序列数据+canvas图片
数据库·学习·知识图谱·学习方法
power-辰南3 小时前
Zookeeper 底层原理解析
分布式·zookeeper·云原生
power-辰南3 小时前
Zookeeper常见面试题解析
分布式·zookeeper·云原生
程序员黄同学4 小时前
如何使用 Python 连接 MySQL 数据库?
数据库·python·mysql
新手小袁_J5 小时前
实现Python将csv数据导入到Neo4j
数据库·python·neo4j·《我是刑警》·python连接neo4j·python导入csv·csv数据集导入neo4j