UBERF-7501: Copy few blobs in parallel (#5995)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-07-03 21:29:59 +07:00 committed by GitHub
parent 6756c43e4e
commit b836039903
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 68 additions and 46 deletions

View File

@ -41,6 +41,7 @@
"@hcengineering/core": "^0.6.32", "@hcengineering/core": "^0.6.32",
"@hcengineering/platform": "^0.6.11", "@hcengineering/platform": "^0.6.11",
"@hcengineering/storage": "^0.6.0", "@hcengineering/storage": "^0.6.0",
"@hcengineering/analytics": "^0.6.0",
"toposort": "^2.0.2", "toposort": "^2.0.2",
"fast-equals": "^5.0.1" "fast-equals": "^5.0.1"
}, },

View File

@ -1,3 +1,4 @@
import { Analytics } from '@hcengineering/analytics'
import core, { import core, {
Class, Class,
Client, Client,
@ -154,7 +155,13 @@ export async function tryMigrate (client: MigrationClient, plugin: string, migra
const states = client.migrateState.get(plugin) ?? new Set() const states = client.migrateState.get(plugin) ?? new Set()
for (const migration of migrations) { for (const migration of migrations) {
if (states.has(migration.state)) continue if (states.has(migration.state)) continue
await migration.func(client) try {
await migration.func(client)
} catch (err: any) {
console.error(err)
Analytics.handleError(err)
continue
}
const st: MigrationState = { const st: MigrationState = {
plugin, plugin,
state: migration.state, state: migration.state,
@ -181,7 +188,13 @@ export async function tryUpgrade (
for (const migration of migrations) { for (const migration of migrations) {
if (states.has(migration.state)) continue if (states.has(migration.state)) continue
const _client = await client() const _client = await client()
await migration.func(_client) try {
await migration.func(_client)
} catch (err: any) {
console.error(err)
Analytics.handleError(err)
continue
}
const st: Data<MigrationState> = { const st: Data<MigrationState> = {
plugin, plugin,
state: migration.state state: migration.state

View File

@ -968,13 +968,13 @@ export async function createWorkspace (
getWorkspaceId(workspaceInfo.workspace, productId), getWorkspaceId(workspaceInfo.workspace, productId),
true, true,
async (value) => { async (value) => {
await updateInfo({ createProgress: 20 + Math.round((Math.min(value, 100) / 100) * 30) }) await updateInfo({ createProgress: 20 + Math.round((Math.min(value, 100) / 100) * 70) })
}, },
true, true,
getStorageAdapter() getStorageAdapter()
) )
const modelVersion = getModelVersion() const modelVersion = getModelVersion()
await updateInfo({ createProgress: 50 }) await updateInfo({ createProgress: 90 })
// Skip tx update if version of init workspace are proper one. // Skip tx update if version of init workspace are proper one.
const skipTxUpdate = const skipTxUpdate =
@ -992,11 +992,11 @@ export async function createWorkspace (
ctxModellogger, ctxModellogger,
skipTxUpdate, skipTxUpdate,
async (value) => { async (value) => {
await updateInfo({ createProgress: Math.round(50 + (Math.min(value, 100) / 100) * 40) }) await updateInfo({ createProgress: Math.round(90 + (Math.min(value, 100) / 100) * 10) })
} }
) )
) )
await updateInfo({ createProgress: 90 }) await updateInfo({ createProgress: 99 })
} else { } else {
await childLogger.withLog('init-workspace', {}, async (ctx) => { await childLogger.withLog('init-workspace', {}, async (ctx) => {
await initModel(ctx, getTransactor(), wsId, txes, migrationOperation, ctxModellogger, async (value) => { await initModel(ctx, getTransactor(), wsId, txes, migrationOperation, ctxModellogger, async (value) => {

View File

@ -46,6 +46,7 @@
"@hcengineering/client-resources": "^0.6.27", "@hcengineering/client-resources": "^0.6.27",
"@hcengineering/client": "^0.6.18", "@hcengineering/client": "^0.6.18",
"@hcengineering/model": "^0.6.11", "@hcengineering/model": "^0.6.11",
"@hcengineering/analytics": "^0.6.0",
"tar-stream": "^2.2.0", "tar-stream": "^2.2.0",
"@hcengineering/server-tool": "^0.6.0", "@hcengineering/server-tool": "^0.6.0",
"@hcengineering/server-core": "^0.6.1" "@hcengineering/server-core": "^0.6.1"

View File

@ -43,6 +43,7 @@ import { Writable } from 'stream'
import { extract, Pack, pack } from 'tar-stream' import { extract, Pack, pack } from 'tar-stream'
import { createGunzip, gunzipSync, gzipSync } from 'zlib' import { createGunzip, gunzipSync, gzipSync } from 'zlib'
import { BackupStorage } from './storage' import { BackupStorage } from './storage'
import { Analytics } from '@hcengineering/analytics'
export * from './storage' export * from './storage'
const dataBlobSize = 50 * 1024 * 1024 const dataBlobSize = 50 * 1024 * 1024
@ -231,7 +232,7 @@ export async function cloneWorkspace (
clearTime: boolean = true, clearTime: boolean = true,
progress: (value: number) => Promise<void>, progress: (value: number) => Promise<void>,
skipFullText: boolean, skipFullText: boolean,
storageAdapter?: StorageAdapter storageAdapter: StorageAdapter
): Promise<void> { ): Promise<void> {
await ctx.with( await ctx.with(
'clone-workspace', 'clone-workspace',
@ -255,10 +256,6 @@ export async function cloneWorkspace (
admin: 'true' admin: 'true'
})) as unknown as CoreClient & BackupClient })) as unknown as CoreClient & BackupClient
) )
const blobClientSource = new BlobClient(transactorUrl, sourceWorkspaceId)
const blobClientTarget = new BlobClient(transactorUrl, targetWorkspaceId)
try { try {
const domains = sourceConnection const domains = sourceConnection
.getHierarchy() .getHierarchy()
@ -290,6 +287,7 @@ export async function cloneWorkspace (
const needRetrieveChunks: Ref<Doc>[][] = [] const needRetrieveChunks: Ref<Doc>[][] = []
let processed = 0 let processed = 0
let domainProgress = 0
let st = Date.now() let st = Date.now()
// Load all digest from collection. // Load all digest from collection.
await ctx.with('retrieve-domain-info', { domain: c }, async (ctx) => { await ctx.with('retrieve-domain-info', { domain: c }, async (ctx) => {
@ -351,12 +349,12 @@ export async function cloneWorkspace (
if (clearTime) { if (clearTime) {
docs = prepareClonedDocuments(docs, sourceConnection, skipFullText) docs = prepareClonedDocuments(docs, sourceConnection, skipFullText)
} }
const executor = new RateLimiter(10)
for (const d of docs) { for (const d of docs) {
if (d._class === core.class.Blob) { if (d._class === core.class.Blob) {
const blob = d as Blob const blob = d as Blob
const blobs: Buffer[] = [] await executor.exec(async () => {
try { try {
if (storageAdapter !== undefined) {
ctx.info('clone blob', { name: blob._id, contentType: blob.contentType }) ctx.info('clone blob', { name: blob._id, contentType: blob.contentType })
const readable = await storageAdapter.get(ctx, sourceWorkspaceId, blob._id) const readable = await storageAdapter.get(ctx, sourceWorkspaceId, blob._id)
const passThrue = new PassThrough() const passThrue = new PassThrough()
@ -369,29 +367,18 @@ export async function cloneWorkspace (
blob.contentType, blob.contentType,
blob.size blob.size
) )
} else { } catch (err: any) {
ctx.info('clone blob', { name: blob._id, contentType: blob.contentType }) Analytics.handleError(err)
await ctx.with('download-blob', { contentType: blob.contentType }, async (ctx) => { console.error(err)
await blobClientSource.writeTo(ctx, blob._id, blob.size, {
write: (b, cb) => {
blobs.push(b)
cb()
},
end: (cb) => {
cb()
}
})
})
await ctx.with('upload-blob', { contentType: blob.contentType }, async (ctx) => {
const buffer = Buffer.concat(blobs)
await blobClientTarget.upload(ctx, blob._id, buffer.length, blob.contentType, buffer)
})
} }
} catch (err: any) { domainProgress++
console.error(err) await progress((100 / domains.length) * i + (100 / domains.length / processed) * domainProgress)
} })
} else {
domainProgress++
} }
} }
await executor.waitProcessing()
await ctx.with( await ctx.with(
'upload-docs', 'upload-docs',
{}, {},
@ -400,8 +387,10 @@ export async function cloneWorkspace (
}, },
{ length: docs.length } { length: docs.length }
) )
await progress((100 / domains.length) * i + (100 / domains.length / processed) * domainProgress)
} catch (err: any) { } catch (err: any) {
console.log(err) console.log(err)
Analytics.handleError(err)
// Put back. // Put back.
needRetrieveChunks.push(needRetrieve) needRetrieveChunks.push(needRetrieve)
continue continue
@ -414,6 +403,7 @@ export async function cloneWorkspace (
} }
} catch (err: any) { } catch (err: any) {
console.error(err) console.error(err)
Analytics.handleError(err)
} finally { } finally {
ctx.info('end clone') ctx.info('end clone')
await ctx.with('close-source', {}, async (ctx) => { await ctx.with('close-source', {}, async (ctx) => {

View File

@ -1,3 +1,4 @@
import { Analytics } from '@hcengineering/analytics'
import type { import type {
Doc, Doc,
Domain, Domain,
@ -5,24 +6,32 @@ import type {
FieldIndex, FieldIndex,
Hierarchy, Hierarchy,
MeasureContext, MeasureContext,
ModelDb ModelDb,
WorkspaceId
} from '@hcengineering/core' } from '@hcengineering/core'
import core, { DOMAIN_MODEL, IndexKind, IndexOrder } from '@hcengineering/core' import core, { DOMAIN_MODEL, IndexKind, IndexOrder } from '@hcengineering/core'
import { deepEqual } from 'fast-equals' import { deepEqual } from 'fast-equals'
import type { DomainHelper, DomainHelperOperations } from '../adapter' import type { DomainHelper, DomainHelperOperations } from '../adapter'
import { Analytics } from '@hcengineering/analytics'
export class DomainIndexHelperImpl implements DomainHelper { export class DomainIndexHelperImpl implements DomainHelper {
domains = new Map<Domain, Set<string | FieldIndex<Doc>>>() domains = new Map<Domain, Set<string | FieldIndex<Doc>>>()
domainConfigurations: DomainIndexConfiguration[] = [] domainConfigurations: DomainIndexConfiguration[] = []
constructor ( constructor (
readonly ctx: MeasureContext,
readonly hierarchy: Hierarchy, readonly hierarchy: Hierarchy,
readonly model: ModelDb readonly model: ModelDb,
readonly workspaceId: WorkspaceId
) { ) {
const classes = model.findAllSync(core.class.Class, {}) const classes = model.findAllSync(core.class.Class, {})
this.domainConfigurations = try {
model.findAllSync<DomainIndexConfiguration>(core.class.DomainIndexConfiguration, {}) ?? [] this.domainConfigurations =
model.findAllSync<DomainIndexConfiguration>(core.class.DomainIndexConfiguration, {}) ?? []
} catch (err: any) {
this.domainConfigurations = []
Analytics.handleError(err)
ctx.error('failed to find domain index configuration', { err })
}
this.domains = new Map<Domain, Set<string | FieldIndex<Doc>>>() this.domains = new Map<Domain, Set<string | FieldIndex<Doc>>>()
// Find all domains and indexed fields inside // Find all domains and indexed fields inside
@ -81,7 +90,7 @@ export class DomainIndexHelperImpl implements DomainHelper {
if (forceCreate && !exists) { if (forceCreate && !exists) {
await operations.create(domain) await operations.create(domain)
console.log('collection will be created', domain) ctx.info('collection will be created', domain)
exists = true exists = true
} }
if (!exists) { if (!exists) {

View File

@ -163,7 +163,7 @@ export async function createServerStorage (
) )
} }
const domainHelper = new DomainIndexHelperImpl(hierarchy, modelDb) const domainHelper = new DomainIndexHelperImpl(metrics, hierarchy, modelDb, conf.workspace)
return new TServerStorage( return new TServerStorage(
conf.domains, conf.domains,

View File

@ -155,9 +155,16 @@ export async function initModel (
await progress(30) await progress(30)
// Create update indexes // Create update indexes
await createUpdateIndexes(ctx, connection, db, logger, async (value) => { await createUpdateIndexes(
await progress(30 + (Math.min(value, 100) / 100) * 70) ctx,
}) connection,
db,
logger,
async (value) => {
await progress(30 + (Math.min(value, 100) / 100) * 70)
},
workspaceId
)
await progress(100) await progress(100)
} catch (e: any) { } catch (e: any) {
logger.error('error', { error: e }) logger.error('error', { error: e })
@ -403,9 +410,10 @@ async function createUpdateIndexes (
connection: CoreClient, connection: CoreClient,
db: Db, db: Db,
logger: ModelLogger, logger: ModelLogger,
progress: (value: number) => Promise<void> progress: (value: number) => Promise<void>,
workspaceId: WorkspaceId
): Promise<void> { ): Promise<void> {
const domainHelper = new DomainIndexHelperImpl(connection.getHierarchy(), connection.getModel()) const domainHelper = new DomainIndexHelperImpl(ctx, connection.getHierarchy(), connection.getModel(), workspaceId)
const dbHelper = new DBCollectionHelper(db) const dbHelper = new DBCollectionHelper(db)
await dbHelper.init() await dbHelper.init()
let completed = 0 let completed = 0