Origin Private File System を基盤とするブラウザ内の SQLite Wasm

SQLite を使用して、ウェブですべてのストレージニーズを高パフォーマンスで処理します。

SQLite は、人気のあるオープンソースの軽量な埋め込みリレーショナル データベース管理システムです。多くのデベロッパーは、構造化された使いやすい方法でデータを保存するためにこれを使用しています。SQLite はサイズが小さくメモリ要件が低いため、モバイル デバイス、デスクトップ アプリケーション、ウェブブラウザでデータベース エンジンとして使用されることがよくあります。

SQLite の主な機能の 1 つは、サーバーレス データベースであるということです。つまり、動作に個別のサーバー プロセスは必要ありません。代わりに、データベースはユーザーのデバイス上の単一のファイルに保存されるため、アプリケーションに簡単に統合できます。

SQLite のロゴ。

Web Assembly ベースの SQLite

Web Assembly(Wasm)に基づく非公式の SQLite バージョンがいくつかあり、ウェブブラウザで使用できます(sql.js など)。sqlite3 WASM/JS サブプロジェクトは、SQLite プロジェクトに正式に関連付けられた最初の取り組みであり、ライブラリの Wasm ビルドをサポートされている SQLite 配信ファミリーの確立されたメンバーにします。このプロジェクトの具体的な目標は次のとおりです。

  • 使用の観点から可能な限り C に近い低レベルの sqlite3 API をバインディングします。
  • 低レベルの API に直接対応する、sql.jsNode.js スタイルの実装に似た、より高レベルのオブジェクト指向 API。この API は、低レベル API と同じスレッドから使用する必要があります。
  • Worker ベースの API で、Worker メッセージを介して以前の API と通信します。これは、ローレベルの API をワーカー スレッドにインストールし、ワーカー メッセージ経由で通信するメインスレッドでの使用を目的としています。
  • Worker API の Promise ベースのバリエーション。スレッド間の通信をユーザーから完全に隠します。
  • 利用可能な JavaScript API(オリジン プライベート ファイル システム(OPFS)など)を使用した永続的なクライアントサイド ストレージのサポート。

オリジンのプライベート ファイル システム永続バックエンドで SQLite Wasm を使用する

npm からライブラリをインストールする

次のコマンドを使用して、npm から @sqlite.org/sqlite-wasm パッケージをインストールします。

npm install @sqlite.org/sqlite-wasm

Origin のプライベート ファイル システム

オリジンのプライベート ファイル システム(OPFS、File System Access API の一部)は、データに非常に高いパフォーマンスでアクセスできる特別なサーフェスで拡張されています。この新しいサーフェスは、ファイルのコンテンツへのインプレース書き込み専用アクセスを提供するという点で、既存のサーフェスと異なります。この変更により、フラッシュされていない変更を一貫して読み取る機能と、専用ワーカーで同期バリアントを使用できるようになり、パフォーマンスが大幅に向上し、新しいユースケースのブロックが解除されます。

ご想像のとおり、プロジェクトの目標の最後のポイントである、利用可能な JavaScript API を使用した永続的なクライアントサイド ストレージのサポートには、データベース ファイルへのデータの永続化に関する厳格なパフォーマンス要件が伴います。ここで、オリジンのプライベート ファイル システム、具体的には FileSystemFileHandle オブジェクトの createSyncAccessHandle() メソッドが使用されます。このメソッドは、ファイルの同期読み取りと書き込みに使用できる FileSystemSyncAccessHandle オブジェクトに解決される Promise を返します。このメソッドは同期的であるため、パフォーマンス上の利点がありますが、そのため、オリジンのプライベート ファイル システム内のファイルに対して専用の ウェブワーカー内でのみ使用でき、メインスレッドをブロックすることはできません。

必要なヘッダーを設定する

ダウンロードした SQLite Wasm アーカイブには、sqlite3 WASM/JS ビルドを構成する sqlite3.js ファイルと sqlite3.wasm ファイルが含まれています。jswasm ディレクトリにはコア sqlite3 の成果物が含まれ、最上位ディレクトリにはデモアプリとテストアプリが含まれます。ブラウザは file:// URL から Wasm ファイルを提供しないため、これを使用してビルドするアプリにはウェブサーバーが必要です。そのサーバーは、ファイルを提供するときにレスポンスに次のヘッダーを含める必要があります。

  • Cross-Origin-Opener-Policysame-origin ディレクティブに設定します。これにより、ブラウジング コンテキストが同一オリジンのドキュメントに限定されます。クロスオリジン ドキュメントは、同じブラウジング コンテキストで読み込まれません。
  • Cross-Origin-Embedder-Policyrequire-corp ディレクティブに設定されているため、ドキュメントは同じオリジンまたは別のオリジンから読み込み可能として明示的にマークされたリソースのみを読み込むことができます。

これらのヘッダーが必要な理由は、SQLite Wasm が SharedArrayBuffer に依存しており、これらのヘッダーの設定がセキュリティ要件の一部であるためです。

DevTools でトラフィックを調べると、次の情報が表示されます。

上記の 2 つのヘッダー(Cross-Origin-Embedder-Policy と Cross-Origin-Opener-Policy)が Chrome DevTools でハイライト表示されています。

Speedtest

SQLite チームは、非推奨の Web SQL と比較して、WebAssembly 実装に関するベンチマークを実施しました。これらのベンチマークは、SQLite Wasm が一般的に Web SQL と同程度の速度であることを示しています。少し遅くなる場合もあれば、少し速くなる場合もあります。すべての詳細は結果ページで確認できます。

スタートガイドのコードサンプル

前述のように、Origin プライベート ファイル システム永続性バックエンドを使用する SQLite Wasm は、Worker コンテキストから実行する必要があります。ライブラリは、これらの処理をすべて自動的に処理し、メインスレッドから直接使用できます。

import { sqlite3Worker1Promiser } from '@sqlite.org/sqlite-wasm';

(async () => {
  try {
    console.log('Loading and initializing SQLite3 module...');

    const promiser = await new Promise((resolve) => {
      const _promiser = sqlite3Worker1Promiser({
        onready: () => {
          resolve(_promiser);
        },
      });
    });

    console.log('Done initializing. Running demo...');

    let response;

    response = await promiser('config-get', {});
    console.log('Running SQLite3 version', response.result.version.libVersion);

    response = await promiser('open', {
      filename: 'file:worker-promiser.sqlite3?vfs=opfs',
    });
    const { dbId } = response;
    console.log(
      'OPFS is available, created persisted database at',
      response.result.filename.replace(/^file:(.*?)\?vfs=opfs$/, '$1'),
    );

    await promiser('exec', { dbId, sql: 'CREATE TABLE IF NOT EXISTS t(a,b)' });
    console.log('Creating a table...');

    console.log('Insert some data using exec()...');
    for (let i = 20; i <= 25; ++i) {
      await promiser('exec', {
        dbId,
        sql: 'INSERT INTO t(a,b) VALUES (?,?)',
        bind: [i, i * 2],
      });
    }

    console.log('Query data with exec()');
    await promiser('exec', {
      dbId,
      sql: 'SELECT a FROM t ORDER BY a LIMIT 3',
      callback: (result) => {
        if (!result.row) {
          return;
        }
        console.log(result.row);
      },
    });

    await promiser('close', { dbId });
  } catch (err) {
    if (!(err instanceof Error)) {
      err = new Error(err.result.message);
    }
    console.error(err.name, err.message);
  }
})();

デモ

上記のコードの動作については、デモをご覧ください。Glitch のソースコードもぜひご覧ください。以下の埋め込みバージョンでは OPFS バックエンドが使用されていませんが、別のタブでデモを開くと使用されます。

オリジンの非公開ファイル システムをデバッグする

SQLite Wasm のオリジン プライベート ファイル システムの出力をデバッグするには、OPFS Explorer Chrome 拡張機能を使用してください。

Chrome ウェブストアの OPFS Explorer。

拡張機能をインストールしたら、Chrome DevTools を開き、[OPFS Explorer] タブを選択します。これで、SQLite Wasm がオリジンの非公開ファイル システムに書き込む内容を検査できるようになります。

デモアプリの Origin Private File System の構造を示す OPFS Explorer Chrome 拡張機能。

DevTools の OPFS Explorer ウィンドウでファイルを選択すると、ローカル ディスクに保存できます。その後、SQLite Viewer などのアプリを使用してデータベースを検査し、SQLite Wasm が実際に期待どおりに動作することを確認できます。

SQLite Wasm デモからデータベース ファイルを開くために使用される SQLite Viewer アプリ。

ヘルプとフィードバック

SQLite Wasm は SQLite コミュニティによって開発、保守されています。サポート フォーラムで検索したり、投稿したりして、ヘルプやフィードバックを入手してください。詳細なドキュメントは SQLite のサイトで確認できます。