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