Lecture#20 Database Crash Recovery

Posted Angelia-Wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lecture#20 Database Crash Recovery相关的知识,希望对你有一定的参考价值。

上节课介绍到,故障恢复算法由两个部分构成:

  • 在事务执行过程中采取的行动来确保出现故障时能够恢复 (上节课)
  • 在故障发生后的恢复机制,确保原子性、一致性和持久性 (本节课)

1 ARIES

本节课介绍的是 Algorithms for Recovery and Isolation Exploiting Semantics (ARIES),由 IBM Research 在 90 年代初为 DB2 DBMS 研发的基于 WAL 的故障恢复机制,尽管并非所有 DBMS 都严格按照 ARIES paper 实现故障恢复机制,但它们的思路基本一致。

ARIES 的核心思想可以总结为 3 点:

  • Write-Ahead Logging (WAL)
    • 在数据落盘之前,所有写操作都必须记录在日志中并落盘
    • 必须使用 Steal + No-Force 缓存管理策略 (buffer pool policies)
  • Repeating History During Redo
    • 当 DBMS 重启时,按照日志记录的内容重做数据,恢复到故障发生前的状态
  • Logging Changes During Undo
    • 在 undo 过程中记录 undo 操作到日志中,确保在恢复期间再次出现故障时不会执行多次相同的 undo 操作

本节大纲:

  • Log Sequence Numbers
  • Normal Commit & Abort Operations
  • Fuzzy Checkpointing
  • Recovery Algorithm

2 Log Sequence Numbers

WAL 中的每条日志记录都需要包含一个全局唯一的 log sequence number (LSN),一般 LSN 单调递增。

DBMS 中的不同部分都需要记录相关的 LSN 信息,举例如下:

Name Where Definition
flushedLSN memory 最后落盘的那个 LSN
pageLSN buffer pool page 与某 page data 相关的最新 LSN
recLSN buffer pool page 在上次落盘之后,与某 page data 相关的最老 LSN
lastLSN transaction 某事务最后一条日志的 LSN
MasterRecord disk 最近一次 checkpoint 的 LSN

pageLSN 和 recLSN 对应一个 page 尚未落盘的最新 log 和最老 log。

在 buffer pool manager 中,每个 data page 都维护着 pageLSN,而 DBMS 本身需要追踪 flushedLSN。

事务修改某 page 中的数据时,要更新该 page 的 pageLSN,在将操作日志写进 WAL 后,DBMS 会更新 flushedLSN 为最新落盘的 log record 的 LSN。

在 page x 落盘前,DBMS 必须保证以下条件成立,表明该 page 的对应的 log 已落盘:

\\[pageLSN_x ≤ flushedLSN \\]

3 Normal Commit & Abort Operations

每个事务都会包含一些列的读和写操作,然后提交 (commit) 或中止 (abort),本节我们来看下不存在故障时,事务的正常执行过程。

在讨论之前,我们需要约定 4 个假设,简化问题:

  • 所有日志记录都能放进一个 page 中
  • 写一个 page 到磁盘能保持原子性
  • 没有 MVCC,使用严格的 2PL
  • 使用 WAL 记录操作日志,buffer pool policy 为 Steal + No-Force

3.1 Transaction Commit

当事务提交时,DBMS 先写入一条 COMMIT 记录到 WAL ,然后将 COMMIT 及之前的日志落盘,当落盘完成后,flushedLSN 被修改为 COMMIT 记录的 LSN,同时 DBMS 将内存中 COMMIT 及其之前的日志清除。最后等事务涉及的脏数据页也落盘后,再写入一条 TXN-END 记录到 WAL 中,作为内部记录,对于执行提交的事务来说,COMMIT 与 TXN-END 之间没有别的操作。

3.2 Transaction Abort

要处理事务回滚,就必须从 WAL 中找出所有与该事务相关的日志及其执行顺序。由于在 DBMS 中执行的所有事务的操作记录都会写到 WAL 中,因此为了提高效率,同一个事务的每条日志中需要记录上一条记录的 LSN,即 prevLSN,一个特殊情况是:第一条 BEGIN 记录的 prevLSN 为空。

实际上中止事务是 ARIES undo 操作的一种特殊情况:回滚单个事务。

可以看到,T4 的每条日志都记录着 prevLSN,当 T4 要中止时,DBMS 先向 WAL 中写入一条 ABORT 记录,然后寻着 LSN 与 prevLSN 连接串成的链表,找到之前的操作,倒序回滚恢复旧值,为了防止在回滚过程中再次故障导致部分操作被执行多次,回滚操作也需要写入日志中,等待所有操作回滚完毕后,DBMS 再往 WAL 中写入 TXN-END 记录,意味着所有与这个事务有关的日志都已经写完,不会再出现相关信息。

那么,如何记录回滚操作呢?这就是我们马上要介绍的 CLR。

3.3 Compensation Log Records

CLR 描述了为 undo 先前更新记录的操作而采取的操作,它记录了原操作 log record 的所有字段 和 undoNext 指针 (指向下一个将要被 undo 的 LSN),CLR 本身也是操作记录,因此它也需要像其它操作一样写进 WAL 中,但DBMS在通知 application 事务中止前,不会强制将它们落盘。

CMU15-445 Lecture #03: Database Storage (Part I)

课程链接:15-445/645 Database Systems (Fall 2020)

本文由 nefu-ljw 翻译于Notes:https://15445.courses.cs.cmu.edu/fall2020/notes/03-storage1.pdf

所有Notes已同步更新于我的github仓库:https://github.com/nefu-ljw/database-notes

1. Storage

我们将重点介绍面向磁盘的(disk-oriented) DBMS体系结构,该体系结构假定数据库的主存储位置位于非易失性磁盘上。

在存储层次结构的顶部,您拥有距离CPU最近的设备。这是最快的存储,但也是最小和最贵的。您离CPU越远,存储设备就越大,但速度越慢。这些设备的每GB价格也变得更低。

易失性设备(内存):

  • 易失性意味着如果您从机器上拔下电源,那么数据就会丢失。
  • 易失性存储支持字节可寻址位置的快速随机访问。这意味着程序可以跳转到任何字节地址并获取那里的数据。
  • 出于我们的目的,我们始终将此存储类称为内存(Memory)。

非易失性设备(磁盘):

  • 非易失性意味着不需要为存储设备提供连续的电源,以便该设备保留其正在存储的位(bits)。
  • 它也是块/页可寻址的。这意味着,为了读取特定偏移量上的值,程序首先必须将4KB页加载到保存程序要读取的值的内存中。
  • 非易失性存储传统上更擅长顺序访问(同时读取多个连续的数据块)。
  • 我们将其称为磁盘(disk)。我们不会对固态存储(SSD)和旋转硬盘(HDD)进行主要区分。

还有一种新的存储设备正在变得越来越流行,称为持久内存(persistant memory)。这些设备被设计成两全其美:速度几乎与DRAM一样快,并且具有磁盘持久性。我们不会在本课程中介绍这些设备。

因为系统假设数据库存储在磁盘上,所以DBMS的组件负责确定如何在非易失性磁盘和易失性存储器之间移动数据,因为系统不能直接操作磁盘上的数据。

我们将关注如何隐藏磁盘的延迟,而不是关注寄存器和缓存的优化,因为从磁盘获取数据非常慢。如果从L1缓存引用读取数据需要半秒,则从SSD读取将需要1.7天,从HDD读取将需要16.5周。

2. Disk-Oriented DBMS Overview

数据库都在磁盘上,数据库文件中的数据被组织成页,第一页是目录页。为了对数据进行操作,DBMS需要将数据放入内存。它通过使用缓冲池来管理数据在磁盘和内存之间的来回移动来实现这一点。DBMS还具有执行查询的执行引擎。执行引擎将向缓冲池请求特定页面,而缓冲池负责将该页面放入内存,并为执行引擎提供指向内存中该页面的指针。 缓冲池管理器将确保在执行引擎对该部分内存进行操作时页面在那里。

3. DBMS vs. OS

DBMS的高级设计目标是支持超过可用内存量的数据库。由于读/写磁盘的开销很大,因此必须小心管理。我们不希望从磁盘获取数据时出现较大的停顿,从而降低其他所有操作的速度。我们希望DBMS在等待从磁盘获取数据时能够处理其他查询。

这个高级设计目标类似于虚拟内存,其中有很大的地址空间和供操作系统从磁盘引入页面的位置。

实现这种虚拟内存的一种方法是使用mmap将文件内容映射到进程的地址空间,这使得操作系统负责在磁盘和内存之间来回移动页面。不幸的是,这意味着如果mmap遇到页面错误,进程将被阻塞。

  • 如果你需要写入,你永远不会想要在DBMS中使用mmap。
  • DBMS(几乎)总是希望自己控制事物,并且可以做得更好,因为它更了解正在访问的数据和正在处理的查询。
  • 操作系统不是你的朋友。

可以通过以下方式使用操作系统:

  • madvise: 告知操作系统知道您何时计划读取某些页面。
  • mlock: 告知操作系统不要将内存范围向外交换到磁盘。
  • msync: 通知操作系统将内存范围刷新到磁盘。

出于正确性和性能原因,我们不建议在DBMS中使用mmap。

尽管DBMS的功能看起来像是操作系统可以提供的功能,但让DBMS自己实现这些过程会给它带来更好的控制和性能。

4. File Storage

在其最基本的形式中,DBMS将数据库存储为磁盘上的文件。有些可能使用文件层次结构,有些可能使用单个文件(例如SQLite)。

操作系统对这些文件的内容一无所知。只有DBMS知道如何解密它们的内容,因为它是以特定于DBMS的方式编码的。

DBMS的存储管理器(storage manager)负责管理数据库的文件。它将文件表示为页面集合。它还跟踪哪些数据已被读取和写入页面,以及这些页面中有多少可用空间。

5. Database Pages

DBMS在一个或多个文件(称为pages的固定大小的数据块)中组织数据库。页面(pages)可以包含不同类型的数据(元组、索引等)。大多数系统不会在页面中混合使用这些类型。一些系统将要求页面是自包含的(self-contained),这意味着阅读每个页面所需的所有信息都在页面本身上。

每个页面都有一个唯一的标识符。如果数据库是单个文件,那么页面id可以只是文件偏移量。大多数DBMS都有一个间接层,它将页面id映射到文件路径和偏移量。系统的上层将要求一个特定的页码。然后,存储管理器必须将该页码转换为一个文件和一个偏移量才能找到该页。

大多数DBMS使用固定大小的页面来避免支持可变大小页面所需的工程开销。例如,对于可变大小的页面,删除页面可能会在DBMS无法轻松用新页面填充的文件中造成漏洞。

DBMS中的页面有三个概念:

  1. 硬件页(通常为4KB)。
  2. 操作系统页(4 KB)。
  3. 数据库页(1-16KB)。

存储设备保证硬件页大小的原子写入。如果硬件页为4KB,并且系统尝试将4KB写入磁盘,则要么全部写入4KB,要么全部不写入。这意味着如果我们的数据库页大于我们的硬件页,DBMS将不得不采取额外的措施来确保数据被安全地写出,因为当系统崩溃时,程序可以中途将数据库页写入磁盘。

6. Database Heap

有几种方法可以找到DBMS想要的页面在磁盘上的位置,堆文件组织就是其中之一。

堆文件(heap file)是一个无序的页面集合,其中元组以随机顺序存储。

DBMS可以通过使用页面的链接列表(Linked List)或页面目录(Page Directory),在给定页面ID的情况下定位磁盘上的页面。

  • Linked List: 首页(header page)包含指向空闲页面列表和数据页面列表的指针。但是,如果DBMS正在查找特定的页,它必须对数据页列表进行顺序扫描,直到找到它要查找的页。
  • Page Directory: DBMS维护特殊的页面,这些页面跟踪数据页面的位置以及每页上的空闲空间量。

7. Page Layout

每个页面都包括一个数据头(header),该header记录有关页面内容的元数据:

  • 页面大小。
  • 校验和。
  • DBMS版本。
  • 事务可见性。
  • 自包含性。(有些系统如Oracle,需要这样做)

布局数据的一种strawman方法是跟踪DBMS在一个页面中存储了多少个元组,然后在每次添加新的元组时将其追加到末尾。但是,当删除元组或元组具有可变长度属性时,就会出现问题。

在页面中布局数据有两种主要方法:(1)分页布局(slotted-pages),(2)日志结构布局(log-structured)。

Slotted Pages:页面将槽(slots)映射到偏移量(offsets)。

  • 当今DBMS中最常用的方法。

  • Header跟踪使用的slot数量、最后使用的slot的起始位置的偏移量,以及slot数组(跟踪每个元组的起始位置)。

  • 要添加元组,slot数组将从头到尾增长,而元组的数据将从尾到头增长。当slot数组和元组数据相遇时,认为该页已满。

Log-Structured: DBMS只存储日志记录,而不存储元组。

  • 将数据库如何修改(插入、更新、删除)的记录存储到文件中。
  • 为了读取记录,DBMS向后扫描日志文件并重新创建(recreates)元组。
  • 写入速度较快,读取速度可能较慢。
  • 在仅附加(append-only)存储器上运行良好,因为DBMS不能返回和更新数据。
  • 为了避免长时间读取,DBMS可以使用索引来允许它跳转到日志中的特定位置。它还可以定期压缩日志(如果它有一个元组,然后对其进行更新,则可以将其压缩为只插入更新后的元组)。压缩的问题是DBMS最终会出现写入放大。(它会一遍又一遍地重写相同的数据)

8. Tuple Layout

元组本质上是一个字节序列。DBMS的工作是将这些字节解释为属性类型和值。

Tuple Header: 包含有关元组的元数据。

  • DBMS的并发控制协议的可见性信息(关于哪个事务创建/修改了该元组的信息)。
  • NULL值的位图(Bit Map)。
  • 注意,DBMS不需要在这里存储有关数据库模式(schema)的元数据。

Tuple Data: 属性的实际数据。

  • 属性通常按照您在创建表时指定的顺序存储。
  • 大多数DBMS不允许元组超过页面大小。

Unique Identifier:

  • 数据库中的每个元组都被分配了唯一标识符(unique identifier)。
  • 最常见的:page_id + (offset or slot)
  • 应用程序不能依赖这些ID来表示任何意义。

Denormalized Tuple Data: 非规格化元组数据。如果两个表是相关的,DBMS可以预先联接(pre-join)它们,因此这两个表最终会出现在同一页上。这使得读取速度更快,因为DBMS只需加载一个页面,而不是两个单独的页面。但是,由于DBMS需要为每个元组提供更多空间,这会使更新成本更高。

以上是关于Lecture#20 Database Crash Recovery的主要内容,如果未能解决你的问题,请参考以下文章

CMU15-445 Lecture #03: Database Storage (Part I)

CMU15-445 Lecture #04: Database Storage (Part II)

RHEL 7.2 ORA-27300 ORA-27301 Database Crash

翻译自mos文章设置了RemoveIPC=yes 的RHEL7.2上 会crash掉Oracle asm 实例和Oracle database实例

RemoveIPC=yes 的RHEL7.2 会crash掉Oracle asm 实例和Oracle database实例

CS20SI-tensorflow for research笔记: Lecture3