mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-22 19:38:17 +00:00
UBERF-8899: Reconnect performance issues (#7611)
Some checks are pending
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions
Some checks are pending
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
d63db0fca6
commit
12e0aaa5f7
@ -329,6 +329,7 @@ async function tryLoadModel (
|
||||
|
||||
if (conn.getLastHash !== undefined && (await conn.getLastHash(ctx)) === current.hash) {
|
||||
// We have same model hash.
|
||||
current.full = false // Since we load, no need to send full
|
||||
return current
|
||||
}
|
||||
const lastTxTime = getLastTxTime(current.transactions)
|
||||
|
@ -44,6 +44,7 @@ import core, {
|
||||
TxApplyIf,
|
||||
TxHandler,
|
||||
TxResult,
|
||||
clone,
|
||||
generateId,
|
||||
toFindResult,
|
||||
type MeasureContext
|
||||
@ -108,6 +109,8 @@ class Connection implements ClientConnection {
|
||||
|
||||
private helloRecieved: boolean = false
|
||||
|
||||
private account: Account | undefined
|
||||
|
||||
onConnect?: (event: ClientConnectEvent, lastTx: string | undefined, data: any) => Promise<void>
|
||||
|
||||
rpcHandler = new RPCHandler()
|
||||
@ -303,7 +306,7 @@ class Connection implements ClientConnection {
|
||||
this.websocket?.close()
|
||||
return
|
||||
}
|
||||
|
||||
this.account = helloResp.account
|
||||
this.helloRecieved = true
|
||||
if (this.upgrading) {
|
||||
// We need to call upgrade since connection is upgraded
|
||||
@ -322,8 +325,8 @@ class Connection implements ClientConnection {
|
||||
}
|
||||
|
||||
void this.onConnect?.(
|
||||
(resp as HelloResponse).reconnect === true ? ClientConnectEvent.Reconnected : ClientConnectEvent.Connected,
|
||||
(resp as HelloResponse).lastTx,
|
||||
helloResp.reconnect === true ? ClientConnectEvent.Reconnected : ClientConnectEvent.Connected,
|
||||
helloResp.lastTx,
|
||||
this.sessionId
|
||||
)
|
||||
this.schedulePing(socketId)
|
||||
@ -635,6 +638,9 @@ class Connection implements ClientConnection {
|
||||
}
|
||||
|
||||
getAccount (): Promise<Account> {
|
||||
if (this.account !== undefined) {
|
||||
return clone(this.account)
|
||||
}
|
||||
return this.sendRequest({ method: 'getAccount', params: [] })
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (event === ClientConnectEvent.Connected) {
|
||||
if (event === ClientConnectEvent.Connected || event === ClientConnectEvent.Reconnected) {
|
||||
setMetadata(presentation.metadata.SessionId, data)
|
||||
}
|
||||
if ((_clientSet && event === ClientConnectEvent.Connected) || event === ClientConnectEvent.Refresh) {
|
||||
|
@ -64,43 +64,47 @@ export class DbAdapterManagerImpl implements DBAdapterManager {
|
||||
return this.defaultAdapter
|
||||
}
|
||||
|
||||
async registerHelper (helper: DomainHelper): Promise<void> {
|
||||
async registerHelper (ctx: MeasureContext, helper: DomainHelper): Promise<void> {
|
||||
this.domainHelper = helper
|
||||
await this.initDomains()
|
||||
await this.initDomains(ctx)
|
||||
}
|
||||
|
||||
async initDomains (): Promise<void> {
|
||||
async initDomains (ctx: MeasureContext): Promise<void> {
|
||||
const adapterDomains = new Map<DbAdapter, Set<Domain>>()
|
||||
for (const d of this.context.hierarchy.domains()) {
|
||||
// We need to init domain info
|
||||
const info = this.getDomainInfo(d)
|
||||
await this.updateInfo(d, adapterDomains, info)
|
||||
await ctx.with('update-info', { domain: d }, async (ctx) => {
|
||||
const info = this.getDomainInfo(d)
|
||||
await this.updateInfo(d, adapterDomains, info)
|
||||
})
|
||||
}
|
||||
for (const adapter of this.adapters.values()) {
|
||||
adapter.on?.((domain, event, count, helper) => {
|
||||
const info = this.getDomainInfo(domain)
|
||||
const oldDocuments = info.documents
|
||||
switch (event) {
|
||||
case 'add':
|
||||
info.documents += count
|
||||
break
|
||||
case 'update':
|
||||
break
|
||||
case 'delete':
|
||||
info.documents -= count
|
||||
break
|
||||
case 'read':
|
||||
break
|
||||
}
|
||||
for (const [name, adapter] of this.adapters.entries()) {
|
||||
await ctx.with('domain-helper', { name }, async (ctx) => {
|
||||
adapter.on?.((domain, event, count, helper) => {
|
||||
const info = this.getDomainInfo(domain)
|
||||
const oldDocuments = info.documents
|
||||
switch (event) {
|
||||
case 'add':
|
||||
info.documents += count
|
||||
break
|
||||
case 'update':
|
||||
break
|
||||
case 'delete':
|
||||
info.documents -= count
|
||||
break
|
||||
case 'read':
|
||||
break
|
||||
}
|
||||
|
||||
if (oldDocuments < 50 && info.documents > 50) {
|
||||
// We have more 50 documents, we need to check for indexes
|
||||
void this.domainHelper?.checkDomain(this.metrics, domain, info.documents, helper)
|
||||
}
|
||||
if (oldDocuments > 50 && info.documents < 50) {
|
||||
// We have more 50 documents, we need to check for indexes
|
||||
void this.domainHelper?.checkDomain(this.metrics, domain, info.documents, helper)
|
||||
}
|
||||
if (oldDocuments < 50 && info.documents > 50) {
|
||||
// We have more 50 documents, we need to check for indexes
|
||||
void this.domainHelper?.checkDomain(this.metrics, domain, info.documents, helper)
|
||||
}
|
||||
if (oldDocuments > 50 && info.documents < 50) {
|
||||
// We have more 50 documents, we need to check for indexes
|
||||
void this.domainHelper?.checkDomain(this.metrics, domain, info.documents, helper)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import {
|
||||
toFindResult,
|
||||
withContext,
|
||||
type Class,
|
||||
type Doc,
|
||||
type DocumentQuery,
|
||||
@ -66,6 +67,7 @@ class PipelineImpl implements Pipeline {
|
||||
return pipeline
|
||||
}
|
||||
|
||||
@withContext('build-chain')
|
||||
private async buildChain (
|
||||
ctx: MeasureContext,
|
||||
constructors: MiddlewareCreator[],
|
||||
|
@ -151,7 +151,7 @@ export interface DBAdapterManager {
|
||||
|
||||
close: () => Promise<void>
|
||||
|
||||
registerHelper: (helper: DomainHelper) => Promise<void>
|
||||
registerHelper: (ctx: MeasureContext, helper: DomainHelper) => Promise<void>
|
||||
|
||||
initAdapters: (ctx: MeasureContext) => Promise<void>
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type MeasureContext } from '@hcengineering/core'
|
||||
import { withContext, type MeasureContext } from '@hcengineering/core'
|
||||
import type { Middleware, PipelineContext } from '@hcengineering/server-core'
|
||||
import { BaseMiddleware, DomainIndexHelperImpl } from '@hcengineering/server-core'
|
||||
|
||||
@ -21,14 +21,19 @@ import { BaseMiddleware, DomainIndexHelperImpl } from '@hcengineering/server-cor
|
||||
* @public
|
||||
*/
|
||||
export class DBAdapterInitMiddleware extends BaseMiddleware implements Middleware {
|
||||
@withContext('db-adapter-init')
|
||||
static async create (
|
||||
ctx: MeasureContext,
|
||||
context: PipelineContext,
|
||||
next?: Middleware
|
||||
): Promise<Middleware | undefined> {
|
||||
await context.adapterManager?.initAdapters?.(ctx)
|
||||
await ctx.with('init-adapters', {}, async (ctx) => {
|
||||
await context.adapterManager?.initAdapters?.(ctx)
|
||||
})
|
||||
const domainHelper = new DomainIndexHelperImpl(ctx, context.hierarchy, context.modelDb, context.workspace)
|
||||
await context.adapterManager?.registerHelper?.(domainHelper)
|
||||
await ctx.with('register-helper', {}, async (ctx) => {
|
||||
await context.adapterManager?.registerHelper?.(ctx, domainHelper)
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import core, {
|
||||
Domain,
|
||||
groupByArray,
|
||||
TxProcessor,
|
||||
withContext,
|
||||
type Doc,
|
||||
type MeasureContext,
|
||||
type SessionData,
|
||||
@ -41,6 +42,7 @@ import { BaseMiddleware } from '@hcengineering/server-core'
|
||||
export class DomainTxMiddleware extends BaseMiddleware implements Middleware {
|
||||
adapterManager!: DBAdapterManager
|
||||
|
||||
@withContext('domainTx-middleware')
|
||||
static async create (ctx: MeasureContext, context: PipelineContext, next?: Middleware): Promise<Middleware> {
|
||||
const middleware = new DomainTxMiddleware(context, next)
|
||||
if (context.adapterManager == null) {
|
||||
|
@ -20,7 +20,8 @@ import core, {
|
||||
type Timestamp,
|
||||
type Tx,
|
||||
type TxCUD,
|
||||
DOMAIN_TX
|
||||
DOMAIN_TX,
|
||||
withContext
|
||||
} from '@hcengineering/core'
|
||||
import { PlatformError, unknownError } from '@hcengineering/platform'
|
||||
import type {
|
||||
@ -51,6 +52,7 @@ export class ModelMiddleware extends BaseMiddleware implements Middleware {
|
||||
super(context, next)
|
||||
}
|
||||
|
||||
@withContext('modelAdapter-middleware')
|
||||
static async doCreate (
|
||||
ctx: MeasureContext,
|
||||
context: PipelineContext,
|
||||
|
@ -450,12 +450,8 @@ export class DBCollectionHelper implements DomainHelperOperations {
|
||||
async create (domain: Domain): Promise<void> {}
|
||||
|
||||
async exists (domain: Domain): Promise<boolean> {
|
||||
const exists = await this.client`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_name = '${this.client(translateDomain(domain))}'
|
||||
`
|
||||
return exists.length > 0
|
||||
// Always exists. We don't need to check for index existence
|
||||
return true
|
||||
}
|
||||
|
||||
async listDomains (): Promise<Set<Domain>> {
|
||||
@ -469,10 +465,8 @@ export class DBCollectionHelper implements DomainHelperOperations {
|
||||
}
|
||||
|
||||
async estimatedCount (domain: Domain): Promise<number> {
|
||||
const res = await this
|
||||
.client`SELECT COUNT(_id) FROM ${this.client(translateDomain(domain))} WHERE "workspaceId" = ${this.workspaceId.name}`
|
||||
|
||||
return res.count
|
||||
// We should always return 0, since no controlled index stuff is required for postgres driver
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Account } from '@hcengineering/core'
|
||||
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
|
||||
import { Packr } from 'msgpackr'
|
||||
|
||||
@ -48,6 +49,7 @@ export interface HelloResponse extends Response<any> {
|
||||
serverVersion: string
|
||||
lastTx?: string
|
||||
lastHash?: string // Last model hash
|
||||
account: Account
|
||||
}
|
||||
|
||||
function replacer (key: string, value: any): any {
|
||||
|
@ -418,6 +418,7 @@ class TSessionManager implements SessionManager {
|
||||
})
|
||||
workspace = this.createWorkspace(
|
||||
ctx.parent ?? ctx,
|
||||
ctx,
|
||||
pipelineFactory,
|
||||
token,
|
||||
workspaceInfo.workspaceUrl ?? workspaceInfo.workspaceId,
|
||||
@ -435,7 +436,7 @@ class TSessionManager implements SessionManager {
|
||||
workspace: workspaceInfo.workspaceId,
|
||||
wsUrl: workspaceInfo.workspaceUrl
|
||||
})
|
||||
pipeline = await ctx.with('💤 wait', { workspaceName }, () => (workspace as Workspace).pipeline)
|
||||
pipeline = await ctx.with('💤 wait-pipeline', {}, () => (workspace as Workspace).pipeline)
|
||||
} else {
|
||||
ctx.warn('reconnect workspace in upgrade switch', {
|
||||
email: token.email,
|
||||
@ -466,9 +467,10 @@ class TSessionManager implements SessionManager {
|
||||
})
|
||||
return { upgrade: true }
|
||||
}
|
||||
|
||||
try {
|
||||
if (workspace.pipeline instanceof Promise) {
|
||||
pipeline = await workspace.pipeline
|
||||
pipeline = await ctx.with('💤 wait-pipeline', {}, () => (workspace as Workspace).pipeline)
|
||||
workspace.pipeline = pipeline
|
||||
} else {
|
||||
pipeline = workspace.pipeline
|
||||
@ -645,6 +647,7 @@ class TSessionManager implements SessionManager {
|
||||
|
||||
private createWorkspace (
|
||||
ctx: MeasureContext,
|
||||
pipelineCtx: MeasureContext,
|
||||
pipelineFactory: PipelineFactory,
|
||||
token: Token,
|
||||
workspaceUrl: string,
|
||||
@ -655,7 +658,6 @@ class TSessionManager implements SessionManager {
|
||||
const wsId = toWorkspaceString(token.workspace)
|
||||
const upgrade = token.extra?.model === 'upgrade'
|
||||
const context = ctx.newChild('🧲 session', {})
|
||||
const pipelineCtx = context.newChild('🧲 pipeline-factory', {})
|
||||
const workspace: Workspace = {
|
||||
context,
|
||||
id: generateId(),
|
||||
@ -1106,7 +1108,8 @@ class TSessionManager implements SessionManager {
|
||||
reconnect,
|
||||
serverVersion: this.serverVersion,
|
||||
lastTx: pipeline.context.lastTx,
|
||||
lastHash: pipeline.context.lastHash
|
||||
lastHash: pipeline.context.lastHash,
|
||||
account: service.getRawAccount(pipeline)
|
||||
}
|
||||
ws.send(requestCtx, helloResponse, false, false)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user