perf(y-indexeddb): improve boost and loading time (#1879)
This commit is contained in:
@@ -5662,6 +5662,7 @@ __metadata:
|
|||||||
idb: ^7.1.1
|
idb: ^7.1.1
|
||||||
vite: ^4.2.1
|
vite: ^4.2.1
|
||||||
vite-plugin-dts: ^2.2.0
|
vite-plugin-dts: ^2.2.0
|
||||||
|
y-indexeddb: ^9.0.10
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
yjs: ^13.5.51
|
yjs: ^13.5.51
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
@@ -12383,7 +12384,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"lib0@npm:^0.2.42, lib0@npm:^0.2.68, lib0@npm:^0.2.72, lib0@npm:^0.2.73":
|
"lib0@npm:^0.2.35, lib0@npm:^0.2.42, lib0@npm:^0.2.68, lib0@npm:^0.2.72, lib0@npm:^0.2.73":
|
||||||
version: 0.2.73
|
version: 0.2.73
|
||||||
resolution: "lib0@npm:0.2.73"
|
resolution: "lib0@npm:0.2.73"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17474,6 +17475,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"y-indexeddb@npm:^9.0.10":
|
||||||
|
version: 9.0.10
|
||||||
|
resolution: "y-indexeddb@npm:9.0.10"
|
||||||
|
dependencies:
|
||||||
|
lib0: ^0.2.35
|
||||||
|
peerDependencies:
|
||||||
|
yjs: ^13.0.0
|
||||||
|
checksum: 6a57825b599cdf77da7c9857b1acc0f782492fc41531618bd7392bdfbcf11c783ff1a30b82ae080b050a5ebafd54754a978de7a6ac42144ec59eb1fbdebd090b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"y-protocols@npm:^1.0.5":
|
"y-protocols@npm:^1.0.5":
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
resolution: "y-protocols@npm:1.0.5"
|
resolution: "y-protocols@npm:1.0.5"
|
||||||
|
|||||||
103
packages/y-indexeddb/benchmark/index.ts
Normal file
103
packages/y-indexeddb/benchmark/index.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env ts-node-esm
|
||||||
|
import 'fake-indexeddb/auto';
|
||||||
|
|
||||||
|
const map = new Map();
|
||||||
|
const localStorage = {
|
||||||
|
getItem: (key: string) => map.get(key),
|
||||||
|
setItem: (key: string, value: string) => map.set(key, value),
|
||||||
|
clear: () => map.clear(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
globalThis.localStorage = localStorage;
|
||||||
|
|
||||||
|
import { Workspace } from '@blocksuite/store';
|
||||||
|
import { IndexeddbPersistence } from 'y-indexeddb';
|
||||||
|
|
||||||
|
const Y = Workspace.Y;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
|
import { createIndexedDBProvider } from '../src/index.js';
|
||||||
|
|
||||||
|
async function yjs_create_persistence(n = 1e3) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const yDoc = new Y.Doc();
|
||||||
|
const persistence = new IndexeddbPersistence('test', yDoc);
|
||||||
|
await persistence.whenSynced;
|
||||||
|
persistence.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function yjs_single_persistence(n = 1e5) {
|
||||||
|
const yDoc = new Y.Doc();
|
||||||
|
const map = yDoc.getMap();
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
map.set(`${i}`, i);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const persistence = new IndexeddbPersistence('test', yDoc);
|
||||||
|
await persistence.whenSynced;
|
||||||
|
persistence.destroy();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const persistence = new IndexeddbPersistence('test', yDoc);
|
||||||
|
await persistence.whenSynced;
|
||||||
|
persistence.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toeverything_create_provider(n = 1e3) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const yDoc = new Y.Doc();
|
||||||
|
const provider = createIndexedDBProvider('test', yDoc);
|
||||||
|
provider.connect();
|
||||||
|
await provider.whenSynced;
|
||||||
|
provider.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function toeverything_single_persistence(n = 1e5) {
|
||||||
|
const yDoc = new Y.Doc();
|
||||||
|
const map = yDoc.getMap();
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
map.set(`${i}`, i);
|
||||||
|
}
|
||||||
|
const provider = createIndexedDBProvider('test', yDoc, 'test');
|
||||||
|
provider.connect();
|
||||||
|
await provider.whenSynced;
|
||||||
|
provider.disconnect();
|
||||||
|
provider.connect();
|
||||||
|
await provider.whenSynced;
|
||||||
|
provider.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('create many persistence');
|
||||||
|
performance.mark('start');
|
||||||
|
await yjs_create_persistence();
|
||||||
|
performance.mark('end');
|
||||||
|
performance.measure('yjs', 'start', 'end');
|
||||||
|
indexedDB.deleteDatabase('test');
|
||||||
|
performance.mark('start');
|
||||||
|
await toeverything_create_provider();
|
||||||
|
performance.mark('end');
|
||||||
|
performance.measure('toeverything', 'start', 'end');
|
||||||
|
console.log(performance.getEntriesByType('measure'));
|
||||||
|
indexedDB.deleteDatabase('test');
|
||||||
|
performance.clearMarks();
|
||||||
|
performance.clearMeasures();
|
||||||
|
localStorage.clear();
|
||||||
|
|
||||||
|
console.log('single persistence with huge updates');
|
||||||
|
performance.mark('start');
|
||||||
|
await yjs_single_persistence();
|
||||||
|
performance.mark('end');
|
||||||
|
performance.measure('yjs', 'start', 'end');
|
||||||
|
indexedDB.deleteDatabase('test');
|
||||||
|
performance.mark('start');
|
||||||
|
await toeverything_single_persistence();
|
||||||
|
performance.mark('end');
|
||||||
|
performance.measure('toeverything', 'start', 'end');
|
||||||
|
console.log(performance.getEntriesByType('measure'));
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then();
|
||||||
@@ -28,7 +28,8 @@
|
|||||||
"@blocksuite/blocks": "0.0.0-20230411141436-ec6b051d-nightly",
|
"@blocksuite/blocks": "0.0.0-20230411141436-ec6b051d-nightly",
|
||||||
"@blocksuite/store": "0.0.0-20230411141436-ec6b051d-nightly",
|
"@blocksuite/store": "0.0.0-20230411141436-ec6b051d-nightly",
|
||||||
"vite": "^4.2.1",
|
"vite": "^4.2.1",
|
||||||
"vite-plugin-dts": "^2.2.0"
|
"vite-plugin-dts": "^2.2.0",
|
||||||
|
"y-indexeddb": "^9.0.10"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"yjs": "^13.5.51"
|
"yjs": "^13.5.51"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'fake-indexeddb/auto';
|
|||||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||||
import { assertExists, uuidv4, Workspace } from '@blocksuite/store';
|
import { assertExists, uuidv4, Workspace } from '@blocksuite/store';
|
||||||
import { openDB } from 'idb';
|
import { openDB } from 'idb';
|
||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs';
|
import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs';
|
||||||
|
|
||||||
import type { WorkspacePersist } from '../index';
|
import type { WorkspacePersist } from '../index';
|
||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
} from '../index';
|
} from '../index';
|
||||||
|
|
||||||
async function getUpdates(id: string): Promise<ArrayBuffer[]> {
|
async function getUpdates(id: string): Promise<ArrayBuffer[]> {
|
||||||
const db = await openDB('affine-local', dbVersion);
|
const db = await openDB(rootDBName, dbVersion);
|
||||||
const store = await db
|
const store = await db
|
||||||
.transaction('workspace', 'readonly')
|
.transaction('workspace', 'readonly')
|
||||||
.objectStore('workspace');
|
.objectStore('workspace');
|
||||||
@@ -32,6 +32,7 @@ async function getUpdates(id: string): Promise<ArrayBuffer[]> {
|
|||||||
|
|
||||||
let id: string;
|
let id: string;
|
||||||
let workspace: Workspace;
|
let workspace: Workspace;
|
||||||
|
const rootDBName = 'affine-local';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
id = uuidv4();
|
id = uuidv4();
|
||||||
@@ -42,12 +43,16 @@ beforeEach(() => {
|
|||||||
workspace.register(AffineSchemas).register(__unstableSchemas);
|
workspace.register(AffineSchemas).register(__unstableSchemas);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
indexedDB.deleteDatabase('affine-local');
|
||||||
|
});
|
||||||
|
|
||||||
describe('indexeddb provider', () => {
|
describe('indexeddb provider', () => {
|
||||||
test('connect', async () => {
|
test('connect', async () => {
|
||||||
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
||||||
provider.connect();
|
provider.connect();
|
||||||
await provider.whenSynced;
|
await provider.whenSynced;
|
||||||
const db = await openDB('affine-local', dbVersion);
|
const db = await openDB(rootDBName, dbVersion);
|
||||||
{
|
{
|
||||||
const store = await db
|
const store = await db
|
||||||
.transaction('workspace', 'readonly')
|
.transaction('workspace', 'readonly')
|
||||||
@@ -89,7 +94,8 @@ describe('indexeddb provider', () => {
|
|||||||
.register(__unstableSchemas);
|
.register(__unstableSchemas);
|
||||||
const provider2 = createIndexedDBProvider(
|
const provider2 = createIndexedDBProvider(
|
||||||
secondWorkspace.id,
|
secondWorkspace.id,
|
||||||
secondWorkspace.doc
|
secondWorkspace.doc,
|
||||||
|
rootDBName
|
||||||
);
|
);
|
||||||
provider2.connect();
|
provider2.connect();
|
||||||
await provider2.whenSynced;
|
await provider2.whenSynced;
|
||||||
@@ -99,7 +105,11 @@ describe('indexeddb provider', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('disconnect suddenly', async () => {
|
test('disconnect suddenly', async () => {
|
||||||
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
const provider = createIndexedDBProvider(
|
||||||
|
workspace.id,
|
||||||
|
workspace.doc,
|
||||||
|
rootDBName
|
||||||
|
);
|
||||||
const fn = vi.fn();
|
const fn = vi.fn();
|
||||||
provider.connect();
|
provider.connect();
|
||||||
provider.disconnect();
|
provider.disconnect();
|
||||||
@@ -109,10 +119,14 @@ describe('indexeddb provider', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('connect and disconnect', async () => {
|
test('connect and disconnect', async () => {
|
||||||
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
const provider = createIndexedDBProvider(
|
||||||
|
workspace.id,
|
||||||
|
workspace.doc,
|
||||||
|
rootDBName
|
||||||
|
);
|
||||||
provider.connect();
|
provider.connect();
|
||||||
const p1 = provider.whenSynced;
|
const p1 = provider.whenSynced;
|
||||||
await provider.whenSynced;
|
await p1;
|
||||||
provider.disconnect();
|
provider.disconnect();
|
||||||
{
|
{
|
||||||
const page = workspace.createPage('page0');
|
const page = workspace.createPage('page0');
|
||||||
@@ -126,7 +140,7 @@ describe('indexeddb provider', () => {
|
|||||||
}
|
}
|
||||||
provider.connect();
|
provider.connect();
|
||||||
const p2 = provider.whenSynced;
|
const p2 = provider.whenSynced;
|
||||||
await provider.whenSynced;
|
await p2;
|
||||||
{
|
{
|
||||||
const updates = await getUpdates(workspace.id);
|
const updates = await getUpdates(workspace.id);
|
||||||
expect(updates).not.toEqual([]);
|
expect(updates).not.toEqual([]);
|
||||||
@@ -137,7 +151,11 @@ describe('indexeddb provider', () => {
|
|||||||
|
|
||||||
test('merge', async () => {
|
test('merge', async () => {
|
||||||
setMergeCount(5);
|
setMergeCount(5);
|
||||||
const provider = createIndexedDBProvider(workspace.id, workspace.doc);
|
const provider = createIndexedDBProvider(
|
||||||
|
workspace.id,
|
||||||
|
workspace.doc,
|
||||||
|
rootDBName
|
||||||
|
);
|
||||||
provider.connect();
|
provider.connect();
|
||||||
{
|
{
|
||||||
const page = workspace.createPage('page0');
|
const page = workspace.createPage('page0');
|
||||||
@@ -153,6 +171,31 @@ describe('indexeddb provider', () => {
|
|||||||
expect(updates.length).lessThanOrEqual(5);
|
expect(updates.length).lessThanOrEqual(5);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("data won't be lost", async () => {
|
||||||
|
const id = uuidv4();
|
||||||
|
const doc = new Workspace.Y.Doc();
|
||||||
|
const map = doc.getMap('map');
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
map.set(`${i}`, i);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const provider = createIndexedDBProvider(id, doc, rootDBName);
|
||||||
|
provider.connect();
|
||||||
|
await provider.whenSynced;
|
||||||
|
provider.disconnect();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const newDoc = new Workspace.Y.Doc();
|
||||||
|
const provider = createIndexedDBProvider(id, newDoc, rootDBName);
|
||||||
|
provider.connect();
|
||||||
|
await provider.whenSynced;
|
||||||
|
provider.disconnect();
|
||||||
|
newDoc.getMap('map').forEach((value, key) => {
|
||||||
|
expect(value).toBe(parseInt(key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('milestone', () => {
|
describe('milestone', () => {
|
||||||
|
|||||||
@@ -299,8 +299,16 @@ export const createIndexedDBProvider = (
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const updates = data.updates.map(({ update }) => update);
|
const updates = data.updates.map(({ update }) => update);
|
||||||
const update = mergeUpdates(updates);
|
const fakeDoc = new Doc();
|
||||||
const newUpdate = diffUpdate(encodeStateAsUpdate(doc), update);
|
fakeDoc.transact(() => {
|
||||||
|
updates.forEach(update => {
|
||||||
|
applyUpdate(fakeDoc, update);
|
||||||
|
});
|
||||||
|
}, indexeddbOrigin);
|
||||||
|
const newUpdate = diffUpdate(
|
||||||
|
encodeStateAsUpdate(doc),
|
||||||
|
encodeStateAsUpdate(fakeDoc)
|
||||||
|
);
|
||||||
await store.put({
|
await store.put({
|
||||||
...data,
|
...data,
|
||||||
updates: [
|
updates: [
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { ok } from 'node:assert';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
import type { PageMeta } from '@blocksuite/store';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import type { Page } from '@playwright/test';
|
import type { Page } from '@playwright/test';
|
||||||
@@ -7,6 +10,13 @@ const user1 = require('@affine-test/fixtures/built-in-user1.json');
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const user2 = require('@affine-test/fixtures/built-in-user2.json');
|
const user2 = require('@affine-test/fixtures/built-in-user2.json');
|
||||||
|
|
||||||
|
export const rootDir = resolve(__dirname, '..', '..');
|
||||||
|
// assert that the rootDir is the root of the project
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
ok(require(resolve(rootDir, 'package.json')).name.toLowerCase() === 'affine');
|
||||||
|
|
||||||
|
export const testResultDir = resolve(rootDir, 'test-results');
|
||||||
|
|
||||||
export async function getBuiltInUser() {
|
export async function getBuiltInUser() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
fetch('http://localhost:3000/api/user/token', {
|
fetch('http://localhost:3000/api/user/token', {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
import { expect } from '@playwright/test';
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
import { openHomePage } from '../libs/load-page';
|
import { openHomePage } from '../libs/load-page';
|
||||||
import { waitMarkdownImported } from '../libs/page-logic';
|
import { waitMarkdownImported } from '../libs/page-logic';
|
||||||
import { test } from '../libs/playwright';
|
import { test } from '../libs/playwright';
|
||||||
import { clickSideBarSettingButton } from '../libs/sidebar';
|
import { clickSideBarSettingButton } from '../libs/sidebar';
|
||||||
|
import { testResultDir } from '../libs/utils';
|
||||||
|
|
||||||
test.describe('Local first setting page', () => {
|
test.describe('Local first setting page', () => {
|
||||||
test('Should highlight the setting page menu when selected', async ({
|
test('Should highlight the setting page menu when selected', async ({
|
||||||
@@ -14,13 +17,20 @@ test.describe('Local first setting page', () => {
|
|||||||
const element = await page.getByTestId(
|
const element = await page.getByTestId(
|
||||||
'slider-bar-workspace-setting-button'
|
'slider-bar-workspace-setting-button'
|
||||||
);
|
);
|
||||||
const prevColor = await element.evaluate(
|
const prev = await element.screenshot({
|
||||||
element => window.getComputedStyle(element).color
|
path: resolve(
|
||||||
);
|
testResultDir,
|
||||||
|
'slider-bar-workspace-setting-button-prev.png'
|
||||||
|
),
|
||||||
|
});
|
||||||
await clickSideBarSettingButton(page);
|
await clickSideBarSettingButton(page);
|
||||||
const currentColor = await element.evaluate(
|
await page.waitForTimeout(50);
|
||||||
element => window.getComputedStyle(element).color
|
const after = await element.screenshot({
|
||||||
);
|
path: resolve(
|
||||||
expect(prevColor).not.toBe(currentColor);
|
testResultDir,
|
||||||
|
'slider-bar-workspace-setting-button-after.png'
|
||||||
|
),
|
||||||
|
});
|
||||||
|
expect(prev).not.toEqual(after);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
14
yarn.lock
14
yarn.lock
@@ -6219,6 +6219,7 @@ __metadata:
|
|||||||
idb: ^7.1.1
|
idb: ^7.1.1
|
||||||
vite: ^4.2.1
|
vite: ^4.2.1
|
||||||
vite-plugin-dts: ^2.2.0
|
vite-plugin-dts: ^2.2.0
|
||||||
|
y-indexeddb: ^9.0.10
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
yjs: ^13.5.51
|
yjs: ^13.5.51
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
@@ -14229,7 +14230,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"lib0@npm:^0.2.42, lib0@npm:^0.2.68, lib0@npm:^0.2.72, lib0@npm:^0.2.73":
|
"lib0@npm:^0.2.35, lib0@npm:^0.2.42, lib0@npm:^0.2.68, lib0@npm:^0.2.72, lib0@npm:^0.2.73":
|
||||||
version: 0.2.73
|
version: 0.2.73
|
||||||
resolution: "lib0@npm:0.2.73"
|
resolution: "lib0@npm:0.2.73"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -20163,6 +20164,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"y-indexeddb@npm:^9.0.10":
|
||||||
|
version: 9.0.10
|
||||||
|
resolution: "y-indexeddb@npm:9.0.10"
|
||||||
|
dependencies:
|
||||||
|
lib0: ^0.2.35
|
||||||
|
peerDependencies:
|
||||||
|
yjs: ^13.0.0
|
||||||
|
checksum: 6a57825b599cdf77da7c9857b1acc0f782492fc41531618bd7392bdfbcf11c783ff1a30b82ae080b050a5ebafd54754a978de7a6ac42144ec59eb1fbdebd090b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"y-protocols@npm:^1.0.5":
|
"y-protocols@npm:^1.0.5":
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
resolution: "y-protocols@npm:1.0.5"
|
resolution: "y-protocols@npm:1.0.5"
|
||||||
|
|||||||
Reference in New Issue
Block a user