Indexed DBでmulti keyを試す
目的
これが可能なのか確かめたい
調査
MDN
sortingでは、配列の中身をみてくれる
keyの一意判定はどうするんだろう
中身を見てくれるようだ
MDNを見るより、W3Cの仕様書を読むのが正確だった 試してみる
code:multi-key-test.ts
/// <reference no-default-lib="true />
/// <reference lib="esnext" />
/// <reference lib="dom" />
import { openDB, DBSchema } from "../idb/mod.ts";
import { getLinks } from "../scrapbox-userscript-std/rest.ts";
import { sumOf } from "../deno_std%2Fcollections/mod.ts";
interface LinkSchema extends DBSchema {
links: {
value: {
id: string;
updated: number;
image?: string;
links: string[];
};
};
}
const db = await openDB<LinkSchema>("multi-key-test", 1, {
upgrade: (db) => {
db.createObjectStore("links", { keyPath: "path" });
}
});
/*
const laps: number[] = [];
let followingId: string | undefined;
const query = IDBKeyRange.bound(project, "", [project, []]); while (true) {
const result = await getLinks(project, { followingId });
if (!result.ok) break;
const pages = result.value.pages;
const now = Date.now();
const tx = db.transaction("links", "readwrite");
await Promise.all(
result.value.pages.map(
({ title, ...page }) => tx.store.put({
...page,
})
)
);
await tx.done;
laps.push(Date.now() - now);
if (!result.value.followingId) {
followingId = undefined;
break;
}
followingId = result.value.followingId;
}
await tx.done;
}
console.debug(Opened ${laps.length} transactions in ${sumOf(laps, (v) => v)}ms, ave: ${(sumOf(laps, (v) => v) / laps.length).toFixed(1)}ms, max: ${Math.max(...laps)}ms, min: ${Math.min(...laps)}ms.);
*/
const laps: number[] = [];
let followingId: string | undefined;
const updatedLinks = new Set<string>();
const query = IDBKeyRange.bound(project, "", [project, []]); const LinksToDelete = new Set(
(await db.getAllKeys("links", query)).map((,title) => title) );
while (true) {
const result = await getLinks(project, { followingId });
if (!result.ok) break;
const pages = result.value.pages;
const now = Date.now();
const tx = db.transaction("links", "readwrite");
await Promise.all(
result.value.pages.map(
async ({ title, ...page }) => {
if (prev) LinksToDelete.delete(title);
if (prev?.updated >= page.updated) return;
updatedLinks.add(title);
await tx.store.put({
...page,
});
}
)
);
await tx.done;
laps.push(Date.now() - now);
if (!result.value.followingId) {
followingId = undefined;
break;
}
followingId = result.value.followingId;
}
const tx = db.transaction("links", "readwrite");
await Promise.all(
)
);
await tx.done;
console.debug(Update ${updatedLinks.size} links and Delete ${LinksToDelete.size} links in "${project}");
}
console.debug(Opened ${laps.length} transactions in ${sumOf(laps, (v) => v)}ms, ave: ${(sumOf(laps, (v) => v) / laps.length).toFixed(1)}ms, max: ${Math.max(...laps)}ms, min: ${Math.min(...laps)}ms.);
/*
const laps: number[] = [];
let followingId: string | undefined;
while (true) {
const result = await getLinks(project, { followingId });
if (!result.ok) break;
const pages = result.value.pages;
const now = Date.now();
const tx = db.transaction("links", "readwrite");
await Promise.all(
result.value.pages.map(
({ title, ...page }) => tx.store.put({
...page,
})
)
);
await tx.done;
laps.push(Date.now() - now);
if (!result.value.followingId) {
followingId = undefined;
break;
}
followingId = result.value.followingId;
}
}
console.debug(Opened ${laps.length} transactions in ${sumOf(laps, (v) => v)}ms, ave: ${(sumOf(laps, (v) => v) / laps.length).toFixed(1)}ms, max: ${Math.max(...laps)}ms, min: ${Math.min(...laps)}ms.);
*/
うまく動いていそう
keyのfiltering
特定のprojectのリンクを取得する
第2要素に、全ての文字列をカバーできるような値を入れればいい
DB挿入にかかった時間
逐次挿入
Opened 42 transactions in 2772ms, ave: 66.0ms, max: 108ms, min: 23ms.
Opened 42 transactions in 2785ms, ave: 66.3ms, max: 107ms, min: 19ms.
一括挿入
Opend a transaction in 2568ms.
一括削除 then 一括挿入
Opend a transaction in 2316ms.
逐次挿入 then 一括削除
sample1
Update 1 links and Delete 0 links in "takker"
Update 2 links and Delete 0 links in "villagepump"
Opened 42 transactions in 2157ms, ave: 51.4ms, max: 76ms, min: 24ms.
同じrecordを上書きしない分だけ、若干速くなっている?
sample2
Update 12 links and Delete 0 links in "takker"
Update 74 links and Delete 1 links in "villagepump"
Opened 42 transactions in 3359ms, ave: 80.0ms, max: 141ms, min: 40ms.
いや、そうでもないか
0.2秒ほど遅くなった。
逐次挿入のほうが全体で遅いとはいえ、途中結果をすぐに返せることを考えると、逐次挿入のほうがユーザーとしてははやく感じそう
IDBCursorを1回だけ回してDBの操作を完了させたいなら、全データをfetchしてから、cursorを回して更新 or 削除し、最後に新規追加したほうがいい? fetchし終わるのを待つより、DBを何度も読み書きするほうが早く済むなら、逐次挿入でもいいかも
結果
shokaiからtakkerまでを含めるなら、これで問題ない
$ let keyRange = IDBKeyRange.bound("shokai", "", ["takker", []], true, true); 特定の複数のprojectのデータを一度に取得する方法はない
IDBKeyRange.bound(["shokai", ""], ["shokai", []], true, true);などをprojectの数だけ繰り返し実行するしかない
[project, title]ではなく、id(page id)をキーにして、project,title,updatedをindexにもたせるほうが検索効率いいかも
速度面と耐障害性面
前者は途中でエラーが出ても、そのときまでにfetchしたデータを書き込める
各種UserScriptへの更新配信も逐次行える
後者は一度にDBに書き込むため、DBへの総書き込み時間は減るはず
なお、v0.1.5のschemeだと、すべてのリンクデータを取得してからでないと計算できない項目があるので、後者しか選べない
本ページの変更が可能であれば、逐次格納できるようになる
各レコードの行が独立する