mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-22 01:25:00 +02:00
UBERF-9114: Optimize memory usage (#7643)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -34,20 +34,22 @@ import type {
|
||||
import { BaseMiddleware } from '@hcengineering/server-core'
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
const isUserTx = (it: Tx): boolean =>
|
||||
it.modifiedBy !== core.account.System ||
|
||||
(it as TxCUD<Doc>).objectClass === 'contact:class:Person' ||
|
||||
(it as TxCUD<Doc>).objectClass === 'contact:class:PersonAccount'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class ModelMiddleware extends BaseMiddleware implements Middleware {
|
||||
hashes!: Map<string, number> // Hash to position
|
||||
lastHash: string = ''
|
||||
lastHashResponse!: Promise<LoadModelResponse>
|
||||
model!: Tx[]
|
||||
|
||||
constructor (
|
||||
context: PipelineContext,
|
||||
next: Middleware | undefined,
|
||||
readonly systemTx: Tx[],
|
||||
readonly holdModel: boolean = true
|
||||
readonly systemTx: Tx[]
|
||||
) {
|
||||
super(context, next)
|
||||
}
|
||||
@@ -57,107 +59,88 @@ export class ModelMiddleware extends BaseMiddleware implements Middleware {
|
||||
ctx: MeasureContext,
|
||||
context: PipelineContext,
|
||||
next: Middleware | undefined,
|
||||
systemTx: Tx[],
|
||||
holdModel: boolean = true
|
||||
systemTx: Tx[]
|
||||
): Promise<Middleware> {
|
||||
const middleware = new ModelMiddleware(context, next, systemTx, holdModel)
|
||||
const middleware = new ModelMiddleware(context, next, systemTx)
|
||||
await middleware.init(ctx)
|
||||
return middleware
|
||||
}
|
||||
|
||||
static create (tx: Tx[], holdModel: boolean = true): MiddlewareCreator {
|
||||
static create (tx: Tx[]): MiddlewareCreator {
|
||||
return (ctx, context, next) => {
|
||||
return this.doCreate(ctx, context, next, tx, holdModel)
|
||||
return this.doCreate(ctx, context, next, tx)
|
||||
}
|
||||
}
|
||||
|
||||
@withContext('get-model')
|
||||
async getUserTx (ctx: MeasureContext, txAdapter: TxAdapter): Promise<Tx[]> {
|
||||
const allUserTxes = await ctx.with('fetch-model', {}, (ctx) => txAdapter.getModel(ctx))
|
||||
return allUserTxes.filter((it) => isUserTx(it))
|
||||
}
|
||||
|
||||
async init (ctx: MeasureContext): Promise<void> {
|
||||
if (this.context.adapterManager == null) {
|
||||
throw new PlatformError(unknownError('Adapter manager should be configured'))
|
||||
}
|
||||
const txAdapter = this.context.adapterManager.getAdapter(DOMAIN_TX, true) as TxAdapter
|
||||
|
||||
const isUserTx = (it: Tx): boolean =>
|
||||
it.modifiedBy !== core.account.System ||
|
||||
(it as TxCUD<Doc>).objectClass === 'contact:class:Person' ||
|
||||
(it as TxCUD<Doc>).objectClass === 'contact:class:PersonAccount'
|
||||
|
||||
this.model = await ctx.with('get-model', {}, async (ctx) => {
|
||||
const allUserTxes = await ctx.with('fetch-model', {}, (ctx) => txAdapter.getModel(ctx))
|
||||
const userTxes = allUserTxes.filter((it) => isUserTx(it))
|
||||
const model = this.systemTx.concat(userTxes)
|
||||
for (const tx of model) {
|
||||
try {
|
||||
this.context.hierarchy.tx(tx)
|
||||
} catch (err: any) {
|
||||
ctx.warn('failed to apply model transaction, skipping', { tx: JSON.stringify(tx), err })
|
||||
}
|
||||
const userTx = await this.getUserTx(ctx, txAdapter)
|
||||
const model = this.systemTx.concat(userTx)
|
||||
for (const tx of model) {
|
||||
try {
|
||||
this.context.hierarchy.tx(tx)
|
||||
} catch (err: any) {
|
||||
ctx.warn('failed to apply model transaction, skipping', { tx: JSON.stringify(tx), err })
|
||||
}
|
||||
this.context.modelDb.addTxes(ctx, model, true)
|
||||
if (this.holdModel) {
|
||||
return model
|
||||
}
|
||||
return []
|
||||
})
|
||||
}
|
||||
this.context.modelDb.addTxes(ctx, model, true)
|
||||
|
||||
this.setModel(this.model)
|
||||
this.setModel(model)
|
||||
}
|
||||
|
||||
private addModelTx (tx: Tx): void {
|
||||
if (this.holdModel) {
|
||||
this.model.push(tx)
|
||||
const h = crypto.createHash('sha1')
|
||||
h.update(this.lastHash)
|
||||
h.update(JSON.stringify(tx))
|
||||
const hash = h.digest('hex')
|
||||
this.hashes.set(hash, this.model.length - 1)
|
||||
this.setLastHash(hash)
|
||||
}
|
||||
const h = crypto.createHash('sha1')
|
||||
h.update(this.lastHash)
|
||||
h.update(JSON.stringify(tx))
|
||||
const hash = h.digest('hex')
|
||||
this.setLastHash(hash)
|
||||
}
|
||||
|
||||
private setLastHash (hash: string): void {
|
||||
this.lastHash = hash
|
||||
this.context.lastHash = this.lastHash
|
||||
this.lastHashResponse = Promise.resolve({
|
||||
full: false,
|
||||
hash,
|
||||
transactions: []
|
||||
})
|
||||
}
|
||||
|
||||
private setModel (model: Tx[]): void {
|
||||
let last = ''
|
||||
const values: [string, number][] = model.map((it, index) => {
|
||||
model.map((it, index) => {
|
||||
const h = crypto.createHash('sha1')
|
||||
h.update(last)
|
||||
h.update(JSON.stringify(it))
|
||||
last = h.digest('hex')
|
||||
return [last, index]
|
||||
})
|
||||
this.hashes = new Map(values)
|
||||
this.setLastHash(last)
|
||||
}
|
||||
|
||||
loadModel (ctx: MeasureContext, lastModelTx: Timestamp, hash?: string): Promise<Tx[] | LoadModelResponse> {
|
||||
async loadModel (ctx: MeasureContext, lastModelTx: Timestamp, hash?: string): Promise<Tx[] | LoadModelResponse> {
|
||||
if (hash !== undefined) {
|
||||
if (hash === this.lastHash) {
|
||||
return this.lastHashResponse
|
||||
}
|
||||
const pos = this.hashes.get(hash)
|
||||
if (pos != null && pos >= 0) {
|
||||
return Promise.resolve({
|
||||
return {
|
||||
full: false,
|
||||
hash: this.lastHash,
|
||||
transactions: this.model.slice(pos + 1)
|
||||
})
|
||||
hash,
|
||||
transactions: []
|
||||
}
|
||||
}
|
||||
return Promise.resolve({
|
||||
const txAdapter = this.context.adapterManager?.getAdapter(DOMAIN_TX, true) as TxAdapter
|
||||
return {
|
||||
full: true,
|
||||
hash: this.lastHash,
|
||||
transactions: [...this.model]
|
||||
})
|
||||
transactions: this.systemTx.concat(await this.getUserTx(ctx, txAdapter))
|
||||
}
|
||||
}
|
||||
return Promise.resolve(this.model.filter((it) => it.modifiedOn > lastModelTx))
|
||||
const txAdapter = this.context.adapterManager?.getAdapter(DOMAIN_TX, true) as TxAdapter
|
||||
return this.systemTx.concat(await this.getUserTx(ctx, txAdapter)).filter((it) => it.modifiedOn > lastModelTx)
|
||||
}
|
||||
|
||||
tx (ctx: MeasureContext, tx: Tx[]): Promise<TxMiddlewareResult> {
|
||||
|
||||
Reference in New Issue
Block a user