Merge pull request #10377 from thingsboard/fix/vc-performance
VC restore: optional rollback on error to vastly increase performance
This commit is contained in:
commit
20c4ec1917
@ -101,7 +101,11 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
|
|||||||
ctx.putInternalId(exportData.getExternalId(), importResult.getSavedEntity().getId());
|
ctx.putInternalId(exportData.getExternalId(), importResult.getSavedEntity().getId());
|
||||||
|
|
||||||
ctx.addReferenceCallback(exportData.getExternalId(), importResult.getSaveReferencesCallback());
|
ctx.addReferenceCallback(exportData.getExternalId(), importResult.getSaveReferencesCallback());
|
||||||
|
if (ctx.isRollbackOnError()) {
|
||||||
ctx.addEventCallback(importResult.getSendEventsCallback());
|
ctx.addEventCallback(importResult.getSendEventsCallback());
|
||||||
|
} else {
|
||||||
|
importResult.getSendEventsCallback().run();
|
||||||
|
}
|
||||||
return importResult;
|
return importResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
|
|||||||
import org.thingsboard.server.common.data.id.HasId;
|
import org.thingsboard.server.common.data.id.HasId;
|
||||||
import org.thingsboard.server.common.data.id.TenantId;
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
import org.thingsboard.server.common.data.page.PageData;
|
import org.thingsboard.server.common.data.page.PageData;
|
||||||
|
import org.thingsboard.server.common.data.page.PageDataIterable;
|
||||||
import org.thingsboard.server.common.data.page.PageLink;
|
import org.thingsboard.server.common.data.page.PageLink;
|
||||||
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
|
||||||
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
|
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
|
||||||
@ -89,6 +90,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -246,16 +248,18 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
|
|||||||
switch (request.getType()) {
|
switch (request.getType()) {
|
||||||
case SINGLE_ENTITY: {
|
case SINGLE_ENTITY: {
|
||||||
SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request;
|
SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request;
|
||||||
|
ctx.setRollbackOnError(true);
|
||||||
VersionLoadConfig config = versionLoadRequest.getConfig();
|
VersionLoadConfig config = versionLoadRequest.getConfig();
|
||||||
ListenableFuture<EntityExportData> future = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), versionLoadRequest.getExternalEntityId());
|
ListenableFuture<EntityExportData> future = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), versionLoadRequest.getExternalEntityId());
|
||||||
DonAsynchron.withCallback(future,
|
DonAsynchron.withCallback(future,
|
||||||
entityData -> doInTemplate(ctx, request, c -> loadSingleEntity(c, config, entityData)),
|
entityData -> load(ctx, request, c -> loadSingleEntity(c, config, entityData)),
|
||||||
e -> processLoadError(ctx, e), executor);
|
e -> processLoadError(ctx, e), executor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ENTITY_TYPE: {
|
case ENTITY_TYPE: {
|
||||||
EntityTypeVersionLoadRequest versionLoadRequest = (EntityTypeVersionLoadRequest) request;
|
EntityTypeVersionLoadRequest versionLoadRequest = (EntityTypeVersionLoadRequest) request;
|
||||||
executor.submit(() -> doInTemplate(ctx, request, c -> loadMultipleEntities(c, versionLoadRequest)));
|
ctx.setRollbackOnError(versionLoadRequest.isRollbackOnError());
|
||||||
|
executor.submit(() -> load(ctx, request, c -> loadMultipleEntities(c, versionLoadRequest)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -265,19 +269,24 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
|
|||||||
return ctx.getRequestId();
|
return ctx.getRequestId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private <R> VersionLoadResult doInTemplate(EntitiesImportCtx ctx, VersionLoadRequest request, Function<EntitiesImportCtx, VersionLoadResult> function) {
|
private <R> VersionLoadResult load(EntitiesImportCtx ctx, VersionLoadRequest request, Function<EntitiesImportCtx, VersionLoadResult> loadFunction) {
|
||||||
try {
|
try {
|
||||||
VersionLoadResult result = transactionTemplate.execute(status -> {
|
VersionLoadResult result;
|
||||||
|
if (ctx.isRollbackOnError()) {
|
||||||
|
result = transactionTemplate.execute(status -> {
|
||||||
try {
|
try {
|
||||||
return function.apply(ctx);
|
return loadFunction.apply(ctx);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e); // to prevent UndeclaredThrowableException
|
throw new RuntimeException(e); // to prevent UndeclaredThrowableException
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (ThrowingRunnable throwingRunnable : ctx.getEventCallbacks()) {
|
for (ThrowingRunnable eventCallback : ctx.getEventCallbacks()) {
|
||||||
throwingRunnable.run();
|
eventCallback.run();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = loadFunction.apply(ctx);
|
||||||
}
|
}
|
||||||
result.setDone(true);
|
result.setDone(true);
|
||||||
return cachePut(ctx.getRequestId(), result);
|
return cachePut(ctx.getRequestId(), result);
|
||||||
@ -324,7 +333,6 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
|
|||||||
sw.startNew("Entities " + entityType.name());
|
sw.startNew("Entities " + entityType.name());
|
||||||
ctx.setSettings(getEntityImportSettings(request, entityType));
|
ctx.setSettings(getEntityImportSettings(request, entityType));
|
||||||
importEntities(ctx, entityType);
|
importEntities(ctx, entityType);
|
||||||
persistToCache(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sw.startNew("Reimport");
|
sw.startNew("Reimport");
|
||||||
@ -336,7 +344,6 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
|
|||||||
.filter(entityType -> request.getEntityTypes().get(entityType).isRemoveOtherEntities())
|
.filter(entityType -> request.getEntityTypes().get(entityType).isRemoveOtherEntities())
|
||||||
.sorted(exportImportService.getEntityTypeComparatorForImport().reversed())
|
.sorted(exportImportService.getEntityTypeComparatorForImport().reversed())
|
||||||
.forEach(entityType -> removeOtherEntities(ctx, entityType));
|
.forEach(entityType -> removeOtherEntities(ctx, entityType));
|
||||||
persistToCache(ctx);
|
|
||||||
|
|
||||||
sw.startNew("References and Relations");
|
sw.startNew("References and Relations");
|
||||||
exportImportService.saveReferencesAndRelations(ctx);
|
exportImportService.saveReferencesAndRelations(ctx);
|
||||||
@ -389,6 +396,8 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
|
|||||||
ctx.getImportedEntities().computeIfAbsent(entityType, t -> new HashSet<>())
|
ctx.getImportedEntities().computeIfAbsent(entityType, t -> new HashSet<>())
|
||||||
.add(importResult.getSavedEntity().getId());
|
.add(importResult.getSavedEntity().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
persistToCache(ctx);
|
||||||
log.debug("Imported {} pack ({}) for tenant {}", entityType, entityDataList.size(), ctx.getTenantId());
|
log.debug("Imported {} pack ({}) for tenant {}", entityType, entityDataList.size(), ctx.getTenantId());
|
||||||
offset += limit;
|
offset += limit;
|
||||||
} while (entityDataList.size() == limit);
|
} while (entityDataList.size() == limit);
|
||||||
@ -413,17 +422,34 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeOtherEntities(EntitiesImportCtx ctx, EntityType entityType) {
|
private void removeOtherEntities(EntitiesImportCtx ctx, EntityType entityType) {
|
||||||
DaoUtil.processInBatches(pageLink -> {
|
var entities = new PageDataIterable<>(link -> exportableEntitiesService.findEntitiesIdsByTenantId(ctx.getTenantId(), entityType, link), 100);
|
||||||
return exportableEntitiesService.findEntitiesByTenantId(ctx.getTenantId(), entityType, pageLink);
|
Set<EntityId> toRemove = new HashSet<>();
|
||||||
}, 100, entity -> {
|
for (EntityId entityId : entities) {
|
||||||
if (ctx.getImportedEntities().get(entityType) == null || !ctx.getImportedEntities().get(entityType).contains(entity.getId())) {
|
if (ctx.getImportedEntities().get(entityType) == null || !ctx.getImportedEntities().get(entityType).contains(entityId)) {
|
||||||
exportableEntitiesService.removeById(ctx.getTenantId(), entity.getId());
|
toRemove.add(entityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.addEventCallback(() -> logEntityActionService.logEntityAction(ctx.getTenantId(), entity.getId(), entity, null,
|
for (EntityId entityId : toRemove) {
|
||||||
ActionType.DELETED, ctx.getUser()));
|
ExportableEntity<EntityId> entity = exportableEntitiesService.findEntityById(entityId);
|
||||||
|
exportableEntitiesService.removeById(ctx.getTenantId(), entityId);
|
||||||
|
|
||||||
|
ThrowingRunnable callback = () -> {
|
||||||
|
logEntityActionService.logEntityAction(ctx.getTenantId(), entity.getId(), entity, null,
|
||||||
|
ActionType.DELETED, ctx.getUser());
|
||||||
|
};
|
||||||
|
if (ctx.isRollbackOnError()) {
|
||||||
|
ctx.addEventCallback(callback);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
callback.run();
|
||||||
|
} catch (ThingsboardException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
ctx.registerDeleted(entityType);
|
ctx.registerDeleted(entityType);
|
||||||
}
|
}
|
||||||
});
|
persistToCache(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private VersionLoadResult onError(EntityId externalId, Throwable e) {
|
private VersionLoadResult onError(EntityId externalId, Throwable e) {
|
||||||
|
|||||||
@ -58,6 +58,7 @@ public class EntitiesImportCtx {
|
|||||||
private boolean finalImportAttempt = false;
|
private boolean finalImportAttempt = false;
|
||||||
private EntityImportSettings settings;
|
private EntityImportSettings settings;
|
||||||
private EntityImportResult<?> currentImportResult;
|
private EntityImportResult<?> currentImportResult;
|
||||||
|
private boolean rollbackOnError;
|
||||||
|
|
||||||
public EntitiesImportCtx(UUID requestId, User user, String versionId) {
|
public EntitiesImportCtx(UUID requestId, User user, String versionId) {
|
||||||
this(requestId, user, versionId, null);
|
this(requestId, user, versionId, null);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import java.util.Map;
|
|||||||
public class EntityTypeVersionLoadRequest extends VersionLoadRequest {
|
public class EntityTypeVersionLoadRequest extends VersionLoadRequest {
|
||||||
|
|
||||||
private Map<EntityType, EntityTypeVersionLoadConfig> entityTypes;
|
private Map<EntityType, EntityTypeVersionLoadConfig> entityTypes;
|
||||||
|
private boolean rollbackOnError;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VersionLoadRequestType getType() {
|
public VersionLoadRequestType getType() {
|
||||||
|
|||||||
@ -23,11 +23,16 @@
|
|||||||
<mat-progress-bar color="warn" style="z-index: 10; width: 100%; margin-bottom: -4px;" mode="indeterminate"
|
<mat-progress-bar color="warn" style="z-index: 10; width: 100%; margin-bottom: -4px;" mode="indeterminate"
|
||||||
*ngIf="isLoading$ | async">
|
*ngIf="isLoading$ | async">
|
||||||
</mat-progress-bar>
|
</mat-progress-bar>
|
||||||
<form [formGroup]="loadVersionFormGroup" fxLayout="column" style="flex: 1; padding-top: 16px; overflow: auto;">
|
<form [formGroup]="loadVersionFormGroup" fxLayout="column" style="flex: 1; padding: 16px 0; overflow-y: auto; overflow-x: hidden">
|
||||||
<tb-entity-types-version-load
|
<tb-entity-types-version-load
|
||||||
formControlName="entityTypes">
|
formControlName="entityTypes">
|
||||||
</tb-entity-types-version-load>
|
</tb-entity-types-version-load>
|
||||||
</form>
|
</form>
|
||||||
|
<div tb-hint-tooltip-icon="{{ 'version-control.rollback-on-error-hint' | translate }}" [formGroup]="loadVersionFormGroup">
|
||||||
|
<mat-slide-toggle formControlName="rollbackOnError">
|
||||||
|
{{ 'version-control.rollback-on-error' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</div>
|
||||||
<div fxLayoutAlign="end center" fxLayoutGap="8px" style="padding-top: 16px;">
|
<div fxLayoutAlign="end center" fxLayoutGap="8px" style="padding-top: 16px;">
|
||||||
<button mat-button color="primary"
|
<button mat-button color="primary"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -79,6 +79,7 @@ export class ComplexVersionLoadComponent extends PageComponent implements OnInit
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.loadVersionFormGroup = this.fb.group({
|
this.loadVersionFormGroup = this.fb.group({
|
||||||
entityTypes: [createDefaultEntityTypesVersionLoad(), []],
|
entityTypes: [createDefaultEntityTypesVersionLoad(), []],
|
||||||
|
rollbackOnError: [true]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +117,7 @@ export class ComplexVersionLoadComponent extends PageComponent implements OnInit
|
|||||||
const request: EntityTypeVersionLoadRequest = {
|
const request: EntityTypeVersionLoadRequest = {
|
||||||
versionId: this.versionId,
|
versionId: this.versionId,
|
||||||
entityTypes: this.loadVersionFormGroup.get('entityTypes').value,
|
entityTypes: this.loadVersionFormGroup.get('entityTypes').value,
|
||||||
|
rollbackOnError: this.loadVersionFormGroup.get('rollbackOnError').value,
|
||||||
type: VersionLoadRequestType.ENTITY_TYPE
|
type: VersionLoadRequestType.ENTITY_TYPE
|
||||||
};
|
};
|
||||||
this.versionLoadResult$ = this.entitiesVersionControlService.loadEntitiesVersion(request, {ignoreErrors: true}).pipe(
|
this.versionLoadResult$ = this.entitiesVersionControlService.loadEntitiesVersion(request, {ignoreErrors: true}).pipe(
|
||||||
|
|||||||
@ -144,6 +144,7 @@ export interface EntityTypeVersionLoadConfig extends VersionLoadConfig {
|
|||||||
export interface EntityTypeVersionLoadRequest extends VersionLoadRequest {
|
export interface EntityTypeVersionLoadRequest extends VersionLoadRequest {
|
||||||
entityTypes: {[entityType: string]: EntityTypeVersionLoadConfig};
|
entityTypes: {[entityType: string]: EntityTypeVersionLoadConfig};
|
||||||
type: VersionLoadRequestType.ENTITY_TYPE;
|
type: VersionLoadRequestType.ENTITY_TYPE;
|
||||||
|
rollbackOnError: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDefaultEntityTypesVersionLoad(): {[entityType: string]: EntityTypeVersionLoadConfig} {
|
export function createDefaultEntityTypesVersionLoad(): {[entityType: string]: EntityTypeVersionLoadConfig} {
|
||||||
|
|||||||
@ -5016,7 +5016,9 @@
|
|||||||
"device-credentials-conflict": "Failed to load the device with external id <b>{{entityId}}</b><br/>due to the same credentials are already present in the database for another device.<br/>Please consider disabling the <b>load credentials</b> setting in the restore form.",
|
"device-credentials-conflict": "Failed to load the device with external id <b>{{entityId}}</b><br/>due to the same credentials are already present in the database for another device.<br/>Please consider disabling the <b>load credentials</b> setting in the restore form.",
|
||||||
"missing-referenced-entity": "Failed to load the <b>{{sourceEntityTypeName}}</b> with external id <b>{{sourceEntityId}}</b><br/>because it references missing <b>{{targetEntityTypeName}}</b> with id <b>{{targetEntityId}}</b>.",
|
"missing-referenced-entity": "Failed to load the <b>{{sourceEntityTypeName}}</b> with external id <b>{{sourceEntityId}}</b><br/>because it references missing <b>{{targetEntityTypeName}}</b> with id <b>{{targetEntityId}}</b>.",
|
||||||
"runtime-failed": "<b>Failed:</b> {{message}}",
|
"runtime-failed": "<b>Failed:</b> {{message}}",
|
||||||
"auto-commit-settings-read-only-hint": "Auto-commit feature doesn't work with enabled read-only option in Repository settings."
|
"auto-commit-settings-read-only-hint": "Auto-commit feature doesn't work with enabled read-only option in Repository settings.",
|
||||||
|
"rollback-on-error": "Rollback on error",
|
||||||
|
"rollback-on-error-hint": "If you have a large amount of entities to restore, consider disabling this option to increase performance.\n Note, if an error occurs over the course of version loading, already persisted entities (with relations, attributes, etc.) will stay as is"
|
||||||
},
|
},
|
||||||
"widget": {
|
"widget": {
|
||||||
"widget-library": "Widgets library",
|
"widget-library": "Widgets library",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user