作者:张旭 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 的事务支持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 数据表
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可获得数倍以上的性能提升。
关键词:超融合数据库、多模数据库、云原生数据库、国产数据库。