mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
feat: improve idb perf (#14159)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Performance**
* Optimized database operations through improved batch processing to
accelerate data retrieval, updates, and deletion operations for better
efficiency.
* **Reliability**
* Enhanced transaction durability handling to strengthen data
consistency and ensure more reliable persistence of database changes and
updates.
<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,9 +1,5 @@
|
||||
import { share } from '../../connection';
|
||||
import {
|
||||
type BlobRecord,
|
||||
BlobStorageBase,
|
||||
type ListedBlobRecord,
|
||||
} from '../../storage';
|
||||
import { type BlobRecord, BlobStorageBase } from '../../storage';
|
||||
import { IDBConnection, type IDBConnectionOptions } from './db';
|
||||
|
||||
export class IndexedDBBlobStorage extends BlobStorageBase {
|
||||
@@ -36,7 +32,9 @@ export class IndexedDBBlobStorage extends BlobStorageBase {
|
||||
}
|
||||
|
||||
override async set(blob: BlobRecord) {
|
||||
const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite');
|
||||
const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite', {
|
||||
durability: 'relaxed',
|
||||
});
|
||||
await trx.objectStore('blobs').put({
|
||||
key: blob.key,
|
||||
mime: blob.mime,
|
||||
@@ -52,11 +50,15 @@ export class IndexedDBBlobStorage extends BlobStorageBase {
|
||||
|
||||
override async delete(key: string, permanently: boolean) {
|
||||
if (permanently) {
|
||||
const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite');
|
||||
const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite', {
|
||||
durability: 'relaxed',
|
||||
});
|
||||
await trx.objectStore('blobs').delete(key);
|
||||
await trx.objectStore('blobData').delete(key);
|
||||
} else {
|
||||
const trx = this.db.transaction('blobs', 'readwrite');
|
||||
const trx = this.db.transaction('blobs', 'readwrite', {
|
||||
durability: 'relaxed',
|
||||
});
|
||||
const blob = await trx.store.get(key);
|
||||
if (blob) {
|
||||
await trx.store.put({
|
||||
@@ -68,29 +70,37 @@ export class IndexedDBBlobStorage extends BlobStorageBase {
|
||||
}
|
||||
|
||||
override async release() {
|
||||
const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite');
|
||||
const trx = this.db.transaction(['blobs', 'blobData'], 'readwrite', {
|
||||
durability: 'relaxed',
|
||||
});
|
||||
|
||||
const it = trx.objectStore('blobs').iterate();
|
||||
const store = trx.objectStore('blobs');
|
||||
const getAllRecords = store.getAllRecords?.bind(store);
|
||||
const blobs =
|
||||
typeof getAllRecords === 'function'
|
||||
? (await getAllRecords()).map(record => record.value)
|
||||
: await store.getAll();
|
||||
|
||||
for await (const item of it) {
|
||||
if (item.value.deletedAt) {
|
||||
await item.delete();
|
||||
await trx.objectStore('blobData').delete(item.value.key);
|
||||
}
|
||||
}
|
||||
const deleted = blobs.filter(blob => blob.deletedAt);
|
||||
|
||||
await Promise.all(
|
||||
deleted.map(blob =>
|
||||
Promise.all([
|
||||
store.delete(blob.key),
|
||||
trx.objectStore('blobData').delete(blob.key),
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
override async list() {
|
||||
const trx = this.db.transaction('blobs', 'readonly');
|
||||
const it = trx.store.iterate();
|
||||
const getAllRecords = trx.store.getAllRecords?.bind(trx.store);
|
||||
const blobs =
|
||||
typeof getAllRecords === 'function'
|
||||
? (await getAllRecords()).map(record => record.value)
|
||||
: await trx.store.getAll();
|
||||
|
||||
const blobs: ListedBlobRecord[] = [];
|
||||
for await (const item of it) {
|
||||
if (!item.value.deletedAt) {
|
||||
blobs.push(item.value);
|
||||
}
|
||||
}
|
||||
|
||||
return blobs;
|
||||
return blobs.filter(blob => !blob.deletedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,38 @@ import { AutoReconnectConnection } from '../../connection';
|
||||
import type { SpaceType } from '../../utils/universal-id';
|
||||
import { type DocStorageSchema, migrator } from './schema';
|
||||
|
||||
declare module 'idb' {
|
||||
interface IDBPObjectStore {
|
||||
getAllRecords?(
|
||||
query?: IDBValidKey | IDBKeyRange | null,
|
||||
count?: number | { direction?: IDBCursorDirection; count?: number }
|
||||
): Promise<IDBRecord[]>;
|
||||
}
|
||||
interface IDBPIndex {
|
||||
getAllRecords?(
|
||||
query?: IDBValidKey | IDBKeyRange | null,
|
||||
count?: number | { direction?: IDBCursorDirection; count?: number }
|
||||
): Promise<IDBRecord[]>;
|
||||
}
|
||||
interface IDBObjectStore {
|
||||
getAllRecords?(
|
||||
query?: IDBValidKey | IDBKeyRange | null,
|
||||
count?: number | { direction?: IDBCursorDirection; count?: number }
|
||||
): Promise<IDBRecord[]>;
|
||||
}
|
||||
interface IDBIndex {
|
||||
getAllRecords?(
|
||||
query?: IDBValidKey | IDBKeyRange | null,
|
||||
count?: number | { direction?: IDBCursorDirection; count?: number }
|
||||
): Promise<IDBRecord[]>;
|
||||
}
|
||||
interface IDBRecord {
|
||||
key: IDBValidKey;
|
||||
primaryKey: IDBValidKey;
|
||||
value: any;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDBConnectionOptions {
|
||||
flavour: string;
|
||||
type: SpaceType;
|
||||
|
||||
@@ -37,7 +37,9 @@ export class IndexedDBDocStorage extends DocStorageBase<IDBConnectionOptions> {
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const trx = this.db.transaction(['updates', 'clocks'], 'readwrite');
|
||||
const trx = this.db.transaction(['updates', 'clocks'], 'readwrite', {
|
||||
durability: 'relaxed',
|
||||
});
|
||||
|
||||
await trx.objectStore('updates').add({
|
||||
...update,
|
||||
@@ -103,15 +105,15 @@ export class IndexedDBDocStorage extends DocStorageBase<IDBConnectionOptions> {
|
||||
override async deleteDoc(docId: string) {
|
||||
const trx = this.db.transaction(
|
||||
['snapshots', 'updates', 'clocks'],
|
||||
'readwrite'
|
||||
'readwrite',
|
||||
{ durability: 'relaxed' }
|
||||
);
|
||||
|
||||
const idx = trx.objectStore('updates').index('docId');
|
||||
const iter = idx.iterate(IDBKeyRange.only(docId));
|
||||
const updates = trx.objectStore('updates');
|
||||
const idx = updates.index('docId');
|
||||
const keys = await idx.getAllKeys(IDBKeyRange.only(docId));
|
||||
|
||||
for await (const { value } of iter) {
|
||||
await trx.objectStore('updates').delete([value.docId, value.createdAt]);
|
||||
}
|
||||
await Promise.all(keys.map(key => updates.delete(key)));
|
||||
|
||||
await trx.objectStore('snapshots').delete(docId);
|
||||
await trx.objectStore('clocks').delete(docId);
|
||||
@@ -120,6 +122,18 @@ export class IndexedDBDocStorage extends DocStorageBase<IDBConnectionOptions> {
|
||||
override async getDocTimestamps(after: Date = new Date(0)) {
|
||||
const trx = this.db.transaction('clocks', 'readonly');
|
||||
|
||||
const getAllRecords = trx.store.getAllRecords?.bind(trx.store);
|
||||
|
||||
if (typeof getAllRecords === 'function') {
|
||||
const records = await getAllRecords();
|
||||
return records.reduce((ret, cur) => {
|
||||
if (cur.value.timestamp > after) {
|
||||
ret[cur.value.docId] = cur.value.timestamp;
|
||||
}
|
||||
return ret;
|
||||
}, {} as DocClocks);
|
||||
}
|
||||
|
||||
const clocks = await trx.store.getAll();
|
||||
|
||||
return clocks.reduce((ret, cur) => {
|
||||
@@ -157,7 +171,19 @@ export class IndexedDBDocStorage extends DocStorageBase<IDBConnectionOptions> {
|
||||
|
||||
protected override async getDocUpdates(docId: string): Promise<DocRecord[]> {
|
||||
const trx = this.db.transaction('updates', 'readonly');
|
||||
const updates = await trx.store.index('docId').getAll(docId);
|
||||
const idx = trx.store.index('docId');
|
||||
const getAllRecords = idx.getAllRecords?.bind(idx);
|
||||
|
||||
if (typeof getAllRecords === 'function') {
|
||||
const records = await getAllRecords(IDBKeyRange.only(docId));
|
||||
return records.map(record => ({
|
||||
docId,
|
||||
bin: record.value.bin,
|
||||
timestamp: record.value.createdAt,
|
||||
}));
|
||||
}
|
||||
|
||||
const updates = await idx.getAll(docId);
|
||||
|
||||
return updates.map(update => ({
|
||||
docId,
|
||||
@@ -170,7 +196,9 @@ export class IndexedDBDocStorage extends DocStorageBase<IDBConnectionOptions> {
|
||||
docId: string,
|
||||
updates: DocRecord[]
|
||||
): Promise<number> {
|
||||
const trx = this.db.transaction('updates', 'readwrite');
|
||||
const trx = this.db.transaction('updates', 'readwrite', {
|
||||
durability: 'relaxed',
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
updates.map(update => trx.store.delete([docId, update.timestamp]))
|
||||
|
||||
Reference in New Issue
Block a user