Skip to content

Commit 9bd0a72

Browse files
joseph-flinnThomas-AveryHinton
authored
Update our EDD process documentation (#166)
* Initial pass at updating our EDD database change processes * Fix file name of new image * Switch from 'rerunnable' to 'repeatable' * Push the quick fixes from feedback * Removed repeated use of Fowler's name as well as the repeated use of EDD * Fix the image caption * Removing all added personal pronouns * Use markdown text styling * Rename the application code version in the Phase definitions to be more clear * Update terminology definitions * Update language to be more focused * Accepted introduction summary improvements * Accepted suggested changes. * Accept revised defenition and examples of non-destructive changes * Update docs/contributing/database-migrations/edd.mdx Co-authored-by: Thomas Avery <[email protected]> * addtional updates * revert to JSX to fix the broken image link * Tweak the EDD doc (#184) --------- Co-authored-by: Thomas Avery <[email protected]> Co-authored-by: Oscar Hinton <[email protected]>
1 parent f979c32 commit 9bd0a72

File tree

7 files changed

+224
-69
lines changed

7 files changed

+224
-69
lines changed

docs/contributing/database-migrations/edd.mdx

+145-59
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import Tabs from "@theme/Tabs";
22
import TabItem from "@theme/TabItem";
33

4-
# Evolutionary Database Design
4+
# Evolutionary database design
55

6-
At Bitwarden we follow
7-
[Evolutionary Database Design (EDD)](https://en.wikipedia.org/wiki/Evolutionary_database_design).
8-
EDD describes a process where the database schema is continuously updated while still ensuring
9-
compatibility with older releases by using database transition phases.
6+
At Bitwarden we follow [Evolutionary Database Design (EDD)][edd-wiki]. EDD describes a process where
7+
the database schema is continuously updated while still ensuring compatibility with older releases
8+
by defining a database transition phases.
109

11-
In short the Database Schema for the Bitwarden Server **must** support the previous release of the
12-
server. The database migrations will be performed before the code deployment, and in the event of a
13-
release rollback the database schema will **not** be updated.
10+
Bitwarden also needs to support:
11+
12+
- **Zero-downtime deployments**: Which means that multiple versions of the application will be
13+
running concurrently during the deployment window.
14+
- **Code rollback**: Critical defects in code should be able to be rolled back to the previous
15+
version.
16+
17+
To fulfill these additional requirements the database schema **must** support the previous release
18+
of the server.
1419

1520
<bitwarden>
1621

@@ -24,26 +29,76 @@ For background on this decision please see the [Evolutionary Database Design RFD
2429

2530
## Design
2631

27-
### Nullable
32+
Database changes can be categorized into two categories: destructive and non-destructive changes
33+
\[[1](./edd#further-reading)\]. A destructive change prevents existing functionality from working as
34+
expected without an accompanying code change. A non-destructive change is the opposite: a database
35+
change that does not require a code change to allow the non-application to continue working as
36+
expected.
37+
38+
### Non-destructive changes
39+
40+
Many database changes can be designed in a backwards compatible manner by using a mix of nullable
41+
fields and default values in the database tables, views, and stored procedures. This ensures that
42+
the stored procedures can be called without the new columns and allow them to run with both the old
43+
and new code.
44+
45+
### Destructive changes
46+
47+
Any change that cannot be done in a non-destructive manner is a destructive change. This can be as
48+
simple as adding a non nullable column where the value needs to be computed from existing fields, or
49+
renaming an existing column. To handle destructive changes it's necessary to break them up into
50+
three phases: _Start_, _Transition_, and _End_ as shown in the diagram below.
51+
52+
<figure>
53+
54+
![Refactoring Stages](./transitions.png)
55+
56+
<figcaption>Refactoring Phases</figcaption>
57+
58+
</figure>
59+
60+
It's worth noting that the _Refactoring Phases_ are usually rolling, and the _End phase_ of one
61+
refactor is the _Transition phase_ of another. The table below details which application releases
62+
needs to be supported during which database phase.
2863

29-
Database tables, views and stored procedures should almost always use either nullable fields or have
30-
a default value. Since this will allow stored procedures to omit columns, which is a requirement
31-
when running both old and new code.
64+
| Database Phase | Release X | Release X+1 | Release X+2 |
65+
| -------------- | --------- | ----------- | ----------- |
66+
| Start ||||
67+
| Transition ||||
68+
| End ||||
3269

33-
### EDD Process
70+
### Migrations
3471

35-
The EDD breaks up each database migration into three phases. _Start_, _Transition_ and _End_.
72+
The three different migrations described in the diagram above are, _Initial migration_, _Transition
73+
migration_ and _ Finalization migration_.
3674

37-
![Refactoring Stages](./stages_refactoring.jpg)
38-
[https://www.martinfowler.com/articles/evodb.html#TransitionPhase](https://www.martinfowler.com/articles/evodb.html#TransitionPhase)
75+
#### Initial migration
3976

40-
This necessitates two different database migrations. The first migration adds new content and is
41-
backwards compatible with the existing code. The second migration removes content and is not
42-
backwards compatible with that same code prior to the first migration.
77+
The initial migration runs before the code deployment, and its purpose is to add support for
78+
_Release X+1_ without breaking support of _Release X_. The migration should execute quickly and not
79+
contain any costly operations to ensure zero downtime.
80+
81+
#### Transition migration
82+
83+
The transition migration are run sometime during the transition phase, and provides an optional data
84+
migration should it be too slow or put too much load on the database, or otherwise make it
85+
unsuitable for the _Initial migration_.
86+
87+
- Compatible with _Release X_ **and** _Release X+1_ application.
88+
- Only data population migrations may be run at this time, if they are needed
89+
- Must be run as a background task during the Transition phase.
90+
- Operation is batched or otherwise optimized to ensure the database stays responsive.
91+
- Schema changes are NOT to be run during this phase.
92+
93+
#### Finalization migration
94+
95+
The finalization migration removes the temporary measurements that were needed to retain backwards
96+
compatibility with _Release X_, and the database schema henceforth only supports _Release X+1_.
97+
These migrations are run as part of the deployment of _Release X+2_.
4398

4499
### Example
45100

46-
Lets look at an example, the rename column refactor is shown in the image below.
101+
Let's look at an example, the rename column refactor is shown in the image below.
47102

48103
![Rename Column Refactor](./rename-column.gif)
49104

@@ -73,7 +128,7 @@ actions.
73128
:::
74129

75130
<Tabs>
76-
<TabItem value="first" label="First Migration" default>
131+
<TabItem value="first" label="Initial Migration" default>
77132

78133
```sql
79134
-- Add Column
@@ -120,7 +175,7 @@ END
120175
```
121176

122177
</TabItem>
123-
<TabItem value="data" label="Data Migration">
178+
<TabItem value="data" label="Transition Migration">
124179

125180
```sql
126181
UPDATE [dbo].Customer SET
@@ -129,7 +184,7 @@ WHERE FirstName IS NULL
129184
```
130185

131186
</TabItem>
132-
<TabItem value="second" label="Second Migration">
187+
<TabItem value="second" label="Finalization Migration">
133188

134189
```sql
135190
-- Remove Column
@@ -173,65 +228,96 @@ END
173228
</TabItem>
174229
</Tabs>
175230

176-
## Workflow
231+
## Deployment orchestration
232+
233+
There are some important constraints to the implementation of the process:
234+
235+
- Bitwarden Production environments are required to be on at all times
236+
- Self-host instances must support the same database change process; however, they do not have the
237+
same always-on application constraint
238+
- Minimization of manual steps in the process
239+
240+
The process to support all of these constraints is a complex one. Below is an image of a state
241+
machine that will hopefully help visualize the process and what it supports. It assumes that all
242+
database changes follow the standards that are laid out in [Migrations](./).
243+
244+
---
245+
246+
![Bitwarden EDD State Machine](./edd_state_machine.jpg) \[Open Image in a new tab for better
247+
viewing\]
248+
249+
---
177250

178-
The Bitwarden specific workflow for writing migrations are described below.
251+
### Online environments
179252

180-
### Developer
253+
Schema migrations and data migrations as just migrations. The underlying implementation issue is
254+
orchestrating the runtime constraints on the migration. Eventually, all migrations will end up in
255+
`DbScripts`. However, to orchestrate the running of _Transition_ and associated _Finalization_
256+
migrations, they are kept outside of `DbScripts` until the correct timing.
181257

182-
The development flow is described in [Migrations](./).
258+
In environments with always-on applications, _Transition_ scripts must be run after the new code has
259+
been rolled out. To execute a full deploy, all new migrations in `DbScripts` are run, the new code
260+
is rolled out, and then all _Transition_ migrations in the `DbScripts_transition` directory are run
261+
as soon as all of the new code services are online. In the case of a critical failure after the new
262+
code is rolled out, a Rollback would be conducted (see Rollbacks below). _Finalization_ migrations
263+
will not be run until the start of the next deploy when they are moved into `DbScripts`.
183264

184-
### Devops
265+
After this deploy, to prep for the next release, all migrations in `DbScripts_transition` are moved
266+
to `DbScripts` and then all migrations in `DbScripts_finalization` are moved to `DbScripts`,
267+
conserving their execution order for a clean install. For the current branching strategy, PRs will
268+
be open against `master` when `rc` is cut to prep for this release. This PR automation will also
269+
handle renaming the migration file and updating any reference of `[dbo_finalization]` to `[dbo]`.
185270

186-
#### On `rc` cut
271+
The next deploy will pick up the newly added migrations in `DbScripts` and set the previously
272+
repeatable _Transition_ migrations to no longer be repeatable, execute the _Finalization_
273+
migrations, and then execute any new migrations associated with the code changes that are about to
274+
go out.
187275

188-
Create a PR moving the future scripts.
276+
The state of migrations in the different directories at any one time is is saved and versioned in
277+
the Migrator Utility which supports the phased migration process in both types of environments.
189278

190-
- `DbScripts_future` to `DbScripts`, prefix the script with the current date, but retain the
191-
existing date.
192-
- `dbo_future` to `dbo`.
193-
<bitwarden>
194-
<li>
195-
Create a ticket in Jira with a `Due Date` of the release date to ensure future migrations are
196-
merged in and ready to be executed. Set the ticket that created the future migration as a
197-
blocker.
198-
</li>
199-
</bitwarden>
279+
### Offline environments
200280

201-
#### After server release
281+
The process for offline environments is similar to the always-on ones. However, since they do not
282+
have the constraint of always being on, the _Initial_ and _Transition_ migrations will be run one
283+
after the other:
202284

203-
1. Run whatever data migration scripts might be needed. (This might need to be batched and executed
204-
until all the data has been migrated)
205-
2. After having the server run for a while execute the future migration script to clean up the
206-
database.
285+
- Stop the Bitwarden stack as done today
286+
- Start the database
287+
- Run all new migrations in `DbScripts` (both _Finalization_ migrations from the last deploy and any
288+
_Initial_ migrations from the deploy currently going out)
289+
- Run all _Transition_ migrations
290+
- Restart the Bitwarden stack.
207291

208292
## Rollbacks
209293

210294
In the event the server release failed and needs to be rolled back, it should be as simple as just
211295
re-deploying the previous version again. The database will **stay** in the transition phase until a
212-
hotfix can be released, and the server can be updated.
296+
patch can be released, and the server can be updated. Once a patch is ready to go out, it is
297+
deployed the _Transition_ migrations are rerun to verify that the DB is in the state that it is
298+
required to be in.
213299

214-
The goal is to resolve the issue quickly and re-deploy the fixed code to minimize the time the
215-
database stays in the transition phase. Should a feature need to be completely pulled, a new
216-
migration needs to be written to undo the database changes and the future migration will also need
217-
to be updated to work with the database changes. This is generally not recommended since pending
218-
migrations (for other releases) will need to be revisited.
300+
Should a feature need to be completely pulled, a new migration needs to be written to undo the
301+
database changes and the future migration will also need to be updated to work with the database
302+
changes. This is generally not recommended since pending migrations (for other releases) will need
303+
to be revisited.
219304

220305
## Testing
221306

222307
Prior to merging a PR please ensure that the database changes run well on the currently released
223308
version. We currently do not have an automated test suite for this and it’s up to the developers to
224309
ensure their database changes run correctly against the currently released version.
225310

226-
## Further Reading
311+
## Further reading
227312

228-
- [Evolutionary Database Design](https://martinfowler.com/articles/evodb.html) (Particularly
229-
[All database changes are database refactorings](https://martinfowler.com/articles/evodb.html#AllDatabaseChangesAreMigrations))
230-
- [The Agile Data (AD) Method](http://agiledata.org/) (Particularly
231-
[Catalog of Database Refactorings](http://agiledata.org/essays/databaseRefactoringCatalog.html))
232-
- [Refactoring Databases: Evolutionary Database](https://databaserefactoring.com/)
233-
- Refactoring Databases: Evolutionary Database Design (Addison-Wesley Signature Series (Fowler))
234-
ISBN-10: 0321774515
313+
1. [Evolutionary Database Design](https://martinfowler.com/articles/evodb.html) (Particularly
314+
[All database changes are database refactorings](https://martinfowler.com/articles/evodb.html#AllDatabaseChangesAreMigrations))
315+
2. [The Agile Data (AD) Method](http://agiledata.org/) (Particularly
316+
[Catalog of Database Refactorings](http://agiledata.org/essays/databaseRefactoringCatalog.html))
317+
3. [Refactoring Databases: Evolutionary Database](https://databaserefactoring.com/)
318+
4. Refactoring Databases: Evolutionary Database Design (Addison-Wesley Signature Series (Fowler))
319+
ISBN-10: 0321774515
235320

321+
[edd-wiki]: https://en.wikipedia.org/wiki/Evolutionary_database_design
236322
[edd-rfd]:
237323
https://bitwarden.atlassian.net/wiki/spaces/PIQ/pages/177701412/Adopt+Evolutionary+database+design
Loading

docs/contributing/database-migrations/index.md

+21-10
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
sidebar_position: 2
33
---
44

5-
# Database Migrations
5+
# Database migrations
66

7-
## Applying Migrations
7+
## Applying migrations
88

99
We use a `migrate.ps1` PowerShell script to apply migrations to the local development database. This
1010
script handles the different database providers that we support.
@@ -13,12 +13,12 @@ For instructions on how to use `migrate.ps1`, see the Getting Started section fo
1313
[MSSQL](../../getting-started/server/database/mssql/index.md#updating-the-database) and
1414
[Entity Framework](../../getting-started/server/database/ef/index.mdx#migrations)
1515

16-
## Creating Migrations for New Changes
16+
## Creating migrations for new changes
1717

1818
Any database change must be scripted as a migration for both our primary DBMS - MSSQL - as well as
1919
for Entity Framework. Follow the instructions below for each provider.
2020

21-
### MSSQL Migrations
21+
### MSSQL migrations
2222

2323
:::tip
2424

@@ -37,24 +37,24 @@ It is possible that a change may not require a non-backwards-compatible end phas
3737
may be backwards-compatible in their final form). In that case, only one phase of changes is
3838
required.
3939

40-
#### Backwards Compatible Migration
40+
#### Backwards compatible migration
4141

4242
1. Modify the source `.sql` files in `src/Sql/dbo`.
4343
2. Write a migration script, and place it in `util/Migrator/DbScripts`. Each script must be prefixed
4444
with the current date.
4545

46-
#### Non-Backwards Compatible Migration
46+
#### Non-backwards compatible migration
4747

48-
1. Copy the relevant `.sql` files from `src/Sql/dbo` to `src/Sql/dbo_future`.
48+
1. Copy the relevant `.sql` files from `src/Sql/dbo` to `src/Sql/dbo_finalization`.
4949
2. Remove the backwards compatibility that is no longer needed.
50-
3. Write a new Migration and place it in `src/Migrator/DbScripts_future`. Name it
51-
`YYYY-0M-FutureMigration.sql`.
50+
3. Write a new Migration and place it in `src/Migrator/DbScripts_finalization`. Name it
51+
`YYYY-0M-FinalizationMigration.sql`.
5252
- Typically migrations are designed to be run in sequence. However since the migrations in
5353
DbScripts_future can be run out of order, care must be taken to ensure they remain compatible
5454
with the changes to DbScripts. In order to achieve this we only keep a single migration, which
5555
executes all backwards incompatible schema changes.
5656

57-
### EF Migrations
57+
### EF migrations
5858

5959
If you alter the database schema, you must create an EF migration script to ensure that EF databases
6060
keep pace with these changes. Developers must do this and include the migrations with their PR.
@@ -72,4 +72,15 @@ pwsh ef_migrate.ps1 [NAME_OF_MIGRATION]
7272

7373
This will generate the migrations, which should then be included in your PR.
7474

75+
### [Not Yet Implemented] Manual MSSQL migrations
76+
77+
There may be a need for a migration to be run outside of our normal update process. These types of
78+
migrations should be saved for very exceptional purposes. One such reason could be an Index rebuild.
79+
80+
1. Write a new Migration with a prefixed current date and place it in
81+
`src/Migrator/DbScripts_manual`
82+
2. After it has been run against our Cloud environments and we are satisfied with the outcome,
83+
create a PR to move it to `DbScripts`. This will enable it to be run by our Migrator processes in
84+
self-host and clean installs of both cloud and self-host environments
85+
7586
[code-style-sql]: ../code-style/sql.md
Binary file not shown.

0 commit comments

Comments
 (0)