Client model persistence (#3796)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-10-06 14:20:34 +07:00 committed by GitHub
parent 08a686e3d5
commit f3db427f1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 82 additions and 11 deletions

View File

@ -158,13 +158,22 @@ class ClientImpl implements AccountClient, BackupClient {
} }
} }
/**
* @public
*/
export interface TxPersistenceStore {
load: () => Promise<Tx[]>
store: (tx: Tx[]) => Promise<void>
}
/** /**
* @public * @public
*/ */
export async function createClient ( export async function createClient (
connect: (txHandler: TxHandler) => Promise<ClientConnection>, connect: (txHandler: TxHandler) => Promise<ClientConnection>,
// If set will build model with only allowed plugins. // If set will build model with only allowed plugins.
allowedPlugins?: Plugin[] allowedPlugins?: Plugin[],
txPersistence?: TxPersistenceStore
): Promise<AccountClient> { ): Promise<AccountClient> {
let client: ClientImpl | null = null let client: ClientImpl | null = null
@ -193,7 +202,7 @@ export async function createClient (
const conn = await connect(txHandler) const conn = await connect(txHandler)
lastTxTime = await loadModel(conn, lastTxTime, allowedPlugins, configs, hierarchy, model) lastTxTime = await loadModel(conn, lastTxTime, allowedPlugins, configs, hierarchy, model, false, txPersistence)
txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model || tx.modifiedOn > lastTxTime) txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model || tx.modifiedOn > lastTxTime)
@ -255,11 +264,18 @@ async function loadModel (
configs: Map<Ref<PluginConfiguration>, PluginConfiguration>, configs: Map<Ref<PluginConfiguration>, PluginConfiguration>,
hierarchy: Hierarchy, hierarchy: Hierarchy,
model: ModelDb, model: ModelDb,
reload = false reload = false,
persistence?: TxPersistenceStore
): Promise<Timestamp> { ): Promise<Timestamp> {
const t = Date.now() const t = Date.now()
let atxes = [] let ltxes: Tx[] = []
if (lastTxTime === 0 && persistence !== undefined) {
ltxes = await persistence.load()
lastTxTime = getLastTxTime(ltxes)
}
let atxes: Tx[] = []
try { try {
atxes = await conn.loadModel(lastTxTime) atxes = await conn.loadModel(lastTxTime)
} catch (err: any) { } catch (err: any) {
@ -274,6 +290,12 @@ async function loadModel (
return -1 return -1
} }
if (atxes.length < modelTransactionThreshold) {
atxes = ltxes.concat(atxes)
}
await persistence?.store(atxes)
let systemTx: Tx[] = [] let systemTx: Tx[] = []
const userTx: Tx[] = [] const userTx: Tx[] = []
console.log('find' + (lastTxTime >= 0 ? 'full model' : 'model diff'), atxes.length, Date.now() - t) console.log('find' + (lastTxTime >= 0 ? 'full model' : 'model diff'), atxes.length, Date.now() - t)
@ -302,11 +324,7 @@ async function loadModel (
const txes = systemTx.concat(userTx) const txes = systemTx.concat(userTx)
for (const tx of txes) { lastTxTime = getLastTxTime(txes)
if (tx.modifiedOn > lastTxTime) {
lastTxTime = tx.modifiedOn
}
}
for (const tx of txes) { for (const tx of txes) {
try { try {
@ -325,6 +343,16 @@ async function loadModel (
return lastTxTime return lastTxTime
} }
function getLastTxTime (txes: Tx[]): number {
let lastTxTime = 0
for (const tx of txes) {
if (tx.modifiedOn > lastTxTime) {
lastTxTime = tx.modifiedOn
}
}
return lastTxTime
}
function fillConfiguration (systemTx: Tx[], configs: Map<Ref<PluginConfiguration>, PluginConfiguration>): void { function fillConfiguration (systemTx: Tx[], configs: Map<Ref<PluginConfiguration>, PluginConfiguration>): void {
for (const t of systemTx) { for (const t of systemTx) {
if (t._class === core.class.TxCreateDoc) { if (t._class === core.class.TxCreateDoc) {

View File

@ -60,7 +60,24 @@ export default async () => {
return connect(url.href, upgradeHandler, onUpgrade, onUnauthorized, onConnect) return connect(url.href, upgradeHandler, onUpgrade, onUnauthorized, onConnect)
}, },
filterModel ? [...getPlugins(), ...(getMetadata(clientPlugin.metadata.ExtraPlugins) ?? [])] : undefined filterModel ? [...getPlugins(), ...(getMetadata(clientPlugin.metadata.ExtraPlugins) ?? [])] : undefined,
{
load: async () => {
if (typeof localStorage !== 'undefined') {
const dta = localStorage.getItem('stored_model_' + token) ?? null
if (dta === null) {
return []
}
return JSON.parse(dta)
}
return []
},
store: async (txes) => {
if (typeof localStorage !== 'undefined') {
localStorage.setItem('stored_model_' + token, JSON.stringify(txes))
}
}
}
) )
// Check if we had dev hook for client. // Check if we had dev hook for client.
client = hookClient(client) client = hookClient(client)

View File

@ -30,6 +30,7 @@ import core, {
WorkspaceId, WorkspaceId,
_getOperator, _getOperator,
setObjectValue, setObjectValue,
toFindResult,
versionToString versionToString
} from '@hcengineering/core' } from '@hcengineering/core'
import { DbAdapter } from '../adapter' import { DbAdapter } from '../adapter'
@ -371,7 +372,7 @@ export class FullTextIndexPipeline implements FullTextPipeline {
.filter((it) => it[1] > 3) .filter((it) => it[1] > 3)
.map((it) => it[0]) .map((it) => it[0])
const result = await this.metrics.with( let result = await this.metrics.with(
'get-to-index', 'get-to-index',
{}, {},
async () => async () =>
@ -390,6 +391,31 @@ export class FullTextIndexPipeline implements FullTextPipeline {
} }
) )
) )
const toRemove: DocIndexState[] = []
// Check and remove missing class documents.
result = toFindResult(
result.filter((doc) => {
const _class = this.model.findObject(doc.objectClass)
if (_class === undefined) {
// no _class present, remove doc
toRemove.push(doc)
return false
}
return true
}),
result.total
)
if (toRemove.length > 0) {
try {
await this.storage.clean(
DOMAIN_DOC_INDEX_STATE,
toRemove.map((it) => it._id)
)
} catch (err: any) {
// QuotaExceededError, ignore
}
}
if (result.length > 0) { if (result.length > 0) {
console.log( console.log(