'tm'에 해당되는 글 2건

  1. 2010.09.21 COM+ Transaction VS TransactionScope 8
  2. 2010.07.25 TransactionScope 그리고 Stored Procedure 4
COM+ Transaction과 System.Transaction 네임스페이스 안의 TransactionScope의 트랜잭션 관점에서의 차이점을 어떻게 알고 계시나요? 저는 COM+ Transaction은 무조건 분산 트랜잭션으로 TransactionScope은 ConnectionString에 따라서 로컬 또는 분산 트랜잭션을 사용하는 것으로 알고 있었습니다. 그런데 최근에 COM+ 관련 테스트를 하다가 재미있는 점을 발견하였습니다. COM+ Transaction도 로컬 트랜잭션으로 사용되는 것과 같은 현상(?!)을 보게 되었습니다. 그러면 저에게 이러한 혼란을 준 현상을 SQL Profiler과 Component Services로 트랜잭션을 모니터링 해보도록 하겠습니다.

테스트 환경
 - .NET Framework 4.0
 - Windows 7 64bit Ultimate
 - MS SQL 2008 R1(local에 설치)

먼저 COM+ 관련 예제 코드는 아래와 같습니다.
[Transaction(TransactionOption.Required)]
public class COMTxObject : ServicedComponent
{
    string connStr = "Data Source=(local);Initial Catalog=EFWithSQL;Integrated Security=True;";
    string connStr2 = "Data Source=(local);Initial Catalog=EFWithSQL;Integrated Security=true;";

    [AutoComplete(true)]
    public void SQL_Select()
    {
        string name = Guid.NewGuid().ToString();

        string query = @"
select top 1 [id] from [dbo].[Entity1Set]
";

        using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();

            SqlCommand cmd = new SqlCommand(query, conn);
            object result = cmd.ExecuteScalar();
            conn.Close();
        }
    }

    [AutoComplete(true)]
    public void SQL_Insert()
    {
        string name = Guid.NewGuid().ToString();

        string query = @"
insert [dbo].[Entity1Set]([Name])
values ('" + name + @"')
select [Id]
from [dbo].[Entity1Set]
where @@ROWCOUNT > 0 and [Id] = scope_identity()
        ";

        using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(query, conn);
            int result = cmd.ExecuteNonQuery();
            conn.Close();
        }

        SQL_Insert1_1();
        
    }

    void SQL_Insert1_1()
    {
        string name = Guid.NewGuid().ToString();

        string query = @"
insert [dbo].[Entity1Set]([Name])
values ('" + name + @"')
select [Id]
from [dbo].[Entity1Set]
where @@ROWCOUNT > 0 and [Id] = scope_identity()
        ";

        using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(query, conn);
            int result = cmd.ExecuteNonQuery();
            conn.Close();
        }
    }

    [AutoComplete(true)]
    public void SQL_Insert2()
    {
        string name = Guid.NewGuid().ToString();

        string query = @"
insert [dbo].[Entity1Set]([Name])
values ('" + name + @"')
select [Id]
from [dbo].[Entity1Set]
where @@ROWCOUNT > 0 and [Id] = scope_identity()
        ";

        using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(query, conn);
            int result = cmd.ExecuteNonQuery();
            conn.Close();
        }

        SQL_Insert2_1();
    }

    void SQL_Insert2_1()
    {
        string name = Guid.NewGuid().ToString();

        string query = @"
insert [dbo].[Entity1Set]([Name])
values ('" + name + @"')
select [Id]
from [dbo].[Entity1Set]
where @@ROWCOUNT > 0 and [Id] = scope_identity()
        ";

        using (SqlConnection conn = new SqlConnection(connStr2))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(query, conn);
            int result = cmd.ExecuteNonQuery();
            conn.Close();
        }
    }
}
=> DB는 Entity1Set이라는 테이블에 ID, Name을 컬럼으로 가지고 있으며 ID의 경우에는 int형의 IDENTITY로 지정하여 자동으로 증가하게 되어 있습니다. COM+ 이기 때문에 ServicedComponents를 상속받고 있으며 해당 클래스 어트리뷰트로 TransactionOption이 Required로 지정된 Transaction 어트리뷰트가 지정되어 있습니다. 세 개의 메서드가 존재하는데 하나는 조회를 하는 쿼리이며 두 개는 ConnectionString이 동일한 상태에서의 삽입이고(SQL_Insert()), 다른 하나는 ConnectionString이 다른 상태에서의 삽입(SQL_Insert2())입니다. 각각 메서드 위에는 자동으로 Complete를 지정해주는 AutoComplete라는 어트리뷰트가 선언되어 있습니다. (참고 : COM+에서는 수동으로 Commmit, Rollback를 해주기 위해서 ContextUtil이라는 클래스를 사용하기도 합니다.)
그리고 해당 어셈블리를 강력한 이름으로 서명을 해주고 AssemblyInfo.cs파일에 ComVisible을 true로 변경해 준 후에 빌드를 하고 Regsvcs.exe를 사용해서 COM+ 카달로그에 등록을 해주었습니다.
그러면 조회 메소드(SQL_Select)를 실행하였습니다.
1. SQL Profiler 결과
=> 헛.. 이상합니다. COM+는 분산 트랜잭션을 사용하기 때문에 DTCTransaction이 보여야 하는데 보이지 않습니다.

2. Component Services 결과
=> 헛.. 역시 이상합니다. COM+ Transaction이 발생하였는데 DTCTransaction이 보이지가 않습니다.

이번에는 조회 메소드 안에서 강제로 예외를 발생시켜 보았습니다.
1. SQL Profiler 결과
=> 드디어 DTCTransaction이 보입니다. Rollback이 정상적으로 됩니다.

2. Component Services 결과
=> 역시 Aborted된 트랜잭션의 개수가 보입니다. 헛헛.. 그렇다면 COM+ 트랜잭션이 정상적으로 Commit이 될 경우에는 로컬 트랜잭션을 사용하고 Rollback이 될 경우에는 분산 트랜잭션을 사용한다는 말입니다. 지금까지 COM+ Transaction은 분산 트랜잭션을 사용해서 느리다라고 알 고 있었는데 제가 먼가 새로운 것을 발견한걸까요? ㅎㅎㅎ;;;
여기서 몇 가지 테스트를 더 해보기 하였습니다.
(참고 : Regsvcs로 컴포넌트를 등록해줄 경우에도 DTC를 사용하여 Committed 개수가 증가합니다. 따라서 모든 테스트 전에는 DTC Service를 멈추었다가 다시 시작해주었습니다.)

먼저 로컬 트랜잭션과 분산 트랜잭션 과연 성능의 차이는 얼마나 있는 것일까요? (성능은 환경적인 요인에 따라 다르게 측정되기 때문에 여기서는 단순한 비교(?!)를 통해서 대략 이렇다 정도로만 테스트를 합니다.)
이번에는 조금 더 많이 사용해 본 TransactionScope클래스를 사용해서 테스트를 해보았습니다.
코드는 아래와 같습니다.
Stopwatch watch = new Stopwatch();
            
for (int i = 0; i < 1000; i++)
{
    using (TransactionScope scope = new TransactionScope(System.Transactions.TransactionScopeOption.Required))
    {
        var helper = new SqlHelper();
        watch.Start();
        helper.SQL_Insert();
        //helper.SQL_Insert2();
        scope.Complete();
    }
    watch.Stop();
}
=> SqlHelper클래스는 이전에 사용한 COMTxObject클래스에서 COM+관련 부분만을 제거한 클래스입니다. 주석 처리된 부분을 보면 해당 테스트가 어떻게 될 것인지 아실 수 있습니다. 먼저 동일한 ConnectionString으로 삽입되는 SQL_Insert()메소드를 호출해보고 ConnectionString이 다른 SQL_Insert2()메소드를 호출하여 실행시켜서 로컬, 분산 트랜잭션의 결과를 비교해 보는 것입니다. 실행 시간을 측정하기 위해서 Stopwatch클래스를 사용하였으며 COM+의 객체 생성시간이 더 오래 걸리기 때문에 트랜잭션과 관련이 있는 메소드의 실행시간만을 측정하기 위해서 메소드 실행 전 후로 Start, Stop 메소드를 호출해주고 있습니다.
먼저 로컬 트랜잭션(1000번)으로 처리하였을 경우에는 대략 3~5초 정도의 시간이 걸립니다. 분산 트랜잭션으로 처리하였을 경우에는 대략 6~9초 정도의 시간이 걸렸습니다. SQL Profiler에서는 로컬시에는 TM(Transaction Manger) 분산시에는 DTCTransaction을 사용하는 것으로 보였고 Component Service에서는 로컬 트랜잭션일 경우에는 아무런 반응이 없다가 분산 트랜잭션을 사용할 경우에 아래와 같이 Commited 숫자가 증가하였습니다.
=> 총 5번을 실행(1000*5) 한 결과입니다.

이번에는 COM+ Transaction으로 동일한 테스트를 해보았습니다.
코드는 아래와 같습니다.
Stopwatch watch = new Stopwatch();

for (int i = 0; i < 1000; i++)
{
    var com = new COMPlusLibrary.EFCOMTxObject();
    watch.Start();
    com.SQL_Insert();
    //com.SQL_Insert2();
    watch.Stop();
}
=> SQL_Insert()를 실행시켰을 때 SQL Profiler에서는 TM만 보이며 Component Service에서 역시 아무런 변화가 없습니다. 그런데 로컬 트랜잭션 이였다고 말하기에 한 가지 이상한 점이 있습니다. 실행시간이 조금 더 걸린다는 겁니다. 거의 TransactionScope을 분산 트랜잭션으로 처리하는 것과 비슷한 시간입니다. SQL_Insert2()를 실행시켰을 때 SQL Profilier에서는 DTCTransaction이 보이며 시간은 더 오래 걸립니다. 그래서 해당 부분을 다시 여러번 실행시켜 실행시간을 다시 측정해보았습니다.
   COM+ Transaction TransactionScope 
 로컬 트랜잭션  4.4~6.3  3.7~5.2
 분산 트랜잭션  7.3~10.4  6.1~9.2
=> 1000번씩 실행되는 구문을 20번 실행시켜 보고 최소값과 최대값을 제외하고 실행 시간의 범위를 표시해보았습니다. 여기서는 실행 모드(Ctrl + F5)로 측정한 값입니다. 로컬이든 분산이든 우리가 이미 알고 있듯이 COM+ Transaction보다는 TransactionScope의 성능이 더 뛰어나다고 말할 수 있습니다. (객체의 생성시간을 제외하고 측정한 시간이기 때문에 COM+ Transaction에게 더 유리한(?!) 측정이였습니다.)

2011-02-18 수정내용
성능 관련 측정을 다시 해보니 문제가 될만한 의심되는 부분이 있었습니다. 정확한 결과는 정확한 확인 이후에 다시 올리도록 하겠습니다.


COM+ Transaction 사용시에 로컬 트랜잭션을 사용한다는 것은 DTC를 사용하지 않는다는 말이 될 수도 있다는 생각에 DTC Service를 종료한 이후에 COM+ TransactionOption을 주고 실행해보았습니다. 아래와 같은 결과를 얻을 수 있었습니다.
 TransactionOption DTC 사용 여부 
 Disable  X
 Not Supported  X
 Supported  X
 Required  O
 RequiredNew  O
=> 물론 여기서 Supported 옵션은 해당 클래스(COMTxObject)의 메소드가 실행되기 전에 트랜잭션이 발생된 이후에 실행되었다면 이전 트랜잭션에 참여하기 때문에 DTC를 사용할 수도 있습니다. DTC를 사용해야지만 트랜잭션이 발생한다는 것을 알 수 있습니다. 또한 DTC를 사용했다고 해서 꼭 DTCTransaction이 발생하는 것은 아니라는 점도 알 수 있습니다.

결론을 내기 전에 지금까지 사용된 용어들을 정리해보는 차원에서 유수석님의 글을 참조 하겠습니다.
http://www.simpleisbest.net/archive/2005/08/23/208.aspx

 분산 트랜잭션이란 네트워크에 분산되어 있는 자원들에 대해 트랜잭션을 수행하는 것을 말한다. 예를 들어, 어떤 애플리케이션이 SQL 서버와 오라클에 데이터를 트랜잭션 하에서 수행해야 한다면 분산 트랜잭션을 필요로 할 것이다. 대개의 경우 분산 트랜잭션은 턱시도, 티맥스, 엔트라와 같은 TP 모니터나 COM+, EJB 등과 같은 컴포넌트 기반 미들웨어가 그 기능들을 제공한다.

분산 트랜잭션의 반대되는 개념으로서 로컬 트랜잭션은 단일 자원(데이터베이스)에 대한 커밋과 롤백을 수행하며 1-phase 커밋으로서 트랜잭션을 수행한다. 한편 분산 트랜잭션의 핵심은 2-phase 커밋으로 볼 수 있다. 커밋(Commit)과 취소(Abort)(분산 트랜잭션에서는 롤백(rollback)보다는 abort라는 용어를 사용한다)를 수행하는데 두 단계를 거친다는 말이다. 첫 번째 phase는 준비 단계(prepare phase)로서, 데이터베이스는 커밋을 위한 모든 준비를 수행한다. 분산 트랜잭션에 참여한 모든 데이터베이스가 준비 단계를 성공한 후에야 두 번째 phase인 커밋 단계가 수행되는 것이다. 만약 분산 트랜잭션에 참여한 어느 한 데이터베이스라도 준비 단계를 실패한다면 두 번째 phase는 커밋이 아닌 취소를 수행하게 될 것이다.

이렇게 각 데이터베이스에게 준비 혹은 커밋/취소의 두 단계를 수행하도록 트랜잭션을 제어하는 트랜잭션 관리자를 TM (Transaction Manager)이라 부르며 이는 MSDTC(Microsoft Distributed Transaction Coordinator)가 담당한다. 지금까지 단순히 분산 트랜잭션에 참여하는 ‘데이터베이스’라고 참조했지만 데이터베이스 외에 다른 자원들도 분산 트랜잭션에 참여할 수 있다. 대표적인 예로 MSMQ, COM+의 Shared Property가 이에 해당된다. 따라서 분산 트랜잭션에서는 트랜잭션에 참여하는 각 자원에 대한 관리를 맡는 RM(Resource Manager)이 존재한다. RM의 대표적인 예는 오라클, SQL 서버와 같은 데이터베이스를 들 수 있다.

요약해 보면, COM+ 컴포넌트의 분산 트랜잭션의 관리는 MSDTC와 같은 TM을 통해 수행하며 TM은 트랜잭션에 참여한 RM들(SQL 서버, 오라클, MSMQ 등)에게 2-phase 커밋을 수행한다는 점이다.


지금까지의 여러 가지 테스트 결과를 확인해보면 COM+ Transaction에서도 로컬과 분산 트랜잭션이 모두 발생하며 동일한 ConnectionString일 경우에 정상적으로 Commit이 될 경우에는 로컬 트랜잭션으로 Rollbakc이 될 경우에는 분산 트랜잭션을 사용하는 것으로 알 수 있습니다. 하지만 COM+ Transaction은 TransactionScope를 사용하는 경우보다 성능적인 면에서 떨어지며 TransactionScope의 경우에 동일한 ConnectionString일 경우에 Commit, Rollback의 여부에 상관없이 모두 로컬 트랜잭션이 발생하며 MSDTC 또한 사용하지 않습니다.
Posted by resisa
,


드디어 Strored Procedure(이하 SP)를 사용하는 프로젝트에 참여하고 있습니다. 아쉽게(?) 실질적으로 SP를 호출하고 결과를 받아서 UI에 보여주는 작업은 아닙니다.

SP로 실질적인 프로젝트를 하지 않아서 전부터 궁금하던 사실이 있었습니다. 'SP를 사용하면 굳이 서버쪽에 Layer가 필요한 것일까?' 란 의문이였습니다. 프로젝트의 규모가 작고 하나의 SP에서 대부분의 로직을 처리할 수 있다면 굳이 필요하지 않아보입니다만 또 하나의 의문은 SP로 작성된 구문 사이의 트랜잭션의 범위에 대한 의문이였습니다. 일반적으로 Biz Layer에서 트랜잭션을 발생시켜 주고 Dac Layer에서 여러 SP를 호출해줄 경우 Biz에서 발생하는 트랜잭션과 SP내의 트랜재션과의 관계와 그 결과에 대한 의문이였습니다. 

조금 더 이해를 돕기 위해서 Biz Layer에서는 TransactionScope 클래스를 사용하였다고 가정하고 SP내에서는 ADO.NET 트랜잭션 메소드(BeginTransaction/Commit/Rollback)를 사용하였다는 가정입니다. TransactionScope는 System.Transaction 네임스페이스 하위의 클래스이며 System.Transaction 네임스페이스에서 관리하는 트랜잭션은 항상 LTM(Lightweight Transaction Manager)을 통해서 관리된다고 합니다. 따라서 해당 테스트는 LTM과 ADO.NET 트랜잭션 메소드 사이의 관계에 대해서 설명하자고 하는 글입니다.

LTM에 대해서는 아래의 사이트를 참고하세요.

http://www.simpleisbest.net/articles/996.aspx


그럼 이제 테스트를 해보도록 하겠습니다. (참고 : 테스트 DB는 SQL Server 2008(R2는 아님)입니다.)

DB 데이터 베이스는 ID(identity), Name 가지고 있는 테이블입니다.

이제 SP를 DB에 만들어 보겠습니다. SP의 내용은 아래와 같습니다.

CREATE PROCEDURE EFSP

 

@name nvarchar(Max),

@txOutput int OUTPUT

 

AS

 

BEGIN TRAN

 

insert [dbo].[Entity1Set]([Name])

values (@name)

select [Id]

from [dbo].[Entity1Set]

where @@ROWCOUNT > 0 and [Id] = scope_identity()

 

-- 위의 insert 구문 2 추가

 

SET @txOutput = @@TRANCOUNT


COMMIT TRAN

GO 

Entity Framework에서 해당 SP Function으로 Import 있습니다. System.Data.SqlClient 사용하셔도 됩니다. 여기서 @@TRANCOUNT로 발생하는 트랜잭션의 개수를 SP의 output 파라미터에 대입해주는 것을 확인하실 있습니다. ADO.NET 트랜잭션 메소드는 SQL Profiler SQLTransaction으로 표현이 되는데 SQLTransaction 해당 테스트와 상관없이 너무나 자주 발생하여 트랜잭션이 발생하는 개수를 파악하기 위한 방법으로 사용되었습니다.

그러면 이제 단순히 SP 실행해보도록 하겠습니다.


=> 위에 말했던 대로 SP내부에서 사용한 ADO.NET 트랜잭션 메소드는 SQLTransaction으로 표시가 되며 EventSubClass 열로 트랜잭션이 어느 부분부터 시작되며 어느 부분에서 종료가 되는지 아실 있습니다. ObjectName 통해서는 사용자가 발생한 트랜잭션인지 SQL Server내부에서 발생하는 트랜잭션인지를 구분할 있습니다.

 

그러면 이제 TransactionScope 클래스와 함께 SP 실행해보도록 하겠습니다.


=> TransactionScope 클래스를 사용하였기 때문에 TM(LTM) 관여하는 것을 보실 있으며 DBM_INIT이라는 내부 Object 의해서 무엇인지는 정확하게 없지만 SQLTransaction 발생한 것도 있습니다. 여기서 중요한 것은 바로 sp_reset_connection 함수입니다. 함수는 커넥션 풀링이 되었다는 의미입니다. 이상합니다. 하나의 SP만을 실행준 것인데 커넥션 풀링이 필요한 것일까요?

 

TransactionScope 클래스를 사용한 구문을 보도록 하겠습니다.

using (TransactionScope scope = new TransactionScope())

{

    // 하나의 SP실행

 

    scope.Complete();

} 

=> 앞에서 말했듯이 System.Transaction 네임스페이스의 트랜잭션은 TM 관리하였습니다. 단순히 SP만을 실행해줄 경우에는 SQLTransaction에서 commit 이후에 커넥션이 닫혔습니다. 그러나 TransactionScope안에서 SP 실행할 경우에 SQLTransaction commit되기 전에 커넥션이 먼저 닫히게 됩니다. 이것으로 보아 TransactionScope 사용하게 되면서 ADO.NET 트랜잭션 메소드가 TM 관리하는 트랜잭션으로 관리된다는 것을 있습니다. TM Commit 되기 전에 커넥션이 닫혔기 때문에 TM에서는 커넥션 풀링을 통해서 다시 커넥션을 맺고 내부의 SQLTransaction 발생한 이후에 SQLTransaction commit해주며 이후에 TM Commit 되고 마지막으로 커넥션을 닫아주는 것입니다.

가지 테스트를 통해서 TransactonScope 옵션(TransactionScopeOption) 따라서 ADO.NET 트랜잭션 메소드가 참여하게 된다는 사실을 확인할 있었습니다.

이렇게 하여 LTM SQLTransaction 관계에 대해서 대부분(?!) 파악이 되었습니다. 가지 의문점은 SP안에서는 트랜잭션의 Scope 범위(TransactionScopeOption) 지정하는 부분은 없어보이지만 IsolationLevel 대해서는 지정을 있습니다. 또한 TransactionScope에서도 IsolationLevel 대해서 지정할 있습니다. 그래서 IsolationLevel 따른 LTM SQLTransaction 관계가 어떻게 될지도 살짝 궁금해집니다. 하지만 실질적으로 이러한 것을 프로젝트에서 정확하게 알고 사용하는 개발자가 얼마나 될지도 의문이며 IsolationLevel 트랜잭션 사이의 잠금(lock) 대한 것이기 때문에 성능보다는 안전성을 중요시하는 우리 나라의 대부분의 프로젝트에서는 ReadCommitted 사용하는 것이 개발자의 정신건강에 이로워 보입니다. ^^

Posted by resisa
,