From 38dc9e3095f6ae251ed868446abcbed31d40b1c2 Mon Sep 17 00:00:00 2001 From: Iaroslav Zeigerman Date: Mon, 13 Feb 2023 14:37:08 -0800 Subject: [PATCH 1/2] Update the Environments doc page to make it more cohesive with the Plans page --- docs/concepts/environments.md | 28 +++++++++++++---------- docs/concepts/plans.md | 8 +++---- docs/concepts/plans/model_versioning.png | Bin 43183 -> 43124 bytes 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/docs/concepts/environments.md b/docs/concepts/environments.md index 868614fb47..b0f27e0f4d 100644 --- a/docs/concepts/environments.md +++ b/docs/concepts/environments.md @@ -1,17 +1,19 @@ ## Environments -Environments are isolated namespaces that allow you to develop and deploy SQLMesh projects. If an environment isn't specified, the `prod` environment is used, which does not append a prefix to model names. Given a [model](/concepts/models) `db.table`, the `prod` environment would create this model in `db.table`. The `dev` environment would be located at `dev__db.table`. All environments other than `prod` are considered to be development environments. +Environments are isolated namespaces that allow you to test and preview your changes. -Models in `dev` environments also get a special suffix appended to the schema portion of their names. For example, if the model's name is `db.model_a`, it will be available under the name `db__my_dev.model_a` in the `my_dev` environment. +SQLMesh differentiates between production and development environments. Currently only the environment with the name `prod` is treated by SQLMesh as the production one. Environments with other names are considered to be development ones. -By default, the [`sqlmesh plan`](/concepts/plans) command targets the `prod` environment. +[Models](/concepts/models) in development environments get a special suffix appended to the schema portion of their names. For example, to access data for a model with name `db.model_a` in the target environment `my_dev`, the `db__my_dev.model_a` table name should be used in a query. Models in the production environment are referred to by their original names. -## Why use environments? -It is important to be able to iterate and test changes to models with production data. Data pipelines can be very complex and can consist of many chained jobs. Being able to recreate your entire warehouse with these changes is powerful in order to understand the full impact of your changes, but usually expensive or time consuming. +By default, the [`sqlmesh plan`](/concepts/plans) command targets the production (`prod`) environment. -SQLMesh environments allow you to easily spin up 'clones' of your warehouse quickly and efficiently. SQLMesh understands which models have changed compared to the base environment, and only recomputes/backfills what doesn't already exist. Any changes or backfills within this environment **will not impact** other environments. However, any work that was done in this environment **can be reused safely** from other environments. +## Why use environments +Data pipelines and their dependencies tend to grow in complexity over time, and at some point accurately assessing the impact of any given local change becomes quite challenging. Pipeline owners may not be aware of all downstream consumers of their pipelines, or may drastically underestimate the impact a change would have. That's why it is so important to be able to iterate and test model changes using production dependencies and data, while simultaneously avoiding any impact to existing datasets and/or pipelines that are currently used in production. Recreating the entire data warehouse with given changes would be an ideal solution to fully understand their impact, but this process is usually excessively expensive and time consuming. -## How do you use an environment? -When running the [plan](/concepts/plans) command, the environment is the first variable. You can specify any string as your environment name. The only special environment by default is `prod`. All other environments will prefix the environment name to all models. +SQLMesh environments allow you to easily spin up shallow 'clones' of the data warehouse quickly and efficiently. SQLMesh understands which models have changed compared to the target environment, and only computes data gaps that have been directly caused by the changes. Any changes or backfills within the target environment **do not impact** other environments. At the same time any computation that was done in this environment **can be safely reused** in other environments. + +## How to use environments +When running the [plan](/concepts/plans) command, the environment name can be supplied in the first argument. An arbitrary string can be used as an environment name. The only special environment name by default is `prod`, which refers to the production environment. Environment with names other than `prod` are considered to be development environments. ### Example A custom name can be provided as an argument to create/update a development environment. For example, to target an environment with name `my_dev`, run: @@ -21,8 +23,10 @@ $ sqlmesh plan my_dev ``` A new environment is created automatically the first time a plan is applied to it. -## How do environments work? -Every model definition has a unique [fingerprint](/concepts/architecture/snapshots/#fingerprints). This fingerprint allows SQLMesh to detect if it exists in another environment or if it brand new. Because models depend on other models, the fingerprint also takes into account its upstream fingerprints. If a fingerpint already exists in SQLMesh, it is safe to reuse the existing table because the logic is exactly the same. An environment is essentially a collection of [snapshots](/concepts/architecture/snapshots) of models. +## How do environments work +Every time a change is made to a model definition, a new model snapshot is created and gets assigned a unique [fingerprint](/concepts/architecture/snapshots/#fingerprints). This fingerprint allows SQLMesh to detect if a given model variant exists in other environments or if it's a brand new variant. Because models may depend on other models, the fingerprint of a target model variant also includes fingerprints of its upstream dependencies. If a fingerprint already exists in SQLMesh, it is safe to reuse the existing physical table associated with that model variant, since we're confident that the logic that populates that table is exactly the same. This makes an environment a collection of references to model [snapshots](/concepts/architecture/snapshots). + +Please refer to the [Plans](/concepts/plans) page for additional details. -## Date ranges ## -A non-production environment consists of a start date and end date. When creating development environments, you usally want to test your data on a subset of dates, such as the last week or last month of data. Non-production environments do not automatically schedule recurring jobs. +## Date range +A development environment includes a start date and end date. When creating a development environment, the intent is usually to test changes on a subset of data. The size of such a subset is determined by a time range defined through the start and end date of the environment. Both start and end date are provided during the [plan](/concepts/plan) creation. diff --git a/docs/concepts/plans.md b/docs/concepts/plans.md index f1739ba2cc..deded1179f 100644 --- a/docs/concepts/plans.md +++ b/docs/concepts/plans.md @@ -4,7 +4,7 @@ A plan is a set of changes that summarizes the difference between the local stat During plan creation: -* the local state of the SQLMesh project is compared against the state of a target environment. The difference computed is what constitutes a plan. +* the local state of the SQLMesh project is compared against the state of a target environment. The difference computed is what constitutes a plan. * users are prompted to categorize changes (refer to [change categories](#change-categories)) to existing models in order for SQLMesh to devise a backfill strategy for models that have been affected indirectly (by being downstream dependencies of updated models). * each plan requires a date range to which it will be applied. If not specified, the date range is derived automatically based on model definitions and the target environment. @@ -33,7 +33,7 @@ A directly-modified model that is classified as non-breaking will be backfilled, ## Plan application Once a plan has been created and reviewed, it should then be applied in order for the changes that are part of it to take effect. -Typically, each model changed in a plan gets assigned with a new version. In turn, each model version gets a separate physical location for data. Data between different model versions is never shared, therefore an environment is simply a collection of references to physical tables of model versions which that environment has been created/updated with. +Every time a model is changed as part of a plan, a new variant of this model gets created behind the scene (see [snapshots](/concepts/architecture/snapshots)). In turn, each model variant gets a separate physical location for data (i.e. table). Data between different variants of the same model is never shared, therefore an environment can be viewed as a collection of references to physical tables of model versions which that environment has been created/updated with. ![Each model version gets its own physical table while environments only contain references to these tables](plans/model_versioning.png) @@ -52,7 +52,7 @@ Another benefit of the aforementioned approach is that data for a new model vers ## Forward-only plans Sometimes the runtime cost associated with rebuilding an entire physical table is too high, and outweighs the benefits a separate table provides. This is when a forward-only plan comes in handy. -When a forward-only plan is applied, all of the contained model changes will not get separate physical tables assigned to them. Instead, physical tables of previous model versions are reused. The benefit of such a plan is that no backfilling is required, so there is no runtime overhead and hence no cost. The drawback is that reverting to a previous version is no longer as straightforward, and requires a combination of additional forward-only changes and restatements (refer to [restatement plans](#restatement-plans)). +When a forward-only plan is applied, all of the contained model changes will not get separate physical tables assigned to them. Instead, physical tables of previous model versions are reused. The benefit of such a plan is that no backfilling is required, so there is no runtime overhead and hence no cost. The drawback is that reverting to a previous version is no longer as straightforward, and requires a combination of additional forward-only changes and restatements (refer to [restatement plans](#restatement-plans)). Also note that once a forward-only change is applied to production, all development environments that referred to the previous versions of the updated models will be impacted. @@ -69,7 +69,7 @@ $ sqlmesh plan --forward-only There are cases when models need to be re-evaluated for a given time range, even though changes may not have been made to those model definitions. This could be due to an upstream issue with a dataset defined outside the SQLMesh platform, or when a [forward-only plan](#forward-only-plans) change needs to be applied retroactively to a bounded interval of historical data. For this reason, the `plan` command supports the `--restate-model` option, which allows users to specify one or more names of a model to be reprocessed. Each name can also refer to an external table defined outside SQLMesh. - + Application of such a plan will trigger a cascading backfill for all specified models (excluding external tables), as well as all models downstream from them. The plan's date range in this case determines data intervals that will be affected. For example: ```bash diff --git a/docs/concepts/plans/model_versioning.png b/docs/concepts/plans/model_versioning.png index 682b7311ef50dec0c458968e770e15da8b2d466c..a405489d704538d72f435ea98337337c8d9e1b5d 100644 GIT binary patch delta 16564 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 OpW;Im*+OY!^#2F(dJ1&_ delta 16622 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 Date: Mon, 13 Feb 2023 15:10:13 -0800 Subject: [PATCH 2/2] Address comments --- docs/concepts/environments.md | 6 +++--- docs/concepts/plans.md | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/concepts/environments.md b/docs/concepts/environments.md index b0f27e0f4d..2809af25f7 100644 --- a/docs/concepts/environments.md +++ b/docs/concepts/environments.md @@ -8,9 +8,9 @@ SQLMesh differentiates between production and development environments. Currentl By default, the [`sqlmesh plan`](/concepts/plans) command targets the production (`prod`) environment. ## Why use environments -Data pipelines and their dependencies tend to grow in complexity over time, and at some point accurately assessing the impact of any given local change becomes quite challenging. Pipeline owners may not be aware of all downstream consumers of their pipelines, or may drastically underestimate the impact a change would have. That's why it is so important to be able to iterate and test model changes using production dependencies and data, while simultaneously avoiding any impact to existing datasets and/or pipelines that are currently used in production. Recreating the entire data warehouse with given changes would be an ideal solution to fully understand their impact, but this process is usually excessively expensive and time consuming. +Data pipelines and their dependencies tend to grow in complexity over time and so assessing the impact of local changes can become quite challenging. Pipeline owners may not be aware of all downstream consumers of their pipelines, or may drastically underestimate the impact a change would have. That's why it is so important to be able to iterate and test model changes using production dependencies and data, while simultaneously avoiding any impact to existing datasets and/or pipelines that are currently used in production. Recreating the entire data warehouse with given changes would be an ideal solution to fully understand their impact, but this process is usually excessively expensive and time consuming. -SQLMesh environments allow you to easily spin up shallow 'clones' of the data warehouse quickly and efficiently. SQLMesh understands which models have changed compared to the target environment, and only computes data gaps that have been directly caused by the changes. Any changes or backfills within the target environment **do not impact** other environments. At the same time any computation that was done in this environment **can be safely reused** in other environments. +SQLMesh environments allow you to easily spin up shallow 'clones' of the data warehouse quickly and efficiently. SQLMesh understands which models have changed compared to the target environment, and only computes data gaps that have been directly caused by the changes. Any changes or backfills within the target environment **do not impact** other environments. At the same time, any computation that was done in this environment **can be safely reused** in other environments. ## How to use environments When running the [plan](/concepts/plans) command, the environment name can be supplied in the first argument. An arbitrary string can be used as an environment name. The only special environment name by default is `prod`, which refers to the production environment. Environment with names other than `prod` are considered to be development environments. @@ -24,7 +24,7 @@ $ sqlmesh plan my_dev A new environment is created automatically the first time a plan is applied to it. ## How do environments work -Every time a change is made to a model definition, a new model snapshot is created and gets assigned a unique [fingerprint](/concepts/architecture/snapshots/#fingerprints). This fingerprint allows SQLMesh to detect if a given model variant exists in other environments or if it's a brand new variant. Because models may depend on other models, the fingerprint of a target model variant also includes fingerprints of its upstream dependencies. If a fingerprint already exists in SQLMesh, it is safe to reuse the existing physical table associated with that model variant, since we're confident that the logic that populates that table is exactly the same. This makes an environment a collection of references to model [snapshots](/concepts/architecture/snapshots). +Whenever a model definition changes, a new model snapshot is created with a unique [fingerprint](/concepts/architecture/snapshots/#fingerprints). This fingerprint allows SQLMesh to detect if a given model variant exists in other environments or if it's a brand new variant. Because models may depend on other models, the fingerprint of a target model variant also includes fingerprints of its upstream dependencies. If a fingerprint already exists in SQLMesh, it is safe to reuse the existing physical table associated with that model variant, since we're confident that the logic that populates that table is exactly the same. This makes an environment a collection of references to model [snapshots](/concepts/architecture/snapshots). Please refer to the [Plans](/concepts/plans) page for additional details. diff --git a/docs/concepts/plans.md b/docs/concepts/plans.md index deded1179f..c91d779b8e 100644 --- a/docs/concepts/plans.md +++ b/docs/concepts/plans.md @@ -31,9 +31,11 @@ If a directly modified model change is categorized as breaking, then it will be A directly-modified model that is classified as non-breaking will be backfilled, but its downstream dependencies will not. This is a common choice in scenarios such as an addition of a new column, an action which doesn't affect downstream models as new columns can't be used by downstream models without modifying them directly. ## Plan application -Once a plan has been created and reviewed, it should then be applied in order for the changes that are part of it to take effect. +Once a plan has been created and reviewed, it should then be applied to a target [environment](/concepts/environments) in order for the changes that are part of it to take effect. -Every time a model is changed as part of a plan, a new variant of this model gets created behind the scene (see [snapshots](/concepts/architecture/snapshots)). In turn, each model variant gets a separate physical location for data (i.e. table). Data between different variants of the same model is never shared, therefore an environment can be viewed as a collection of references to physical tables of model versions which that environment has been created/updated with. +Every time a model is changed as part of a plan, a new variant of this model gets created behind the scenes (see [snapshots](/concepts/architecture/snapshots)). In turn, each model variant gets a separate physical location for data (i.e. table). Data between different variants of the same model is never shared (except for the [forward-only](#forward-only-plans) case). + +When a plan is applied to an environment, that environment gets associated with a collection of model variants that are part of that plan. In other words each environment is a collection of references to model variants and the physical tables associated with them. ![Each model version gets its own physical table while environments only contain references to these tables](plans/model_versioning.png)