IndexedDB のバージョンアップと複数タブ問題
IndexedDB には、標準でバージョン管理機能が組み込まれています。
そのため、データベースの構造に変更を加える際には、「バージョンアップ」という特別な処理が発生します。
この記事では、標準 IndexedDB API を使ううえで必ず理解しておきたい
- バージョンアップとは何か
- バージョンアップで発生する問題
onblockedイベントonversionchangeイベント
の 4 点を中心に整理します。
バージョンアップとは何か
IndexedDB におけるバージョンアップとは、データベースの構造(スキーマ)を変更することを意味します。
ここでいうデータベースの構造を変更するとは、次のようなものを指します。
- Object Store(テーブル)の追加・削除
- Index(インデックス)の追加・削除
- keyPath の変更
- autoIncrement の設定変更
これらはすべて indexedDB.open() のパラメータのバージョン番号を上げないと実行できません。
具体例:従来の v1 のデータベースに、新たに Object Store を追加する。
const request = indexedDB.open("mydb", 2);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore("users", { keyPath: "id" });
};
具体例:従来の v1 のデータベースに、新たに Index を追加する。
const request = indexedDB.open("mydb", 2);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const store = event.target.transaction.objectStore("users");
store.createIndex("by_name", "name");
};
👉 indexedDB.open() のバージョンを v1 → v2 に変更してオープンした後、onupgradeneeded イベント内で追加する必要があります。
尚、保存するオブジェクトのプロパティが増えるだけなら、構造は変わらないためバージョンアップは不要です。
バージョンアップで発生する問題
大前提として、IndexedDB は、複数のタブからのアクセスが可能です。その為、同一ドメインである場合、同一のデータベースに同時にアクセスすることになります。
そこで、既にデータベースをオープンしているタグとは異なるタブで、該当データベースの構造変更を行おうとした場合、問題(コンフリクト)が発生します。
IndexedDB のデータベースのバージョンアップは IDBOpenDBRequest の onupgradeneeded イベント内で行う必要があります。そして、この処理は排他的(exclusive) です。
つまり、複数のタブが同時に構造変更を行うことはできない。
さらに、古いバージョンのデータベースを開いているタブがあると、新しいバージョンへのアップグレードは進められないという制約があります。
注)「通常の読み書き」は、この排他的な影響を受けません。複数タブでの同時アクセスが可能です。
コンフリクトの典型例
- タブ A:データベースを v1 を開いている
- タブ B:データベースを v2 で open()(構造変更が必要)
👉 タブ A が古いデータベースを開いたままなので、タブ B のアップグレードが進まない。
このときに発生するのが IDBOpenDBRequest の onblocked イベント と IDBDatabase の onversionchange イベント です。
IndexedDBには、コンフリクトに対応するバージョン管理機能が標準で用意されています。
これらに適切な処理を記述することで、データベースの構成変更時に柔軟な対応を行うことが可能です。
onblocked イベント
データベースの構造変更を行おうとする側が、バージョンを変更して、indexedDB.open() を行った際に、既に古いバージョンでのオープンが別のところで行われていた場合、発生します。
const request = indexedDB.open("mydb", 2);
request.onblocked = () => {
console.log("他のタブが古いDBを開いているため、アップグレードできません");
};
👉 メッセージ表示後、他のタブで開かれているデータベースが、close() されるまで、処理が中断し、待たされます。
onblocked イベントは、「エラー」ではありません。
onblocked イベントが発生した場合、open() の処理は、onsuccess にも onerror にもならず、待ち状態のまま停止します。
他のタブがデータベースを閉じれば、open() はそのまま続行されるため、“待ち状態の通知” にすぎないのです。
onversionchange イベント
データベースをオープンしている状態で、別のところからデータベースの構造変更を行おうとした際に、IDBDatabase の通知として発生します。
これは、IndexedDB が標準で持っているタブ間通知の仕組みです。
db.onversionchange = () => {
console.log("他のタブが新しいバージョンでDBを開こうとしています");
db.close(); // 道を開ける
};
古いバージョンのデータベースを開いたままの為、他タブでの構造変更を妨害しています。この場合、データベースを閉じれば、アップグレードが進みます。
データベースの構造変更を行いたい側からは、「閉じてください」と onversionchange イベントにて通知を行いますが、ブラウザが強制的にデータベースを閉じることはありません。db.close() を呼ばない限り、他タブのアップグレードは進みません。
その為、データベースの構造変更を円滑に進める為には、onversionchange イベント内で、db.close() を呼ぶ処理が必要になります。
まとめ
IndexedDB バージョンアップの流れ
例:タブBでデータベースの構造変更を実施
┌───────────────────────────────┐
│ タブA │ タブB │
└───────────────────────────────┘
① open(v1)
② onversionchange ←───── ① open(v2)
(他タブを妨害している)
│
└─→db.close() ② onblocked
│ (自分が待たされる)
│
└──────→ ③ onupgradeneeded
(アップグレードが続行される)
IndexedDB のバージョンアップと複数タブ問題は、標準 API を使ううえで避けて通れない重要な概念です。
| 概念 | 意味 | 誰が待たされている? |
|---|---|---|
| onblocked | 自分の構造変更が進められない | 自分 |
| onversionchange | 他タブの構造変更を妨害している | 他タブ |
この 2 つを理解すると、 IndexedDB のバージョン管理と複数タブ間の問題の挙動が自然に理解でき、より安全で堅牢なアプリ設計ができるようになります。