최신 EF의 경우 이전(EF 4.0)에 비해 600% 정도 빨라진 것이 인상적임.

예전에는 생 ADO.NET에 비해 EF가 엄청나게 느렸는데 격차가 많이 줄어 들었음.




저작자 표시
신고
by 흥배 2015.04.07 08:00

잘못된 SqlConnection

 

// 코드 1

using (var connection = new SqlConnection("접속문자열"))

{

    connection.Open();

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}

  • <코드 1>을 실행하면 크래쉬가 발생

  • 이유는 간단 SqlConnection가 스레드 세이프 하지 않기 때문
  • 위의 코드를 올바르게 수정하려면 아래와 같이 변경
     

    // 코드 2

    Parallel.For(1, 1000, x =>

    {

        using (var connection = new SqlConnection("접속문자열"))

        {

            connection.Open();

            var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper

        }

    });

  • <코드 2>는 안전하지만 아마 위 방식은 원하는 방식이 아닐 것이다.

 

 

ThreadLocal

  • 위의 문제를 ThreadLocal을 사용하여 해결해 보자
     

    // 코드 3

    using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }))

    {

        Parallel.For(1, 1000, x =>

        {

            var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

        });

    }

  • 성능 테스트를 해보면 싱글 스레드를 사용하면 16초, <코드 2>는 5초, <코드 3>은 2초

 

 

<코드 3>은 안전한가?

  • <코드 3>은 위험한 코드
    • 이유는 연결한 후 Dispose 하지 않기 때문.
    • ThreadLocal의 Dispose는 어디까지는 ThreadLocal의 Dispose이기 때문에 그 안의 객체를 Dispose 해주지 않는다.
  • 이 문제는 trackAllValues 라는 옵션을 사용하여 간단하게 해결 가능!

 

// 코드 4

using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }

    , trackAllValues: true)) // ThreadLocalの.Values 프로퍼티의 참조를 유효화 한다

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

    

    // 생성된 모든 Connection을 일괄적으로 Dispose

    foreach (var item in connection.Values.OfType<IDisposable>()) item.Dispose();

}

  • 단 trackAllValues는 닷넷프레임워크 4.5 부터 사용 가능

 

 

trackAllValues 랩퍼 클래스 사용

 

public static class DisposableThreadLocal

{

    public static DisposableThreadLocal<T> Create<T>(Func<T> valueFactory)

        where T : IDisposable

    {

        return new DisposableThreadLocal<T>(valueFactory);

    }

}

    

public class DisposableThreadLocal<T> : ThreadLocal<T>

    where T : IDisposable

{

    public DisposableThreadLocal(Func<T> valueFactory)

        : base(valueFactory, trackAllValues: true)

    {

    

    }

    

    protected override void Dispose(bool disposing)

    {

        var exceptions = new List<Exception>();

        foreach (var item in this.Values.OfType<IDisposable>())

        {

            try

            {

                item.Dispose();

            }

            catch (Exception e)

            {

                exceptions.Add(e);

            }

        }

    

        base.Dispose(disposing);

    

        if (exceptions.Any()) throw new AggregateException(exceptions);

    }

}

   

// 사용

using (var connection = DisposableThreadLocal.Create(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }))

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}

 

 

 

출처: http://neue.cc/2013/03/09_400.html

신고
by 흥배 2013.10.11 08:00
ADO.NET의 연결 풀의 잘못된 사용에 대해 알수 있는 좋은 글입니다.
http://andromedarabbit.net/wp/myth_of_ado_connection_pool/




우선 퀴즈 하나! 이 VB.NET 코드는 연결 풀을 사용할까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Module Module1
    Sub Main()
        Dim cn(20)
        Dim rs
        Dim n
 
        For n = 0 To 2
            cn(n) = CreateObject("ADODB.Connection")
 
            cn(n).Open("Provider=SQLNCLI; DataTypeCompatibility=80; MARS Connection=True; Server=localhost\SQLEXPRESS; Trusted_Connection=yes; Database=SpaceDB;")
 
            cn(n).Execute("exec sp_help sp_help")
 
            cn(n).Close()
            cn(n) = Nothing
        Next
 
    End Sub
End Module

우선 MSDN 등의 문서에 따르면 ADO는 따로 설정하지 않는 한 알아서 연결 풀을 활성화한다고 한다. 그렇다면 이 코드는 연결 풀을 이용할 거라 생각할 수 있다. SQL Server Profiler를 사용해 실제로 연결 풀이 활성화됐는지 확인해보자.

그림 1. Visual Basic .NET

ADO 연결 풀의 미신 - VB

[그림 1]에서 주목할 부분은 Audit Logout 이벤트 클래스와 exec sp_reset_connection이다. Audit Logout을 보고 앗! 연결을 끊었네. 연결 풀을 안 쓰나 보다.라고 생각할 수도 있지만(내가 그러는 바람에 고생 꽤나 했다), MSDN을 보면 이렇게 쓰여 있다.

Events in this class are fired by new connections or by connections that are reused from a connection pool.

이 이벤트 클래스는 새 연결이 붙었을 때나 연결 풀이 연결을 다시 사용할 때 발생한다

그러므로 이 이벤트 클래스만 봐서는 연결 풀을 사용하는지 사용하지 않는지 알 수 없다. 주목할 것은 Audit Logout이 아닌 sp_reset_connection이다. SQL 자습서에 문서화되어 있지 않은 이 저장 프로시저는 연결 풀을 구현할 때 쓴다. 이 저장 프로시저는 SET ANSI NULL ON 등의 연결 설정 값을 초기화함으로써, 재사용하는 연결을 새 연결처럼 다시 초기화시키는 역할을 한다. 이 저장 프로시저가 보인다면 필시 연결 풀이 활성화된 것이다. 다시 말해 [그림 1]은 앞선 VB.NET 코드가 연결 풀을 사용한다는 걸 보여준다.

이제 Visual C++ 코드를 보자. 얼핏 보기에 이 코드는 앞선 VB.NET 코드와 동일해 보이며, 당연히 연결 풀을 사용할 것 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main()
{
    if(FAILED(::CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE | COINIT_SPEED_OVER_MEMORY)))
        return;
 
    const TCHAR* const  connectionString = _T("Provider=SQLNCLI; Data Source=localhost\\SQLEXPRESS; Trusted_Connection=yes; Database=SpaceDB;");
 
    for(int i=0; i<3; i++)
    {
        _ConnectionPtr pConnection1 = NULL;
 
        // Open a connection using OLE DB syntax.
        TESTHR(pConnection1.CreateInstance(_T("ADODB.Connection")));        pConnection1->Open(connectionString,"","",adConnectUnspecified);
 
        pConnection1->Execute(_T("exec sp_help sp_help"), NULL, adCmdText);
 
        if (pConnection1)
            if (pConnection1->State == adStateOpen)
                pConnection1->Close();
    }
 
    ::CoUninitialize();
}

하지만 프로파일링 결과를 보면 아주 황당하다.

그림 2. Visual C++

ADO 연결 풀의 미신 - VC

보다시피 sp_reset_connection 호출이 전혀 없다. 연결 풀을 전혀 사용 안하고 있는 것이다. 이 문제를 반나절이 넘게 추적하고 나서야 뭐가 문제인지 알 수 있었다.

Pooling in the Microsoft Data Access Components은 ODBC, OLEDB, ADO의 연결 풀에 대해 설명하는 문서인데, 여기에 ADO 사용자를 위한 팁이 조금 나와있다. 이걸 J씨가 찾아주지 않았으면 반나절이 아니라 하루를 넘게 고생했을지 모를 일이다.

The ADO Connection object implicitly uses IDataInitialize. However, this means your application needs to keep at least one instance of a Connection object instantiated for each unique user-at all times. Otherwise, the pool will be destroyed when the last Connection object for that string is closed. (The exception to this is if you use Microsoft Transaction Server. In this case, the pool is destroyed only if all of the connections in the pool have been closed by client applications and are allowed to time out.)

ADO Connection 객체는 암시적으로 IDataInitialize를 사용합니다. 하지만 이는 애플리케이션이 적어도 사용자마다 인스턴스화된 Connection 객체를 적어도 하나씩은 유지해야 한다는 뜻입니다. 그렇지 않으면 해당 문자열에 대한 마지막 Connection 객체가 닫힐 때 풀도 닫힙니다.

그러니 C++로 ADO를 쓸 때는 다음과 같이 최초의 연결 객체를 닫지 않고 놔둬야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void main()
{
    if(FAILED(::CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE | COINIT_SPEED_OVER_MEMORY)))
        return;
 
    const TCHAR* const  connectionString = _T("Provider=SQLNCLI; Data Source=localhost\\SQLEXPRESS; Trusted_Connection=yes; Database=SpaceDB;");
 
    // 연결 풀 활성화
    _ConnectionPtr pConnection = NULL;
    TESTHR(pConnection.CreateInstance(_T("ADODB.Connection")));
    pConnection->Open(connectionString,"","",adConnectUnspecified);
 
    for(int i=0; i<3; i++)
    {
        _ConnectionPtr pConnection1 = NULL;
 
        // Open a connection using OLE DB syntax.
        TESTHR(pConnection1.CreateInstance(_T("ADODB.Connection")));        pConnection1->Open(connectionString,"","",adConnectUnspecified);
 
        pConnection1->Execute(_T("exec sp_help sp_help"), NULL, adCmdText);
 
        if (pConnection1)
            if (pConnection1->State == adStateOpen)
                pConnection1->Close();
    }
 
    ::CoUninitialize();
}

만약 루프를 도는 중간에 네트워크가 끊기면 어떻게 될까? 처음에 연 연결 객체를 닫고 다시 열어야 할까? 아니면 그냥 놔두면 네트워크가 복구됐을 때 연결 풀을 사용할까? 루프를 도는 중간에 중단점을 걸고 랜 선을 뽑았다가 다시 연결해보니, 연결 풀을 사용하고 있었다. 그러니 애플리케이션 초기화를 하거나 해당 데이터베이스에 처음 연결할 때, 전역 객체를 하나 생성해두면 연결 풀을 계속 사용할 수 있다.

이 글의 교훈? 알아서 해준다는 말을 너무 믿으면 안 된다.


저작자 표시
신고
by 흥배 2009.06.03 18:07
| 1 |