Fix backup all and disable full check for migration

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2025-03-05 14:19:03 +07:00
parent 059cab66fb
commit 622844c42f
No known key found for this signature in database
GPG Key ID: BD80F68D68D8F7F2
4 changed files with 80 additions and 56 deletions

View File

@ -25,7 +25,15 @@ import accountPlugin, {
type AccountDB type AccountDB
} from '@hcengineering/account' } from '@hcengineering/account'
import { setMetadata } from '@hcengineering/platform' import { setMetadata } from '@hcengineering/platform'
import { backup, createFileBackupStorage, createStorageBackupStorage, restore } from '@hcengineering/server-backup' import {
backup,
backupFind,
checkBackupIntegrity,
compactBackup,
createFileBackupStorage,
createStorageBackupStorage,
restore
} from '@hcengineering/server-backup'
import serverClientPlugin, { getAccountClient } from '@hcengineering/server-client' import serverClientPlugin, { getAccountClient } from '@hcengineering/server-client'
import { import {
registerAdapterFactory, registerAdapterFactory,
@ -36,7 +44,7 @@ import {
setAdapterSecurity, setAdapterSecurity,
sharedPipelineContextVars sharedPipelineContextVars
} from '@hcengineering/server-pipeline' } from '@hcengineering/server-pipeline'
import serverToken from '@hcengineering/server-token' import serverToken, { generateToken } from '@hcengineering/server-token'
import { createWorkspace, upgradeWorkspace } from '@hcengineering/workspace-service' import { createWorkspace, upgradeWorkspace } from '@hcengineering/workspace-service'
import { buildStorageFromConfig, createStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage' import { buildStorageFromConfig, createStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage'
@ -47,12 +55,15 @@ import {
AccountRole, AccountRole,
MeasureMetricsContext, MeasureMetricsContext,
metricsToString, metricsToString,
type PersonId, systemAccountUuid,
type WorkspaceUuid,
type Data, type Data,
type Doc,
type PersonId,
type Ref,
type Tx, type Tx,
type Version, type Version,
type WorkspaceDataId type WorkspaceDataId,
type WorkspaceUuid
} from '@hcengineering/core' } from '@hcengineering/core'
import { consoleModelLogger, type MigrateOperation } from '@hcengineering/model' import { consoleModelLogger, type MigrateOperation } from '@hcengineering/model'
import { import {
@ -76,8 +87,8 @@ import { getAccountDBUrl, getMongoDBUrl } from './__start'
import { changeConfiguration } from './configuration' import { changeConfiguration } from './configuration'
import { reindexWorkspace } from './fulltext' import { reindexWorkspace } from './fulltext'
import { getToolToken, getWorkspace, getWorkspaceTransactorEndpoint } from './utils'
import { moveAccountDbFromMongoToPG } from './db' import { moveAccountDbFromMongoToPG } from './db'
import { getToolToken, getWorkspace, getWorkspaceTransactorEndpoint } from './utils'
const colorConstants = { const colorConstants = {
colorRed: '\u001b[31m', colorRed: '\u001b[31m',
@ -958,30 +969,30 @@ export function devTool (
}) })
} }
) )
// program program
// .command('backup-find <dirName> <fileId>') .command('backup-find <dirName> <fileId>')
// .description('dump workspace transactions and minio resources') .description('dump workspace transactions and minio resources')
// .option('-d, --domain <domain>', 'Check only domain') .option('-d, --domain <domain>', 'Check only domain')
// .action(async (dirName: string, fileId: string, cmd: { domain: string | undefined }) => { .action(async (dirName: string, fileId: string, cmd: { domain: string | undefined }) => {
// const storage = await createFileBackupStorage(dirName) const storage = await createFileBackupStorage(dirName)
// await backupFind(storage, fileId as unknown as Ref<Doc>, cmd.domain) await backupFind(storage, fileId as unknown as Ref<Doc>, cmd.domain)
// }) })
// program program
// .command('backup-compact <dirName>') .command('backup-compact <dirName>')
// .description('Compact a given backup, will create one snapshot clean unused resources') .description('Compact a given backup, will create one snapshot clean unused resources')
// .option('-f, --force', 'Force compact.', false) .option('-f, --force', 'Force compact.', false)
// .action(async (dirName: string, cmd: { force: boolean }) => { .action(async (dirName: string, cmd: { force: boolean }) => {
// const storage = await createFileBackupStorage(dirName) const storage = await createFileBackupStorage(dirName)
// await compactBackup(toolCtx, storage, cmd.force) await compactBackup(toolCtx, storage, cmd.force)
// }) })
// program program
// .command('backup-check <dirName>') .command('backup-check <dirName>')
// .description('Compact a given backup, will create one snapshot clean unused resources') .description('Compact a given backup, will create one snapshot clean unused resources')
// .action(async (dirName: string, cmd: any) => { .action(async (dirName: string, cmd: any) => {
// const storage = await createFileBackupStorage(dirName) const storage = await createFileBackupStorage(dirName)
// await checkBackupIntegrity(toolCtx, storage) await checkBackupIntegrity(toolCtx, storage)
// }) })
program program
.command('backup-check-all') .command('backup-check-all')
@ -999,23 +1010,23 @@ export function devTool (
const skipWorkspaces = new Set(cmd.skip.split(',').map((it) => it.trim())) const skipWorkspaces = new Set(cmd.skip.split(',').map((it) => it.trim()))
const token = generateToken(systemAccountEmail, getWorkspaceId('')) const token = generateToken(systemAccountUuid, '' as WorkspaceUuid)
const workspaces = (await listAccountWorkspaces(token, cmd.region)) const workspaces = (await getAccountClient(token).listWorkspaces(cmd.region))
.sort((a, b) => { .sort((a, b) => {
const bsize = b.backupInfo?.backupSize ?? 0 const bsize = b.backupInfo?.backupSize ?? 0
const asize = a.backupInfo?.backupSize ?? 0 const asize = a.backupInfo?.backupSize ?? 0
return bsize - asize return bsize - asize
}) })
.filter((it) => (cmd.workspace === '' || cmd.workspace === it.workspace) && !skipWorkspaces.has(it.workspace)) .filter((it) => (cmd.workspace === '' || cmd.workspace === it.url) && !skipWorkspaces.has(it.url))
const backupStorageConfig = storageConfigFromEnv(process.env.STORAGE) const backupStorageConfig = storageConfigFromEnv(process.env.STORAGE)
const storageAdapter = createStorageFromConfig(backupStorageConfig.storages[0]) const storageAdapter = createStorageFromConfig(backupStorageConfig.storages[0])
for (const ws of workspaces) { for (const ws of workspaces) {
const lastVisitDays = Math.floor((Date.now() - ws.lastVisit) / 1000 / 3600 / 24) const lastVisitDays = Math.floor((Date.now() - (ws.lastVisit ?? 0)) / 1000 / 3600 / 24)
toolCtx.warn('--- checking workspace backup', { toolCtx.warn('--- checking workspace backup', {
url: ws.workspaceUrl, url: ws.url,
id: ws.workspace, id: ws.uuid,
lastVisitDays, lastVisitDays,
backupSize: ws.backupInfo?.blobsSize ?? 0, backupSize: ws.backupInfo?.blobsSize ?? 0,
mode: ws.mode mode: ws.mode
@ -1030,8 +1041,12 @@ export function devTool (
const storage = await createStorageBackupStorage( const storage = await createStorageBackupStorage(
toolCtx, toolCtx,
storageAdapter, storageAdapter,
getWorkspaceId(bucketName), {
ws.workspace uuid: 'backup' as WorkspaceUuid,
url: 'backup',
dataId: bucketName as WorkspaceDataId
},
ws.dataId ?? ws.uuid
) )
await checkBackupIntegrity(toolCtx, storage) await checkBackupIntegrity(toolCtx, storage)
} catch (err: any) { } catch (err: any) {
@ -1042,7 +1057,7 @@ export function devTool (
time: ed - st time: ed - st
}) })
} catch (err: any) { } catch (err: any) {
toolCtx.error('REstore of f workspace failedarchive workspace', { workspace: ws.workspace }) toolCtx.error('Restore of f workspace failedarchive workspace', { workspace: ws.url })
} }
} }
await storageAdapter.close() await storageAdapter.close()

View File

@ -16,15 +16,16 @@
import core, { import core, {
DOMAIN_TX, DOMAIN_TX,
getWorkspaceId,
type BackupClient, type BackupClient,
type BaseWorkspaceInfo,
type Class, type Class,
type Client as CoreClient, type Client as CoreClient,
type Doc, type Doc,
type MeasureContext, type MeasureContext,
type Ref, type Ref,
type Tx, type Tx,
type WorkspaceDataId,
type WorkspaceIds,
type WorkspaceInfoWithStatus,
type WorkspaceUuid type WorkspaceUuid
} from '@hcengineering/core' } from '@hcengineering/core'
import { getMongoClient, getWorkspaceMongoDB } from '@hcengineering/mongo' import { getMongoClient, getWorkspaceMongoDB } from '@hcengineering/mongo'
@ -120,7 +121,7 @@ export async function backupRestore (
ctx: MeasureContext, ctx: MeasureContext,
dbURL: string, dbURL: string,
bucketName: string, bucketName: string,
workspace: BaseWorkspaceInfo, workspace: WorkspaceInfoWithStatus,
pipelineFactoryFactory: (mongoUrl: string, storage: StorageAdapter) => PipelineFactory, pipelineFactoryFactory: (mongoUrl: string, storage: StorageAdapter) => PipelineFactory,
skipDomains: string[] skipDomains: string[]
): Promise<boolean> { ): Promise<boolean> {
@ -144,17 +145,20 @@ export async function backupRestore (
const storage = await createStorageBackupStorage( const storage = await createStorageBackupStorage(
ctx, ctx,
storageAdapter, storageAdapter,
getWorkspaceId(bucketName), {
workspace.workspace uuid: 'backup' as WorkspaceUuid,
url: bucketName,
dataId: bucketName as WorkspaceDataId
},
workspace.dataId ?? workspace.uuid
) )
const wsUrl: WorkspaceIdWithUrl = { const wsUrl: WorkspaceIds = {
name: workspace.workspace,
uuid: workspace.uuid, uuid: workspace.uuid,
workspaceName: workspace.workspaceName ?? '', dataId: workspace.dataId,
workspaceUrl: workspace.workspaceUrl ?? '' url: workspace.url
} }
const result: boolean = await ctx.with('restore', { workspace: workspace.workspace }, (ctx) => const result: boolean = await ctx.with('restore', { workspace: workspace.url }, (ctx) =>
restore(ctx, '', getWorkspaceId(workspace.workspace), storage, { restore(ctx, '', wsUrl, storage, {
date: -1, date: -1,
skip: new Set(skipDomains), skip: new Set(skipDomains),
recheck: false, recheck: false,

View File

@ -2062,7 +2062,7 @@ export async function restore (
if (docsToAdd.size === 0) { if (docsToAdd.size === 0) {
break break
} }
ctx.info('processing', { storageFile: sf, processed, workspace: workspaceId.name }) ctx.info('processing', { storageFile: sf, processed, workspace: wsIds.url })
try { try {
const readStream = await storage.load(sf) const readStream = await storage.load(sf)
const ex = extract() const ex = extract()
@ -2107,7 +2107,7 @@ export async function restore (
try { try {
doc = JSON.parse(bf.toString()) as Doc doc = JSON.parse(bf.toString()) as Doc
} catch (err) { } catch (err) {
ctx.warn('failed to parse blob metadata', { name, workspace: workspaceId.name, err }) ctx.warn('failed to parse blob metadata', { name, workspace: wsIds.url, err })
next() next()
return return
} }
@ -2165,7 +2165,7 @@ export async function restore (
await endPromise await endPromise
} catch (err: any) { } catch (err: any) {
ctx.error('failed to processing', { storageFile: sf, processed, workspace: workspaceId.name }) ctx.error('failed to processing', { storageFile: sf, processed, workspace: wsIds.url })
} }
} }
} }

View File

@ -479,7 +479,7 @@ export class WorkspaceWorker {
await sendEvent('archiving-backup-started', 0) await sendEvent('archiving-backup-started', 0)
await this.sendTransactorMaitenance(token, workspace.uuid) await this.sendTransactorMaitenance(token, workspace.uuid)
if (await this.doBackup(ctx, workspace, opt)) { if (await this.doBackup(ctx, workspace, opt, true)) {
await sendEvent('archiving-backup-done', 100) await sendEvent('archiving-backup-done', 100)
} }
break break
@ -516,7 +516,7 @@ export class WorkspaceWorker {
case 'migration-backup': case 'migration-backup':
await sendEvent('migrate-backup-started', 0) await sendEvent('migrate-backup-started', 0)
await this.sendTransactorMaitenance(token, workspace.uuid) await this.sendTransactorMaitenance(token, workspace.uuid)
if (await this.doBackup(ctx, workspace, opt)) { if (await this.doBackup(ctx, workspace, opt, false)) {
await sendEvent('migrate-backup-done', 100) await sendEvent('migrate-backup-done', 100)
} }
break break
@ -551,7 +551,12 @@ export class WorkspaceWorker {
} }
} }
private async doBackup (ctx: MeasureContext, workspace: WorkspaceInfoWithStatus, opt: WorkspaceOptions): Promise<boolean> { private async doBackup (
ctx: MeasureContext,
workspace: WorkspaceInfoWithStatus,
opt: WorkspaceOptions,
doFullCheck: boolean
): Promise<boolean> {
if (opt.backup === undefined) { if (opt.backup === undefined) {
return false return false
} }
@ -619,7 +624,7 @@ export class WorkspaceWorker {
50000, 50000,
['blob'], ['blob'],
sharedPipelineContextVars, sharedPipelineContextVars,
true, // Do a full check doFullCheck, // Do full check based on config, do not do for migration, it is to slow, will perform before migration.
(_p: number) => { (_p: number) => {
if (progress !== Math.round(_p)) { if (progress !== Math.round(_p)) {
progress = Math.round(_p) progress = Math.round(_p)