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

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

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

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

SQLite のロゴ。

Web Assembly ベースの SQLite

WebAssembly(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 ベースのバリエーション。スレッド間の通信をユーザーから完全に隠します。
  • Origin Private File System(OPFS)など、利用可能な JavaScript API を使用した永続的なクライアント側ストレージをサポートします。

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 が元のプライベート ファイル システムに書き込む内容を調べる準備が整います。

デモアプリのオリジン プライベート ファイル システムの構造を示す OPFS Explorer Chrome 拡張機能。

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

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

ヘルプとフィードバック

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