Skip to content

분산 트랜잭션과 로컬 트랜잭션

Fox Transactions 은 분산 트랜잭션(distributed transaction)과 로컬 트랜잭션(local transaction)을 모두 지원합니다. 분산 트랜잭션은 Fox Transactions 가 사용하는 선언적 자동 트랜잭션과 매우 잘 어울리지만 설정상 복잡성과 성능상 불리함을 갖습니다. 반면에 로컬 트랜잭션은 간단하고 성능적으로 우수하지만 코드 작성에 있어서 제약을 갖습니다. 이 문서에서는 Fox Transactions 관점에서 이해해야할 분산 트랜잭션과 로컬 트랜잭션의 특징과 주의할 점을 살펴봅니다.

이 문서와 관련된 예제 코드는 다음을 참조 하십시요.

분산 트랜잭션

기본적으로 분산 트랜잭션은 하나 이상의 자원(데이터베이스)에 대해 트랜잭션을 사용하는 것을 말합니다. 예를 들어 어떤 앱이 Oracle 데이터베이스와 SQL Server 데이터베이스에 데이터를 기록하는데 트랜잭션을 사용해야 한다면 분산 트랜잭션을 사용해야 합니다.

구성 요소

다음 그림은 분산 트랜잭션의 구성 요소들을 보여 줍니다.

분산 트랜잭션 개요

분산 트랜잭션에서 트랜잭션 관리자(TM; Transaction Manager)는 분산 트랜잭션을 관리하는 가장 중요한 역할을 담당합니다. 앱(이 경우 Fox Transactions)의 요청에 따라 분산 트랜잭션을 시작하고 분산 트랜잭션 ID와 같은 정보들을 관리하며 트랜잭션이 종료한 경우 데이터베이스와 통신하며 2PC(2 Phase Commit)를 수행합니다.

Fox Transactions 를 포함하여 닷넷 환경에서 사용가능한 유일한 TM 은 MSDTC(Microsoft Distributed Transaction Coordinator)입니다. 현재 MSDTC 는 Windows 에서만 제공되는 서비스이며 리눅스와 같은 다른 플랫폼에서 사용할 수 없습니다. Fox Transactions 를 포함하여 System.Transactions 네임스페이스를 사용하여 분산 트랜잭션을 처리하는 앱들은 모두 MSDTC 를 사용합니다. MSDTC 는 별도의 프로세스로 구동되는 서비스이기 때문에 필연적으로 프로세스간 통신을 요구하기 때문에 성능적으로 오버헤드가 발생합니다.

Fox Transactions 를 포함한 앱은 TM 을 통해 분산 트랜잭션을 시작하고 분산 트랜잭션을 지원하는 자원 관리자(RM; Resource Manager)를 액세스하여 자원 관리자가 분산 트랜잭션에 참여(enlist)하도록 합니다. 자원 관리자는 전형적으로 MSDTC 분산 트랜잭션을 지원하는 데이터베이스(Oracle, SQL Server, PostgreSQL 등)이며 Windows 파일 시스템도 분산 트랜잭션을 지원하는 대표적인 비-데이터베이스 자원 관리자 입니다. 앱에 의해 트랜잭션이 종료되면 TM 은 분산 트랜잭션에 참여한 RM 들과 통신하여 2PC(2 Phase Commit)를 수행합니다.

앱과 TM, 앱과 RM, TM과 RM 의 통신이 필요하며 이들 통신 사이의 프로토콜 등 분산 트랜잭션은 복잡한 과정과 설정이 요구됩니다. Azure, AWS, OCI 등의 클라우드에서 분산 트랜잭션을 지원하는 환경을 꾸미기 위해서는 정적인 네트워크 환경을 가진 가상 머신들을 사용해야 합니다. 앱의 물리적 위치, 데이터베이스의 위치 등이 유동적으로 변할 수 있는 마이크로 서비스나 데이터베이스 환경에서 분산 트랜잭션은 지원되지 않습니다.

전통적인 분산 트랜잭션 예제

다음 코드는 System.Transactions 네임스페이스의 TransactionScope 클래스를 사용하여 Oracle 과 PostgreSQL 데이터베이스에 데이터를 추가하는 분산 트랜잭션을 수행하는 예를 보여줍니다. TranactionScope 클래스는 Fox Tranactions 와 유사하게 데이터액세스가 자동으로 트랜잭션에 참여(enlist)

using FoxDbAccess oracleDb = FoxDbAccess.CreateDbAccess("Oracle");
using FoxDbAccess npgsqlDb = FoxDbAccess.CreateDbAccess("PostgreSQL");
using (TransactionScope scope = new(TransactionScopeOption.Required))
{
    // Oracle 데이터 추가
    oracleDb.Open();
    oracleDb.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(998, 9, 'X')");
    oracleDb.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(999, 9, 'Y')");
    oracleDb.Close();

    // PostgreSQl 데이터 추가
    npgsqlDb.Open();
    npgsqlDb.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(998, 9, 'X')");
    npgsqlDb.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(999, 9, 'Y')");
    npgsqlDb.Close();

    scope.Complete();
    // TransactionScope.Dispose() 시점에 커밋 또는 롤백이 수행됩니다.
    // 두 데이터베이스에 데이터가 모두 추가되거나(commit) 모두 추가되지 않습니다(rollback).
}

Fox Transactions 에서 분산 트랜잭션

Fox Transactions 는 기본적으로 분산 트랜잭션을 사용하도록 구성되어 있습니다. FoxBizBase 에서 파생된 클래스를 사용하면 System.Transactions 네임스페이스를 내부적으로 사용하는 FoxFastTransactionController 클래스가 트랜잭션을 시작/커밋/롤백하는 컨트롤러로 사용됩니다.

로컬 트랜잭션

로컬 트랜잭션은 단일 자원(데이터베이스)에 대한 트랜잭션입니다. 앱은 데이터베이스에 연결하고 트랜잭션을 시작하며 트랜잭션을 커밋하거나 롤백합니다. 로컬 트랜잭션은 거의 모든 데이터베이스에 의해 지원되며 단일 연결(세션) 내에서 트랜잭션을 시작하고 종료하기 때문에 로컬 트랜잭션은 서로에게 영향을 줄 수 없습니다.

개요

로컬 트랜잭션은 앱과 단일 데이터베이스만이 트랜잭션에 관여하며 단순하기 때문에 분산 트랜잭션에 비해 성능이 우수하며 별도의 설정이나 서비스가 필요하지 않습니다. 따라서 클라우드 환경에서는 분산 트랜잭션 대신 로컬 트랜잭션만을 사용할 수 있습니다.

다음 코드는 FoxDbAccess 가 제공하는 트랜잭션 메서드들을 사용한 로컬 트랜잭션 예제입니다. 로컬 트랜잭션은 BeginTrans, CommitTrans, RollbackTrans 메서드와 유사한 API 수준의 트랜잭션 시작/커밋/롤백 호출을 사용합니다.

using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess(TestUtils.TestDB1);
dbAccess.Open();
dbAccess.BeginTrans();
try
{
    dbAccess.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(998, 9, 'X')");
    dbAccess.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(999, 9, 'Y')");
    dbAccess.CommitTrans();
}
catch (Exception ex)
{
    AnsiConsole.MarkupLine($"[red]Error occurred: {ex.Message}[/]");
    dbAccess.RollbackTrans();
    return;
}
finally
{
    dbAccess.Close();
}

문제점

간단하고 성능이 우수하지만 여러 데이터베이스에 로컬 트랜잭션만을 사용하여 데이터를 추가/수정/삭제해야 한다면 코드는 매우 복잡해 집니다. 다음 코드는 로컬 트랜잭션을 사용하여 두 데이터베이스에 데이터를 추가하는 코드입니다.

using FoxDbAccess oracleDb = FoxDbAccess.CreateDbAccess(TestUtils.TestDB1);
using FoxDbAccess npgsqlDb = FoxDbAccess.CreateDbAccess(TestUtils.TestDB2);

oracleDb.Open();
npgsqlDb.Open();

oracleDb.BeginTrans();
npgsqlDb.BeginTrans();
try
{
    oracleDb.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(998, 9, 'X')");
    oracleDb.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(999, 9, 'Y')");
    npgsqlDb.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(998, 9, 'X')");
    npgsqlDb.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(999, 9, 'Y')");

    oracleDb.CommitTrans();
    npgsqlDb.CommitTrans();
}
catch (Exception ex)
{
    AnsiConsole.MarkupLine($"[red]Error occurred: {ex.Message}[/]");
    oracleDb.RollbackTrans();
    npgsqlDb.RollbackTrans();
    return;
}
finally
{
    oracleDb.Close();
    npgsqlDb.Close();
}

위 코드가 복잡해 보이지 않지만 실제로는 많은 문제점을 가지고 있습니다. 예를 들어 Oracle 에 대해 트랜잭션을 시작한 이후 PostgreSQL 에 대한 트랜잭션 시작이 실패한다면 어떻게 해야 할까요? 물론 기존 Oracle 트랜잭션은 롤백하고 코드를 종료애햐 할 것입니다. 위 코드는 다음과 같은 코드들이 추가되어야 할 것입니다.

oralceDb.BeginTrans();
try
{
    npgsqlDb.BeginTrans();
}
catch (Exception ex)
{
    oracleDb.RollbackTrans();
    throw;
}

위 코드 뿐만 아니라 트랜잭션의 원자성과 일관성을 유지하기 위해 더 많은 안정성 확인 코드들이 필요합니다. 이 외에도 풀기 어려운 문제점들도 존재합니다. 예를 들어 Oralce 에 대해 커밋을 수행한 이후 PostgreSQL 에 대한 커밋 도중 오류가 발생한다면 어떻게 해야 할까요? 이미 Oracle 에 추가한 데이터는 커밋되었으므로 롤백할 손쉬운 방법이 존재하지 않습니다. 이러한 원자성 문제는 롤백을 수행할 때에도 동일하게 발생합니다.

Note

분산 트랜잭션은 2PC(2-Phase-Commit) 프로토콜을 사용하여 트랜잭션에 참여한 여러 데이터베이스에 대해 문제 없이 커밋을 수행할 수 있습니다.

이러한 이유에서 여러 데이터베이스가 관여되면 로컬 트랜잭션 보다는 분산 트랜잭션을 사용하는 것이 성능적으로 불리하더라도 압도적으로 편리합니다.

Fox Transactions 에서 로컬 트랜잭션

Fox Transactions 에서는 일부 제약 사항만 준수 한다면 분산 트랜잭션을 사용하는 것과 유사하게 로컬 트랜잭션도 지원합니다. FoxTransactionControllerAttribute 특성을 사용하여 FoxLocalTranactionController 를 사용하도록 명시적으로 설정되면 System.Transactions 네임스페이스를 사용하지 않고 로컬 트랜잭션을 사용할 수 있습니다. 상세한 내용은 로컬 트랜잭션 컨트롤러 를 참고하십시요.

단일 데이터베이스 트랜잭션

단일 데이터베이스를 액세스 하는 상황에서도 분산 트랜잭션은 사용될 수 있으며 매우 유용합니다. 분산 트랜잭션은 단일 리소스에 국한되지 안고 단일 리소스에 대한 다중 접속도 분산 트랜잭션으로 처리될 수 있습니다.

문제점 파악

하나의 데이터베이스에 데이터를 추가하는 InsertData 메서드를 고려해 봅시다. 메서드는 재 사용성을 높이기 위해 최대한 외부 코드들과 독립적으로 작동하는 것이 좋습니다. 다음 InsertData 메서드는 데이터를 추가하는 잘 작동하는 독립적인 메서드로 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
static void InsertData(int id, int col1, string col2)
{
    using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess(TestUtils.TestDB1);
    var parameters = dbAccess.CreateParamCollection();
    parameters.AddWithValue("pk", id);
    parameters.AddWithValue("col1", col1);
    parameters.AddWithValue("col2", col2);
    dbAccess.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(:PK, :COL1, :COL2)", parameters);
}

하지만 InsertData 메서드가 로컬 트랜잭션 하에서 사용되어야 한다면 문제가 복잡해 집니다. InsertData 메서드는 1 건의 데이터를 추가하는 단일 트랜잭션에서 호출될 수 있지만 2 건 이상의 데이터를 추가하는 트랜잭션에서 호출될 수도 있습니다. InsertData 메서드는 이러한 상황을 미리 알 수 없기 때문에 트랜잭션 시작/커밋/롤백은 호출자에 의해 수행되어야 합니다.

InsertData 메서드는 호출자가 트랜잭션을 시작했는지 여부에 따라서 로컬 트랜잭션을 시작해야 하며 트랜잭션 시작을 위해 데이터베이스 연결을 수행해야 할 수도 있습니다. 데이터 INSERT 이후, 트랜잭션을 호출자가 시작했는지 여부에 따라서 커밋 혹은 롤백을 수행해야 하며 데이베이스 연결을 자신이 수행했는지 여부에 따라서 연결을 닫아야 할 수도 있습니다.

static void InsertData(FoxDbAccess dbAccess, int id, int col1, string col2)
{
    bool ownsTransaction = false;
    bool ownsConnection = false;
    if (dbAccess.IsInLocalTransaction == false)
    {
        if (dbAccess.IsOpen == false)
        {
            dbAccess.Open();
            ownsConnection = true;
        }
        dbAccess.BeginTrans();
        ownsTransaction = true;
    }
    try
    {
        var parameters = dbAccess.CreateParamCollection();
        parameters.AddWithValue("pk", id);
        parameters.AddWithValue("col1", col1);
        parameters.AddWithValue("col2", col2);
        dbAccess.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(:PK, :COL1, :COL2)", parameters);
        if (ownsTransaction)
        {
            dbAccess.CommitTrans();
        }
    }
    catch
    {
        if (ownsTransaction)
        {
            dbAccess.RollbackTrans();
        }
        throw;
    }
    finally
    {
        if (ownsConnection)
        {
            dbAccess.Close();
        }
    }
}

InsertData 메서드를 호출하는 InsertMany 메서드는 다음과 같습니다. 여러 데이터를 추가하기 위해 로컬 트랜잭션을 추가하고 반복적으로 InsertData 메서드를 호출하고 트랜잭션을 커밋하거나 롤백합니다.

static void InsertMany(int[] ids)
{
    using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess(TestUtils.TestDB1);
    dbAccess.Open();
    dbAccess.BeginTrans();
    try
    {
        foreach(int id in ids)
        {
            InsertData(dbAccess, id, id, "STR #" + id);
        }
        dbAccess.CommitTrans();
    }
    catch
    {
        AnsiConsole.MarkupLine($"[red]Error occurred: {ex.Message}[/]");
        dbAccess.RollbackTrans();
        throw;
    }
    finally
    {
        dbAccess.Close();
    }
}

만약 InsertMany 메서드를 호출하는 다른 메서드가 트랜잭션을 사용해야 한다면 어떻게 해야 할까요? InsertData 메서드가 수행했었던 데이터베이스 연결 관리 및 트랜잭션 관리를 InsertMany 메서드에서도 동일하게 수행해야 합니다!!!

분산 트랜잭션 사용

단일 데이터베이스만을 사용하더라도 분산 트랜잭션을 적용하면 코드는 획기적으로 단순화할 수 있습니다. 다음 코드는 분산 트랜잭션을 사용하는 InsertMany 메서드입니다.

static void InsertMany(int[] ids)
{
    using TransactionScope scope = new(TransactionScopeOption.Required);
    foreach(int id in ids)
    {
        string col2 = "STR #" + i;
        InsertData(id, id, "STR #" + id);
    }
    scope.Complete();
}

static void InsertData(int id, int col1, string col2)
{
    using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess(TestUtils.TestDB1);
    var parameters = dbAccess.CreateParamCollection();
    parameters.AddWithValue("pk", id);
    parameters.AddWithValue("col1", col1);
    parameters.AddWithValue("col2", col2);
    dbAccess.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(:PK, :COL1, :COL2)", parameters);
}

InsertData 메서드는 메서드가 호출될 때 마다 연결을 수행하고 데이터를 추가합니다. 여러 건을 추가하는 InsertMany 메서드를 통해 InsertData 메서드가 호출되면 여러 연결을 통해 데이터가 추가되며 이들 데이터 추가는 분산 트랜잭션을 구성하게 됩니다.

물론 분산 트랜잭션을 사용하는 InsertMany 메서드는 로컬 트랜잭션을 사용하는 InsertMany 메서드에 비해 성능적으로 불리합니다. 하지만 분산 트랜잭션을 사용하는 InsertMany 메서드는 대단히 직관적이며 로컬 트랜잭션을 사용하는 코드에 비해 대단히 작성이 용이합니다. 분산 트랜잭션과 로컬 트랜잭션의 성능 차이는 많은 앱에서 감당이 가능한 수준이므로 개발 생산성을 크게 높여줄 수 있는 분산 트랜잭션이 유리하다고 볼 수 있습니다.

Fox Transactions 에서 단일 데이터베이스 트랜잭션

단일 데이터베이스에 대한 트랜잭션이라면 Fox Transactions 는 개발자에게 분산 트랜잭션과 로컬 트랜잭션 옵션을 모두 제공합니다. 분산 트랜잭션과 비슷한 개발 방식을 사용하면서 로컬 트랜잭션을 사용할 수 있다면 개발자에게 가장 좋은 선택지가 될 수도 있습니다.

Fox Transactions 는 FoxTransactionControllerAttribute 특성을 사용하여 분산 트랜잭션 코딩 방식과 유사하게 로컬 트랜잭션을 사용할 수 있습니다. 다음 코드는 로컬 트랜잭션을 사용하는 Fox Transactions 코드의 예를 보여 줍니다. 마치 분산 트랜잭션을 사용하듯이 InsertData 메서드를 호출하는 InsertMany 메서드의 코드에 주목하십시요. InsertData 메서드와 InsertMany 메서드는 어떤 트랜잭션 관리 코드도 사용하지 않으며 로컬 트랜잭션은 자동으로 시작되고 예외가 발생하지 않으면 트랜잭션은 커밋되며 예외가 발생하면 트랜잭션은 롤백 될 것입니다.

[FoxTransaction(FoxTransactionOption.Required)]
[FoxTransactionController(FoxTransactionControllerKind.LocalTransaction)]
public class BizClass : FoxBizBase, IBizClass
{
    [FoxAutoComplete]
    public void InsertMany(int[] ids)
    {
        using DacClass dac = new();
        IDacClass itf = dac.CreateExecution<IDacClass>();
        foreach(int id in ids)
        {
            string col2 = "STR #" + i;
            InsertData(id, id, col2);
        }
    }
}

[FoxTransaction(FoxTransactionOption.Supported)]
[FoxTransactionController(FoxTransactionControllerKind.LocalTransaction)]
public class DacClass : FoxDacBase, IDacClass
{
    [FoxAutoComplete]
    public void InsertData(int id, int col1, string col2)
    {
        var parameters = this.DbAccess.CreateParamCollection();
        parameters.AddWithValue("pk", id);
        parameters.AddWithValue("col1", col1);
        parameters.AddWithValue("col2", col2);
        this.DbAccess.ExecuteSqlNonQuery("INSERT INTO TxTestTable(PK, COL1, COL2) VALUES(:PK, :COL1, :COL2)", parameters);
    }
}

트랜잭션 승격

Fox Trasnactions 에서 내부적으로 사용하는 System.Transactions 네임스페이스 기반 분산 트랜잭션은 효율적으로 트랜잭션을 처리하기 위해 트랜잭션 승격(transaction promotion) 기능을 사용합니다.

트랜잭션 승격이란 System.Transactions 기반 환경 트랜잭션(ambient transaction)이 사용되면 일단 트랜잭션은 로컬 트랜잭션으로 시작되며 트랜잭션 진행 과정에 추가적인 데이터베이스 연결이 트랜잭션에 참여하는 등의 작업이 발생하면 필요에 의해 분산 트랜잭션으로 승격되는 것을 말합니다.

앞서 분산 트랜잭션을 사용하는 InsertMany 메서드를 예를 들어 보겠습니다. InsertMany 메서드에 대해 다음 호출이 수행되었다고 가정해 봅시다.

InsertMany([997, 998, 999]);

첫번째 데이터를 추가하는 InsertData 메서드 호출은 로컬 트랜잭션으로 수행됩니다. 그리고 두번째 데이터 이후의 데이터를 추가하는 InsertData 메서드 호출은 새로운 데이터베이스 연결이 사용되었기 때문에 분산 트랜잭션으로 승격됩니다.

트랜잭션 승격은 다음과 같은 1건의 데이터를 추가하는 상황에서 불필요하게 분산 트랜잭션을 사용하지 않기 때문에 성능적으로 대단히 효율적으로 작동합니다.

InsertMany([997]);

개발하는 앱의 성격에 따라서 분산 트랜잭션이 사용되는 빈도가 크게 낮아질 수도 있기 때문에 분산 트랜잭션이 주는 개발 생산성을 최대화하면서 성능상의 불리함을 크게 낮출 수도 있습니다.

트랜잭션 승격 확인

트랜잭션 승격의 발생 시점을 시각적으로 확인할 수 있도록 InsertMany 메서드에 메시지 출력을 다음과 같이 추가하고,

1
2
3
4
5
6
7
8
9
static void InsertMany(int[] ids)
{
    using TransactionScope scope = new(TransactionScopeOption.Required);
    foreach(int id in ids)
    {
        Console.WriteLine($"Inserting data #{id}");
        InsertData(id, id, "STR #" + id);
    }
}

System.Transactions.TransactionManger 클래스의 정적 멤버인 DistributedTransactionStarted 이벤트 핸들러에 메시지 출력을 다음과 같이 추가 하면,

1
2
3
4
TransactionManager.DistributedTransactionStarted += (s, e) =>
{
    Console.WriteLine("==> Distributed Transaction Started.");
};

다음과 같은 결과를 얻을 수 있습니다.

1
2
3
4
Inserting data #997
Inserting data #998
==> Distributed Transaction Started.
Inserting data #999

Note

만약 두번째 호출에서 첫번째 호출에 사용되었던 데이터베이스 연결이 연결 풀링(connection pooling)에 의해 운 좋게 재사용 되는 경우 트랜잭션 승격은 발생하지 않습니다. 하나의 연결만이 트랜잭션에서 사용되었기 때문입니다. 여러 동시 사용자가 존재하는 운영 환경에서는 발생 가능성이 낮지만 개발 환경에서는 자주 발생될 수 있으므로 트랜잭션 승격이 발생하지 않더라도 놀랄 필요는 없습니다. 이러한 경우 연결 문자열에서 연결 풀링을 끄는 옵션(pooling=false)을 추가해 보십시요.

Warning

테스트를 위해서만 연결 풀링을 꺼야 하며 개발이나 운영 상황에서 연결 풀링을 꺼서는 안됩니다.

Summary

Fox Transactions는 여러 데이터베이스에 걸친 트랜잭션을 위한 분산 트랜잭션과 단일 데이터베이스 내에서 효율적으로 작동하는 로컬 트랜잭션을 모두 지원합니다.

기본적으로 사용되는 분산 트랜잭션은 System.Transactions를 기반으로 하며, 여러 데이터베이스 연결이 하나의 트랜잭션에 참여해야 하는 복잡한 시나리오를 매우 간단하게 만들어 줍니다. 하지만 Windows의 MSDTC와 같은 트랜잭션 관리자에 의존하므로 설정이 복잡하고 Linux나 일부 클라우드 환경에서는 사용이 제한될 수 있습니다.

반면, 로컬 트랜잭션은 단일 데이터베이스 연결에 한정되지만, 가볍고 빠르며 플랫폼에 제약이 없습니다. Fox Transactions는 FoxLocalTranactionController를 통해 분산 트랜잭션과 유사한 선언적 프로그래밍 모델을 유지하면서 로컬 트랜잭션을 사용할 수 있도록 지원합니다.

특히 System.Transactions트랜잭션 승격(Transaction Promotion) 기능을 제공하여, 트랜잭션이 처음에는 가벼운 로컬 트랜잭션으로 시작하고 두 번째 데이터베이스 연결과 같은 추가 리소스가 참여할 때만 완전한 분산 트랜잭션으로 승격됩니다. 이 덕분에 단일 데이터베이스만 사용하는 대부분의 경우, 분산 트랜잭션 모델을 사용하더라도 성능 저하 없이 개발 생산성의 이점을 누릴 수 있습니다.

결론적으로, 개발 생산성과 코드 단순성을 위해 분산 트랜잭션 모델을 우선적으로 고려하고, 플랫폼 제약이나 성능이 극도로 중요한 특정 상황에서는 제약 사항을 이해하고 로컬 트랜잭션 컨트롤러를 사용하는 것이 좋습니다.