Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

C# - Oracle.ManagedDataAccess의 Pool 및 그것의 연결 개체 수를 알아내는 방법

이것을 알아내는 방법은, 공식적으로는 성능 카운터를 이용하는 방법이 있습니다.

C# - (.NET Framework를 위한) Oracle.ManagedDataAccess 패키지의 성능 카운터 설정 방법
; https://www.sysnet.pe.kr/2/0/13003

C# - Oracle.ManagedDataAccess.Core의 성능 카운터 설정 방법
; https://www.sysnet.pe.kr/2/0/13000

하지만, 아쉽게도 좀 번거롭다는 단점이 있습니다. 그냥 저런 설정 없이 알아낼 수 있다면 좋을 텐데요, 다행히 리플렉션을 이용하면 그런대로 원하는 목적을 이룰 수 있습니다.




그럼, 어디서 시작해볼까요? ^^

우선, OracleConnection.ClearAllPools 메서드가 있는데요,

public static void ClearAllPools()
{
    // ...[생략]...
    OracleConnectionDispenser<OraclePoolManager, OraclePool, OracleConnectionImpl>.ClearAllPools();
    // ...[생략]...
}

여기서 다시 OracleConnectionDispenser`3.ClearAllPools를 들어가면,

internal static void ClearAllPools()
{
    // ...[생략]...

    List<PM> list = OracleConnectionDispenser<PM, CP, PR>.m_listPM.GetList();
    for (int index = 0; index < list.Count; ++index)
        list[index].ClearAllPools(default (PR), false);
    }

    // ...[생략]...
}

OracleConnectionDispenser`3 제네릭 클래스의 m_listPM 정적 필드에 Pool 개체들이 담겨 있음을 짐작게 합니다. 해당 제네릭 타입은 3개의 타입 인자를 받아들이므로,

OracleConnectionDispenser<OraclePoolManager, OraclePool, OracleConnectionImpl>

이전에 설명한 방법을 사용해 일단 이렇게 m_listPM 목록을 구할 수 있습니다.

using Oracle.ManagedDataAccess.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

internal class Program
{
    static void Main(string[] args)
    {
        Assembly asm = typeof(OracleConnection).Assembly;
        Dictionary<string, Type> typeArgs = new Dictionary<string, Type>()
        {
            ["OracleInternal.ConnectionPool.OraclePoolManager"] = null,
            ["OracleInternal.ConnectionPool.OraclePool"] = null,
            ["OracleInternal.ServiceObjects.OracleConnectionImpl"] = null,
        };

        Type poolType = null;

        foreach (Type type in asm.GetTypes())
        {
            string name = type.FullName;

            if (typeArgs.ContainsKey(type.FullName) == true)
            {
                typeArgs[type.FullName] = type;
                continue;
            }

            if (name.IndexOf("OracleConnectionDispenser") == -1)
            {
                continue;
            }

            poolType = type;
        }

        bool resolvedAll = typeArgs.All((e) => e.Value != null);
        if (resolvedAll == false)
        {
            return;
        }

        Type[] genericArgs = new Type[]
        {
                typeArgs["OracleInternal.ConnectionPool.OraclePoolManager"],
                typeArgs["OracleInternal.ConnectionPool.OraclePool"],
                typeArgs["OracleInternal.ServiceObjects.OracleConnectionImpl"]
        };

        Type poolGenericType = poolType.MakeGenericType(genericArgs);

        {
            FieldInfo fi = poolGenericType.GetField("m_listPM", BindingFlags.Static | BindingFlags.Public);
            object syncQList = fi.GetValue(null);

            Console.WriteLine(syncQList);
        }

    }
}

그다음, syncQList에는 개체 풀을 유지하는 목록이 들어 있을 텐데요, 해당 타입을 보면,

using System.Collections.Generic;

namespace OracleInternal.Common
{
  internal class SyncQueueList<T>
  {
    internal List<T> m_list;
    internal object m_sync;
    internal int m_max;
    internal bool m_bMaxExplicitlySet;

    // ...[생략]...
  }
}

다시 내부의 리스트로 보관하고 있습니다. 어쩔 수 없이 그 필드를 구하는,

{
    FieldInfo fi = poolGenericType.GetField("m_listPM", BindingFlags.Static | BindingFlags.Public);
    object syncQList = fi.GetValue(null);

    object poolList = GetInstanceFieldValue(syncQList, "m_list", BindingFlags.NonPublic | BindingFlags.Instance);

    int numberOfPool = 0;

    foreach (var pool in poolList as IEnumerable)
    {
        numberOfPool++;
    }

    Console.WriteLine(numberOfPool);
}

private static object GetInstanceFieldValue(object objInstance, string fieldName, BindingFlags flags)
{
    return objInstance.GetType().GetField(fieldName, flags).GetValue(objInstance);
}

위의 코드를 실행하면, 현재 풀이 만들어진 적이 없으므로 numberOfPool 값은 0이 나옵니다. 이 상태에서 다음의 예제 코드를 한번 실행시켜 두면,


string oradb = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=hr;Password=hr2;";

DoOracleManagedCall(oradb, 0);

protected static void DoOracleManagedCall(string oradb, int delay)
{
    using (OracleConnection oraConnection = new OracleConnection(oradb))
    {
        oraConnection.Open();

        if (delay > 0)
        {
            Thread.Sleep(delay);
        }
    }
}

이제 리플렉션 코드는 numberOfPool의 개수로 1을 반환합니다.




자, 그럼 저 m_list는 OraclePoolManager의 목록을 가지고 있게 되는데요, (OraclePoolManager가 상속받은) PoolManager.ClearAllPools의 메서드를 보면,

public virtual void ClearAllPools(PR pr, bool isHAEvnt = false)
{
    // ...[생략]...
    List<CP> list = this.m_pmListCP.GetList();
    for (int index = 0; index < list.Count; ++index)
        list[index].ClearPool(pr, isHAEvnt);
    }
    // ...[생략]...
}
System.Collections.Generic.List`1[OracleInternal.ConnectionPool.OraclePoolManager] m_list

다시, 내부에서 구성한 List를 열거하면서 ClearPool 메서드를 호출하고 있습니다. 여기서 잘 이해가 안 됩니다. 일반적으로 다중 Pool이 제공되고 각각의 Pool에서 다시 연결 개체를 소유하는 것으로 알고 있었는데, Pool 목록에서 다시 개별 요소들이 Pool을 가지고 있는 유형으로 나옵니다.

어쨌든, m_pmListCP도 SyncQueueList`1 타입이므로 최종적으로 다음과 같이 열거할 수 있습니다.

// ...[생략]...
int numberOfPool = 0;

foreach (var pool in poolList as IEnumerable)
{
    numberOfPool++;

    object pmListCP = GetInstanceFieldValue(pool, "m_pmListCP", BindingFlags.Public | BindingFlags.Instance);
    object pmListCPList = GetInstanceFieldValue(pmListCP, "m_list", BindingFlags.NonPublic | BindingFlags.Instance);

    foreach (var pmCP in pmListCPList as IEnumerable)
    {
        Console.WriteLine(pmCP); // OracleInternal.ConnectionPool.OraclePool
    }
}

Console.WriteLine(numberOfPool);

ClearAllPools 메서드에서는 결국 저렇게 얻은 OracleInternal.ConnectionPool.OraclePool 개체의 ClearPool 메서드를 호출하는 건데요,

public void ClearPool(PR prToRetain, bool isHAEvnt = false)
{
    // ...[생략]...
    List<PR> list = this.m_cpListPR.GetList();
    for (int index = 0; index < list.Count; ++index)
    {
        // ...[생략]...
        PR pr = list[index];
        if ((object) pr != null)
        {
            // ...[생략]...
            pr.m_pm.Close(pr, (OracleConnection) null);
            // ...[생략]...
        }
        // ...[생략]...
    }
    // ...[생략]...
}

보는 바와 같이 m_cpListPR이라는 목록을 연결 개체가 보관하는 듯하고, Close 메서드를 호출함으로써 연결을 정리하고 있습니다. 그래서 대략 다음과 같은 식으로 코드를 완성할 수 있습니다.

int numberOfPool = 0;
int numberOfCount = 0;

foreach (var pool in poolList as IEnumerable)
{
    numberOfPool++;

    object pmListCP = GetInstanceFieldValue(pool, "m_pmListCP", BindingFlags.Public | BindingFlags.Instance);
    object pmListCPList = GetInstanceFieldValue(pmListCP, "m_list", BindingFlags.NonPublic | BindingFlags.Instance);

    foreach (var pmCP in pmListCPList as IEnumerable)
    {
        // OracleInternal.Common.SyncQueueList`1[OracleInternal.ServiceObjects.OracleConnectionImpl]
        object cpListPR = GetInstanceFieldValue(pmCP, "m_cpListPR", BindingFlags.Public | BindingFlags.Instance);

        object cpListPRList = GetInstanceFieldValue(cpListPR, "m_list", BindingFlags.NonPublic | BindingFlags.Instance);

        int cpListSize = (int)GetInstanceFieldValue(cpListPRList, "_size", BindingFlags.NonPublic | BindingFlags.Instance);
        numberOfCount += cpListSize;
    }
}

Console.WriteLine($"# of pools: {numberOfPool}");
Console.WriteLine($"# of connections: {numberOfCount}");




실제로 저 코드를 WriteCount라는 메서드로 분리해서 다음과 같이 실행해 보면,

string oradb1 = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=hr;Password=hr2;Min Pool Size=15;";
string oradb2 = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=hr;Password=hr2;Min Pool Size=10;";
string oradb3 = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=hr;Password=hr2;";

do
{
    Type poolGenericType = poolType.MakeGenericType(genericArgs);
    WriteCount(poolGenericType);

    for (int i = 0; i < 17; i++)
    {
        DoOracleManagedCall(oradb1, 1000);
    }

    WriteCount(poolGenericType);
    Thread.Sleep(1000);

    for (int i = 0; i < 17; i++)
    {
        DoOracleManagedCall(oradb2, 1000);
    }

    WriteCount(poolGenericType);
    Thread.Sleep(1000);

    for (int i = 0; i < 17; i++)
    {
        DoOracleManagedCall(oradb3, 1000);
    }

    WriteCount(poolGenericType);

    Console.WriteLine("---------------------------------------------------------------");
} while (true);

서로 다른 연결 개체를 3개 사용했으므로, 3개의 연결 개체 풀이 생성될 것이고, 각각의 풀에는 17개의 연결 개체가 들어 있을 것입니다. 운이 좋은 경우 정확히 3 * 17 = 51개가 나오는 경우도 있지만, 대개의 경우 풀 내부의 정책으로 더 있을 수 있습니다.

실제로 이를 성능 모니터링 도구와 함께 확인해 보면 다음과 같이 나옵니다.

oracle_pool_status_1.png

보시면, 3개의 풀이 있고, 각각의 풀에는 21개, 17개, 17개까지 총 55개의 연결 개체가 생성돼 있는 것을 볼 수 있습니다. 그리고 그 값은 정확하게 성능 모니터링 도구의 "NumberOfPooledConnections" 수와 일치합니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 3/18/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1780정성태10/15/201424235오류 유형: 249. The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID
1779정성태10/15/201419761오류 유형: 248. Active Directory에서 OU가 지워지지 않는 경우
1778정성태10/10/201418223오류 유형: 247. The Netlogon service could not create server share C:\Windows\SYSVOL\sysvol\[도메인명]\SCRIPTS.
1777정성태10/10/201421325오류 유형: 246. The processing of Group Policy failed. Windows attempted to read the file \\[도메인]\sysvol\[도메인]\Policies\{...GUID...}\gpt.ini
1776정성태10/10/201418338오류 유형: 245. 이벤트 로그 - Name resolution for the name _ldap._tcp.dc._msdcs.[도메인명]. timed out after none of the configured DNS servers responded.
1775정성태10/9/201419460오류 유형: 244. Visual Studio 디버깅 (2) - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1774정성태10/9/201426654개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
1773정성태10/8/201429815.NET Framework: 471. 웹 브라우저로 다운로드가 되는 파일을 왜 C# 코드로 하면 안되는 걸까요? [1]
1772정성태10/3/201418615.NET Framework: 470. C# 3.0의 기본 인자(default parameter)가 .NET 1.1/2.0에서도 실행될까? [3]
1771정성태10/2/201428118개발 환경 구성: 245. 실행된 프로세스(EXE)의 명령행 인자를 확인하고 싶다면 - Sysmon [4]
1770정성태10/2/201421718개발 환경 구성: 244. 매크로 정의를 이용해 파일 하나로 C++과 C#에서 공유하는 방법 [1]파일 다운로드1
1769정성태10/1/201424138개발 환경 구성: 243. Scala 개발 환경 구성(JVM, 닷넷) [1]
1768정성태10/1/201419559개발 환경 구성: 242. 배치 파일에서 Thread.Sleep 효과를 주는 방법 [5]
1767정성태10/1/201424664VS.NET IDE: 94. Visual Studio 2012/2013에서의 매크로 구현 - Visual Commander [2]
1766정성태10/1/201422520개발 환경 구성: 241. 책 "프로그래밍 클로저: Lisp"을 읽고 나서. [1]
1765정성태9/30/201426065.NET Framework: 469. Unity3d에서 transform을 변수에 할당해 사용하는 특별한 이유가 있을까요?
1764정성태9/30/201422309오류 유형: 243. 파일 삭제가 안 되는 경우 - The action can't be comleted because the file is open in System
1763정성태9/30/201423880.NET Framework: 468. PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법파일 다운로드1
1762정성태9/30/201424559.NET Framework: 467. 닷넷에서 EIP/RIP 레지스터 값을 구하는 방법 [1]파일 다운로드1
1761정성태9/29/201421624.NET Framework: 466. 윈도우 운영체제의 보안 그룹 이름 및 설명 문자열을 바꾸는 방법파일 다운로드1
1760정성태9/28/201419903.NET Framework: 465. ICorProfilerInfo::GetILToNativeMapping 메서드가 0x80131358을 반환하는 경우
1759정성태9/27/201431004개발 환경 구성: 240. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법파일 다운로드1
1758정성태9/23/201437903개발 환경 구성: 239. 원격 데스크톱 접속(RDP)을 기존의 콘솔 모드처럼 사용하는 방법 [1]
1757정성태9/23/201418454오류 유형: 242. Lync로 모임 참여 시 소리만 들리지 않는 경우 - 두 번째 이야기
1756정성태9/23/201427478기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434270오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...