-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow for additive mappings update without creating a new version ind…
…ex (#149326) Fixes [#147237](#147237) Based on the same principle as [#147371](#147371), the goal of this PR is to **avoid reindexing if possible**. This time, the idea is to check whether the new mappings are still compatible with the ones stored in ES. To to so, we attempt to update the mappings in place in the existing index, introducing a new `CHECK_COMPATIBLE_MAPPINGS` step: * If the update operation fails, we assume the mappings are NOT compatible, and we continue with the normal reindexing flow. * If the update operation succeeds, we assume the mappings ARE compatible, and we skip reindexing, just like [#147371](#147371) does. ![image](https://user-images.githubusercontent.com/25349407/216979882-9fe9f034-b521-4171-b85d-50be6a13e179.png) --------- Co-authored-by: kibanamachine <[email protected]>
- Loading branch information
1 parent
5514f93
commit 5dd8742
Showing
27 changed files
with
1,254 additions
and
730 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
147 changes: 147 additions & 0 deletions
147
...-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import * as Either from 'fp-ts/lib/Either'; | ||
import type { TransportResult } from '@elastic/elasticsearch'; | ||
import { errors as EsErrors } from '@elastic/elasticsearch'; | ||
import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; | ||
import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; | ||
import { updateMappings } from './update_mappings'; | ||
import { DEFAULT_TIMEOUT } from './constants'; | ||
|
||
jest.mock('./catch_retryable_es_client_errors'); | ||
|
||
describe('updateMappings', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
const createErrorClient = (response: Partial<TransportResult<Record<string, any>>>) => { | ||
// Create a mock client that returns the desired response | ||
const apiResponse = elasticsearchClientMock.createApiResponse(response); | ||
const error = new EsErrors.ResponseError(apiResponse); | ||
const client = elasticsearchClientMock.createInternalClient( | ||
elasticsearchClientMock.createErrorTransportRequestPromise(error) | ||
); | ||
|
||
return { client, error }; | ||
}; | ||
|
||
it('resolves left if the mappings are not compatible (aka 400 illegal_argument_exception from ES)', async () => { | ||
const { client } = createErrorClient({ | ||
statusCode: 400, | ||
body: { | ||
error: { | ||
type: 'illegal_argument_exception', | ||
reason: 'mapper [action.actionTypeId] cannot be changed from type [keyword] to [text]', | ||
}, | ||
}, | ||
}); | ||
|
||
const task = updateMappings({ | ||
client, | ||
index: 'new_index', | ||
mappings: { | ||
properties: { | ||
created_at: { | ||
type: 'date', | ||
}, | ||
}, | ||
_meta: { | ||
migrationMappingPropertyHashes: { | ||
references: '7997cf5a56cc02bdc9c93361bde732b0', | ||
'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', | ||
'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const res = await task(); | ||
|
||
expect(Either.isLeft(res)).toEqual(true); | ||
expect(res).toMatchInlineSnapshot(` | ||
Object { | ||
"_tag": "Left", | ||
"left": Object { | ||
"type": "incompatible_mapping_exception", | ||
}, | ||
} | ||
`); | ||
}); | ||
|
||
it('calls catchRetryableEsClientErrors when the promise rejects', async () => { | ||
const { client, error: retryableError } = createErrorClient({ | ||
statusCode: 503, | ||
body: { error: { type: 'es_type', reason: 'es_reason' } }, | ||
}); | ||
|
||
const task = updateMappings({ | ||
client, | ||
index: 'new_index', | ||
mappings: { | ||
properties: { | ||
created_at: { | ||
type: 'date', | ||
}, | ||
}, | ||
_meta: {}, | ||
}, | ||
}); | ||
try { | ||
await task(); | ||
} catch (e) { | ||
/** ignore */ | ||
} | ||
|
||
expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); | ||
}); | ||
|
||
it('updates the mapping information of the desired index', async () => { | ||
const client = elasticsearchClientMock.createInternalClient(); | ||
|
||
const task = updateMappings({ | ||
client, | ||
index: 'new_index', | ||
mappings: { | ||
properties: { | ||
created_at: { | ||
type: 'date', | ||
}, | ||
}, | ||
_meta: { | ||
migrationMappingPropertyHashes: { | ||
references: '7997cf5a56cc02bdc9c93361bde732b0', | ||
'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', | ||
'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const res = await task(); | ||
expect(Either.isRight(res)).toBe(true); | ||
expect(client.indices.putMapping).toHaveBeenCalledTimes(1); | ||
expect(client.indices.putMapping).toHaveBeenCalledWith({ | ||
index: 'new_index', | ||
timeout: DEFAULT_TIMEOUT, | ||
properties: { | ||
created_at: { | ||
type: 'date', | ||
}, | ||
}, | ||
_meta: { | ||
migrationMappingPropertyHashes: { | ||
references: '7997cf5a56cc02bdc9c93361bde732b0', | ||
'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', | ||
'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', | ||
}, | ||
}, | ||
}); | ||
}); | ||
}); |
63 changes: 63 additions & 0 deletions
63
...saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import * as Either from 'fp-ts/lib/Either'; | ||
import * as TaskEither from 'fp-ts/lib/TaskEither'; | ||
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; | ||
import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; | ||
import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; | ||
import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; | ||
import { DEFAULT_TIMEOUT } from './constants'; | ||
|
||
/** @internal */ | ||
export interface UpdateMappingsParams { | ||
client: ElasticsearchClient; | ||
index: string; | ||
mappings: IndexMapping; | ||
} | ||
|
||
/** @internal */ | ||
export interface IncompatibleMappingException { | ||
type: 'incompatible_mapping_exception'; | ||
} | ||
|
||
/** | ||
* Updates an index's mappings and runs an pickupUpdatedMappings task so that the mapping | ||
* changes are "picked up". Returns a taskId to track progress. | ||
*/ | ||
export const updateMappings = ({ | ||
client, | ||
index, | ||
mappings, | ||
}: UpdateMappingsParams): TaskEither.TaskEither< | ||
RetryableEsClientError | IncompatibleMappingException, | ||
'update_mappings_succeeded' | ||
> => { | ||
return () => { | ||
return client.indices | ||
.putMapping({ | ||
index, | ||
timeout: DEFAULT_TIMEOUT, | ||
...mappings, | ||
}) | ||
.then(() => Either.right('update_mappings_succeeded' as const)) | ||
.catch((res) => { | ||
const errorType = res?.body?.error?.type; | ||
// ES throws this exact error when attempting to make incompatible updates to the mappigns | ||
if ( | ||
res?.statusCode === 400 && | ||
(errorType === 'illegal_argument_exception' || | ||
errorType === 'strict_dynamic_mapping_exception' || | ||
errorType === 'mapper_parsing_exception') | ||
) { | ||
return Either.left({ type: 'incompatible_mapping_exception' }); | ||
} | ||
return catchRetryableEsClientErrors(res); | ||
}); | ||
}; | ||
}; |
80 changes: 0 additions & 80 deletions
80
...e-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.test.ts
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.