Oracle REDO Log の内部構造を徹底解説:Redo Strand、LGWR、Log Switch から Checkpoint まで

はじめに

この記事では、Oracle DatabaseのREDO Logの内部実装を深掘りします。

InnoDB、PostgreSQLに続く3本目です。OracleのREDO Logは歴史が長く、WALの原型ともいえる存在です。Oracle 19c / 23aiを中心に、以下を解説します:

  • REDO Logの役割(InnoDBやPostgreSQLとの共通点)
  • Change VectorとRedo Record(記録の単位)
  • Redo Log Bufferと書き込みの仕組み(LGWR、Redo Strand)
  • Online Redo LogとLog Switch(循環利用の仕組み)
  • Checkpoint(InnoDBやPostgreSQLとの違い)

そもそもOracle REDO Logって何?

InnoDBのREDO Log、PostgreSQLのWALと同じ役割です。WAL(Write-Ahead Logging)の原則に従い、データブロックを変更する前にREDO情報をディスクに書きます。

Oracleの用語で整理すると:

Oracle用語              InnoDB用語           PostgreSQL用語
──────────────────────────────────────────────────────────
Redo Log               REDO Log             WAL
SCN (System Change     LSN                  LSN
  Number)
Redo Log Buffer        Log Buffer           WAL Buffer
LGWR                   log_writer           バックエンド自身
Online Redo Log File   ib_logfile0/1        WALセグメントファイル
Archived Redo Log      ―                    WALアーカイブ

大きな違いはSCN(System Change Number)です。InnoDBやPostgreSQLのLSNがログの物理的なバイト位置を表すのに対し、OracleのSCNは論理的なタイムスタンプです。SCNはデータベース全体で単調増加する番号で、トランザクションの順序を決定します。

Change VectorとRedo Record

InnoDBでは65種類のREDOタイプ、PostgreSQLではResource Manager方式でした。OracleではChange VectorRedo Recordという2層構造です。

Change Vector

Change Vectorは「1つのデータブロックに対する1つの変更」を記録する最小単位です。InnoDBの1 REDOレコードに相当します。

Change Vector:
| OpCode | Block Address | Change Data |
|---|---|---|
| (操作種別) | (DBA) | (変更内容) |

- **OpCode**:操作の種類(例:11.2 = INSERT、11.3 = DELETE、11.5 = UPDATE)
- **DBA(Data Block Address)**:対象ブロックのファイル番号+ブロック番号
- **Change Data**:変更内容

### Redo Record

Redo Recordは1つ以上のChange Vectorをまとめたものです。1つのアトミック操作(例:INSERT文)が複数ブロックを変更する場合、それらのChange Vectorが1つのRedo Recordにまとめられます。

Redo Record: – Redo Record Header – ├─ SCN – ├─ Thread Number – └─ Record Length – Change Vector 1 (UNDO block変更) – Change Vector 2 (Data block変更) – Change Vector 3 (Index block変更)

PostgreSQLの1 WALレコードが複数ページを参照できるのと似ていますね。InnoDBだけが「1レコード = 1ページ」の制約を持っています。

ALTER SYSTEM DUMP LOGFILEで実際に見てみる

ALTER SYSTEM DUMP LOGFILE '/path/to/redo01.log';

トレースファイルに以下のような出力が得られます:

REDO RECORD - Thread:1 RBA: 0x000012.00000002.0010 LEN: 0x0200 VLD: 0x0d
SCN: 0x0000.001a3f50 SUBSCN: 1
CHANGE #1 TYP:0 CLS:1 AFN:4 DBA:0x01000080 OBJ:73142 SCN:0x0000.001a3f4e
  Undo Block or Undo Segment Header
CHANGE #2 TYP:2 CLS:1 AFN:4 DBA:0x01400021 OBJ:73142 SCN:0x0000.001a3f4e
  KDO Op code: URP  -- Update Row Piece

Redo Log Bufferと書き込み

Redo Log Buffer

Redo Log BufferはSGA(System Global Area)内の共有メモリ領域です。サイズはLOG_BUFFERパラメータで設定します。

SHOW PARAMETER log_buffer;
-- デフォルト: 自動計算(通常数MB〜数十MB)

書き込みの流れ

サーバープロセスがデータを変更すると:

1. サーバープロセス
   ├─ Change Vectorを生成
   ├─ Redo Log Bufferにコピー(redo copy latch / redo allocation latch)
   └─ 完了

2. LGWR(Log Writer)プロセス
   ├─ Redo Log BufferからOnline Redo Logファイルに書き込み
   └─ 以下のタイミングで起動:
       ├─ コミット時(ユーザーがCOMMIT発行)
       ├─ Redo Log Bufferが1/3埋まったとき
       ├─ 3秒ごと
       └─ DBWnがダーティブロックを書く前

InnoDBのlog_writer/log_flusherと似た専用プロセス方式です。PostgreSQLの「各バックエンドが自分で書く」方式とは対照的です。

Redo Strand(Private Redo)

高負荷環境では、Redo Log Bufferへの書き込みがボトルネックになります。Oracle 12c以降、Private Redo(Redo Strand)という仕組みが導入されました。

従来:
  全サーバープロセス → [共有Redo Log Buffer] → LGWR → ディスク
                        ↑ ボトルネック

Private Redo(12c〜):
  プロセスA → [Private Strand A]─┐
  プロセスB → [Private Strand B]─┼→ LGWR → ディスク
  プロセスC → [Private Strand C]─┘

各サーバープロセスが自分専用のStrandにREDOを書き、LGWRがまとめてディスクに書きます。InnoDBのMySQL 8.0 Lock-free設計(fetch_addで専用領域を確保)と発想が似ています。

Private Redoが使われるかどうかは自動的に判断されます。小さなトランザクションはPrivate Strandを使い、大きなトランザクション(LOBの更新など)は従来の共有Redo Log Bufferを使います。

Group Commit

OracleでもGroup Commitが行われます。複数のトランザクションが同時にコミットすると、LGWRは1回のI/Oでまとめて書き込みます。

-- コミット時の書き込み待ちの挙動を制御
ALTER SYSTEM SET commit_logging = 'BATCH';       -- バッチ書き込み
ALTER SYSTEM SET commit_wait = 'NOWAIT';         -- fsyncを待たない(危険)
設定 挙動 InnoDB対応 PostgreSQL対応
デフォルト fsyncまで待つ = 1 synchronous_commit = on
commit_wait = NOWAIT 待たない = 0 synchronous_commit = off
commit_logging = BATCH バッチ化

Partial Write問題:Oracleはどう対処するか?

InnoDBはDouble Write Buffer、PostgreSQLはFull Page Writeで対処していました。Oracleはどうでしょうか?

REDO Log側:問題になりにくい

OracleのREDO Logは512バイトのブロック単位で書き込まれます。ほとんどのストレージはセクタサイズ512Bの原子書き込みを保証するため、REDO Logブロックが中途半端に書かれる可能性は極めて低いです。

データブロック側:別の仕組みで保護

Oracleのデータブロック(デフォルト8KB)はPartial Writeの影響を受け得ます。Oracleは以下の仕組みで対処します:

  • DB_BLOCK_CHECKSUM:各データブロックにチェックサムを記録。読み取り時に破損を検出
  • Lost Write Protection(19c〜):Data Guardのスタンバイ側でブロックのSCNを比較し、プライマリ側のロストライトを検出
  • DB_LOST_WRITE_PROTECT:Shadow Lost Write Protectionを有効化し、スタンバイなしでもロストライトを検出

InnoDBやPostgreSQLが「壊れたページをログから復元する」アプローチなのに対し、Oracleは「壊れたことを検出する」アプローチが中心です。OracleはASMやストレージレイヤーとの連携でI/O整合性を担保する設計思想があり、DB単体でページ復元まで行うInnoDBやPostgreSQLとはアーキテクチャの前提が異なります。

Online Redo LogとLog Switch

Online Redo Logの構造

OracleはOnline Redo Logグループ単位で管理します。各グループには1つ以上のメンバー(物理ファイル)があります。同じグループのメンバーは同一内容のミラーコピーです。

Group 1: /u01/oradata/redo01a.log, /u02/oradata/redo01b.log  ← ミラー
Group 2: /u01/oradata/redo02a.log, /u02/oradata/redo02b.log  ← ミラー
Group 3: /u01/oradata/redo03a.log, /u02/oradata/redo03b.log  ← ミラー

InnoDBのib_logfile0/1やPostgreSQLのWALセグメントにはミラーの概念がありません。Oracleはストレージ障害に対する耐性をREDO Log自体に組み込んでいます(ただし実際にはASMやRAIDで冗長化することが多い)。

Log Switch

グループは循環的に使われます。1つのグループが一杯になると、次のグループに切り替わります。これがLog Switchです。

Group 1 (CURRENT) → Group 2 (CURRENT) → Group 3 (CURRENT) → Group 1 ...
         ↓                   ↓                   ↓
      ACTIVE/             ACTIVE/             ACTIVE/
      INACTIVE            INACTIVE            INACTIVE

各グループのステータス: – CURRENT:LGWRが現在書き込み中 – ACTIVE:リカバリにまだ必要(Checkpointが完了していない) – INACTIVE:リカバリに不要(上書き可能)

SELECT group#, status, bytes/1024/1024 AS size_mb FROM v$log;

GROUP#  STATUS     SIZE_MB
------  --------   -------
     1  CURRENT        200
     2  INACTIVE       200
     3  INACTIVE       200

InnoDBはファイル内を循環利用しますが、Oracleはファイル(グループ)単位で切り替えます。PostgreSQLはセグメントファイルを作成/削除します。

Archived Redo Log

ARCHIVELOGモードでは、Log Switch時にLGWRが書き終わったグループをARCn(Archiver)プロセスがアーカイブ先にコピーします。これにより、Online Redo Logが上書きされても過去のREDO情報が保持され、PITR(Point-in-Time Recovery)が可能になります。

Online Redo Log          Archived Redo Log
Group 1 ──Log Switch──→ /u01/archive/arc_0001.log
Group 2 ──Log Switch──→ /u01/archive/arc_0002.log
Group 3 ──Log Switch──→ /u01/archive/arc_0003.log

PostgreSQLのarchive_command/archive_libraryによるWALアーカイブと同じ概念です。InnoDBにはネイティブのアーカイブ機能がなく、バイナリログ(binlog)が別途その役割を担います。

Checkpoint

OracleのCheckpointの種類

Oracleには複数種類のCheckpointがあります:

種類 トリガー 動作
Full Checkpoint ALTER SYSTEM CHECKPOINT、DB停止時 全ダーティブロックをフラッシュ
Incremental Checkpoint 定期的(自動) DBWnが少しずつダーティブロックをフラッシュ
Log Switch Checkpoint Log Switch時 そのグループに関連するダーティブロックをフラッシュ

Incremental Checkpoint

通常運用で最も重要なのはIncremental Checkpointです。OracleのDBWn(Database Writer)プロセスが、ダーティブロックを少しずつ継続的にフラッシュします。

これはInnoDBのPage Cleanerに近い動作です。PostgreSQLのCheckpointが「全ダーティページを一気にフラッシュする重い操作」だったのとは対照的です。

InnoDB:      Page Cleanerが常時フラッシュ + Checkpointは軽い(LSN記録のみ)
Oracle:      DBWnが常時フラッシュ + Incremental Checkpointで位置を進める
PostgreSQL:  Checkpoint時に全ダーティページをフラッシュ(重い)

Checkpoint位置の管理

Oracleは制御ファイル(Control File)にCheckpoint情報を記録します。各データファイルのヘッダにも、そのファイルに関するCheckpoint SCNが記録されます。

SELECT file#, checkpoint_change# FROM v$datafile_header;

FILE#  CHECKPOINT_CHANGE#
-----  ------------------
    1          1732456
    2          1732456
    3          1732450    ← このファイルだけ少し遅れている

InnoDBがib_logfile0のCheckpoint Blockに記録し、PostgreSQLがpg_controlに記録するのと同様に、Oracleは制御ファイルに記録します。

まとめ

トピック InnoDB PostgreSQL Oracle
名称 REDO Log WAL REDO Log
順序管理 LSN(物理位置) LSN(物理位置) SCN(論理タイムスタンプ)
記録単位 REDOレコード(1ページ) WALレコード(複数ページ可) Redo Record(複数Change Vector)
Partial Write対策 Double Write Buffer Full Page Write ― (ブロックサイズが小さい+ロストライト保護)
書き込み主体 専用スレッド(log_writer) 各バックエンド 専用プロセス(LGWR)
並列化 Lock-free(fetch_add) LWLock(複数スロット) Private Redo Strand
ファイル管理 固定数ファイル循環 セグメント作成/削除 グループ単位で循環 + アーカイブ
ミラーリング なし なし グループ内メンバーでミラー
Checkpoint 軽い(LSN記録) 重い(全ページフラッシュ) Incremental(DBWnが常時フラッシュ)

Oracleは最も歴史が長いだけあって、Online Redo Logのグループ/メンバー構造やArchived Redo Logなど、運用面での成熟度が高い設計です。Private Redo Strandによる並列化も、InnoDBのLock-free設計とは異なるアプローチで同じ問題を解決しています。

次回はSQL Server Transaction Logを同じ切り口で解説します。

参考文献

コメントする