fix: split tsvector migration, add configurable DB timeout, reorder 1.19 commands (#18614)
## Summary - **Split tsvector migration into individual per-field transactions**: each tsvector field now runs in its own `workspaceMigrationRunnerService.run()` call (its own DB transaction). Since STORED generated columns trigger full table rewrites, a timeout on one large table (e.g. `timelineActivity`) no longer rolls back the others. Each field has its own idempotency check, so the migration is fully resumable. - **Add configurable `DATABASE_STATEMENT_TIMEOUT_MS` env var** (default 15000ms): controls the `query_timeout` on the core datasource globally, allowing operators to raise it for long-running upgrade commands without code changes. - **Reorder 1.19 upgrade commands**: move `fixRoleAndAgentUniversalIdentifiersCommand` first so that subsequent commands see corrected universal identifiers.
This commit is contained in:
+70
-23
@@ -1,6 +1,7 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@@ -11,11 +12,33 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { type WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service';
|
||||
|
||||
const FIRST_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
ADD_MISSING_SYSTEM_FIELDS_TO_STANDARD_OBJECTS_1771420702241.actions[0]
|
||||
.flatEntity.universalIdentifier;
|
||||
const { applicationUniversalIdentifier, actions: allActions } =
|
||||
ADD_MISSING_SYSTEM_FIELDS_TO_STANDARD_OBJECTS_1771420702241;
|
||||
|
||||
const NON_TS_VECTOR_MIGRATION: WorkspaceMigration = {
|
||||
applicationUniversalIdentifier,
|
||||
actions: allActions.filter(
|
||||
(action) => action.flatEntity.type !== FieldMetadataType.TS_VECTOR,
|
||||
),
|
||||
};
|
||||
|
||||
const TS_VECTOR_INDIVIDUAL_MIGRATIONS = allActions
|
||||
.filter((action) => action.flatEntity.type === FieldMetadataType.TS_VECTOR)
|
||||
.map((action) => ({
|
||||
universalIdentifier: action.flatEntity.universalIdentifier,
|
||||
fieldName: action.flatEntity.name,
|
||||
objectIdentifier: action.flatEntity.objectMetadataUniversalIdentifier,
|
||||
migration: {
|
||||
applicationUniversalIdentifier,
|
||||
actions: [action],
|
||||
} satisfies WorkspaceMigration,
|
||||
}));
|
||||
|
||||
const FIRST_NON_TS_VECTOR_UNIVERSAL_IDENTIFIER =
|
||||
allActions[0].flatEntity.universalIdentifier;
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-19:add-missing-system-fields-to-standard-objects',
|
||||
@@ -34,19 +57,17 @@ export class AddMissingSystemFieldsToStandardObjectsCommand extends ActiveOrSusp
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
// The entire migration runs in a single transaction, so checking the first
|
||||
// field is enough to know whether the migration has already been applied.
|
||||
// In the future we will maintain a list of passed migrations
|
||||
private async hasAlreadyRun(workspaceId: string): Promise<boolean> {
|
||||
private async hasFieldBeenCreated(
|
||||
workspaceId: string,
|
||||
universalIdentifier: string,
|
||||
): Promise<boolean> {
|
||||
const { flatFieldMetadataMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatFieldMetadataMaps',
|
||||
]);
|
||||
|
||||
return isDefined(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier[
|
||||
FIRST_FIELD_UNIVERSAL_IDENTIFIER
|
||||
],
|
||||
flatFieldMetadataMaps.byUniversalIdentifier[universalIdentifier],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,25 +83,51 @@ export class AddMissingSystemFieldsToStandardObjectsCommand extends ActiveOrSusp
|
||||
|
||||
if (dryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would add ${ADD_MISSING_SYSTEM_FIELDS_TO_STANDARD_OBJECTS_1771420702241.actions.length} missing system fields to standard objects in workspace ${workspaceId}. Skipping.`,
|
||||
`[DRY RUN] Would add ${NON_TS_VECTOR_MIGRATION.actions.length} non-tsVector fields and ${TS_VECTOR_INDIVIDUAL_MIGRATIONS.length} tsVector fields to standard objects in workspace ${workspaceId}. Skipping.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.hasAlreadyRun(workspaceId)) {
|
||||
this.logger.log(
|
||||
`Migration already applied for workspace ${workspaceId}, skipping.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.workspaceMigrationRunnerService.run({
|
||||
const nonTsVectorAlreadyRan = await this.hasFieldBeenCreated(
|
||||
workspaceId,
|
||||
workspaceMigration:
|
||||
ADD_MISSING_SYSTEM_FIELDS_TO_STANDARD_OBJECTS_1771420702241,
|
||||
});
|
||||
FIRST_NON_TS_VECTOR_UNIVERSAL_IDENTIFIER,
|
||||
);
|
||||
|
||||
if (!nonTsVectorAlreadyRan) {
|
||||
this.logger.log(
|
||||
`Adding ${NON_TS_VECTOR_MIGRATION.actions.length} non-tsVector fields (position, createdBy, updatedBy) in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
await this.workspaceMigrationRunnerService.run({
|
||||
workspaceId,
|
||||
workspaceMigration: NON_TS_VECTOR_MIGRATION,
|
||||
});
|
||||
} else {
|
||||
this.logger.log(
|
||||
`Non-tsVector fields already exist in workspace ${workspaceId}, skipping.`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const tsVectorEntry of TS_VECTOR_INDIVIDUAL_MIGRATIONS) {
|
||||
const alreadyCreated = await this.hasFieldBeenCreated(
|
||||
workspaceId,
|
||||
tsVectorEntry.universalIdentifier,
|
||||
);
|
||||
|
||||
if (alreadyCreated) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Adding tsVector field ${tsVectorEntry.fieldName} on object ${tsVectorEntry.objectIdentifier} in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
await this.workspaceMigrationRunnerService.run({
|
||||
workspaceId,
|
||||
workspaceMigration: tsVectorEntry.migration,
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully added missing system fields to standard objects in workspace ${workspaceId}`,
|
||||
|
||||
+1
-1
@@ -124,11 +124,11 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
];
|
||||
|
||||
const commands_1190: VersionCommands = [
|
||||
this.fixRoleAndAgentUniversalIdentifiersCommand,
|
||||
this.backfillSystemFieldsIsSystemCommand,
|
||||
this.addMissingSystemFieldsToStandardObjectsCommand,
|
||||
this.backfillMessageChannelMessageAssociationMessageFolderCommand,
|
||||
this.backfillMissingStandardViewsCommand,
|
||||
this.fixRoleAndAgentUniversalIdentifiersCommand,
|
||||
this.seedServerIdCommand,
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user