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

SQLite を使用して、ウェブ上で必要なすべてのストレージのパフォーマンスを効率的に処理します。

SQLite について

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

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

SQLite のロゴ。

ウェブ アセンブリに基づく SQLite

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

  • 使用に関して可能な限り C API に近い低レベルの sqlite3 API をバインドします。
  • sql.jsNode.js スタイルの実装によく似ており、低レベル API に直接指示する、高レベルのオブジェクト指向 API。この API は、低レベル API と同じスレッドから使用する必要があります。
  • ワーカー メッセージを介して以前の API と通信するワーカーベースの API。これはメインスレッドで使用するためのものです。メインスレッドでは下位レベルの API がワーカー スレッドにインストールされ、ワーカー メッセージを介して下位レベルの API とやり取りします。
  • スレッド間通信の要素を完全に非表示にする Worker API の Promise ベースのバリアントです。
  • Origin Private File System(OPFS)などの利用可能な JavaScript API を使用したクライアント側の永続ストレージをサポートします。

Origin Private File System の永続性バックエンドで SQLite Wasm を使用する

npm からのライブラリのインストール

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

npm install @sqlite.org/sqlite-wasm

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

Origin Private File System(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 ファイルを提供しないため、これを使用してビルドするアプリにはウェブサーバーが必要です。また、そのサーバーは、ファイルを提供する際のレスポンスに次のヘッダーを含める必要があります。

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

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

DevTools を使用してトラフィックを検査すると、次の情報を確認できます。

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

速度テスト

SQLite チームは、WebAssembly 実装で、非推奨の Web SQL と比較したベンチマークをいくつか実行しました。これらのベンチマークは、SQLite Wasm が一般に Web SQL とほぼ同じであることを示しています。少し遅くなるときもあれば 速いときもあります詳細については、結果ページをご覧ください。

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

前述のように、Origin Private File System の永続性バックエンドを使用する SQLite Wasm は、ワーカー コンテキストから実行する必要があります。幸いなことに、ライブラリではこの処理が自動的に行われ、メインスレッドから直接使用できます。

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 のオリジン プライベート ファイル システムの出力をデバッグするには、Chrome 拡張機能の OPFS Explorer を使用します。

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 のサイトで入手できます。

謝辞

ヒーロー画像(作成者: Tobias Fischer、出典: Unsplash