728x90
윈도우 기반의 서버를 사용할 때 참 유용한 것이 성능 카운터 입니다. 눈으로, 로그로 보이지 않는 것들도 다양한 객체와 성능 카운트를 통해서 확인이 가능하기 때문에 원인을 명확히 알 수 오동작의 단서를 제공하는 유용한 도구입니다. 이 성능 카운터에는 기본적으로 닷넷의 기본 프로바이더가 포함되어 있습니다. 닷넷이 설치된 PC 나 서버에서 해당 카테고리를 선택하면 측정 가능한 다양한 옵션이 보입니다.


그런데 오라클의 ODP.NET 을 사용하는 경우에는 기본적으로 제공되는 성능 개체가 아무것도 없습니다. 그리고 이 말은 " 오라클 클라이언트 10g 까지는 " 이라는 단서를 하나 더 붙이면 정확한 표현이 됩니다. 즉, 성능 카운터를 통해서 ODP.NET 의 다양한 카운터를 확인하기 위해서는 11g 이상의 클라이언트 버전을 설치해 주어야 합니다.


오라클 11g 클라이언트 설치 화면의 옵션을 보면 " Oracle Counters for Windows Performance Monitor " 라는 항목이 있습니다. 클라이언트를 설치하면서 이 옵션을 선택하시면 성능 카운터에 측정 가능한 항목이 추가되게 됩니다. 윈도우 환경에서 시스템 항목의 중요한 요소중 하나인 성능 카운터를 위해서는 클라이언트 버전을 올려야 하는 불편함이 있지만, 무슨일이 일어날지 모르는 미래를 대비한다면 나쁘지 않은 선택이 될 것 같네요.

- NoPD -
728x90
728x90

* 이 글은 오라클 기술 네트워크(Oracle Technology Network)에 공개된 Joh Paul Cook 의 영문 아티클을 번역 / 의역한 글입니다. 원문은 링크(http://tinyurl.com/m69mvh)를 통하여 확인하실 수 있습니다.

Visual C# 구문

프로젝트에 참조 추가를 한 다음 using 구문을 이용하여 네임스페이스를 선언해 주어야 합니다. 기술적으로 네임스페이스 선언은 해주지 않아도 관계 없지만, 간결한 코드를 위해서는 꼭 선언해 주는 것이 좋습니다. 일반적인 경우와 마찬가지로 네임스페이스나 클래스 선언이 시작되기 전에 using 문을 추가해 주면 됩니다.

using Oracle.DataAccess.Client
참조가 정상적으로 추가되었다면 using 문으로 네임스페이스를 선언하는 동안 인텔리센스의 도움을 받으실 수 있었을 것입니다.


연결 문자열과 객체

오라클 연결 문자열은 tnsnames.ora 파일에 정의된 내용과 밀접한 관계를 가지고 있습니다. 여러분들의 tnsnames.ora 파일에 아래와 같이 XE 라는 이름으로 오라클 연결 문자열이 정의되어 있다고 가정해 봅시다. (참고로, tnsnames.ora 파일은 오라클 클라이언트가 설치된 경로의 하위에 network/admin 폴더에 저장되어 있습니다. 클라이언트 버전에 따라 위치는 조금씩 상이할 수 있습니다)

XE =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = DBSVR)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = XE)
    )
  )
XE 라는 약어는 오라클 클라이언트를 위한 데이터베이스 연결 주소 정보를 정의하고 있습니다. tnsnames.ora 파일에 정의된 위의 약어를 사용하기 위해서 비주얼 스튜디오에서 아래와 같은 연결 문자열 선언을 해야합니다.

string oradb = "Data Source=XE;User ID=scott;Password=tiger;";
물론 tnsnames.ora 파일을 사용하지 않기 위하여 연결 문자열을 아래와 같이 변경할 수도 있습니다. tnsnames.ora 파일에 지정된 내용을 그대로 연결 문자열에 정의하면 됩니다.

string oradb = "Data Source=(DESCRIPTION =(ADDRESS_LIST =(ADDRESS = (PROTOCOL = TCP)(HOST = DBSVR)(PORT = 1521))) " +
               "(CONNECT_DATA =(SERVICE_NAME = XE)));" +
               "User Id=scott;Password=tiger;";
위에서 볼 수 있는 것처럼, 사용자 이름과 패스워드는 연결 문자열 속에 평문으로 저장되어 있습니다. 이 방법은 연결 문자열을 만드는 가장 간단한 방법이지만 보안의 관점에서 봤을 때는 평문으로 저장된 사용자 이름, 패스워드는 좋은 선택이 아닙니다. 특히, 닷넷 어플리케이션은 DLL, EXE 를 막론하고 디스 어셈블러를 통해서 소스 코드의 복원이 가능하기 때문에 보안에 취약합니다. 이와 관련한 내용은 다른 글에서 보다 자세히 다루도록 하겠습니다.

다음으로, 여러분은 커넥션 클래스를 이용하여 객체를 생성하고 초기화 해야만 합니다. 연결 문자열은 커넥션 객체에 할당 되어야 합니다.

OracleConnection conn = new OracleConnection(XE);
연결 문자열의 할당은 오버로드된 객체의 생성자를 통해서 전달되어 할당이 가능합니다. 생성자의 다른 오버로드는 아래와 같은 구문도 사용 가능하도록 정의되어 있습니다.

OracleConnection conn = new OracleConnection();
conn.ConnectionString = oradb;
연결 문자열의 할당이 끝나면 이제 남은 일은 커넥션 객체의 Open 메소드를 이용하여 오라클 데이터베이스와의 실제 연결을 만드는 작업을 하면 됩니다. 이 과정에서의 에러 핸들링은 다음 포스트에서 소개하도록 하겠습니다.

conn.Open();
Command 객체

Command 객체는 실행 되어야 하는 SQL 문자열이나 저장 프로시저를 SQL 명령의 형태로 지정할 때 사용됩니다. Connection 객체와 마찬가지로 해당 클래스로부터 인스턴스가 만들어져야 합니다. 아래의 예제에서 ODP.NET 은 부서 테이블로부터 부서 번호가 10인 부서 명(DNAME)을 돌려주는 SQL 구문을 수행할 것입니다.

string sql = "SELECT dname FROM dept WHERE deptno = 10";
OracleCommand cmd = new OracleCommand(sql, conn);
cmd.COmmandType = COmmandType.Text;
오버로드된 다른 생성자를 사용하면 구문은 조금 다르게 변할 수 있습니다. Command 객체는 커맨드 텍스트를 실행할 수 있는 메소드를 가지고 있습니다. 이 메소드들은 서로 다른 형태의 SQL 구문에 적절한 메소드 들입니다.

스칼라 값 얻기

실제로 데이터를 데이터베이스로부터 얻기 위해서 OracleDataReader 객체를 생성하고 ExecuteReader 메소드를 사용하면 됩니다. 데이터는 컬럼 이름을 지정하거나 인덱스 번호를 이용하여 엑세스 할 수 있습니다. 결과 값은 닷넷 고유의 데이터 형을 이용하거나 오라클 고유의 데이터 형을 이용하여 엑세스 할 수 있습니다. 닷넷에 포함되어 있는 어떤 언어를 사용하더라도 이같은 특징을이용할 수 있습니다.

OracleDataReader dr = cmd.ExecuteReader();
dr.Read();

label1.Text= dr["dname"].ToString();
label1.Text = dr.ZGetString(0).ToString();
label1.Text = dr.GetOracleString(0).ToString();
위의 예제에서 우리는 DNAME 이라는 문자열 형태의 리턴 값을 가지고 데이터를 핸들링 하고 있습니다. 그러나 DEPTNO (부서번호) 와 같은 문자열이 아닌 데이터를 핸들링 할 떄는 타입 미스매치(Type Mismatch)에 주의해야 합니다. 닷넷 런타임 라이브러리는 암시적으로 적절한 형 변환을 자동으로 시도합니다. 그러나 간혹 데이터 형의 호환성에 문제가 있거나 암시적인 형 변환이 실패하면 예외가 발생하게 됩니다. 암시적인 형 변환이 에러 없이 수행된다 하더라도 필요한 경우에 대하여 명시적으로 형 변환을 하는 것을 권장합니다.

객체 소멸

데이터베이스 엑세스가 모두 끝나고 나면 Connection 객체의 Close 혹은 Dispose 메소드를 반드시 호출해 주어야 합니다. Dispose 메소드는 내부적으로 Close 메소드를 호출하도록 되어 있습니다.

conn.Close();
conn.Dispose();
코드를 만들 때 using 문을 이용하여 로직을 구성한 경우에는 명시적으로 Close 나 Dispose 메소드를 호출할 필요가 없습니다. 아래의 코드는 using 문을 이용하여 데이터베이스를 연결하는 예제 코드입니다.

using (OracleConnection conn = new OracleConnection(oradb))
{
    conn.Open();
    OracleCommand cmd = new OracleCOmmand();
    cmd.Connection = conn;
    cmd.CommandText = "select dname from dept where deptno = 10";
    cmd.COmmandType = COmmandType.Text;
    OracleDataReader dr = cmd.ExecteReader();
    dr.Read();
    label1.Text = dr.GetString(0);
}
추가적으로 OracleCommand 객체는 Dispose 메소드를 포함하고 있고 OracleDataReader 객체는 Close 와 Dispose 메소드를 포함하고 있습니다. 일반적인 경우에는 객체의 생성과 소멸이 큰 영향을 주지 않을 수 있지만 닷넷 환경에서 시스템의 리소스를 해제하고 어플리케이션의 성능 향상을 위해서 반드시 신경써 주어야 할 부분입니다.

- NoPD -
728x90
728x90
윈도우 모바일 6 부터 기본적으로 SQL Server 2005 Compact Edition (이하 SQL CE)가 기본적으로 OS 이미지에 올라가 있습니다. 배포하는 과정 없이 쉽게 사용할 수 있다보니 이전보다 개발자들이 SQL CE를 자주 쓰는 듯한 요즈음입니다. 하지만 몇가지 이유들로 인하여 데이터 엑세스시에 Not Enough Storage 에러를 발생하는 경우가 있는 것 같습니다.

윈도우 모바일 6 의 DLL 메모리 적재 방식

모든 문제점들의 원인이라고 단정지을 수는 없지만 기본적으로 윈도우 모바일 6가 DLL을 메모리에 적재하는 방식에 대하여 한번 집고 넘어갈 필요가 있습니다. SQL CE 팀블로그에 올라온 포스팅(http://blogs.msdn.com/sqlservercompact/archive/2007/10/26/troubleshooting-can-t-load-sqlce-dll.aspx) 에서 해당 내용을 발췌해 봤습니다.

When a DLL is loaded in one process, Windows CE reserves that address space in every process address space. Multiple processes that use the same DLL share it at the same relative address in every process. If your application does not use the same DLL, it cannot use the memory that is reserved for that DLL. In other words, Windows CE does not map a RAM-based DLL at an address where another process maps another DLL. For example, if Process A loads DLL X at address 0x00970000, Windows CE does not map DLL Y in the Process B address space at 0x00970000. Instead, Windows CE seeks the next lower address that is available, depending on the size of DLL Y. So if DLL Y is in the size range of 128 KB, Windows CE selects 0x00950000 if that address is available for Process B. Because of the way that Windows CE allows processes to share a common DLL, Windows CE does not permit two different processes to load two different DLLs at the same relative address of each process.

당연할 이야기일지 모르지만 서로 다른 DLL 에게 동일한 메모리 어드레스를 할당하지 않는 다는게 요입니다. 당연한 이야기도 한정된 메모리 영역을 가지고 있는 스마트 디바이스로 넘어오면 치명적인 문제가 될 수 있습니다. 여러개의 분산된 DLL 을 동적으로 로딩하는 어플리케이션이 있다고 가정하면, 모든 DLL 들은 자신의 용량과 관계없이 최소한의 메모리 이격을 둔채 (본문에서 128KB) 메모리를 할당받게 될 것입니다. 이는 곳, 메모리 영역이 금방 소진될 수 있다는 의미가 됩니다.

닷넷 컴팩트 프레임워크와 메모리 적재 방식의 상관관계

컴팩트 프레임워크를 포함한 모든 닷넷 프레임워크는 관리코드 (Managed) 사용시에 자동으로 GC (Garbage Collector)가 사용하지 않는 메모리 영역의 객체 인스턴스를 해제하고 자원을 해제시킵니다. 그런데 컴팩트 프레임워크에서 사용되는 SQL CE 관련 객체들에 자원 해제와 관련한 내부적인 문제가 있는 것으로 보입니다.  (참고 : http://support.microsoft.com/kb/824462, SqlCeCommand objects are not automatically disposed if you use a SqlCeDataAdapter object)

If you use the SqlCeDataAdapter object to populate a DataSet object, and you do not explicitly call the Dispose method for all the associated SqlCeCommand instances, you may receive the following error message:
Error Code: 8007000E
Message: Not enough storage is available to complete this operation.
Note The type of SqlCeCommand instances may be select, insert, update, or delete.

쿼리문 할당을 위해 사용한 SqlCeAdapter.SelectCommand, SqlCeAdapter.InsertCommand, SqlCeAdapter.UpdateCommand, SqlCeAdapter.DeleteCommand 객체가 할당된 SqlCeCommand 객체를 명시적으로 소멸(Dispose) 시켜줄 것을 권고 하고 있습니다. 추측컨데, 데이터 Row가 많은 자료의 경우 데이터를 Insert 하면서 객체를 소멸시키지 않고 반복적으로 루프문에서 사용할 가능성이 높은데 이 과정에서 메모리에 계속 새로운 영역들이 Reserve 되는 것이 아닌가 싶습니다.

다만 의아한 것은 이러한 에러가 발생한 시점에 메모리 용량을 점검해보면 수십메가 이상이 남아있는 경우가 많습니다. 이는 아마도 실제 메모리가 소모되어 발생했다기 보다 메모리 주소를 더이상 Reserve 할 수 없어서 발생하는 것이지 싶습니다.

참고자료

- INFO: Understanding Windows CE DLL Load Failures (http://support.microsoft.com/kb/326163)
- Methods to dispose of SQL Server CE, SQL Server 2005 Compact Edition, or SQL Server 2005 Mobile Edition managed objects from memory (링크)
- SQL Mobile 2005 - Error : Not Enough Storage is Available (링크)
728x90

+ Recent posts