目录
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 的事务支持RC和SI的隔离级别,默认是RC。
1.3 事务模式
MatrixOne 的事务支持悲观和乐观的模式,默认是悲观。
1.4 事务并发控制
MatrixOne 使用 MVCC 来实现事务并发控制。并且使用 HLC 来实现事务时钟。
1.5 事务读操作
对于事务的读操作而言,首先需要确定的就是哪些数据对于事务是可见的。在任何一个时刻,对于事务可见的数据包括:
- 对象存储中所有
CommitTimestamp < txn.SnapshotTimestamp的数据 - LogTail中所有
CommitTimestamp < txn.SnapshotTimestamp的数据 - 事务的
Workspace中所有的Uncommitted的数据
在我们明确哪些数据对于事务是可见的时候,就需要确定满足条件的数据在读发生的时候,是否完整。
Workspace中的数据,任何时刻对于事务都是完整的。需要保证的就是对象存储和LogTail的数据了。由于LogTail是整个 MatrixOne 集群最新的数据写入,所以只要保证LogTail的数据完整了,那么对象存储对应的数据对于事务也就完整了。
现在的问题就是如何保证LogTail的数据对于事务是完整的。
MatrixOne 的事务在CN节点创建,事务创建后,就会明确一个事务的SnapshotTimestamp(这个时间戳对于SI是整个事务生命周期不变的,对于RC是每个Statement的生命周期内是不变的)。
LogTail的数据在TN节点产生,并且写入LogService。CN使用订阅的方式来获得最新的LogTail数据,并且把这些LogTail中的数据Apply到CN的内存中。
CN在内存中维护了一个最大的Apply的CommitTimestamp,可以根据这个时间戳的水位和事务的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 数据表
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表
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可获得数倍以上的性能提升。
关键词:超融合数据库、多模数据库、云原生数据库、国产数据库。










