-
Notifications
You must be signed in to change notification settings - Fork 213
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
Force rollback of tables with foreign keys / cascading deletes on automatic db migrations #1290
Conversation
bors try |
7a18339
to
de3825d
Compare
bors r- |
de3825d
to
ce7db9a
Compare
tryBuild failed |
bors try |
tryBuild failed |
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.
Hi @KtorZ
I've had a chance to test your PR, and I was able to successfully start with a wallet that was created with the v2019-12-13
branch. So far so good! 🍾
I do have a couple of questions / concerns though:
-
The trigger condition for performing a forceful rollback is that we see a nonzero migration count. However, this count currently only includes automatic migrations. Should we not also include manual migrations in this count? It's conceivable that a successful manual migration will bring the database into a state where no automatic migrations are necessary. But it might be wiser to err on the side of caution and still perform a forceful rollback in this case. (We can always remove this in future, if we're more confident, and have more testing.)
-
The removal of automatic cascading deletes. The nice thing about having cascading deletes is that we can rely on the database implementation to ensure referential integrity (no dangling references). Even in the case where Persistent's automatic migration mechanism (erroneously) causes rows to be deleted in tables that reference the checkpoint table, we can still rely on SQLite to preserve referential integrity. However, if we remove this automatic mechanism for ensuring referential integrity, do we not increase our testing burden? Haven't we now made it easier for future code to break referential integrity?
Apart from these concerns, this PR looks good to me. I have a couple of comments regarding Haddock comments and names of types.
@@ -211,7 +213,7 @@ startSqliteBackend | |||
-> Migration | |||
-> Tracer IO DBLog | |||
-> Maybe FilePath | |||
-> IO (Either MigrationError SqliteContext) | |||
-> IO (Either MigrationError (Quantity "migration" Int, SqliteContext)) |
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.
Suggestion: use migrations
(plural) or migrationCount
here, to really emphasize that it is a count of the number of migrations applied.
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 disagree here ^.^
The Symbol
attached with a Quantity is used as a unit
. We have several occurrences of this in the source code, like Quantity "lovelace" ...
or Quantity "block" ...
, always singular. Same applies here IMO.
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, Quantity "migration"
is complied with convention adopted in other places
@@ -231,7 +233,8 @@ startSqliteBackend manualMigration migrateAll trace fp = do | |||
Left e -> do | |||
destroyDBLayer ctx | |||
pure $ Left e | |||
Right _ -> pure $ Right ctx | |||
Right ms -> do | |||
pure $ Right (Quantity (length ms), ctx) |
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.
Question: this count (ms
) appears to only include automatic migrations.
Should we not also include any manual migrations in this total count?
So: manual migrations + automatic migrations?
I'm assuming it's possible for there to be cases where there are manual migrations but no automatic migrations. (This would happen if our manual migrations successfully produce the exact schema that Sqlite is expecting.)
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.
Your assumption is correct: there can be manual migrations but no automatic ones.
Still, the migration count here (and this particular fix) is solely about automatic ones. It comes as a work-around for persistent's behavior of deleting tables combined with the cascading deletion of foreign tables.
For manual migration, we are in total control of what's being done and, it is very unlikely we would make a manual migration that requires rolling back the chain. If we ever do, then it'd be probably time to revisit this whole migration logic instead of patching up things on top of each other.
The migration logic has become quite convoluted already. Manual migrations shouldn't be included because they aren't causing implicit uncontrolled behavior here.
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.
@KtorZ wrote:
For manual migration, we are in total control of what's being done and, it is very unlikely we would make a manual migration that requires rolling back the chain. If we ever do, then it'd be probably time to revisit this whole migration logic instead of patching up things on top of each other.
Fair enough! This is a convincing reason.
`persistent` clear foreign tables on automatic migration due to cascading delete. One possible approach to solve this: forcing a manual rollback to genesis whenever there are migrations applied to the database, regardless of the migration. Only the foreign tables are affected, so static metadata and wallet credentials are preserved. This mean that we would force resync of wallets on every updates that have database changes, but that's a fair price to pay for now.
This to prevent 'persistent' from doing it during automated migration! We don't do this for checkpoints because the cascading updates are truly helpful there to manage the database state and, can be more easily rolled back at any time (since the data regarding checkpoints entirely comes from the chain).
6b70b75
to
0583144
Compare
[ "Some automated database migrations happened. Forced to roll back " | ||
, "to genesis." |
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.
minor suggestion
Some automated database migrations happened. Rollback to the genesis was forced.
it is somewhat more natural to me
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.
Why the
however 🤔 ?
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, probably without the
. so no to the genesis
but to genesis
let start = startSqliteBackend (ManualMigration mempty) migrateAll trace fp | ||
(nMigrations, ctx) <- handlingPersistError trace fp start | ||
let dbLayer = mkDBLayer ctx | ||
when (nMigrations > Quantity 0) $ do |
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.
👍
-- tables, and re-creating them from scratch. When paired with automatic cascade | ||
-- deletion, this has dramatic consequences on the wallet data integrity (when a | ||
-- source table is deleted due to a migration, all foreign tables are also | ||
-- deleted, but only the rows in the source table are replaced!). |
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.
💯 for the comment above
@@ -424,7 +424,15 @@ newDBLayer trace defaultFieldValues mDatabaseFile = do | |||
selectWallet wid >>= \case | |||
Nothing -> pure $ Left $ ErrNoSuchWallet wid | |||
Just _ -> Right <$> do | |||
deleteCascadeWhere [WalId ==. wid] | |||
deleteWhere [WalId ==. wid] |
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.
if so then ON DELETE CASCADE in TH.hs are to be deleted
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.
and they are
bors r+ |
1290: Force rollback of tables with foreign keys / cascading deletes on automatic db migrations r=KtorZ a=KtorZ # Issue Number <!-- Put here a reference to the issue this PR relates to and which requirements it tackles --> #1279 # Overview <!-- Detail in a few bullet points the work accomplished in this PR --> - 74c232e force rollback to genesis after automated migrations `persistent` clear foreign tables on automatic migration due to cascading delete. One possible approach to solve this: forcing a manual rollback to genesis whenever there are migrations applied to the database, regardless of the migration. Only the foreign tables are affected, so static metadata and wallet credentials are preserved. This mean that we would force resync of wallets on every updates that have database changes, but that's a fair price to pay for now. - de3825d remove foreign keys to 'Wallet' table and cascade deletes manually This to prevent 'persistent' from doing it during automated migration! We don't do this for checkpoints because the cascading updates are truly helpful there to manage the database state and, can be more easily rolled back at any time (since the data regarding checkpoints entirely comes from the chain). # Comments <!-- Additional comments or screenshots to attach if any --> ~~:warning: Currently manually testing db migration from various previous versions.~~ Automated migration passes! And on old version, we have a nice log showing what's going on, including manual migration, and handling of automated ones! ``` *** Migrating from v2019-12-13 *** [cardano-wallet.application:Info:22] [2020-01-20 17:58:12.47 UTC] Found existing wallet: 99ef09bdab5f62579f2d6db02ba7df5e88b7e865 [cardano-wallet.wallet-db:Notice:28] [2020-01-20 17:58:12.47 UTC] checkpoint table does not contain required field 'active_slot_coeff'. Adding this field with a default value of 1.0. [cardano-wallet.wallet-db:Notice:28] [2020-01-20 17:58:12.52 UTC] 48 migrations were applied to the database. [cardano-wallet.wallet-db:Notice:28] [2020-01-20 17:58:12.52 UTC] Some automated database migrations happened. Forced to rollback to genesis. [cardano-wallet.main:Info:22] [2020-01-20 17:58:12.53 UTC] Wallet backend server listening on 127.0.0.1:8090 [cardano-wallet.wallet-engine:Info:28] [2020-01-20 17:58:12.53 UTC] 99ef09bd: Applying blocks [1167051.7 ... 1167051.7] [cardano-wallet.wallet-engine:Info:28] [2020-01-20 17:58:12.53 UTC] 99ef09bd: Creating checkpoint at 3b2894d8-[1167051.7#1] ``` So, to summarize what's happening: 1. An existing wallet database is found. 2. Before running any automatic migration, the database is inspected and an extra column is found. So we manually alter the table to add a column with a default value. 3. Then, persistent runs its own set of automated migrations on top of the database, running 48 of them. 4. This is then detected and induces immediately a rollback to genesis of all the on-chain data. 5. The wallet starts applying blocks and creating checkpoints, starting with a checkpoint at height `#1` (right after genesis) :tada: <!-- Don't forget to: ✓ Self-review your changes to make sure nothing unexpected slipped through ✓ Assign yourself to the PR ✓ Assign one or several reviewer(s) ✓ Once created, link this PR to its corresponding ticket ✓ Assign the PR to a corresponding milestone ✓ Acknowledge any changes required to the Wiki --> Co-authored-by: KtorZ <matthias.benkort@gmail.com>
Build succeeded |
Issue Number
#1279
Overview
74c232e
force rollback to genesis after automated migrations
persistent
clear foreign tables on automatic migration due tocascading delete. One possible approach to solve this: forcing a
manual rollback to genesis whenever there are migrations applied
to the database, regardless of the migration. Only the foreign
tables are affected, so static metadata and wallet credentials
are preserved. This mean that we would force resync of wallets
on every updates that have database changes, but that's a fair
price to pay for now.
de3825d
remove foreign keys to 'Wallet' table and cascade deletes manually
This to prevent 'persistent' from doing it during automated migration! We don't do
this for checkpoints because the cascading updates are truly helpful there to manage
the database state and, can be more easily rolled back at any time (since the data
regarding checkpoints entirely comes from the chain).
Comments
Automated migration passes! And on old version, we have a nice log showing what's going on, including manual migration, and handling of automated ones!
So, to summarize what's happening:
#1
(right after genesis)🎉