Skip to content

Commit 591a7b8

Browse files
committed
limit number of concurrent legacy url alias deletions
1 parent b0bd664 commit 591a7b8

File tree

2 files changed

+73
-62
lines changed

2 files changed

+73
-62
lines changed

packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts

Lines changed: 67 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import {
8686
type IndexMapping,
8787
type IKibanaMigrator,
8888
} from '@kbn/core-saved-objects-base-server-internal';
89+
import pMap from 'p-map';
8990
import { PointInTimeFinder } from './point_in_time_finder';
9091
import { createRepositoryEsClient, RepositoryEsClient } from './repository_es_client';
9192
import { getSearchDsl } from './search_dsl';
@@ -120,6 +121,7 @@ import type {
120121
BulkDeleteExpectedBulkGetResult,
121122
PreflightCheckForBulkDeleteParams,
122123
ExpectedBulkDeleteMultiNamespaceDocsParams,
124+
ObjectToDeleteAliasesFor,
123125
} from './repository_bulk_delete_internal_types';
124126

125127
// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
@@ -139,6 +141,7 @@ export interface SavedObjectsRepositoryOptions {
139141
export const DEFAULT_REFRESH_SETTING = 'wait_for';
140142
export const DEFAULT_RETRY_COUNT = 3;
141143

144+
const MAX_CONCURRENT_ALIAS_DELETIONS = 10;
142145
/**
143146
* @internal
144147
*/
@@ -980,75 +983,77 @@ export class SavedObjectsRepository implements ISavedObjectsRepository {
980983

981984
// extracted to ensure consistency in the error results returned
982985
let errorResult: BulkDeleteItemErrorResult;
983-
const savedObjects = await Promise.all(
984-
expectedBulkDeleteMultiNamespaceDocsResults.map(async (expectedResult) => {
985-
if (isLeft(expectedResult)) {
986-
return { ...expectedResult.value, success: false };
987-
}
988-
const {
986+
const objectsToDeleteAliasesFor: ObjectToDeleteAliasesFor[] = [];
987+
988+
const savedObjects = expectedBulkDeleteMultiNamespaceDocsResults.map((expectedResult) => {
989+
if (isLeft(expectedResult)) {
990+
return { ...expectedResult.value, success: false };
991+
}
992+
const {
993+
type,
994+
id,
995+
namespaces,
996+
esRequestIndex: esBulkDeleteRequestIndex,
997+
} = expectedResult.value;
998+
// we assume this wouldn't happen but is needed to ensure type consistency
999+
if (bulkDeleteResponse === undefined) {
1000+
throw new Error(
1001+
`Unexpected error in bulkDelete saved objects: bulkDeleteResponse is undefined`
1002+
);
1003+
}
1004+
const rawResponse = Object.values(
1005+
bulkDeleteResponse.items[esBulkDeleteRequestIndex]
1006+
)[0] as NewBulkItemResponse;
1007+
1008+
const error = getBulkOperationError(type, id, rawResponse);
1009+
if (error) {
1010+
return { success: false, type, id, error };
1011+
}
1012+
if (rawResponse.result === 'not_found') {
1013+
return {
1014+
success: false,
9891015
type,
9901016
id,
991-
namespaces,
992-
esRequestIndex: esBulkDeleteRequestIndex,
993-
} = expectedResult.value;
994-
// we assume this wouldn't happen but is needed to ensure type consistency
995-
if (bulkDeleteResponse === undefined) {
996-
throw new Error(
997-
`Unexpected error in bulkDelete saved objects: bulkDeleteResponse is undefined`
998-
);
999-
}
1000-
const rawResponse = Object.values(
1001-
bulkDeleteResponse.items[esBulkDeleteRequestIndex]
1002-
)[0] as NewBulkItemResponse;
1017+
error: errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)),
1018+
};
1019+
}
10031020

1004-
const error = getBulkOperationError(type, id, rawResponse);
1005-
if (error) {
1006-
errorResult = { success: false, type, id, error };
1007-
return errorResult;
1008-
}
1009-
if (rawResponse.result === 'not_found') {
1010-
errorResult = {
1011-
success: false,
1021+
if (rawResponse.result === 'deleted') {
1022+
// `namespaces` should only exist in the expectedResult.value if the type is multi-namespace.
1023+
if (namespaces) {
1024+
objectsToDeleteAliasesFor.push({
10121025
type,
10131026
id,
1014-
error: errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)),
1015-
};
1016-
return errorResult;
1027+
...(namespaces.includes(ALL_NAMESPACES_STRING)
1028+
? { namespaces: [], deleteBehavior: 'exclusive' }
1029+
: { namespaces, deleteBehavior: 'inclusive' }),
1030+
});
10171031
}
1032+
}
1033+
const successfulResult = {
1034+
success: true,
1035+
id,
1036+
type,
1037+
};
1038+
return successfulResult;
1039+
});
1040+
1041+
// Delete aliases if necessary, ensuring we don't have too many concurrent operations running.
1042+
const mapper = async ({ type, id, namespaces, deleteBehavior }: ObjectToDeleteAliasesFor) =>
1043+
await deleteLegacyUrlAliases({
1044+
mappings: this._mappings,
1045+
registry: this._registry,
1046+
client: this.client,
1047+
getIndexForType: this.getIndexForType.bind(this),
1048+
type,
1049+
id,
1050+
namespaces,
1051+
deleteBehavior,
1052+
}).catch((err) => {
1053+
this._logger.error(`Unable to delete aliases when deleting an object: ${err.message}`);
1054+
});
1055+
await pMap(objectsToDeleteAliasesFor, mapper, { concurrency: MAX_CONCURRENT_ALIAS_DELETIONS });
10181056

1019-
if (rawResponse.result === 'deleted') {
1020-
// `namespaces` should only exist in the expectedResult.value if the type is multi-namespace.
1021-
if (namespaces) {
1022-
// in the bulk operation, one cannot specify a namespace from which to delete an object other than the namespace that the operation is performed in.
1023-
// If a multinamespace object exists in more than the current space (from which the call is made), force deleting the object will delete it from all namespaces it exists in.
1024-
// In that case, all legacy url aliases are deleted as well. If force isn't applied, the operation fails and the object isn't deleted.
1025-
await deleteLegacyUrlAliases({
1026-
mappings: this._mappings,
1027-
registry: this._registry,
1028-
client: this.client,
1029-
getIndexForType: this.getIndexForType.bind(this),
1030-
type,
1031-
id,
1032-
...(namespaces.includes(ALL_NAMESPACES_STRING)
1033-
? { namespaces: [], deleteBehavior: 'exclusive' } // delete legacy URL aliases for this type/ID for all spaces not in []. Effectively, it's the same behavior ad inludisve with a defined array of namespaces.
1034-
: { namespaces, deleteBehavior: 'inclusive' }), // delete legacy URL aliases for this type/ID for these specific spaces. In the bulk operation, this behavior is only applicable in the case of multi-namespace isolated types.
1035-
}).catch((err) => {
1036-
// The object has already been deleted, but we caught an error when attempting to delete aliases.
1037-
// A consumer cannot attempt to delete the object again, so just log the error and swallow it.
1038-
this._logger.error(
1039-
`Unable to delete aliases when deleting an object: ${err.message}`
1040-
);
1041-
});
1042-
}
1043-
}
1044-
const successfulResult = {
1045-
success: true,
1046-
id,
1047-
type,
1048-
};
1049-
return successfulResult;
1050-
})
1051-
);
10521057
return { statuses: [...savedObjects] };
10531058
}
10541059

packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from '@elastic/elasticsearch/lib/api/types';
1414
import type { estypes, TransportResult } from '@elastic/elasticsearch';
1515
import { Either } from './internal_utils';
16+
import { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases';
1617

1718
/**
1819
* @internal
@@ -78,3 +79,8 @@ export type BulkDeleteExpectedBulkGetResult = Either<
7879
{ type: string; id: string; error: Payload },
7980
{ type: string; id: string; version?: string; esRequestIndex?: number }
8081
>;
82+
83+
export type ObjectToDeleteAliasesFor = Pick<
84+
DeleteLegacyUrlAliasesParams,
85+
'type' | 'id' | 'namespaces' | 'deleteBehavior'
86+
>;

0 commit comments

Comments
 (0)