-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ZDT] DocumentMigrator: support higher version documents #157895
[ZDT] DocumentMigrator: support higher version documents #157895
Conversation
…-ref HEAD~1..HEAD --fix'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self-review
Note: now that #153117 is merged, I could do the next steps (leveraging the change from the SOR) in the same PR. However I think it makes sense to validate the document migrator changes / API design first before going further, which is why I'm planning on doing the rest in a distinct PR.
* | ||
* See {@link SavedObjectModelVersionBackwardConversionSchema} for more info. | ||
*/ | ||
backwardConversion?: SavedObjectModelVersionBackwardConversionSchema; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know naming is hard, but it was even harder here. I'm still really not sure how we should name that thing.
I started with persistenceSchema
, but it wasn't explicit enough, so I switched to backwardPersistence
, higherVersionConversion
and then backwardConversion
... But nothing seems outstandingly correct here.
If anyone has a better idea, please voice your opinion...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throwing ideas: versionedModelSchema
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reverseConversion
?
export type SavedObjectModelVersionBackwardConversionSchema< | ||
InAttrs = unknown, | ||
OutAttrs = unknown | ||
> = ObjectType | SavedObjectModelVersionBackwardConversionFn<InAttrs, OutAttrs>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably improve this later (e.g with helper methods / factory to build the validation function from another shape of schema or something), but I just KISSed for now. The schema can either be a schema.object
of the properties of the document, or a plain JS function taking the attributes as input and returning the filtered / processed attributes as output.
*/ | ||
export interface SavedObjectsModelVersionSchemaDefinitions { | ||
create?: SavedObjectModelVersionCreateSchema; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: I removed the created
schema from SavedObjectsModelVersionSchemaDefinitions
given it's unused for now (and now that we have an other, used, schema on this type)
const typeMigrations = this.migrations[doc.type]; | ||
if (downgradeRequired(doc, typeMigrations?.latestVersion ?? {})) { | ||
const currentVersion = doc.typeMigrationVersion ?? doc.migrationVersion?.[doc.type]; | ||
const latestVersion = this.migrations[doc.type].latestVersion[TransformType.Migrate]; | ||
if (!allowDowngrade) { | ||
throw Boom.badData( | ||
`Document "${doc.id}" belongs to a more recent version of Kibana [${currentVersion}] when the last known version is [${latestVersion}].` | ||
); | ||
} | ||
return this.transformDown(doc, { targetTypeVersion: latestVersion! }); | ||
} else { | ||
return this.transformUp(doc, { convertNamespaceTypes, allowDowngrade }); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the root change of the PR: DocumentMigrator.migrate
(and the underlying .transform
) now accepts a allowDowngrade
option.
If true, the document will go through the downgrade pipeline instead of throwing an error if the provided document is at an higher version than the last knows by the system.
describe('down migration', () => { | ||
it('accepts to downgrade the document if `allowDowngrade` is true', () => { | ||
const registry = createRegistry({}); | ||
|
||
const fooType = createType({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: only smoke testing in this file, as the actual coverage is done in the down pipeline's tests
if (!transform.transformDown) { | ||
if (this.ignoreMissingTransforms) { | ||
continue; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to reuse the existing downgrade pipeline, even if, realistically given our latest decisions regarding down migrations, we could just rewrite it to only apply the version schema if present.
The reasoning was that
- the system is already in place and tested
- we may eventually need such downward transformations
- the perf impact is negligible given we will just not apply down transforms that are not here.
The only thing is that I had to add this ignoreMissingTransforms
option to change the behavior of the down pipeline to now throw when encountering versions that did not register down transformations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could just rewrite it to only apply the version schema if present
So this is the version where the down pipeline will throw, but we added a flag. What's the reasoning for not just changing how the implementation works since we are not using ignoreMissingTransforms: false
outside of tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you are right. I will remove the option and just keep the only used behavior
private applyVersionSchema(doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc { | ||
const targetVersion = this.targetTypeVersion; | ||
const versionSchema = this.typeTransforms.versionSchemas[targetVersion]; | ||
if (versionSchema) { | ||
return versionSchema(doc); | ||
} | ||
return doc; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where the actual version schema is applied.
Note that, compare to the existing create
schema (not yet ported to model versions), here we will only use the schema matching the exact version we're downgrading to. trying to find and use an older version doesn't make sense for such 'persistence shape' schema I think.
public migrateDocument( | ||
doc: SavedObjectUnsanitizedDoc, | ||
{ allowDowngrade = false }: MigrateDocumentOptions = {} | ||
): SavedObjectUnsanitizedDoc { | ||
return this.documentMigrator.migrate(doc, { allowDowngrade }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also added the allowDowngrade
option to IKibanaMigrator.migrateDocument
, as this is the interface the SOR is using to access the actual document migrator.
Pinging @elastic/kibana-core (Team:Core) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still reviewing just left a comment on what I think is a mistake
@@ -48,7 +48,7 @@ export function buildActiveMigrations({ | |||
referenceTransforms, | |||
}); | |||
|
|||
if (typeTransforms.transforms.length) { | |||
if (!typeTransforms.transforms.length && !Object.keys(typeTransforms.versionSchemas).length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite sure I follow the logic here:
(1) no transforms and (2) no version schemas then we add it to active migration map.
I would have thought we want (1) and (or?) (2) but I may be missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, you're right, I just messed up when I rebased from main. This is what is causing the whole PR to explode btw, thanks :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work @pgayvallet , I left one non-blocker question about the thinking behind schemas
, but I expect this is just me missing some context.
} | ||
|
||
private transform( | ||
private transformUp( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like allowDowngrade
is being passed in here but not used. Shall we create an inline type for the options
object in this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch, will fix
*/ | ||
export type SavedObjectModelVersionCreateSchema = ObjectType; | ||
export interface SavedObjectsModelVersionSchemaDefinitions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What other (eviction) schemas would we want to define for a model version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eviction? None.
We will need to implement the create
schema at least, but it's a more "traditional" validation schema
* Note that These conversion mechanism shouldn't assert the data itself, only strip unknown fields to convert | ||
* the document to the *shape* of the document at the given version. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By shape we also mean format of the fields themselves, the idea is not to transform values -- I am a little concerned that allowing an arbitrary function opens the door for something like that. But I think this doc comment is good enough guidance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I agree, we will need to be fairly careful when reviewing PRs adding such functions.
But given we still don't know what the best way to define these schema is, I needed something low level, which is therefor very permissive...
|
||
export type ConvertedSchema = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc; | ||
|
||
export const convertModelVersionBackwardConversionSchema = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One other idea:
getSchemaConversionFunction
if (!transform.transformDown) { | ||
if (this.ignoreMissingTransforms) { | ||
continue; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could just rewrite it to only apply the version schema if present
So this is the version where the down pipeline will throw, but we added a flag. What's the reasoning for not just changing how the implementation works since we are not using ignoreMissingTransforms: false
outside of tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job! I added 2 comments I'd like to clarify before approving 😇
if (!docTypeVersion || !latestMigrationVersion) { | ||
return false; | ||
} | ||
return Semver.gt(docTypeVersion, latestMigrationVersion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: is this only for V2 versions? I thought model versions didn't look like Semver, do they?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using a compatibility format so that both stack versions (used by old migrations) and model versions are compatible and can share the same logic
Lines 13 to 23 in 9cddb60
/** | |
* Represents the virtual version of a given SO type. | |
* The virtual version is a compatibility format between the old | |
* migration system's versioning, based on the stack version, and the new model versioning. | |
* | |
* A virtual version is a plain semver version. Depending on its major version value, the | |
* underlying version can be the following: | |
* - Major < 10: Old migrations system (stack versions), using the equivalent value (e.g `8.7.0` => migration version `8.7.0`) | |
* - Major == 10: Model versions, using the `10.{modelVersion}.0` format (e.g `10.3.0` => model version 3) | |
*/ | |
export type VirtualVersion = string; |
* | ||
* See {@link SavedObjectModelVersionBackwardConversionSchema} for more info. | ||
*/ | ||
backwardConversion?: SavedObjectModelVersionBackwardConversionSchema; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throwing ideas: versionedModelSchema
?
💚 Build Succeeded
Metrics [docs]Public APIs missing comments
Public APIs missing exports
Unknown metric groupsAPI count
ESLint disabled line counts
Total ESLint disabled count
History
To update your PR or re-run it, just comment with: |
) ## Summary Part of elastic#150312 (next steps depend on elastic#153117) **This PR does two things:** - introduce the concept of version persistence schema - adapt the document migrator to support downward migrations for documents of an higher version. In the follow-up, we will then update the calls from the SOR to the document migrator to allow downward conversions when we're using the ZDT migration algorithm (which requires elastic#153117 to be merged) ### Model version persistence schema. *(This is what has also been named 'eviction schema' or 'known fields schema'.)* A new `SavedObjectsModelVersion.schemas.backwardConversion` property was added to the model version definition. This 'schema' can either be an arbitrary function, or a `schema.object` from `@kbn/config-schema` ```ts type SavedObjectModelVersionBackwardConversionSchema< InAttrs = unknown, OutAttrs = unknown > = ObjectType | SavedObjectModelVersionBackwardConversionFn<InAttrs, OutAttrs>; ``` When specified for a version, the document's attributes will go thought this schema during down conversions by the document migrator. ### Adapt the document migrator to support downward migrations for documents of an higher version. Add an `allowDowngrade` option to `DocumentMigrator.migrate` and `KibanaMigrator.migrateDocument`. When this option is set to `true`, the document migration will accept to 'downgrade' the document if necessary, instead of throwing an error as done when the option is `false` or unspecified (which was the only behavior prior to this PR's changes) --------- Co-authored-by: kibanamachine <[email protected]>
Summary
Part of #150312
(next steps depend on #153117)
This PR does two things:
In the follow-up, we will then update the calls from the SOR to the document migrator to allow downward conversions when we're using the ZDT migration algorithm (which requires #153117 to be merged)
Model version persistence schema.
(This is what has also been named 'eviction schema' or 'known fields schema'.)
A new
SavedObjectsModelVersion.schemas.backwardConversion
property was added to the model version definition.This 'schema' can either be an arbitrary function, or a
schema.object
from@kbn/config-schema
When specified for a version, the document's attributes will go thought this schema during down conversions by the document migrator.
Adapt the document migrator to support downward migrations for documents of an higher version.
Add an
allowDowngrade
option toDocumentMigrator.migrate
andKibanaMigrator.migrateDocument
. When this option is set totrue
, the document migration will accept to 'downgrade' the document if necessary, instead of throwing an error as done when the option isfalse
or unspecified (which was the only behavior prior to this PR's changes)