Public
Edited
Jan 5, 2024
Insert cell
Insert cell
Insert cell
// iddb: read single object, write single object
writes = {
sqlitedb.exec([
"DROP TABLE IF EXISTS keyval;",
"CREATE TABLE keyval (k PRIMARY KEY, v);",
"VACUUM;"
]);
const stmt = sqlitedb.prepare("INSERT OR REPLACE INTO keyval VALUES (?, ?)");

const trials = [1, 10, 100, 1_000, 10_000, 100_000];
const idbNoTx = [];
const idbTx = [];
const idbPaged = [];
const idbPagedPTreap = [];
const sqliteMem = [];
let a = 0;
const comp = (l, r) => l.a - r.a;
for (const numItems of trials) {
if (numItems < 10_000) {
idbNoTx.push({
size: numItems,
time: await bench(async () => {
if (numItems >= 10_000) {
return 0;
}
for (let i = 0; i < numItems; ++i) {
const tx = kvstore.transaction("keyval", "readwrite", {
durability: "relaxed"
});
await tx.store.put({ a: ++a }, makeKey("idbNoTx", numItems, i));
await tx.done;
}
})
});
}
if (numItems < 10_000) {
idbTx.push({
size: numItems,
time: await bench(async () => {
const tx = kvstore.transaction("keyval", "readwrite", {
durability: "relaxed"
});
const promises = [];
for (let i = 0; i < numItems; ++i) {
promises.push(
tx.store.put({ a: ++a }, makeKey("idbTx", numItems, i))
);
}
promises.push(tx.done);
await Promise.all(promises);
})
});
}
idbPaged.push({
size: numItems,
time: await bench(async () => {
const page = [];
for (let i = 0; i < numItems; ++i) {
page.push({ a: ++a });
}
const tx = kvstore.transaction("keyval", "readwrite", {
durability: "relaxed"
});
await tx.store.put(page, makeKey("paged", numItems));
await tx.done;
})
});

idbPagedPTreap.push({
size: numItems,
time: await bench(async () => {
let page = new PersistentTreap(comp);
for (let i = 0; i < numItems; ++i) {
page = page.add({ a: ++a });
}
const tx = kvstore.transaction("keyval", "readwrite", {
durability: "relaxed"
});
await tx.store.put(page._root, makeKey("ppTreap", numItems));
await tx.done;
})
});

sqliteMem.push({
size: numItems,
time: await bench(() => {
sqlitedb.transaction(() => {
for (let i = 0; i < numItems; ++i) {
stmt.bind(1, makeKey("sqlite", numItems, i));
stmt.bind(2, ++a).stepReset();
}
});
})
});
}

return {
idbNoTx,
idbTx,
idbPaged,
idbPagedPTreap,
sqliteMem
};
}
Insert cell
Insert cell
Insert cell
Insert cell
// read what we wrote in the prior bench
// 1. read pages
// 2. read key ranges that fall within the page
// 3. individual cursoring?
reads = {
writes;
const trials = [1, 10, 100, 1_000, 10_000, 100_000];
const sqliteMem = [];
const idbPaged = [];
const idbPagedPTreap = [];
const idbKeyRange = []; // read the single
let a = 0;

const stmt = sqlitedb.prepare("SELECT * FROM keyval WHERE k LIKE ?"); // TODO: change our primary keys to be ints for a more fair comparison `WHERE k > ? AND k < ?`

for (const numItems of trials) {
sqliteMem.push({
size: numItems,
time: await bench(async () => {
const items = [];
stmt.bind(1, `sqlite-${numItems}-%`);
while (stmt.step()) {
items.push(stmt.get([]));
}
stmt.reset();
})
});
idbPaged.push({
size: numItems,
time: await bench(async () => {
const ret = await kvstore.get("keyval", makeKey("paged", numItems));
if (ret.length < numItems) {
throw new Error("failed to retrieve");
}
})
});
idbPagedPTreap.push({
size: numItems,
time: await bench(async () => {
const ret = await kvstore.get("keyval", makeKey("ppTreap", numItems));
if (!ret) {
throw new Error("failed to retrieve"); // todo validate length outside bench
}
})
});

// TODO: test loading a single row that is within a page.
// Presumably that is slow-ish since we incure page load time for just a single item?
}

return {
sqliteMem,
idbPaged,
idbPagedPTreap,
idbKeyRange
};
}
Insert cell
{
return Plot.plot({
marks: [
Object.entries(reads).map(([k, d]) =>
Plot.line(
d.map((n) => ({ ...n, group: k })),
{ x: "size", y: "time", stroke: "group", tip: true }
)
)
],
color: {
domain: ["idbPaged", "idbPagedPTreap", "sqliteMem"],
legend: true
},
width: 1000,
y: {
type: "pow",
exponent: 1 / 2,
domain: [
0,
Math.max(
...[].concat(
...Object.values(writes).map((x) => x.map((x) => x.time))
)
)
],
grid: true
}
});
}
Insert cell
Insert cell
idb = import("https://cdn.jsdelivr.net/npm/idb@8/+esm");
Insert cell
Insert cell
Insert cell
Insert cell
makeKey = (type, numItems, item) => `${type}-${numItems}-${item ?? 0}`
Insert cell
kvstore = {
console.log("opening");
const kvstore = await idb.openDB("test", 1, {
upgrade(db) {
db.createObjectStore("keyval");
},
blocking(currentVersion, blockedVersion, event) {
const db = event.target.result;
db.close();
},
blocked() {
console.log(arguments);
}
});
console.log("opened");
return kvstore;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more