nBackfill

2025-05-31

具体的に説明すると、以下のようになります。

  1. WALファイルとフレーム:

    • WALファイルは、データベースページへの変更を「フレーム」という単位で記録します。各フレームは、データベースの特定のページへの変更を表します。
    • WALファイルには、現在のWALファイル内の有効なフレームの総数を示す「mxFrame」という値があります。
  2. nBackfillの役割:

    • 「nBackfill」は、WALファイル内のフレームのうち、すでにメインのデータベースファイルにコピーされ、適用されたフレームの数を示します。
    • この値は、WALインデックスヘッダー(WALモードで使用される共有メモリ領域の一部)に格納されており、チェックポイントを実行するプロセスによって更新されます。
    • nBackfillの値は常にmxFrame以下です。
  3. チェックポイントとnBackfill:

    • チェックポイントは、WALファイルの内容をデータベースファイルに移動させる操作です。
    • チェックポイントが進行するにつれて、WALファイルからデータベースファイルへのフレームのコピーが進み、nBackfillの値が増加していきます。
    • nBackfillがmxFrameと等しくなると、WALファイル内のすべての変更がデータベースファイルに適用されたことを意味します。この状態になると、もし他の接続がWALファイルへのロックを保持していなければ、WALファイルをリセット(空にするか、上書き可能にする)ことができます。
  4. なぜ重要か?:

    • WALファイルの管理: nBackfillは、WALファイルのどの部分がまだデータベースに適用されていないかをSQLiteが把握するために不可欠です。これにより、不要になったWALフレームを安全に再利用したり、WALファイルを切り詰めたりすることができます。
    • 回復: データベースがクラッシュした場合、SQLiteはWALファイルとnBackfillの値を使用して、データベースを整合性のある状態に回復させます。nBackfillより後のフレームだけを再適用すればよいことを示します。
    • パフォーマンス: nBackfillはチェックポイント処理の効率に影響を与えます。適切にバックフィルが進むことで、WALファイルの肥大化を防ぎ、データベースのパフォーマンスを維持できます。


しかし、nBackfillが適切に機能しない、またはその状態が問題を引き起こしている場合、それは通常、より大きなデータベースの問題の一部として現れます。ここでは、nBackfillに関連する可能性のある一般的なエラーと、そのトラブルシューティングについて説明します。

nBackfillに関連する可能性のある一般的な問題とエラー

    • 現象: WALファイル(-wal拡張子を持つファイル)が非常に大きくなり続け、ディスクスペースを消費し、パフォーマンスが低下する可能性があります。nBackfillの値がmxFrameに近づかず、小さいままになります。
    • 原因:
      • チェックポイントが実行されていない: WALモードでは、自動または手動でチェックポイントが実行されないと、WALファイルは成長し続けます。
      • リーダーのブロック: 読み取りトランザクションが長時間開いている場合、SQLiteはWALファイル内の特定のフレームを削除できません。これは、そのフレームがまだアクティブなリーダーによって参照されている可能性があるためです。この状態が続くと、nBackfillは特定のポイントから先に進めなくなります。
      • 競合: 頻繁な書き込みや競合するアクセスがあると、チェックポイントの完了が阻害されることがあります。
      • クラッシュまたは異常終了: データベースプロセスが予期せず終了した場合、チェックポイントが完了しないことがあります。
  1. SQLITE_BUSY エラー

    • 現象: データベースへの書き込み操作がSQLITE_BUSYエラーで失敗します。これは、nBackfillに直接関連するエラーというよりは、WALモードにおける同時実行性の問題ですが、チェックポイントのブロックが原因である可能性があります。
    • 原因:
      • 単一ライターの制約: WALモードでも、同時にデータベースに書き込めるのは1つのプロセス(または接続)だけです。
      • チェックポイントと書き込みの競合: チェックポイント操作自体が書き込みロックを必要とするため、他の書き込みリクエストと競合する可能性があります。リーダーが多すぎると、チェックポイントが進まず、結果的に書き込みもブロックされることがあります。
  2. データベース破損(database disk image is malformedなど)

    • 現象: データベースファイルが破損し、database disk image is malformedなどのエラーが表示されることがあります。WALファイルやnBackfillの整合性が損なわれている場合に発生する可能性があります。
    • 原因:
      • システムクラッシュや電源喪失: WALファイルやデータベースファイルへの書き込み中にシステムがクラッシュすると、データが不整合な状態になることがあります。
      • ハードウェアの問題: ディスクの故障やメモリの問題が、データの破損を引き起こす可能性があります。
      • ファイルシステムの同期の問題: ファイルシステムが適切に同期されていないと、WALファイルやデータベースファイルの書き込みがディスクに完全に反映されず、nBackfillを含むメタデータに不整合が生じることがあります。
      • 不正なsqlite3_wal_checkpoint_v2の使用: チェックポイントAPIを誤って使用すると、データベースの整合性が損なわれる可能性があります。

トラブルシューティング

  1. WALファイルの肥大化への対応

    • 定期的なチェックポイントの実施:
      • 自動チェックポイント: SQLiteは、デフォルトで一定数のWALフレームが蓄積されると自動的にチェックポイントを実行しますが、アプリケーションで明示的に制御することもできます。
      • 手動チェックポイント: PRAGMA wal_checkpoint(FULL); または sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_FULL, &nLog, &nBackfill); を使用して、明示的にチェックポイントを実行します。これにより、nBackfillmxFrameに追いつき、WALファイルが切り詰められる(または削除される)ことが期待されます。
      • 最適なチェックポイントモードの選択:
        • FULL: すべてのWALフレームをデータベースに書き戻し、WALファイルをリセットします。
        • PASSIVE: WALファイルのリーダーが存在しない場合にのみチェックポイントを実行します。リーダーがいる場合はブロックしません。
        • RESTART: FULLに似ていますが、新しいWALファイルを使用するようにします。
    • 長期実行中の読み取りトランザクションの特定と解決:
      • もしアプリケーションが長時間にわたる読み取りトランザクションを維持している場合、それがnBackfillの進行を妨げている可能性があります。
      • 可能であれば、トランザクションの期間を短縮するか、読み取り専用の接続を別途使用することを検討します。
    • ディスクスペースの監視: WALファイルのサイズを定期的に監視し、異常な増加がないか確認します。
  2. SQLITE_BUSYエラーの処理

    • リトライロジックの実装: SQLITE_BUSYエラーが発生した場合、すぐに失敗するのではなく、一定時間待機してから操作をリトライするロジックを実装します。sqlite3_busy_timeout()関数やsqlite3_busy_handler()コールバックを使用できます。
    • トランザクションの短縮: 書き込みトランザクションの期間をできるだけ短くします。
    • BEGIN IMMEDIATEの使用: トランザクションを開始する際にBEGIN IMMEDIATEを使用すると、トランザクション開始時に書き込みロックを取得するため、その後の操作でSQLITE_BUSYエラーが発生する可能性を減らせます(ただし、BEGIN IMMEDIATE自体がビジーになる可能性はあります)。
    • 競合の少ない設計: データベースへのアクセスパターンを見直し、競合を減らすように設計します。例えば、大きなバッチ書き込みを避けるなど。
  3. データベース破損の対処

    • PRAGMA integrity_check;の実行: データベースが破損している可能性がある場合、このコマンドを実行して破損箇所を特定します。
    • バックアップからの復元: 最も確実な方法は、定期的に取得しているバックアップからデータベースを復元することです。
    • .dumpによるデータ救出: データベースが部分的に破損している場合、sqlite3 your_database.db ".dump" > dump.sqlコマンドを使用して、可能な限りデータをダンプし、新しいデータベースにインポートすることで、一部のデータを救出できる場合があります。
    • ファイルシステムの確認: データベースファイルが配置されているファイルシステムに問題がないか確認します。
    • 堅牢なシャットダウン処理: アプリケーションの終了時に、データベース接続を適切に閉じ、保留中のトランザクションをコミットまたはロールバックするようにします。
  • ログの確認: SQLiteは通常、詳細なログを出力しませんが、アプリケーションレベルでデータベース操作に関するエラーログを収集し、関連する問題を特定するのに役立てます。
  • WALファイルのサイズ監視: OSレベルでWALファイルのサイズを監視することで、異常な肥大化を早期に検出できます。
  • WALインデックスヘッダーの確認: sqlite3_wal_checkpoint_v2関数は、nLog(WALファイル内のフレームの総数、mxFrameに相当)とnBackfillの値を提供します。これらの値を定期的に監視することで、チェックポイントの進行状況を把握できます。


ここでは、SQLite C API (sqlite3_wal_checkpoint_v2) を使用してnBackfillの値を監視し、チェックポイントに関連するプログラミングを行う例をいくつか示します。

sqlite3_wal_checkpoint_v2 関数の説明

この関数は、SQLiteデータベースのWALチェックポイントを制御し、その結果としてnBackfillの値を取得するために使用されます。

int sqlite3_wal_checkpoint_v2(
  sqlite3 *db,          /* Database handle */
  const char *zDb,      /* Database name (e.g., "main") */
  int eMode,            /* Checkpoint mode */
  int *pnLog,           /* OUT: Number of frames in WAL file */
  int *pnBackfill       /* OUT: Number of frames written to DB */
);
  • pnBackfill (出力): データベースファイルにすでに書き戻されたフレームの数(nBackfillの値)が格納されます。
  • pnLog (出力): WALファイル内の合計フレーム数(mxFrameに相当)が格納されます。
  • eMode: チェックポイントのモードを指定します。
    • SQLITE_CHECKPOINT_PASSIVE: アクティブな読み取りトランザクションをブロックせずにチェックポイントを実行します。リーダーが存在しない場合にのみフルチェックポイントが完了します。
    • SQLITE_CHECKPOINT_FULL: すべてのWALフレームをデータベースファイルに書き戻し、WALファイルをリセットしようとします。これは排他的なロックを必要とします。
    • SQLITE_CHECKPOINT_RESTART: FULLに似ていますが、チェックポイント完了後に新しいWALファイルを開始します。
    • SQLITE_CHECKPOINT_TRUNCATE: RESTARTに似ていますが、WALファイルのサイズを可能な限り小さくします。
  • zDb: チェックポイントを行うデータベースの名前(通常は "main")。
  • db: データベース接続ハンドル。

例1: nBackfillの値を取得してチェックポイントの進行状況を監視する

この例では、SQLITE_CHECKPOINT_PASSIVEモードを使用して、現在のWALファイルの状態(nLognBackfill)を取得し、進捗を表示します。これはデータベースに影響を与えません。

#include <stdio.h>
#include <sqlite3.h>
#include <unistd.h> // for sleep

int main() {
    sqlite3 *db;
    char *err_msg = 0;
    int rc;

    // データベースを開く(WALモードで)
    // file=test.db?mode=rwc は WAL モードにするための URI パラメータの例
    // PRAGMA journal_mode=WAL; でも WAL モードに設定できます
    rc = sqlite3_open_v2("test.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return 1;
    }

    // WALモードに設定(もし開くときに設定していなければ)
    rc = sqlite3_exec(db, "PRAGMA journal_mode=WAL;", 0, 0, &err_msg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to set WAL mode: %s\n", err_msg);
        sqlite3_free(err_msg);
        sqlite3_close(db);
        return 1;
    }

    fprintf(stdout, "Database opened in WAL mode.\n");

    int nLog, nBackfill;

    // ダミーの書き込み操作をいくつか行い、WALファイルにデータを蓄積させる
    fprintf(stdout, "Performing some write operations to generate WAL data...\n");
    for (int i = 0; i < 100; ++i) {
        char sql[100];
        snprintf(sql, sizeof(sql), "INSERT INTO data (value) VALUES (%d);", i);
        rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS data (value INTEGER);", 0, 0, &err_msg);
        rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
        if (rc != SQLITE_OK) {
            fprintf(stderr, "SQL error: %s\n", err_msg);
            sqlite3_free(err_msg);
            // エラーが発生しても続行
        }
    }
    fprintf(stdout, "Write operations completed.\n");

    // passive チェックポイントを実行して nLog と nBackfill の値を取得
    rc = sqlite3_wal_checkpoint_v2(db, "main", SQLITE_CHECKPOINT_PASSIVE, &nLog, &nBackfill);
    if (rc == SQLITE_OK) {
        fprintf(stdout, "Passive Checkpoint Status (before full checkpoint):\n");
        fprintf(stdout, "  Total WAL frames (nLog): %d\n", nLog);
        fprintf(stdout, "  Backfilled frames (nBackfill): %d\n", nBackfill);
        if (nLog > 0) {
            fprintf(stdout, "  Progress: %.2f%%\n", (double)nBackfill / nLog * 100);
        } else {
            fprintf(stdout, "  WAL file is empty or not in use.\n");
        }
    } else {
        fprintf(stderr, "Error during passive checkpoint: %s\n", sqlite3_errmsg(db));
    }

    // Full チェックポイントを実行して nBackfill を更新する
    fprintf(stdout, "\nPerforming a FULL checkpoint...\n");
    rc = sqlite3_wal_checkpoint_v2(db, "main", SQLITE_CHECKPOINT_FULL, &nLog, &nBackfill);
    if (rc == SQLITE_OK) {
        fprintf(stdout, "Full Checkpoint Status (after full checkpoint):\n");
        fprintf(stdout, "  Total WAL frames (nLog): %d\n", nLog);
        fprintf(stdout, "  Backfilled frames (nBackfill): %d\n", nBackfill);
        if (nLog > 0) {
            fprintf(stdout, "  Progress: %.2f%%\n", (double)nBackfill / nLog * 100);
        } else {
            fprintf(stdout, "  WAL file is empty or not in use.\n");
        }
        if (nLog == nBackfill) {
            fprintf(stdout, "  Full checkpoint successfully completed. WAL file should be truncated or deleted.\n");
        } else {
            fprintf(stdout, "  Full checkpoint did not complete entirely. Remaining frames: %d\n", nLog - nBackfill);
            fprintf(stdout, "  (This can happen if there are active readers blocking the checkpoint.)\n");
        }
    } else {
        fprintf(stderr, "Error during full checkpoint: %s\n", sqlite3_errmsg(db));
    }

    sqlite3_close(db);
    return 0;
}

コンパイル方法 (GCC): gcc -o wal_checkpoint_example wal_checkpoint_example.c -lsqlite3

実行結果の例:

Database opened in WAL mode.
Performing some write operations to generate WAL data...
Write operations completed.
Passive Checkpoint Status (before full checkpoint):
  Total WAL frames (nLog): 101
  Backfilled frames (nBackfill): 0
  Progress: 0.00%

Performing a FULL checkpoint...
Full Checkpoint Status (after full checkpoint):
  Total WAL frames (nLog): 0
  Backfilled frames (nBackfill): 0
  WAL file is empty or not in use.
  Full checkpoint successfully completed. WAL file should be truncated or deleted.

この例では、書き込み操作後にnBackfillが0であることが示され(自動チェックポイントが発生していなかったため)、FULLチェックポイントを実行した後にnBackfillnLogと等しくなり(両方とも0になる)、WALファイルが正常にクリアされたことがわかります。

例2: PRAGMA wal_checkpoint による同様の操作

SQLiteには、SQLインターフェースからチェックポイントを制御するための PRAGMA wal_checkpoint コマンドも用意されています。これは、C APIのsqlite3_wal_checkpoint_v2関数を内部的に使用しています。

#include <stdio.h>
#include <sqlite3.h>

int main() {
    sqlite3 *db;
    char *err_msg = 0;
    int rc;

    rc = sqlite3_open("test_pragma.db", &db);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return 1;
    }

    rc = sqlite3_exec(db, "PRAGMA journal_mode=WAL;", 0, 0, &err_msg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to set WAL mode: %s\n", err_msg);
        sqlite3_free(err_msg);
        sqlite3_close(db);
        return 1;
    }

    fprintf(stdout, "Database opened in WAL mode (for PRAGMA example).\n");

    // ダミーの書き込み
    sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS pragma_data (id INTEGER PRIMARY KEY, value TEXT);", 0, 0, &err_msg);
    for (int i = 0; i < 50; ++i) {
        char sql[100];
        snprintf(sql, sizeof(sql), "INSERT INTO pragma_data (value) VALUES ('Test %d');", i);
        sqlite3_exec(db, sql, 0, 0, &err_msg);
    }
    fprintf(stdout, "Performed some writes.\n");

    // passive チェックポイントを実行
    // PRAGMA wal_checkpoint(PASSIVE); は nLog/nBackfill の結果を返さないが、チェックポイントは実行される
    fprintf(stdout, "\nExecuting PRAGMA wal_checkpoint(PASSIVE);\n");
    rc = sqlite3_exec(db, "PRAGMA wal_checkpoint(PASSIVE);", 0, 0, &err_msg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "PRAGMA wal_checkpoint(PASSIVE) error: %s\n", err_msg);
        sqlite3_free(err_msg);
    } else {
        fprintf(stdout, "PRAGMA wal_checkpoint(PASSIVE) executed.\n");
        // PRAGMA wal_checkpoint は直接 nLog と nBackfill を返さないため、
        // 外部から WAL ファイルのサイズなどを確認する必要があるか、
        // sqlite3_wal_checkpoint_v2 を使う必要がある。
    }

    // Full チェックポイントを実行
    fprintf(stdout, "\nExecuting PRAGMA wal_checkpoint(FULL);\n");
    rc = sqlite3_exec(db, "PRAGMA wal_checkpoint(FULL);", 0, 0, &err_msg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "PRAGMA wal_checkpoint(FULL) error: %s\n", err_msg);
        sqlite3_free(err_msg);
    } else {
        fprintf(stdout, "PRAGMA wal_checkpoint(FULL) executed. WAL file should be smaller or gone.\n");
    }

    sqlite3_close(db);
    return 0;
}

コンパイル方法 (GCC): gcc -o pragma_wal_checkpoint_example pragma_wal_checkpoint_example.c -lsqlite3

この例では、PRAGMA wal_checkpointコマンドを使用しているため、nLognBackfillの具体的な値を直接取得することはできませんが、チェックポイント操作の効果(WALファイルのサイズが縮小されるなど)は確認できます。

  • パフォーマンスモニタリング: nBackfillnLogの値を監視することで、WALファイルの成長とチェックポイントの効率を把握し、ボトルネックを特定するのに役立ちます。特に、WALファイルが過度に肥大化している場合は、チェックポイント戦略を見直す必要があります。
  • エラーハンドリング: チェックポイント操作は、ビジー状態やロックの問題により失敗することがあります。SQLITE_BUSYなどのエラーを適切に処理し、リトライロジックを実装することが重要です。
  • 長期実行トランザクション: 読み取りトランザクションが非常に長く実行されている場合、WALファイル内の特定のフレームが依然として必要とされているため、nBackfillの値がnLogに到達せず、WALファイルが切り詰められないことがあります。この場合、明示的なチェックポイントを呼び出しても、そのフレームが解放されるまで完全には機能しません。
  • 自動チェックポイント: SQLiteは通常、WALファイルが一定のサイズに達すると自動的にチェックポイントを実行します(デフォルトでは1000ページ)。ほとんどのアプリケーションでは、明示的にsqlite3_wal_checkpoint_v2PRAGMA wal_checkpointを呼び出す必要はありません。


ここで言う「代替方法」とは、sqlite3_wal_checkpoint_v2 関数を直接呼び出す以外の方法で、nBackfill の値に影響を与えたり、WAL モードの管理を行ったりすることを指します。

自動チェックポイントに任せる

SQLite は、デフォルトで一定数の WAL フレームが蓄積されると自動的にチェックポイントを実行します。ほとんどのアプリケーションでは、この自動チェックポイントに任せるのが最も簡単な方法であり、通常はこれで十分です。

  • 欠点: 非常に高負荷なシステムや、長時間にわたる読み取りトランザクションが存在する場合、自動チェックポイントだけではWALファイルの肥大化を十分に防げない可能性があります。
  • 利点: 開発者がコードを書く必要がなく、SQLite が最適なタイミングでチェックポイントを試みます。
  • nBackfill との関係: この設定により、nBackfill の値が nLog に追いつくように、SQLite が自動的にチェックポイントを試みます。WAL ファイルが肥大化しすぎるのを防ぎ、ディスクスペースを管理するのに役立ちます。
  • 詳細: PRAGMA wal_autocheckpoint = N; でこの動作を調整できます。N は、WAL ファイルに N ページ分のデータが書き込まれるたびにチェックポイントをトリガーすることを意味します。デフォルト値は 1000 です。

コミットフックを使用する

sqlite3_commit_hook() 関数は、トランザクションがコミットされるたびに呼び出されるコールバック関数を登録できます。このフック内で、sqlite3_wal_checkpoint_v2 を呼び出すことで、書き込みトランザクションの終了時に明示的にチェックポイントをトリガーできます。

  • 欠点: コミットごとにチェックポイントのオーバーヘッドが発生するため、非常に高頻度でコミットが行われる場合、パフォーマンスに影響を与える可能性があります。SQLITE_CHECKPOINT_PASSIVEモードを使用することで、この影響を最小限に抑えられます。
  • 利点: 書き込み操作の直後にチェックポイントの機会を与えることで、WALファイルの肥大化を効果的に抑制できます。
  • nBackfill との関係: 各コミット後にnBackfillnLogに近づくようにチェックポイントを試みます。これにより、WALファイルの成長をより細かく制御できます。
  • 詳細:
    int commit_hook_callback(void *pArg) {
        sqlite3 *db = (sqlite3 *)pArg;
        int nLog, nBackfill;
        // passive チェックポイントを試みる
        sqlite3_wal_checkpoint_v2(db, "main", SQLITE_CHECKPOINT_PASSIVE, &nLog, &nBackfill);
        // 必要に応じて、nLog と nBackfill の値を確認し、ログ出力などを行う
        return 0; // 0を返すとコミットが続行される
    }
    
    // ...
    sqlite3_open_v2("your_db.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
    sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
    sqlite3_commit_hook(db, commit_hook_callback, db);
    // ...
    

別スレッド/プロセスでチェックポイントを実行する

アプリケーションが長時間実行され、同時に多数のリーダーが存在する場合、SQLITE_CHECKPOINT_FULLのような排他的なチェックポイントがブロックされる可能性があります。この問題に対処するため、データベースとは別のスレッドまたはプロセスで定期的にチェックポイントを実行する方法があります。

  • 欠点: スレッド間/プロセス間の同期を考慮する必要があります。また、チェックポイントのタイミングがメインアプリケーションの動作に依存するため、適切な間隔を設定することが重要です。
  • 利点: メインアプリケーションのパフォーマンスを阻害せずに、WALファイルを定期的に管理できます。特に、常に多数のアクティブなリーダーが存在する環境で有効です。
  • nBackfill との関係: メインアプリケーションのロジックとは独立してnBackfillの値を更新し、WALファイルをクリーンアップできます。
  • 詳細:
    • メインのデータベース接続とは別のデータベース接続を作成します。
    • 新しいスレッドまたはプロセスで、この別の接続を使用して定期的にsqlite3_wal_checkpoint_v2(db, "main", SQLITE_CHECKPOINT_FULL, ...)を呼び出します。
    • このスレッド/プロセスは、メインのアプリケーションが実行中のトランザクションを完了するタイミングを見計らってチェックポイントを試みます。

厳密にはnBackfillを直接操作する代替方法ではありませんが、WALモードの一般的な問題である「リーダーによるチェックポイントのブロック」を回避するための重要なプラクティスです。

  • 欠点: アプリケーション設計で読み取り/書き込み接続の分離を考慮する必要があります。
  • 利点: データベースの並行性を高め、WALファイルの肥大化を防ぎます。
  • nBackfill との関係: 読み取り専用接続は、WALファイル内の特定の時点のスナップショットを参照し続けることがなく、チェックポイントを妨げません。これにより、nBackfillnLogに到達しやすくなり、WALファイルの切り詰めが容易になります。
  • 詳細:
    • データの読み取りのみを行う接続は、SQLITE_OPEN_READONLYフラグを使用して開きます。
    • 書き込みを行う接続は、SQLITE_OPEN_READWRITEフラグを使用します。