Dselans/concurrency support #58
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Hey folks,
So this is a semi-large PR for functionality that was requested in #45. We ran into the exact same need -- our applications run in docker containers on an ephemeral set of nodes and perform in-app migrations. The sql-migrate lib is fantastic, but when used concurrently, you are likely to run into migration collisions. Someone mentioned that this should be handled at a different layer and while that can be done, we have little control over the ops side of things and adding "cluster awareness" to each different service seems .. hacky and difficult to scale (if we're talking about microservices).
#45 suggested to use vendor-specific lock implementations -- while probably doable (and "proper"), it would be a pretty large undertaking. I went with the model of using a db-based mutex instead (ie. a lock record in a
gorp_locktable), that all "migrators" attempt to insert, but only one wins. This ensures that it should work across most db's, without using any vendor-specific lock code (except forsqlite, more on that later).Implementation and notes:
ExecWithLock()andExecMaxWithLock()which will utilize a db mutex to ensure only one 'master' migrator is performing migrationssql-migrateinstances that were not able to acquire the lock go into a wait-state, periodically checking to see if the lock disappears.waitTimeis up - the 'wait state' migrators finish cleanly, with no errors (and0completed migrations)waitTimeis up - the additionalsql-migrateinstances will error with aExceeded lock clearance wait timemessage. You can then tune thewaitTimeaccordingly (it's a param passed toExecWithLock())sqlitedue to concurrent access causingdatabase is lockederrors. It's totally possible that I just missed something obvious, but the easiest route seemed to be to just enable MySQL based integration tests, so I introduced another test suite (MySQLMigrateSuite), which thoroughly tests the lock implementation.go testcalled-enable-mysql-disable-sqlite(default: false); this was done to speed up testing (as the sqlite tests seemed to be pretty slow on my machine)With that said, please let me know if you've got any questions -- this functionality is really crucial for our team and I would much rather prefer running off of upstream than our little fork :-)
TIA!