mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(nbstore): optimize search performance (#11778)
now we can debug indexeddb performance by devtool 
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
type SearchOptions,
|
||||
type SearchResult,
|
||||
} from '../../../storage';
|
||||
import { shallowEqual } from '../../../utils/shallow-equal';
|
||||
import type { DocStorageSchema } from '../schema';
|
||||
import { highlighter } from './highlighter';
|
||||
import {
|
||||
@@ -33,6 +34,8 @@ export type DataStructROTransaction = IDBPTransaction<
|
||||
'readonly' | 'readwrite'
|
||||
>;
|
||||
|
||||
let debugMarkCount = 0;
|
||||
|
||||
export class DataStruct {
|
||||
database: IDBPDatabase<DocStorageSchema> = null as any;
|
||||
invertedIndex = new Map<string, Map<string, InvertedIndex>>();
|
||||
@@ -82,6 +85,7 @@ export class DataStruct {
|
||||
table: keyof IndexerSchema,
|
||||
document: IndexerDocument
|
||||
) {
|
||||
using _ = await this.measure(`update`);
|
||||
const existsNid = await trx
|
||||
.objectStore('indexerRecords')
|
||||
.index('id')
|
||||
@@ -119,6 +123,7 @@ export class DataStruct {
|
||||
if (!iidx) {
|
||||
continue;
|
||||
}
|
||||
using _ = await this.measure(`insert[${typeInfo.type}]`);
|
||||
await iidx.insert(trx, nid, values);
|
||||
}
|
||||
}
|
||||
@@ -129,6 +134,7 @@ export class DataStruct {
|
||||
table: keyof IndexerSchema,
|
||||
document: IndexerDocument
|
||||
) {
|
||||
using _ = await this.measure(`insert`);
|
||||
const existsNid = await trx
|
||||
.objectStore('indexerRecords')
|
||||
.index('id')
|
||||
@@ -160,6 +166,7 @@ export class DataStruct {
|
||||
if (!iidx) {
|
||||
continue;
|
||||
}
|
||||
using _ = await this.measure(`insert[${typeInfo.type}]`);
|
||||
await iidx.insert(trx, nid, values);
|
||||
}
|
||||
}
|
||||
@@ -183,6 +190,7 @@ export class DataStruct {
|
||||
table: keyof IndexerSchema,
|
||||
id: string
|
||||
) {
|
||||
using _ = await this.measure(`delete`);
|
||||
const nid = await trx
|
||||
.objectStore('indexerRecords')
|
||||
.index('id')
|
||||
@@ -195,11 +203,12 @@ export class DataStruct {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteByQuery(
|
||||
private async deleteByQuery(
|
||||
trx: DataStructRWTransaction,
|
||||
table: keyof IndexerSchema,
|
||||
query: Query<any>
|
||||
) {
|
||||
using _ = await this.measure(`deleteByQuery`);
|
||||
const match = await this.queryRaw(trx, table, query);
|
||||
|
||||
for (const nid of match.scores.keys()) {
|
||||
@@ -215,6 +224,7 @@ export class DataStruct {
|
||||
inserts: IndexerDocument<any>[],
|
||||
updates: IndexerDocument<any>[]
|
||||
) {
|
||||
using _ = await this.measure(`batchWrite`);
|
||||
for (const query of deleteByQueries) {
|
||||
await this.deleteByQuery(trx, table, query);
|
||||
}
|
||||
@@ -248,8 +258,14 @@ export class DataStruct {
|
||||
async queryRaw(
|
||||
trx: DataStructROTransaction,
|
||||
table: keyof IndexerSchema,
|
||||
query: Query<any>
|
||||
query: Query<any>,
|
||||
cache: QueryCache = new QueryCache()
|
||||
): Promise<Match> {
|
||||
const cached = cache.get(query);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
using _ = await this.measure(`query[${query.type}]`);
|
||||
if (query.type === 'match') {
|
||||
const iidx = this.invertedIndex.get(table)?.get(query.field as string);
|
||||
if (!iidx) {
|
||||
@@ -259,7 +275,7 @@ export class DataStruct {
|
||||
} else if (query.type === 'boolean') {
|
||||
const weights = [];
|
||||
for (const q of query.queries) {
|
||||
weights.push(await this.queryRaw(trx, table, q));
|
||||
weights.push(await this.queryRaw(trx, table, q, cache));
|
||||
}
|
||||
if (query.occur === 'must') {
|
||||
return weights.reduce((acc, w) => acc.and(w));
|
||||
@@ -272,7 +288,9 @@ export class DataStruct {
|
||||
} else if (query.type === 'all') {
|
||||
return await this.matchAll(trx, table);
|
||||
} else if (query.type === 'boost') {
|
||||
return (await this.queryRaw(trx, table, query.query)).boost(query.boost);
|
||||
return (await this.queryRaw(trx, table, query.query, cache)).boost(
|
||||
query.boost
|
||||
);
|
||||
} else if (query.type === 'exists') {
|
||||
const iidx = this.invertedIndex.get(table)?.get(query.field as string);
|
||||
if (!iidx) {
|
||||
@@ -490,4 +508,30 @@ export class DataStruct {
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
async measure(name: string) {
|
||||
const count = debugMarkCount++;
|
||||
performance.mark(`${name}Start(${count})`);
|
||||
return {
|
||||
[Symbol.dispose]: () => {
|
||||
performance.mark(`${name}End(${count})`);
|
||||
performance.measure(
|
||||
`${name}`,
|
||||
`${name}Start(${count})`,
|
||||
`${name}End(${count})`
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
class QueryCache {
|
||||
private readonly cache: [Query<any>, Match][] = [];
|
||||
|
||||
get(query: Query<any>) {
|
||||
return this.cache.find(q => shallowEqual(q[0], query))?.[1];
|
||||
}
|
||||
|
||||
set(query: Query<any>, match: Match) {
|
||||
this.cache.push([query, match]);
|
||||
}
|
||||
}
|
||||
|
||||
34
packages/common/nbstore/src/utils/shallow-equal.ts
Normal file
34
packages/common/nbstore/src/utils/shallow-equal.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// credit: https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/shallowEqual.js
|
||||
export function shallowEqual(objA: any, objB: any) {
|
||||
if (Object.is(objA, objB)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof objA !== 'object' ||
|
||||
objA === null ||
|
||||
typeof objB !== 'object' ||
|
||||
objB === null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const keysA = Object.keys(objA);
|
||||
const keysB = Object.keys(objB);
|
||||
|
||||
if (keysA.length !== keysB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test for A's keys different from B.
|
||||
for (const key of keysA) {
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(objB, key) ||
|
||||
!Object.is(objA[key], objB[key])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user