diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index d78ddcfe0b..b70aece741 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -142,6 +142,7 @@ services: environment: - SERVER_PORT=3333 - SERVER_SECRET=secret + - SERVER_CURSOR_MAXTIMEMS=30000 - ELASTIC_URL=http://elastic:9200 - MONGO_URL=mongodb://mongodb:27017 - METRICS_CONSOLE=false diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index afb4650d3f..1f7c67fa26 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -501,3 +501,21 @@ function getInNiN (query1: any, query2: any): any { } return {} } + +export function cutObjectArray (obj: any): any { + const r = {} + for (const key of Object.keys(obj)) { + if (Array.isArray(obj[key])) { + if (obj[key].length > 3) { + Object.assign(r, { [key]: `[${obj[key].slice(0, 3)}, ... and ${obj[key].length - 3} more]` }) + } else Object.assign(r, { [key]: obj[key] }) + continue + } + if (typeof obj[key] === 'object') { + Object.assign(r, { [key]: cutObjectArray(obj[key]) }) + continue + } + Object.assign(r, { [key]: obj[key] }) + } + return r +} diff --git a/pods/server/src/__start.ts b/pods/server/src/__start.ts index a10a3516bf..89e2aa00b8 100644 --- a/pods/server/src/__start.ts +++ b/pods/server/src/__start.ts @@ -83,7 +83,9 @@ if (frontUrl === undefined) { } const sesUrl = process.env.SES_URL +const cursorMaxTime = process.env.SERVER_CURSOR_MAXTIMEMS +setMetadata(serverCore.metadata.CursorMaxTimeMS, cursorMaxTime) setMetadata(serverCore.metadata.FrontUrl, frontUrl) setMetadata(serverToken.metadata.Secret, serverSecret) setMetadata(serverNotification.metadata.SesUrl, sesUrl ?? '') diff --git a/server/core/src/adapter.ts b/server/core/src/adapter.ts index 24d28e692f..9b1301c5cc 100644 --- a/server/core/src/adapter.ts +++ b/server/core/src/adapter.ts @@ -43,6 +43,7 @@ export interface DbAdapter { init: (model: Tx[]) => Promise createIndexes: (domain: Domain, config: Pick, 'indexes'>) => Promise + removeOldIndex: (domain: Domain, deletePattern: RegExp, keepPattern: RegExp) => Promise close: () => Promise findAll: ( @@ -102,6 +103,7 @@ export class DummyDbAdapter implements DbAdapter { } async createIndexes (domain: Domain, config: Pick, 'indexes'>): Promise {} + async removeOldIndex (domain: Domain, deletePattern: RegExp, keepPattern: RegExp): Promise {} async tx (...tx: Tx[]): Promise { return {} diff --git a/server/core/src/indexer/fulltextPush.ts b/server/core/src/indexer/fulltextPush.ts index 37b7fad30e..a60b08c023 100644 --- a/server/core/src/indexer/fulltextPush.ts +++ b/server/core/src/indexer/fulltextPush.ts @@ -310,7 +310,12 @@ function updateDoc2Elastic ( const docIdAttr = docKey(attr, docKeyOpts) if (vv !== null) { // Since we replace array of values, we could ignore null - doc[docIdAttr] = typeof doc[docIdAttr] === 'string' ? [doc[docIdAttr]] : [...(doc[docIdAttr] ?? [])] + doc[docIdAttr] = + doc[docIdAttr] == null + ? [] + : typeof doc[docIdAttr] === 'string' || !Array.isArray(doc[docIdAttr]) + ? [doc[docIdAttr]] + : doc[docIdAttr] if (vv !== '') { if (typeof vv !== 'object') { doc[docIdAttr] = Array.from(new Set([...doc[docIdAttr], vv])) diff --git a/server/core/src/indexer/indexer.ts b/server/core/src/indexer/indexer.ts index 643da06deb..13bd45f492 100644 --- a/server/core/src/indexer/indexer.ts +++ b/server/core/src/indexer/indexer.ts @@ -292,7 +292,12 @@ export class FullTextIndexPipeline implements FullTextPipeline { if (!this.indexesCreated) { this.indexesCreated = true // We need to be sure we have individual indexes per stage. + const oldStagesRegex = [/fld-v.*/, /cnt-v.*/, /fts-v.*/, /sum-v.*/] for (const st of this.stages) { + const regexp = oldStagesRegex.find((r) => r.test(st.stageId)) + if (regexp !== undefined) { + await this.storage.removeOldIndex(DOMAIN_DOC_INDEX_STATE, regexp, new RegExp(st.stageId)) + } await this.storage.createIndexes(DOMAIN_DOC_INDEX_STATE, { indexes: [ { diff --git a/server/core/src/indexer/types.ts b/server/core/src/indexer/types.ts index 44ad301eda..d9851d1dd1 100644 --- a/server/core/src/indexer/types.ts +++ b/server/core/src/indexer/types.ts @@ -107,4 +107,4 @@ export const fieldStateId = 'fld-v11' /** * @public */ -export const fullTextPushStageId = 'fts-v8' +export const fullTextPushStageId = 'fts-v9' diff --git a/server/core/src/plugin.ts b/server/core/src/plugin.ts index 1c6c1cfba5..1606e7f3f9 100644 --- a/server/core/src/plugin.ts +++ b/server/core/src/plugin.ts @@ -40,7 +40,8 @@ const serverCore = plugin(serverCoreId, { TriggerState: '' as Ref }, metadata: { - FrontUrl: '' as Metadata + FrontUrl: '' as Metadata, + CursorMaxTimeMS: '' as Metadata } }) diff --git a/server/elastic/src/backup.ts b/server/elastic/src/backup.ts index 3f733abb53..d6d4350a5c 100644 --- a/server/elastic/src/backup.ts +++ b/server/elastic/src/backup.ts @@ -59,6 +59,7 @@ class ElasticDataAdapter implements DbAdapter { async init (model: Tx[]): Promise {} async createIndexes (domain: Domain, config: Pick, 'indexes'>): Promise {} + async removeOldIndex (domain: Domain, deletePattern: RegExp, keepPattern: RegExp): Promise {} async close (): Promise { await this.client.close() diff --git a/server/mongo/src/storage.ts b/server/mongo/src/storage.ts index 38ba574ae7..e91315de68 100644 --- a/server/mongo/src/storage.ts +++ b/server/mongo/src/storage.ts @@ -57,6 +57,7 @@ import core, { getTypeOf } from '@hcengineering/core' import type { DbAdapter, TxAdapter } from '@hcengineering/server-core' +import serverCore from '@hcengineering/server-core' import { type AnyBulkWriteOperation, type Collection, @@ -69,6 +70,8 @@ import { } from 'mongodb' import { createHash } from 'node:crypto' import { getMongoClient, getWorkspaceDB } from './utils' +import { cutObjectArray } from '@hcengineering/core' +import { getMetadata } from '@hcengineering/platform' function translateDoc (doc: Doc): Document { return { ...doc, '%hash%': null } @@ -118,6 +121,21 @@ abstract class MongoAdapterBase implements DbAdapter { } } + async removeOldIndex (domain: Domain, deletePattern: RegExp, keepPattern: RegExp): Promise { + try { + const existingIndexes = await this.db.collection(domain).indexes() + for (const existingIndex of existingIndexes) { + const name: string = existingIndex.name + if (deletePattern.test(name) && !keepPattern.test(name)) { + console.log('removing old index', name, keepPattern) + await this.db.collection(domain).dropIndex(existingIndex.name) + } + } + } catch (err: any) { + console.error(err) + } + } + async tx (...tx: Tx[]): Promise { return {} } @@ -433,8 +451,13 @@ abstract class MongoAdapterBase implements DbAdapter { checkKeys: false, enableUtf8Validation: false }) - cursor.maxTimeMS(30000) - const res = (await cursor.toArray())[0] + cursor.maxTimeMS(parseInt(getMetadata(serverCore.metadata.CursorMaxTimeMS) ?? '30000')) + let res: Document = [] + try { + res = (await cursor.toArray())[0] + } catch (e) { + console.error('error during executing cursor in findWithPipeline', clazz, cutObjectArray(query), options, e) + } const result = res.results as WithLookup[] const total = options?.total === true ? res.totalCount?.shift()?.count ?? 0 : -1 for (const row of result) { @@ -572,10 +595,14 @@ abstract class MongoAdapterBase implements DbAdapter { } // Error in case of timeout - cursor.maxTimeMS(30000) + cursor.maxTimeMS(parseInt(getMetadata(serverCore.metadata.CursorMaxTimeMS) ?? '30000')) cursor.maxAwaitTimeMS(30000) - - const res = await cursor.toArray() + let res: T[] = [] + try { + res = await cursor.toArray() + } catch (e) { + console.error('error during executing cursor in findAll', _class, cutObjectArray(query), options, e) + } if (options?.total === true && options?.limit === undefined) { total = res.length } diff --git a/server/server/src/minio.ts b/server/server/src/minio.ts index 39164ade9a..0124b89e0e 100644 --- a/server/server/src/minio.ts +++ b/server/server/src/minio.ts @@ -56,6 +56,7 @@ class MinioBlobAdapter implements DbAdapter { async init (model: Tx[]): Promise {} async createIndexes (domain: Domain, config: Pick, 'indexes'>): Promise {} + async removeOldIndex (domain: Domain, deletePattern: RegExp, keepPattern: RegExp): Promise {} async close (): Promise {}