본문 바로가기

Dot Database/Concept

DB 동작과정을 이해해보자 (애플리케이션과 DB 커넥션)

    DB 동작과정 살펴보기 

    이 글은 오라클 기준으로 작성하였다.

     

    애플리케이션에서 오라클로의 커넥션 과정의 이해가 필요한 이유는 애플리케이션에서 접속하는 것을 최적화하는 것만으로도 DB의 성능을 끌어올릴 수 있고, 애플리케이션에서 피해야만 하는 코딩 방식을 이해하는 데도 도움이 되기 때문이다.

     

    오라클은 애플리케이션 서버를 사용한 시스템이나 클라이언트/서버 형태의 시스템에서도 많이 사용된다. 즉 오라클과 '오라클을 사용하는 애플리케이션'이 같은 서버에 있는 경우는 드물며, 애플리케이션(오라클 클라이언트)과 오라클이 네트워크를 통해서 통신하는 경우가 많다. 그러므로 접속 설정으로 인한 장애가 쉽게 발생하지만, 간단한 장애일 때는 아키텍처를 이해하고 있다면 대부분 쉽게 해결할 수 있다.

     

    소켓 동작 이미지

    오라클은 TCP/IP의 소켓(socket)을 네트워크 통신 수단으로 사용하고 있다. 그래서 소켓이 어떻게 동작하는지를 머릿속에 이미지로 떠올릴 수 있어야 한다.  소켓을 사용하면 마치 전화처럼 다른 장비에 있는 프로그램과 통신할 수 있다. 장비 안에는 프로그램(프로세스)이 작동하고 있고, 그 프로그램이 수화기에 해당하는 소켓을 가지고 있는 모습을 머릿속에 그려주면 된다.

     

    소켓의 동작 이미지

     

    소켓은 '주소(adress)'와  '포트(port) 번호'라고 불리는 번호의 조합으로 식별할 수 있다. 다음 그림을 보면 동작과정을 이해할 수 있다. 해당 그림에서 중요한 점은 연락이 오기만을 기다리고 있는 프로세스가 존재한다는 점과 연결할 때는 송신 측에서 '주소'와 '기다리고 있는 포트'를 반드시 지정해야 한다는 점이다.

     

    소켓이 연결되는 모습

     

    1. 오라클에서 소켓의 동작

    오라클에서도 소켓을 사용하고 있는 이상, 위의 그림과 같이 동작한다. 오라클에서 수신을 기다리는 프로세스를 리스너(listener)라고 부르고, 리스너로 접속하려는 프로세스는 업무 애플리케이션의 프로세스이다. 

     

    커넥션 처리 1) 리스너 기동

    커넥션 처리를 창고 회사 오라클에 비유하면서 상세히 설명하면 다음과 같이 할 수 있다.

    • 리스너창고 회사 오라클의 접수 데스크이다.
    • listen.ora 파일은 접수 데스크가 가지고 있는 '회사의 대표 번호' 및 '내선 전화번호부'이다.

    하나의 리스너는 여러 개의 데이터베이스에 안내할 수 있다. 물론, 일반적인 상황에서는 하나의 리스너가 데이터베이스 한 개를 담당하게 된다. 

     

    오라클 커넥션의 개요

    리스너의 설정이 끝나면, lsnrctl이라는 도구를 사용해서 리스너를 기동한다. 리스너가 자신이 안내해야 하는 데이터베이스를 인식하는 방법으로는 listener.ora 파일에 기록되어 있는 설정을 읽거나, 데이터베이스가 자동으로 등록하는 방법이 있다. 일반적으로는 사용이 간단한 자동 등록을 선택해서 사용한다.

    os prompt > lsnrctl start  // 기본 리스너 기동 시키는 명령어

     

    이로써 접수 데스크 직원이 출근해서 업무를 시작할 준비(리스너 기동 완료)가 되었다. 고객이 언제 전화를 걸어오더라도 업무를 수행할 수 있다.

     

    커넥션 처리 2) 애플리케이션의 커넥션

    다음으로 업무 애플리케이션 측에서의 커넥션이다. 업무 애플리케이션 안에서 연결하기 위한 명령이 실행되거나, SQL*Plus에서 connect명령어를 실행한 순간에 데이터베이스에 연결된다.

     

    여기서는 먼저 데이터베이스에 연결할 때 필요한 정보를 오라클 클라이언트에 전달할 필요가 있으며, 그 정보를 '커넥션 디스크립터'라고 부른다. 

    • 커넥션 디스크립터 안에는 '주소는 XXX이며, 포트가 XXX이고, 서비스 이름이 XXXX...' 같은 정보가 포함되어 있다.
    • tnsnames.ora에 커넥션 디스크립터를 작성해놓고 커넥션 식별자를 커넥션 디스크립터마다 붙인다. 그래서 연결할 때는 해당 커넥션 식별자를 오라클 클라이언트에 전달하기만 하면 된다. 이를 전화로 비유하면 '단축 다이얼'이다.

     

    일반적으로 오라클 클라이언트는 tnsnames.ora의 커넥션 디스크립터 정보를 사용해서 리스너와 클라이언트 사이에 소켓을 생성하고, 리스너와 클라이언트 사이에 소켓을 생성하고, 리스너에게 '이 데이터베이스와 통신하고 싶어'라고 연락한다.

     

    tnsnames.ora 예시

    • ORA18C : 커넥션 식별자, 임의의 이름으로 설정할 수 있으며, net service name이라고 부름
    • DESCRIPTION : 커넥션 디스크립터
      • ADDRESS : 소켓을 생성하는데 필요한 정보
      • CONNECT_DATA : 리스너에게 전달하기 위한 정보
    ORA18C =
    	(DESCRIPTION = 
       		(ADDRESS = (PROTOCOL = TCP) (HOST = XXXX) (PORT = 1521))
    		(CONNECT_DATA =
    			(SERVER = DEDICATED)
    			(SERVICE_NAME = orcl)
    		)
    	)

     

    커넥션 처리 3) 서버 프로세스의 생성

     마지막 과정은 서버 프로세스를 생성하고 소켓을 인계받는 것이다. 소켓을 생성하면 리스너가 그대로 SQL 처리를 해도 될 것 처럼 보이지만, 한번 SQL 처리를 시작하면 요청받은 SQL을 처리하느라 다른 처리를 할 수 없게 되므로 전담 영업 담당자인 서버 프로세스를 생성하여 SQL처리를 즉시 인계한다.

    • 서버 프로세스의 생성을 비유해서 말하자면, 창고 회사 영업 담당자의 출근이다.
      • 출근은 고객에게 요청이 온 시점에 한다. (현실과 다른 점)

    하지만 서버 프로세스의 생성을 '출근'에 비유한 것이 적절한 이유는 서버 프로세스의 생성은 큰 작업이기 때문이다.

     

    서버 프로세스 생성과정

    1.  먼저 OS상에 프로세스 생성 (일반적으로 프로세스를 생성하는 것은 처리가 무겁다고 알려져 있음)
    2. 서버 프로세스가 사용할 수 있는 공유 메모리를 확보
    3. 서버 프로세스용 전용 메모리(PGA)도 확보
    4. 그 외에 데이터베이스 내부의 처리도 여러가지 남아있다.

    서버 프로세스를 한번 생성하는 데는 가벼운 SQL문을 처리할 때 사용하는 CPU 시간보다 몇 배에서 많게는 몇십 배 많은 CPU시간을 사용한다.

    따라서, 애플리케이션 개발할 때 서버 프로세스의 생성이 필요한 물리 커넥션을 만들고 종료하기를 반복하는 형태는 지양해야 한다.

     

    리스너는 서버 프로세스 생성이 끝나면 소켓을 서버 프로세스에 인계한다. 창고 회사로 비유해서 설명하면 회사의 대표 전화번호로 받은 전화를 접수 데스크가 담당자에게 돌려주는 과정과 같다. 리스너가 인계한 후부터 서버 프로세스와 오라클 클라이언트는 직접 송수신하므로 리스너는 자유로워진다. (해당 부분은 오라클 이해하기 키워드 중 1개인 '병렬처리를 가능하고 높은 처리량 실현'에 해당한다.)

    ... 업무 처리 코드 ...
    /* 
    * ods.getConnection() : 업무 처리를 하는 도중 데이터베이스의 데이터가 필요해져,
    *                                        데이터베이스에 접속한 후 데이터를 가져옴.
    * ~ conn.close(): 그 후에 커넥션을 닫아버림
    **/
    Connection conn = ods.getConnection();
    ... excuteQuery("select ...");
    conn.close();

    ... 업무 처리 코드 ...

    /* * 
    ods.getConnection() : 또 데이터베이스의 데이터가 필요해져서 다시 한번 접속한 후 데이터를 가져옴
    * ~ conn.close(): 그 후에 커넥션을 닫아버림
    **/
    Connection conn = ods.getConnection();
    ... excuteQuery("select ...");
    conn.close();

    ... 업무 처리 코드 ...

     

    2. 성능 개선 방법

    커넥션을 맺는 것과 서버 프로세스의 생성하는 것은 오라클에게는 무거운 작업이라는 것을 설명했다. 이런 힘든 작업을 '병렬 처리를 가능케 하고 싶은 높은 처리량을 실현한다'를 지키면서 개선할 수 있는 방안은 없을까?

     

    여기서 힌트가 되는 것이 '현실의 실제 시스템에서 서버 프로세스는 긴 시간 동안 아무것도 하지 않는다'라는 점이다.

    Problem

    수십, 수백 개의 서버 프로세스가 가동 중인 시스템도 드물지는 않지만, 대부분의 서버 프로세스는 가동 시간 중 일부만이 SQL을 처리한다. 또한 클라이언트와 일대일로 대응하여 서버 프로세스가 존재하고 있는 이상, 서버 프로세스의 수를 줄이는 것(애플리케이션을 줄인다)은 매우 곤란할 수 있다.

     

    그러면 실제 영업 담당자처럼 서버 프로세스 한 명이 여러 개의 클라이언트를 담당하게 하면 어떨까?

    Trouble Shoot1 (blocked)

    하지만 이것에는 큰 문제가 따른다. 서버 프로세스는 SQL을 처리하는 프로세스이므로 한번 SQL 처리를 시작하면 끝날 때까지 다른 작업은 하지 않는다는 점이다. 만약 클라이언트가 한 명이라면 문제가 되진 않겠지만, 서버 프로세스 한 명이서 많은 클라이언트를 담당해야 할 때 요청을 동시에 받으면 대처할 수 없게 된다.

     

    Solution

    그래서 효율을 높이기 위한 방법 중 하나로 여러 서버 프로세스가 여러 클라이언트의 SQL을 처리할 수 있도록 한다면 어떻게 될까? 바꿔서 말하면 '서버 프로세스 몇개 풀로 구성해두고 여러 애플리케이션이 자신이 쓰고 싶을 때만 풀에서 하나를 꺼내 사용한다.'는 구조라면 대부분이 대기 상태이던 서버 프로세스의 가동률을 높일 수 있을 것이다.

     

    이러한 구성을 위해서 오라클의 '공유 서버 구성'이나 '커넥션 풀(연결 풀)'이라고 불리는 구성을 사용할 수 있는데, 최근에 커넥션 풀을 사용하는 일이 많아졌다.

     

    커넥션 풀의 모습

     


     

    + tnsnames.ora을 꼭 사용해야 할까?

    위에서 커넥션 처리2) 애플리케이션의 커넥션 과정에서  tnsnames.ora 파일의 커넥션 식별자는 단축 다이얼과 같다고 했다. 빈번하게 접속해야 하는 환경은 tnsnames.ora파일을 사용하는 것이 권장된다.  하고 안하고의 차이는 다음과 같다.

    사용하지 않고 접속할 때

    SQL > connect scott/tiger@(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = XXXX) (PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = orcl)))Connected.

     

    사용해서 접속할 때 (ORA18C가 커넥션 식별자일 때)

    SQL > connect scott/tiger@ORA18CConnected.

     

    + 정지나 리스너 상태 확인하기

    일반적으로 애플리케이션에서 접속을 종료하는 처리(close나 disconnect)를 수행하면 서버 프로세스도 함께 종료된다.

     

    그러면 리스너는 어떻게 정지할까? 리스너는 lsnrctl의 stop 명령어를 사용해 정지할 수 있다. 또한, lsnrctld의 status 명령어로 현재의 리스너 기동 상태나 listen하고있는 포트의 번호, 보유하고 있는 데이터베이스의 정보 등을 알 수 있으므로 데이터베이스에 접속할 수 없는 장애를 발생할 때 활용할 수 있다.

     

     

    오라클 동작과정을 살펴보자

     

     


    참고

    스기타 아츠시 외 4인 - 오라클 구조