-
Notifications
You must be signed in to change notification settings - Fork 112
Add a system to perform database migrations #1343
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
Conversation
| ### [Build](https://github.com/slskd/slskd/blob/master/docs/build.md) | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### [Migrations](https://github.com/slskd/slskd/blob/master/docs/migrations.md) |
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.
praise: I like that detailed documentation for migrations has been written
| private Dictionary<string, int> GetStateMapping(SqliteConnection connection) | ||
| { | ||
| Log.Debug("Fetching states and mapping to integers..."); | ||
|
|
||
| using var command = new SqliteCommand("SELECT DISTINCT State FROM Transfers;", connection); | ||
| using var reader = command.ExecuteReader(); | ||
|
|
||
| var dict = new Dictionary<string, int>(); | ||
|
|
||
| while (reader.Read()) | ||
| { | ||
| var state = reader.GetString(0); | ||
| dict[state] = (int)Enum.Parse(typeof(TransferStates), state); | ||
| } | ||
|
|
||
| Log.Debug("State -> int map: {Map}", dict); | ||
| return dict; | ||
| } |
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.
note: If enum values for transfer states are ever changed, this migration will be affected. I like to make migrations independent of the current codebase whenever possible.
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 point. i was cringing at needing to generate a list of all possible enum combinations but it should actually be pretty straightforward since i can just increment an integer and loop
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.
there are 8191 possible combinations, and only a couple of dozen make sense. i'm afraid i'll miss one and break somebody, so what i've done instead is copy/paste the enum into the migration so it will remain stable.
appreciate you pointing this out!
| var rename = @" | ||
| ALTER TABLE Transfers RENAME TO Transfers_old; | ||
| "; | ||
|
|
||
| using var renameCommand = new SqliteCommand(rename, connection, transaction); | ||
| renameCommand.ExecuteNonQuery(); | ||
|
|
||
| /* | ||
| create the new table with StateDescription appearing after State | ||
| */ | ||
| var create = @" | ||
| CREATE TABLE Transfers ( | ||
| Id TEXT NOT NULL, | ||
| Username TEXT, | ||
| Direction TEXT NOT NULL, | ||
| Filename TEXT, | ||
| Size INTEGER NOT NULL, | ||
| StartOffset INTEGER NOT NULL, | ||
| State INTEGER NOT NULL, | ||
| StateDescription TEXT, -- new column | ||
| RequestedAt TEXT NOT NULL, | ||
| EnqueuedAt TEXT, | ||
| StartedAt TEXT, | ||
| EndedAt TEXT, | ||
| BytesTransferred INTEGER NOT NULL, | ||
| AverageSpeed REAL NOT NULL, | ||
| PlaceInQueue INTEGER, | ||
| Exception TEXT, | ||
| Removed INTEGER NOT NULL, | ||
| CONSTRAINT PK_Transfers PRIMARY KEY(Id) | ||
| ); | ||
| "; | ||
|
|
||
| using var createCommand = new SqliteCommand(create, connection, transaction); | ||
| createCommand.ExecuteNonQuery(); | ||
|
|
||
| Log.Information("Schema changes applied, copying data..."); | ||
|
|
||
| /* | ||
| copy the existing data into the new table | ||
| */ | ||
| var copy = @" | ||
| INSERT INTO Transfers ( | ||
| Id, Username, Direction, Filename, Size, StartOffset, State, | ||
| RequestedAt, EnqueuedAt, StartedAt, EndedAt, BytesTransferred, | ||
| AverageSpeed, PlaceInQueue, Exception, Removed | ||
| ) | ||
| SELECT | ||
| Id, Username, Direction, Filename, Size, StartOffset, State, | ||
| RequestedAt, EnqueuedAt, StartedAt, EndedAt, BytesTransferred, | ||
| AverageSpeed, PlaceInQueue, Exception, Removed | ||
| FROM Transfers_old;"; | ||
|
|
||
| using var copyCommand = new SqliteCommand(copy, connection, transaction); | ||
| copyCommand.ExecuteNonQuery(); | ||
|
|
||
| Log.Information("Data copied, adjusting columns..."); | ||
|
|
||
| /* | ||
| we no longer need the old table, so drop it | ||
| */ | ||
| using var dropCommand = new SqliteCommand("DROP TABLE Transfers_old;", connection, transaction); | ||
| dropCommand.ExecuteNonQuery(); |
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: Add a column to Transfers instead of replacing the whole table.
note: SQLite's support of transactional DDL limits the corner cases that could crop up
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.
the new table is so that the State and StateDescription columns will be next to each other. i'm thinking maybe it's not worth the extra effort (and file i/o)
src/slskd/Core/Data/Migrator.cs
Outdated
| /* | ||
| load migration history from the history file in the root of the data directory. this file contains a key/value | ||
| pair for each migration that's been applied, along with the migration date. | ||
| if force=true, we're going to disregard history and overwrite it. | ||
| */ |
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: Store migration history in a database table like most migration systems (EF, Alembic, Liquibase, ...)
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 think i will; originally i didn't because Entity Framework would make it a hassle, but i don't have to use EF for this
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.
Since this topic came up, would it save you a lot of (ongoing) effort to use something like Flyway, or the above mentioned? I've used Flyway in a commercial setting and it worked fine, though it was heavy. I've heard https://github.com/golang-migrate/migrate is much better but I can't say I've tried it myself.
These sorts of tools are nice because you can usually stay away from your codebase and keep the separation of concerns very clean.
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's possible eventually. i've actually removed the history file entirely just to simplify this PR a bit; the 1 existing migration can easily tell if it has been applied already so it's not a big deal.
This PR contains the following updates: | Package | Update | Change | |---|---|---| | [slskd/slskd](https://slskd.org) ([source](https://github.com/slskd/slskd)) | minor | `0.22.5` -> `0.23.1` | --- ### Release Notes <details> <summary>slskd/slskd (slskd/slskd)</summary> ### [`v0.23.1`](https://github.com/slskd/slskd/releases/tag/0.23.1) [Compare Source](slskd/slskd@0.23.0...0.23.1) #### What's Changed - Fix regression in scripts and webhooks causing json to be missing detail by [@​jpdillingham](https://github.com/jpdillingham) in slskd/slskd#1389 **Full Changelog**: slskd/slskd@0.23.0...0.23.1 ### [`v0.23.0`](https://github.com/slskd/slskd/releases/tag/0.23.0) [Compare Source](slskd/slskd@0.22.5...0.23.0) ### 🎉 Database Migrations (that preserve data!) This release introduces a new system that performs database migrations on existing data ([#​1343](slskd/slskd#1343)), which was necessary to migrate the Transfers database to correct an out of memory issue at startup for users with a large number of transfer records (see [#​1291](slskd/slskd#1291)). Upon first start after upgrading to 0.23.0 the application will apply the initial migration to the Transfers database (`transfers.db`), if there is one. Depending on the system and the amount of data present, this may take a while, and the UI will be inaccessible during the process. Backups of existing databases are taken prior to the start of the process (you can find them in `/data/backups`) and they aren't deleted automatically, so **your historical data is safe**. A feature to add auto deletion after a period of time will come later. In the unlikely event that the process fails to complete successfully, follow the directions in the new [migration docs](https://github.com/slskd/slskd/blob/master/docs/migrations.md), which explain what to do if something goes wrong, and how to get the application back to a working state if the migration can't be completed. If you find that your database can't be migrated successfully, please file an issue and I can work on providing a standalone migration tool. #### What's Changed - Add a system to perform database migrations by [@​jpdillingham](https://github.com/jpdillingham) in slskd/slskd#1343 - Fix remaining transfter State queries using HasFlag() by [@​jpdillingham](https://github.com/jpdillingham) in slskd/slskd#1356 - Extend health check startup to 60 minutes, set $SHELL for docker container, and bump the GC memory limit from 500MiB to 2GiB by [@​jpdillingham](https://github.com/jpdillingham) in slskd/slskd#1359 - Bump Soulseek.NET to 7.1.0 by [@​jpdillingham](https://github.com/jpdillingham) in slskd/slskd#1363 - Update documentation for scripts by [@​jpdillingham](https://github.com/jpdillingham) in slskd/slskd#1384 - Bump http-proxy-middleware from 2.0.6 to 2.0.9 in /src/web by [@​dependabot](https://github.com/dependabot) in slskd/slskd#1329 **Full Changelog**: slskd/slskd@0.22.5...0.23.0 </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xLjMiLCJ1cGRhdGVkSW5WZXIiOiI0MS4xLjMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImltYWdlIl19--> Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/860 Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net> Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
A couple of unfortunate decisions have led me to need to change how the
Transfersdatabase is laid out, specifically due to #1280 (database/C# enums), which is symptomatic in #1291 (OOM issue with Transfers).I have been putting this off, hoping that it wouldn't be needed, but the time has come.
This will be a "major" version update and might cause significant headaches for folks on low-spec hardware and a lot of historical transfer data.
Testing
Canary migration
Updated my canary deployment and it went fine. Logs:
Result of migration:
Seems ready to go!?