【ACID 特性】Durability (永続性) をわかりやすく解説

DurabilityDurability とは、トランザクションを完了 (COMMIT) した結果が失われない特性です。

トランザクション処理COMMIT した結果が失われる状況は、主に次の2種類が考えられます。

障害の種類説明対策
システム障害OS や電源の障害などで、メモリ上のデータが消失クラッシュリカバリ
- WAL
- ロールバック
- ロールフォワード
- ダブルライトバッファ
メディア障害ストレージの破損などで、メディア上のデータが消失バックアップ
レプリケーション

なお、Durability は一般的に「システム障害」を対象とすることが多いようです。

Durability can be achieved by flushing the transaction's log records to non-volatile storage before acknowledging commitment.

https://en.wikipedia.org/wiki/Durability_(database_systems)

In addition, this recovery must be permanent, meaning all transactions must be reconstructed, even if the database server crashes due to OS failure or power loss.

https://www.techopedia.com/definition/27416/durability-databases

そのため、本記事では「システム障害」の対策方法を解説していきます。
(メディア障害への対策は、以下のバックアップレプリケーション記事をご覧ください)

データベースの基礎知識
ACID 特性の記事一覧
スポンサーリンク

システム障害への対策

システム障害への対策で基本的な考え方は、ストレージにデータを保存することです。
(電源が落ちるとメモリ上のデータは消えますが、ストレージ上のデータは残るため)

しかし、パフォーマンスの観点から、高速に読み書き可能なメモリにデータを置きたいです。

そのため、メモリ上でデータを処理し、メモリの内容をストレージに書き出します。

メモリとストレージが持つデータ

RDBMS におけるメモリストレージで保持するデータは、以下のとおりです。

用語説明
バッファプール ※1テーブルとインデックスなどを保持するメモリ上のキャッシュ
データファイルテーブルとインデックスなどを保持するストレージ上のファイル
ログバッファ ※2トランザクションの API コールを記録したメモリ上のバッファ
Redo ログ ※3トランザクションの API コールを記録したストレージ上のファイル
フラッシュメモリからストレージに書き込むこと
ページメモリを一定サイズに分割した単位
メモリストレージ間で、一度に転送できるデータ量
・複数の行データを格納可能
LRU アルゴリズムで使用頻度の低いページは削除
ダーティページ更新したが、まだデータファイルに書き込まれていないページ
※1 PostgreSQL では共有バッファ、Oracle ではデータベースバッファキャッシュと呼ぶ。MySQL の実態は ibd ファイル
※2 PostgreSQL ではWAL バッファ、Oracle ではログバッファと呼ぶ
※3 PostgreSQL WAL ログ、Oracle ではトランザクションログと呼ぶ。MySQL では ib_logfileN が実態。

MySQL (innodb) の対応するアーキテクチャ

https://dev.mysql.com/doc/refman/8.0/ja/innodb-architecture.html

※テーブルスペース = InnoDB テーブルやインデックスを保持するデータファイル

上記を踏まえて、システム障害から復旧する技術を解説します。

スポンサーリンク

クラッシュリカバリ

クラッシュリカバリは、システム障害から復旧する方法で、主に次の 4 つの要素で構成されます。

WAL (Write Ahead Log)

WAL とは、テーブルの変更などを、データファイルより先にログに書き込むことです。

事前にロギングを行う仕組みは全てWALと呼ばれる。MySQLで言えばInnoDBにおいてRedoログとして利用されるログもWALであるし、MySQL Clusterのディスクテーブル利用時のUndoログだってWALの一種である。

https://nippondanji.blogspot.com/2009/03/wal.html
バッファプールは行データなどテーブルの実際のデータを持ち、ログバッファはトランザクション操作を持ちます

なお、Redo ログへの書き込みが完了した時点で、COMMIT が成功した通知をクライアントに返します。(データファイルには、まだ書き込みをしてません)

WAL によるシステム障害への対策

WAL でデータファイルより先にログへ書き込むことにより、システム障害を次のように防ぎます。

システム障害のタイミング対応
Redo ログに書き込み中Redo の内容を捨てる
データファイルにまだ書き込んでないので問題なし
データファイル書き込み中Redo ログから COMMIT した内容をデータファイルに復元
(ロールフォワードと言います)

WAL によるパフォーマンスの向上

WAL は、システム障害への対策だけではなく、COMMIT 時のパフォーマンスが良くなります。
(最後、データファイルにフラッシュする際のコストは別)

種類Redo ログデータファイル
アクセスシーケンシャルアクセス
(トランザクション操作を追記するだけ)
ランダムアクセスが多い
(実データを更新するため)
書き込み箇所少ない (ログファイルのみ)多い (変更した全てのデータファイル)
速度早い遅い

チェックポイント処理

チェックポイント処理とは、バッファプールからデータファイルにダーティページを書き込む (フラッシュ) することです。

これにより、メモリからストレージページが書き出され、永続化します。(Durability)

チェックポイントを実施するタイミングはこちら
COMMIT は Redo ログに書き込まれるだけで、チェックポイント処理 (データファイルへのフラッシュ) は別で行います

この時、Redo ログに LSN (ログシーケンス番号) を書き込みます。

LSN (ログシーケンス番号)

LSN とは、バッファプールをどこまでデータファイルに書き込んだか示す番号です。

上記の場合、LSN=1 までの変更がデータファイルに書き込み完了していることがわかります。

システム障害が発生した場合、LSN を見ればどこから再開すれば良いか判断できます。
(上記の場合、LSN:2 からデータファイルへの書き込みを再開すればいい)

動作確認

delimiter //
CREATE PROCEDURE insert_loop(max int)
BEGIN
 DECLARE id INT DEFAULT 0;
 REPEAT
  SET id = id + 1;
  INSERT INTO durability VALUES(id);
  IF(MOD(id, 10000) = 0) THEN COMMIT;
  END IF;
 UNTIL id >= max
 END REPEAT;
END//
delimiter ;
SET AUTOCOMMIT = 0;
CREATE TABLE durability(id int);
CALL insert_loop(500000);
SHOW ENGINE INNODB STATUS\G
---
LOG
---
Log sequence number          153604788
Log buffer assigned up to    153604788
Log buffer completed up to   153604788
Log written up to            153604028
Log flushed up to            153604028
Added dirty pages up to      153604788
Pages flushed up to          144025476
Last checkpoint at           144025476

以下の内容が確認できます。

  • LSN (Log sequence number) の値
  • ログバッファは最新情報
  • Redo ログの書き込みは、ログバッファより少し遅れている (COMMIT まで書き込まない)
  • バッファプールにあるダーティページは最新の LSN
  • Redo ログに先、データファイルに後に書き込んでいる (WAL)
  • チェックポイントレコードは、データファイルにフラッシュした最新の LSN

なお、ダーティページの数は以下のコマンドで確認できます。

SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| Innodb_buffer_pool_pages_dirty | 452   |
+--------------------------------+-------+

ダブルライトバッファ

ダブルライトバッファとは、データファイルに書き込む前に、小さなファイルに書き込みます

※ダブルライトバッファはストレージにある小さなファイル (2MB 程度) です。

ダブルライトバッファに書き込む理由はデータファイルのデータを保護するためです。

データファイルの書き込み途中でシステム障害が発生すると、Redo ログでは復旧できません。
(壊れた実データに、Redo ログのトランザクション操作をしてもデータが復元できない)

そこで、ダブルライトバッファを使って、以下のようにデータファイルを保護します。

システム障害のタイミング対処
ダブルライトバッファの書き込み中ダブルライトバッファを捨てるだけ
(データファイルには、まだ書き込んでないので問題なし)
データファイルへの書き込み中ダブルライトバッファからデータファイルを復元

ロールバックセグメント

ロールバックセグメントとは、データを元に戻すための Undo ログを記録する領域です。
undo ログの中身はここ

ダーティページは、チェックポイントで定期的にデータファイルにフラッシュされます。(参考)

この時、COMMIT を行う前にシステム障害が発生すると、データファイルロールバックする (更新前のデータに戻す) 必要があります。

ロールバックセグメントは、ロールバックを実現するために更新前のデータを持ちます。
(なお、更新前のデータは MVCC でも利用します)

クラッシュリカバリ

クラッシュリカバリとは、システム障害から復旧する仕組みです。

クラッシュリカバリは、今まで紹介した以下の技術を組み合わせて復旧します。

クラッシュリカバリは、次の2つのフェーズに分かれます。

フェーズ説明
Redo フェーズWAL により、COMMIT した操作が Redo ファイルにあることを保証
チェックポイント以降の更新を、Redo ログからロールフォワード
ダブルライトバッファで、データファイル整合性を保証
Undo フェーズロールバックセグメントから COMMIT していない内容をロールバック
Undo フェーズには、Change buffer マージと、Purge 処理もあります(参考)
スポンサーリンク

最後に

関連記事

ACID 特性の記事一覧
データベースの基礎知識

参考記事

トランザクションの設計と進化 | ドクセル
ドクセルはスライドやPDFをかんたんに共有できるサイトです