Skip to content
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

Update FAQ with notes about in-process migrations from multiple app servers #11

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions articles/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,89 @@ Possible reasons:
## What are the supported databases?

[!include[Supported databases](../snippets/supported-databases.md)]

## How can I run migrations safely from multiple application servers?

Many server-side applications are load balanced and run multiple instances of the same application simultaneously from multiple web servers. In such a scenarios, if you choose to run migrations in-process (as opposed to an external migration runner), then there is an added risk of multiple processes trying to run migrations at the same time.

FluentMigrator does not automatically handle this scenario because the default transactional behavior is not enough to guarantee that only a single process can be running migrations at any given time. There are, however, some workarounds.

### Database-Dependent Application Locking

This style of solution depends upon MaintenanceMigrations. Two Maintenance Migrations are created: One with `BeforeAll` to atomically acquire a lock, and one `AfterAll` stage to release the lock.

This example is for **SQL Server 2008 and above** and uses [`sp_getapplock`](https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql) to aquire a named lock before all migrations are run, and [`sp_releaseapplock`](https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-releaseapplock-transact-sql) to release the lock after all migrations are finished.

```c#
[Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)]
public class DbMigrationLockBefore : Migration
{
public override void Down()
{
throw new NotImplementedException("Down migrations are not supported for sp_getapplock");
}

public override void Up()
{
Execute.Sql(@"
DECLARE @result INT
EXEC @result = sp_getapplock 'MyApp', 'Exclusive', 'Session'
IF @result < 0
BEGIN
DECLARE @msg NVARCHAR(1000) = 'Received error code ' + CAST(@result AS VARCHAR(10)) + ' from sp_getapplock during migrations';
THROW 99999, @msg, 1;
END
");
}
}

[Maintenance(MigrationStage.AfterAll, TransactionBehavior.None)]
public class DbMigrationUnlockAfter : Migration
{
public override void Down()
{
throw new NotImplementedException("Down migrations are not supported for sp_releaseapplock");
}

public override void Up()
{
Execute.Sql("EXEC sp_releaseapplock 'MyApp', 'Session'");
}
}
```

In the above SQL Server example, we need to use `TransactionBehavior.None` on the Maintenance Migration while specifying the `@LockOwner` parameter to `Session`, which means that the locking behavior applies to the entire Session rather than a single transaction.

While the above is specific to SQL Server, similar concepts may available in other database providers.

* PostgreSQL has [Advisory Locks](https://www.postgresql.org/docs/10/explicit-locking.html#ADVISORY-LOCKS)
* SQL Anywhere has [Schema Locks](http://infocenter.sybase.com/help/topic/com.sybase.help.sqlanywhere.12.0.1/dbusage/transact-s-3443172.html)
* Oracle has [DBMS_LOCK.ALLOCATE_UNIQUE](https://docs.oracle.com/cd/A91202_01/901_doc/appdev.901/a89852/dbms_l2a.htm)
* DB2 has [LOCK TABLESPACE](https://www.ibm.com/support/knowledgecenter/en/SSEPEK_11.0.0/perf/src/tpc/db2z_lockmode.html) (with the caveat that every table in your migration is in the same tablespace)

### External Distributed Lock

If your database doesn't provide a means of acquiring an exclusive lock for migrations, it is still possible to achieve this functionality by using an external service for acquiring a distributed lock.

For example, Redis provides a way to perform [Distributed locks](https://redis.io/topics/distlock) so that different processes can operate on a shared resource in a mutually exclusive way. This scenario can be baked into a `BeforeAll`/`AfterAll` pair of Maintenance Migrations, as demonstrated above, to acquire an exclusive lock for the duration of the migration running.

As an alternative to Maintenance Migrations, which are by necessity specified in different classes, you could simply wrap the `MigrateUp()` call in code that acquires and releases the lock. Consider this pseudo-code which relies on [RedLock.net](https://github.com/samcook/RedLock.net):

```c#
async RunMigrationsWithDistributedLock(IMigrationRunner runner)
{
var resource = "my-app-migrations";
var expiry = TimeSpan.FromMinutes(5);

using (var redLock = await redlockFactory.CreateLockAsync(resource, expiry)) // there are also non async Create() methods
{
// make sure we got the lock
if (redLock.IsAcquired)
{
runner.MigrateUp();
}
}
// the lock is automatically released at the end of the using block
}
```
13 changes: 9 additions & 4 deletions articles/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ This will create a table named `Log` with the columns `Id`, and `Text`.

You have two options to execute your migration:

* Using an in-process runner (preferred when running from a single process)
* Using an out-of-process runner (for some corporate requirements or when running from multiple processes)
* Using an in-process runner (preferred)
* Using an out-of-process runner (for some corporate requirements)

## [In-Process (preferred when running from a single process)](#tab/runner-in-process)
## [In-Process (preferred)](#tab/runner-in-process)

> [!NOTE]
> If you are potentially running migrations from multiple application servers, such as a load balanced set of web servers,
> you will need to acquire a distributed and exclusive lock, either by database-dependent means or through the use of an
> external distributed lock coordinator. [See the FAQ for more information](xref:faq#how-can-i-run-migrations-safely-from-multiple-application-servers).
Change your `Program.cs` to the following code:

Expand All @@ -56,7 +61,7 @@ Change your `Program.cs` to the following code:
As you can see, instantiating the [migration runner](xref:FluentMigrator.Runner.IMigrationRunner) (in `UpdateDatabase`) becomes
very simple and updating the database is straight-forward.

## [Out-of-process (for some corporate requirements or when running from multiple processes)](#tab/runner-dotnet-fm)
## [Out-of-process (for some corporate requirements)](#tab/runner-dotnet-fm)

> [!IMPORTANT]
> You need at least the .NET Core 2.1 preview 2 SDK for this tool.
Expand Down