# 機能設計書 23-WAL（Write-Ahead Logging）

## 概要

本ドキュメントは、SQLiteのWAL（Write-Ahead Logging）サブシステムの機能設計を記述する。WALはjournal_mode=WAL設定時に使用される高性能トランザクション処理機構で、従来のロールバックジャーナルに代わるトランザクションログ方式を提供する。

### 本機能の処理概要

WALは、データベースへの変更を直接DBファイルに書き込むのではなく、先行書き込みログ（WALファイル）に記録する方式を実装する。これにより、リーダーとライターの並行動作が可能となり、高い同時実行性を実現する。

**業務上の目的・背景**：従来のロールバックジャーナル方式では、書き込み中に読み取りがブロックされる。WALモードでは、ライターがWALファイルに書き込む間もリーダーは元のDBファイルから読み取りを継続できるため、読み取り性能が大幅に向上する。また、同期回数が減少するため書き込み性能も向上する。

**機能の利用シーン**：
- 読み取り頻度が高く、書き込みも発生するアプリケーション
- Webサーバーのバックエンドデータベース
- 複数プロセス/スレッドからの同時アクセス環境
- 高スループットが求められるシステム

**主要な処理内容**：
1. WALファイルへの変更フレーム書き込み
2. WALインデックス（-shm共有メモリ）の管理
3. チェックポイント処理（WAL→DBファイルへの転写）
4. 複数リーダー間のスナップショット管理
5. クラッシュリカバリ

**関連システム・外部連携**：ページャーと連携し、VFS経由でWALファイルとshmファイルにアクセスする。

**権限による制御**：WAL_WRITE_LOCK、WAL_CKPT_LOCK、WAL_READ_LOCK(n)による排他制御

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | Speedtest1（Worker版） | 補助機能 | journal_mode設定によるWAL使用 |

## 機能種別

トランザクション管理 / 永続化処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| pDbFd | sqlite3_file* | Yes | DBファイルハンドル | オープン済み |
| zWalName | const char* | Yes | WALファイル名 | 有効なパス |
| bNoShm | int | No | 共有メモリ不使用フラグ | 0または1 |
| mxWalSize | i64 | No | WAL最大サイズ | 正の整数 |

### 入力データソース

- ページャーからの書き込み要求
- PRAGMA journal_mode=WAL設定
- チェックポイント要求（自動/手動）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| rc | int | 操作結果 |
| pnLog | int* | WALフレーム数 |
| pnCkpt | int* | チェックポイント済みフレーム数 |

### 出力先

- WALファイル（*.db-wal）
- WALインデックス（*.db-shm）
- データベースファイル（チェックポイント時）

## 処理フロー

### 処理シーケンス

```
1. WALオープン (sqlite3WalOpen)
   └─ WALファイル・shmファイルオープン/作成

2. 読み取りトランザクション開始 (sqlite3WalBeginReadTransaction)
   └─ 読み取りロック取得、mxFrame記録

3. ページ読み取り (sqlite3WalFindFrame)
   └─ WALインデックスのハッシュテーブル検索

4. 書き込みトランザクション開始 (sqlite3WalBeginWriteTransaction)
   └─ 書き込みロック取得

5. フレーム書き込み (sqlite3WalFrames)
   └─ ページデータをWALに追記

6. トランザクション終了 (sqlite3WalEndWriteTransaction)
   └─ 書き込みロック解放

7. チェックポイント (sqlite3WalCheckpoint)
   └─ WALからDBへバックフィル
```

### フローチャート

```mermaid
flowchart TD
    A[ページ変更要求] --> B[WALフレーム作成]
    B --> C[フレームヘッダ作成]
    C --> D[チェックサム計算]
    D --> E[WALファイル追記]
    E --> F{コミット?}
    F -->|Yes| G[コミットフレームマーク]
    F -->|No| H[次の変更待ち]
    G --> I[WAL同期]
    I --> J{自動チェックポイント閾値?}
    J -->|Yes| K[チェックポイント実行]
    J -->|No| L[終了]
    K --> L
    H --> A
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-23-01 | フレーム順序 | フレームは常にWAL末尾に追記 | 書き込み時 |
| BR-23-02 | チェックサム連鎖 | 各フレームのチェックサムは前フレームに依存 | 全フレーム |
| BR-23-03 | リーダー保護 | チェックポイントは全リーダーのmxFrame以下まで | チェックポイント時 |
| BR-23-04 | ソルト更新 | チェックポイント後にソルト値を更新 | WALリセット時 |

### 計算ロジック

**チェックサム計算**:
```
for i from 0 to n-1 step 2:
    s0 += x[i] + s1;
    s1 += x[i+1] + s0;
```
フィボナッチ重み付きチェックサム。

**WALフレームオフセット計算**:
```c
#define walFrameOffset(iFrame, szPage) \
    (WAL_HDRSIZE + ((iFrame)-1)*(i64)((szPage)+WAL_FRAME_HDRSIZE))
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象ファイル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| フレーム書き込み | WALファイル | WRITE | 変更ページの記録 |
| チェックポイント | DBファイル | WRITE | WALからのバックフィル |
| インデックス更新 | shmファイル | WRITE | ハッシュテーブル更新 |

### WALファイル構造

| 操作 | 項目 | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| WRITE | マジックナンバー | 0x377f0682 or 0x377f0683 | ヘッダ先頭4バイト |
| WRITE | バージョン | 3007000 | ファイルフォーマット |
| WRITE | ページサイズ | 512-65536 | DBと同一 |
| WRITE | チェックポイントシーケンス | インクリメント | チェックポイント毎 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SQLITE_BUSY | ロック競合 | 他プロセスがWALロック保持 | リトライ |
| SQLITE_READONLY_CANTLOCK | 読み取り専用 | shmファイルに書き込み不可 | 読み取り専用モードで動作 |
| SQLITE_IOERR_SHMMAP | メモリマップ失敗 | shm領域のマップ失敗 | エラー返却 |
| SQLITE_CORRUPT | WAL破損 | チェックサム不一致 | 破損フレーム以降を無視 |

### リトライ仕様

SQLITE_BUSY時、busy_handlerが設定されていればコールバックを呼び出す。

## トランザクション仕様

- 読み取りトランザクション: mxFrameを記録し、それ以下のフレームのみ参照
- 書き込みトランザクション: WAL_WRITE_LOCKを取得、コミットフレームにdbsizeを設定
- チェックポイント: WAL_CKPT_LOCKを取得、nBackfillを更新

## パフォーマンス要件

- WALサイズ: PRAGMA wal_autocheckpointで自動チェックポイント閾値設定（デフォルト1000ページ）
- WALインデックス: ハッシュテーブルによるO(1)検索
- チェックポイントモード: PASSIVE/FULL/RESTART/TRUNCATE

## セキュリティ考慮事項

- WALファイル・shmファイルには変更データが平文で保存
- shmファイルは共有メモリとしてマップされるため、アクセス権限に注意
- exclusiveモードでshmファイルを使用しない設定も可能

## 備考

- WALモードはネットワークファイルシステムでは非推奨（共有メモリの問題）
- WAL2モード（実験的）により更なる並行性向上の研究が進行中

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、WALのファイルフォーマットとインデックス構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | wal.c | `src/wal.c` | ファイルヘッダコメント（16-249行目）でWAL/WAL-INDEXフォーマットを理解 |
| 1-2 | wal.c | `src/wal.c` | WalIndexHdr構造体（321-333行目） |
| 1-3 | wal.c | `src/wal.c` | WalCkptInfo構造体（394-400行目） |
| 1-4 | wal.c | `src/wal.c` | Wal構造体（511-554行目） |

**読解のコツ**:
- wal-indexヘッダの図解（404-466行目）が構造理解に有用
- WAL_HDRSIZE=32, WAL_FRAME_HDRSIZE=24の定数に注目
- ソルト値（aSalt[2]）はチェックポイント毎に更新される重要な識別子

#### Step 2: ハッシュテーブル構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | wal.c | `src/wal.c` | ハッシュテーブル定数（615-624行目） |
| 2-2 | wal.c | `src/wal.c` | WALINDEX_PGSZ計算（627-629行目） |

**主要処理フロー**:
1. **615-617行目**: HASHTABLE_NPAGE=4096, HASHTABLE_HASH_1=383, HASHTABLE_NSLOT=8192
2. **624行目**: HASHTABLE_NPAGE_ONE = 4062（最初のブロックはヘッダ分小さい）
3. **ハッシュ計算**: iKey = (P * 383) % HASHTABLE_NSLOT

#### Step 3: Wal構造体を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | wal.c | `src/wal.c` | Wal構造体（511-554行目） |

**主要フィールド**:
- **pWalFd**: WALファイルハンドル
- **apWiData**: wal-indexメモリマップポインタ配列
- **hdr**: 現在のWalIndexHdr
- **readLock**: 保持中の読み取りロック番号（-1=なし）
- **writeLock**: 書き込みロック保持フラグ

#### Step 4: チェックポイント処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | wal.c | `src/wal.c` | WalIterator構造体（591-601行目） |
| 4-2 | wal.c | `src/wal.c` | walCheckpoint()関数 |

**主要処理フロー**:
- WalIteratorでページ番号順にフレームを走査
- 各ページの最新フレームをDBへバックフィル
- nBackfillを更新

### プログラム呼び出し階層図

```
sqlite3WalOpen()
    │
    ├─ sqlite3OsOpen() [WALファイル]
    │
    └─ walIndexPage() [shmメモリマップ]

sqlite3WalBeginReadTransaction()
    │
    ├─ walTryBeginRead()
    │      │
    │      ├─ walLockShared() [読み取りロック]
    │      │
    │      └─ walIndexReadHdr() [ヘッダ読み取り]
    │
    └─ mxFrame記録

sqlite3WalFindFrame()
    │
    └─ walHashGet()
           │
           └─ ハッシュテーブル検索
                  └─ 該当フレーム番号返却

sqlite3WalFrames()
    │
    ├─ フレームヘッダ作成
    │
    ├─ walChecksumBytes() [チェックサム]
    │
    ├─ sqlite3OsWrite() [WAL書き込み]
    │
    └─ walIndexAppend() [インデックス更新]

sqlite3WalCheckpoint()
    │
    ├─ walIteratorInit()
    │
    ├─ ページ番号順ループ
    │      │
    │      ├─ walIteratorNext()
    │      │
    │      └─ DBへバックフィル
    │
    └─ nBackfill更新
```

### データフロー図

```
[入力]                    [処理]                      [出力]

ページ変更 ──────▶ sqlite3WalFrames()
                         │
              フレームヘッダ作成
                         │
              チェックサム計算
                         │
              WAL追記 ────────────────▶ WALファイル
                         │
              インデックス更新 ───────▶ shmファイル

読み取り要求 ────▶ sqlite3WalFindFrame()
                         │
              ハッシュテーブル検索
                         │
              ┌──────────┴──────────┐
              │                     │
          WALに有り             WALに無し
              │                     │
         フレーム番号            0を返却
              │                     │
              └──────────┬──────────┘
                         │
              ページャーへ返却

チェックポイント要求 ▶ sqlite3WalCheckpoint()
                         │
              ページ順走査
                         │
              バックフィル ──────────▶ DBファイル
                         │
              nBackfill更新 ─────────▶ shmファイル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| wal.c | `src/wal.c` | ソース | WAL主実装 |
| wal.h | `src/wal.h` | ヘッダ | 公開API定義 |
| pager.c | `src/pager.c` | ソース | WAL連携のページャー |
| os_unix.c | `src/os_unix.c` | ソース | shmマップ実装（Unix） |
| os_win.c | `src/os_win.c` | ソース | shmマップ実装（Windows） |
