ORA-12545: Connect failed because target host or object does not exist
위의 에러를 가끔 만나게 될때가 있을 것이다.
언제 만나는것인가. ?
말 그대로 host를 못 찾아서 나는 에러 이다.
tnsnames.ora 에 보통 아래 처럼 세팅을 했다고 하자. HOST 를 IP로 등록
DEVD1 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 1192.168.100.71)(PORT = 1524))
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.100.72)(PORT = 1524))
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.100.73)(PORT = 1524))
)
(CONNECT_DATA =
(SRVR = DEDICATED)
(SERVICE_NAME = DEVD)
(FAILOVER_MODE =
(TYPE = SESSION)
(METHOD = BASIC)
(RETRIES = 100)
(DELAY = 3)
)
)
)
여러번 접속 시도를 수행 하다보면 어느 순간 ORA-12545 에러를 뿌려 주는 경우가 발생 된다.
해결 방법 :
클라이언트 PC 또는 클라이언트 서버 에 hosts 파일에
아래 처럼 등록 해 준다.
192.168.100.197 rac1.wizbase.co.kr rac1 ## public
192.168.100.71 rac1-vip.wizbase.co.kr rac1-vip ## vip
10.10.20.11 rac1-priv.wizbase.co.kr rac1-priv ## private
192.168.100.198 rac2.wizbase.co.kr rac2 ## public
192.168.100.72 rac2-vip.wizbase.co.kr rac2-vip ## vip
10.10.20.12 rac2-priv.wizbase.co.kr rac2-priv ## private
192.168.100.199 rac3.wizbase.co.kr rac3 ## public
192.168.100.73 rac3-vip.wizbase.co.kr rac3-vip ## vip
10.10.20.13 rac3-priv.wizbase.co.kr rac3-priv ## private
그리고 tnsnames.ora 에 아래 처럼 IP 에서 HOST 로 수정 한다.
DEVD1 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = rac1-vip )(PORT = 1524))
(ADDRESS = (PROTOCOL = TCP)(HOST = rac2-vip )(PORT = 1524))
(ADDRESS = (PROTOCOL = TCP)(HOST = rac3-vip )(PORT = 1524))
)
(CONNECT_DATA =
(SRVR = DEDICATED)
(SERVICE_NAME = DEVD)
(FAILOVER_MODE =
(TYPE = SESSION)
(METHOD = BASIC)
(RETRIES = 100)
(DELAY = 3)
)
)
)
위의 방법대로 설정 후 접속 테스트를 하면 다시는 ORA-12545 를 만나는일은 없을 것이다.
2010년 6월 9일 수요일
2010년 6월 4일 금요일
The Trouble with the Triggers-트리거 유지 보수 및 구현에 대한 가이드
The Trouble with the Triggers
트리거 유지 보수 및 구현에 대한 가이드
기술 인력들은 트리거 유지 보수 및 구현과 관련하여 어려운 과제에 직면 하고있다
asktom.oracle.com 웹 사이트를 자주 방문하는 분들은 필자가 트리거를 정말 싫어한다는사실을 잘알고 있을 것이다. 한때는 트리거를 아주훌륭한 기술로 생각해 자주 사용했으며 어쩌면 남용했다고 할 수도 있었다. 그렇지만 현재는 가능하면 트리거를 사용하지 않으려고 한다.
이처럼 트리거를 피하게 된 큰 이유는 두 가지가 있다.
| •트리거는 고질적인 유지 보수 문제를 야기한다. 트리거는 직접 실행되지 않는 작은 코드다. 그저 다른 작업의 부산물로“발생”할뿐이다. 작업이 부산물로 이루어지기 때문에 사람들은 트리거가있다는 사실을 종종 잊곤 한다(그리고 모든 부산물에 대한 코드 검토가 불가능한 것은 아니지만 어렵다). •구현된 트리거를 볼 때마다 거의 대부분 잘못 구현되어 있었다. 트리거에는 개발자가 알거나 예상하지 못한 큰 논리적 오류가 있다. 그 이유는 대부분 발생하는 문제를 예상하지 못했기 때문이다. |
유지 보수 문제
트리거를 피하는 첫 번째 이유인 유지 보수 문제는 아마도 쉽게 확인할 수 있을 것이다. 다른 사람의 작업을 인계 받아 다른 사람의 프로젝트를 작업 하고 있다고 가정해 보자. 그리고 트랜잭션을 나타내는 몇 줄의 코드나 그보다 나은 저장된 프로시저가 있으며 이를 읽는다고 생각해 보자. 이런 경우를 상상해 볼수 있다“. 이게 무엇인지 이해가 되는군. 알겠어.”그러나 여기저기에 트리거가 있는시스템에서 작업하고있다면 여러분은 전혀 이해 하지를 못하거나 잘못이해하게 될 것이다.
트리거는 부작용을 야기한다. 트리거는 메인스트림에서 벗어나 있다. 업데이트를 실행하고“행 처리된”것을 보면, 500개의 다른 것도 발생했을 수있다. 필자는 다음과 같은 내용의 비슷한 e-메일을 몇번이나 받았다(내용은 잘라내 붙여넣은 것이며 필자가 만든 것이 아니다).
우리가 열을 업데이트할 때 문제가 발생했다. 해당열을 업데이트 할 때(유 형이 varchar2), 업데이트는 1,972열이 업데이트 된 것을 보여주고 있다 (그리고UPDATE 후 커밋한다). 해당 열을 검색하면 쿼리는 업데이트한 열값을 반환하지 않고 다른열이 업데이트되며 이러한 값을 볼 수 있다. 무엇이 문제일까?
나는 간단히“트리거가 있습니까?”라고 물었다. 내게 돌아온 답변은“오, 이제야 알겠습니다. 문제가 발견되었습니다. 트리거에‘:new.name := :new.fname |‘| ’|| :new.lname;’이있었습니다.”라는것이었다.(나는 이와같은 문제를 매주 듣고 있다. 이런일은 실제로 여러분이 생각하는 것 보다 더 자주 일어난다.)
그래서 트리거를 검토하고 즉시 사용하지 않기로 결정했다. 먼저, NAME 속성은 뷰 내열이었어야한다(또는Oracle Database 11g에서는가상열). 이 이름 정보는 두 가지 다른 열에 있는 값을 기준으로 데이터를 추출한다. NAME 속성은 분명한 함수의 결과이고 이 함수는 연산 집약적이 아니며 필요할경우함수를색인화할수있다. 표에함수결과를저장할이유가없 었다.
둘째, 트리거에 있는NAME 속성을 무조건 덮어쓴다. 따라서 혼동을 일으키고(그렇지 않았다면, 앞의e-메일을 받지 않았을 것이다!) 데이터작업을 어렵게 만든다. 이 시스템에서는 이NAME 속성을 명백하게“fname||’ ‘||lname”으로 예상하지 않았다. 그랬다면 이 업데이트를 시도하지 않았 을것이다(왜냐하면NAME 속성을 해당 함수 결과로 설정하지 않기 때문이다!). 확실히 이들은 다른 어떤 값으로 설정하기 위해 애쓰고 있었다(하지만 트리거를 비활성화 하지 않고는 그렇게할 수 없었을 것이다).
트리거는 기존 시스템을 이해하기 힘들게 만들고, 기존 시스템을 유지 보 수하는 데 어려움을 가중시키며, 혼란을 발생시킬 뿐만 아니라, 데이터 정의언어(DDL: Data Definition Language)와스키마에숨겨져있다. 코드를 검토하는 동안 트리거는 일반적으로 검토 되고 있는코드의 컨텍스트에도 나타나지 않았다. 하지만 나타나야 한다. 트리거는 몇 번이나 반복 해서 호출되는서브루틴과 같다. 트리거는 서브루틴과 흡사 하지만 대부분의 사람들은 트리거를 DDL의 일부로 간주해버린다. 그리고 패키지구현을 검토하는 동안CREATE TABLE 문을 검토하지 않는 것처럼 대부분의 사람들은 트리거에 숨겨진 코드를 검토하지 않는다.
잘못된 구현
트리거를 싫어하는 첫 번째 이유(고질적인 유지 보수 문제)는 단순히 트리 거가“지겨운골칫거리”라는사실을알았기때문이다. 적절한 문서화와 적절한 검토프로세스를 통해 아마도 유지보수 문제를 처리할 수있을 것이다. 그러나 트리거의 잘못된 구현 문제를 해결하는 것은 또 다른 문제다. 종종 사람들은 트리거가 가져다줄 파장을 이해하지 못한상태에서 트리거를 만들것이다. 예를들어, 이트리거에서 심각한 버그를 즉시 알아볼 수 있는가?
확실히 문법적으로 올바르며 컴파일 된다. 레코드를 입력하면 트리거는 문제없이 실행된다. 그러나 아주 잘 못된 것이며, 구현에 심각한 실수가 있다. 실수는 한단어로 요약된다. 바로 롤백이다. 100행을PURCHASE_ORDER 테이블에 입력한 다음 커밋하지 않고 롤백하기로 결정하는 경우 어떤일이 발생할까? SMTP(Simple Mail Transfer Protocol)는 Oracle Database 의 분산 트랜잭션에 참가하지 않으므로 e-메일 전송은 롤백이 되지않는다. 실제로는 결코 발생하지 않는100개의 새로운 주문을 설명하는100개의 e-메일을 전송한다. 이것은 트리거를 사용할때, 즉 롤백 할 수 없는 작업을 수행할 때 발생하는 가장 빈번한 오류일 것이다.
(롤백이 발생하도록 하기 위해 직접 롤백을 실행할 필요는 없다는 점을 유 념하자. 정상 처리의 일부로 Oracle Database는 사용자에게 알리지 않고 업데이트, 삭제 및 병합을 롤백한다. 이와 관련한 자세한 내용은 tkyte. blogspot.com/2005/08/something-different-part-i-of-iii.html, tkyte.blogspot.com/2005/08/part-ii-seeing-restart.html 및 tkyte.blogspot.com/2005/09/part-iii-why-is-restart-importantto. html을참조하자.)
이는 다시 말해서 많은UTL_ 함수를 호출하는 트리거가 잘못될 수 있다는 뜻이다. 예를 들어, 트리거를 사용하여UTL_FILE을 호출해 텍스트를 파일에 쓰는 경우 잘못 될 가능성이 있다. 트리거에서UTL_FILE .PUT_LINE 호출은 롤백이 되지 않는다. 절대 발생하지 않는 이벤트에 대해 파일로 쓰도록 할 것이다. 트리거를 사용하여UTL_HTTP를 호출하여 웹 서버의 서비스를 실행하는 경우 결코 발생하지 않은 이벤트에 대해 해당서비스를 호출했을 것이다. UTL_MAIL, UTL_SMTP 및UTL_TCP가 같은 문제를 일으킨다. 트리거를 실행시키는 트랜잭션을 롤백하는 경우 롤백할 수 없는작업을수행했을것이다.
따라서 트리거를 작성하는 사람들의 경우 트리거 코딩의 첫 번째 규칙은 “롤백 할 수 없는 작업을 수행하지 마라”는 것이다. 트리거가 실행되지만 (코드실행) 트랜잭션이 롤백되는 경우 어떤일이 발생하는지생각해보자. 트리거와 관련한 다음 구현문제는 개발자들이 종종 데이터베이스의 동시성 제어와 격리의미묘한 차이점을 완전히 이해 하지 못하고 있다는 사실에서 기인한 것이다. Oracle Database의 가장유용한기능 중 하나가 읽기가 쓰기를 방해하지 않고 쓰기가 읽기를 방해하지 않는다는 것이다. 그러나 개발자가 완벽하게 이해하지 못할 때 이 단일 기능은 개발자가 특히 일부 “규칙”을 시행하는 트리거를 사용할 때는 책임이 된다. 예를 들어, 최근 asktom.oracle.com에서 다음과 같은시나리오를 제공했다.
기본 통화와 국가 통화 조합을 포함하고 있는 테이블이 있다고 가정한다. 다음은 샘플데이터이다.
우리는 해당국가에 대해 많아야 하나의 통화를 기본 통화로 할 수 있는 규칙을 시행해야 한다. 해당 국가에 기본 통화가 있는지 여부를 확인하기 위해 각 행마다 위의 표에서BEFORE UPDATE 트리거를 사용한다(예기치 않은 오류를 방지하기 위해 독립된 트랜잭션사용).
이것이 전부다. 필자가 읽었을 때 여기에 심각한 버그가 있다는 것을 알게 되었다. 바꾸어말하면:
| •많아야 한가지 통화가 기본통화가 될 수 있다(테이블에서행을가 로지르는 제약 조건이 있음). •여기에 트리거가. . .있다. •예기치 않은 테이블 오류를 방지하기 위해 독립된 트랜잭션을 사용 하고 있다. |
아래와 같은 트리거가 있을것이다.
이제 이 트리거에는 많은 오류가 있다. 그러나 무엇인가 심각하게 잘못되 었다는 첫 번째 확실한 단서는 독립된 트랜잭션을 사용해야 한다는 것이 다. 이는 독립된 트랜잭션없이는 업데이트가 다음과 같은 결과를 낳기 때문이다.
사실 오류라기보다는 경고에 가깝다. 기본적으로“귀하는Oracle Database 가이와 같은작업을 하도록 허용 하지 않는데도 트리거에서 기본적으로 잘못된 방식을 사용하고 있습니다.”라고 말하고 있다. 데이터베이스가 정의된 테이블을 트리거가 읽도록 허용했다면, 업데이트가 처리되면서 트리거는 테이블이 부분적으로 데이트된 것을 확인할 것이다. 5개 행이 업데이트 되는 경우 행트리거는 수정된 행중 하나가 있는 테이블을 본 다음 둘, 셋등으로 진행할 것이다. 이는 테이블을 결코 볼 수 없는 방식으로 테이블을보게된다.
예를 들어, 위의CURRENCIES 테이블이 질문에서 제공한 샘플 데이터를 갖고 있고 테이블을 변경하는동안 트리거가 테이블을 읽는 것을 허용했다고 가정하자. 이제 다음 명령을 실행한다.
기본 통화 플래그를 USD에서 USN으로 이동시키기 때문에 이 명령은 문제가 없다. 명령문이 완료된 후기본 통화 행은 하나만있을 것이다. 그러나 먼저USN, 그다음 USD의 순서로 행이 업데이트 되었다면 어떻게 될까. 트리거를 처음 실행하면PRIMARY_CURRENCY=‘Y’인USN과 PRIMARY_CURRENCY=‘Y’인USD가 표시된다. 트리거는 명령문을 실패하지만 명령문은 성공한 것처럼 보인다. 반면에 먼저USD, 그런다음 USN의 순서로 데이터가 처리 된다면 어떻게 될까? 이경우 트리거가 실행되고 0개의PRIMARY_CURRENCY=‘Y’행을 찾은 다음 다시 실행하면 하나만 표시되고 완료된다.
따라서 이트리거에서, 업데이트는 때때로 일부 데이터에 대해서 만적용된다. 일부 데이터의 경우이터가 있는 2개의 데이터베이스는 다른 행에서는 실패하고 또 다른 행에서는 성공할 것이다. 이는 디스크에서 데이터가 어떻게 구성되었으며 어떤 순서로 처리되는지에 달려 있다. 이런 것은 용납되지 않는다(혼동스러운 것은말할것도없다).
즉 서로를 보호하려는 독립된 테이블 제약 조건이 존재하기 때문이다. 그러나 불행하게도 이 문제를 물어보는 개발자들은 독립된 테이블 제약 조건을 해결하는 방법이 독립된 트랜잭션이라는 것을 발견했다. 이“기능”은 마치 다른세션/트랜잭션에 있는 것처럼 트리거가 실행되는 테이블에 개발자가 쿼리하는 것을 허용한다. 트리거는 스스로가 테이블을 수정하는 것을 알지못하며 바로이점에서“트리거가자신이알수 없는 수정을 확인하려고 시도한다”는 생각에 심각한 모순이있는 것이다 트리거의 유일한 목적은 데이터가 수정 되었는지 확인하는 것이지만, 트리거는수정이 되기전의 데이터를 읽고있다. 따라서 정상적인 작업이 될 수 없다!
개발자는 이 독립된 테이블 제약 조건을 해결하는 다른 방법을 찾을 수 있다. 일반적인 기법은 패키지와3개의 트리거를 사용하는 것이다(asktom. oracle.com/tkyte/Mutate에 설명되어 있다. Oracle Database 11g에는 이“패키지및3개트리거”기법 대신 사용할 수 있지만, 그 결과는 다음에서 설명하는 것과 동일한 복합트리거라는 새로운 기능이 있다).
이 기법에서 패키지는 배열(array) 같이PLSQL 테이블 유형의 글로벌 변수를 사용한다. 글로벌 변수는BEFORE 명령문 트리거에 의해“공백”으로 설정된다. 그런 다음 글로벌 변수는FOR EACH ROW 트리거(수정 된행의키) 또는rowids에 의해 기본키로 채워진다. 마지막으로 AFTER 명령문 트리거는 글로벌 변수값에 대해 반복되고 수정이 이미 일어났기 때문에 트리거가 정의된 테이블을 쿼리할 수 있다. 이는 사용자가 데이터베 이스의 유일한 사용자이고 한 번에 둘 이상의 트랜잭션을 갖고 있지 않은 경우에 효과적이라는 것을 의미한다! 제가 검토한 다른 많은 트리거와 마 찬가지로 이 트리거 솔루션은 순전히 단일 사용자 환경에서 실행된다. 격리된 곳에서는 제대로 실행되지만 여러 사용자가 동시에 트리거를 호출 할 때는 제대로 실행 되지않는다.
예를들어, 통화테이블을 다음과 같이 시작했다고 가정해보자 (primary_currency가모두N):
이제 한세션에서 다음 명령을 실행한다.
3개트리거 솔루션에서는 BEFORE 명령문 트리거가 실행 되고 세션의 글로벌 변수를 공백으로 설정할 것이다. 그런 다음 FOR EACH ROW 트리 거가실행되고:NEW.COUNTRY 및:OLD.COUNTRY 값을 기록하는 것처럼 이 글로벌 변수에서 수정한 국가를 기억한다. 마지막으로, 모든 레코드가 업데이트 된 후에 AFTER 명령문 트리거가 실행되고 발견한 COUNTRY 값에 대해 반복된다. 따라서PRIMARY_CURRENCY= ‘Y’및COUNTRY=‘US’레코드를 쿼리하고 계산하며 한 레코드만 있 다는것을발견한다. 모든 것이 순조롭게 진행된다. 그러나 다른 세션에서, 이 업데이트 직후에(아직 커밋하지 않았음) 다음 명령을 실행한다.
이제 트리거는 이세션에서 실행되고 AFTER 명령문 트리거가 다른 세션에서 했던 것처럼 같은 카운트를이AFTER 명령문 트리거가 수행하면 이세션의 트리거도COUNTRY=‘US’에대해PRIMARY_CURRENCY= ‘Y’인 레코드가 하나만 있는 것을 발견한다! 이와같은 이유로 이트리거의 읽기가 다른세션의 쓰기에 의해 차단되지 않는 것이며 이 세션의 트리거는 다른 세션의 업데이트를 알수없다.
이제 두 트랜잭션을커밋하고COUNTRY=‘US’의두레코드가PRIMARY _CURRENCY value=‘Y’인 테이블을 갖게 된다. 즉, 우리가 확인하려 고 했던 규칙이 확인되지 않았다. 트리거에서 이 규칙을 시행하려면 테이 블을 잠그고 테이블에 순서대로 액세스해야 한다. 두 세션이 같은 국가에 대해동시에기본통화를입력하지않도록해야한다(우리가볼수없는데 이터를 잠그는 것은 매우 어려우며 다른 사람이 입력하는 것을 알 수 없으므로 이 사람이 입력하지 못하도록 해야 한다).
테이블의행에서무결성을시행하는트리거를갖고있는경우이트리거는 참조무결성을시행하려고테이블에접근을시도하고여러분은순차적액 세스를위한LOCK TABLE 명령을사용하지않았기때문에잘못될것은 확실하다. 읽기는 쓰기에 의해 차단되지 않고 마찬가지로 쓰기도 읽기에 의해차단되지않기때문에트리거에서엔티티무결성을시행하는것은매 우복잡하고명시적잠금을포함해야한다. 더욱이이잠금은행수준잠금 보다 훨씬 상위 수준에서 이루어져야 한다. 대개 테이블 수준 잠금이 되어 야한다.
그러나 필자는 머지 않아 이와같은 방식으로LOCK TABLE 명령을 사용 하는 애플리케이션을 보게될 것이다. 트리거를 사용하는 많은 애플리케이션이 엔티티 무결성을 시행하지만 거의 일반적으로 잘못 구현된 것을확인 할 수 있었다. 이들은 다중 사용자 시나리오에서 잘못된 데이터를 입력하 고저장하는경쟁조건을갖고있다. 결국 다 른프로세스가 데이터가“정제 되고 유효하며 규칙 을따른다”는가정하에 작업하는 다운 스트림 처리에서 실패를 초래하게된다.
answer
앞에 언급된 비즈니스 문제에 대한 답은 무엇일까(많아야 하나의 통화가 해당국가의기본통화가될수있음)? 두가지해답이있다. 둘모두동시성 이있고(확장가능), 올바르다(실제로작동). 트리거를사용하는경우매우 동시적이거나 올바른 것 중 하나를 선택할 수 있다. 둘을 동시에 만족시킬 수는없다. 트리거구현으 로 매우 동시적인 경우, 아마도 잘못 수행했을 것이다. 트리거 구현이 올바르다면 거의 분명히 확장성은매우 낮을 것이다. 따라서 데이터모델이 완전히 잘못되었기 때문에 저의 첫번째 답변을 참조 하여 데이터 모델을 수정해야 한다. 이 비즈니스 문제는 기본 통화가 있는 테이블과 다른 통화가 있는 테이블 등 두 테이블에서 오는 것이다. 애플리 케이션이쿼리하기쉬운단일“테이블”을요구하는경우보기는모든데이 터를 통합할수있다“. 많아야 한 통화가 기본 통화가 될수있음”규칙을 시행하기 위해 간단히 다음 명령을실행할것이다.
완료되었다. 우리는하나의“규칙”을구현했다. 나는 여러분이 COUNTRY =‘US’에 대해 두 개의 기본 통화를 만들어 볼 것을 주문할 것이며 그것은 불가능하다. 기본키가 이를 실행한다.
그러나 실제운영환경에서이는일반적으로시작일뿐이다.“ 많아야하나 의 통화가 해당 국가의 기본 통화가 될 수 있음”이라는 다음 규칙은“한 국 가는 하나의 기본 통화를 가져야 함”이라는 말과 같다. 즉, 많아야 하나의 기본통화가있고적어도하나이상의기본통화가있어야한다.
다시말해이를위한코드가필요하지않다. 간단한선언적제약조건이우 리가필요한모든것을수행한다.
우리는다시한번실행했으며, 이번에는원래규칙과후속규칙, 코드가없 고 유지 보수하고 이해하기 쉬운 방법을 사용했다. 외래 키를 사용하면 기 본통화를갖지않고다른통화를갖는것이불가능하다. 그리고이는매우 확장가능한방식으로올바르게수행된다.
실제환경에서는요점을강조하기위해이데이터에필요한규칙을사용하 여 수행하지 않을 것이다. 실질적이고 완벽한 규칙은 아마도 다음과 같을 것이다.“ 한 국가는 최소한, 그리고 많아야 하나의 기본 통화를 가져야 하 며기본통화는다른통화가될수없습니다.”즉, USD가미국의기본통화 이면USD는COUNTRY=‘US’일때OTHER_CURRENCIES 테이 블에나타날수없다.
이것은 까다로운 작업이다. 마치 존재하지 않는“반외래 키”같다. 그러나 우리는 데이터베이스 규칙으로 이를 구현할 수 있다. 기본적으로 우리는 COUNTRY 및CURRENCY에 의해PRIMARY_CURRENCY를 OTHER_CURRENCIES에 조인하는 경우 결과적으로 항상 제로 레코 드가있는지확인해야한다.
리스팅1은 조인의 결과로 데이터가 생성되지 않도록 조인하고 제약 조건 을설정하는materialized view를만든다.
코드리스팅1: 조인이데이터를생성하지않도록하는Materialized view 및제약조건
따라서 이제 커밋 할 때 리프레시 되는 materialized view를 갖게 되었고 두 테이블 간에 데이터가 조인할 수 없도록 할 수 있다. 이 materialized view는항상비어있다. 그리고확장가능하며(직렬화를위한유일한기회 가COMMIT 시간에 있음) 올바르다. 데이터베이스는 우리를 대신하여 이 제약조건을시행한다.
이것이원래규칙“많아야 하나의 통화가 해당국가의 기본통화가 될 수 있음”을 충족시키는 첫번째 응답이며 같은 정보에 대해 추가규칙이 필요할 수있다. 두번째 응답은 원래규칙이 유일한 규칙인 경우(위에서설명한규 칙의다른두부분이없음) 원래테이블에서 다음 색인을 사용할 수 있다.
이 응답은PRIMARY_CURRENCY=‘Y’일 때COUNTRY를 고유 하게색인화한다. PRIMARY_CURRENCY가‘Y’가 아니면 케이스 명령문은 색인화 되지 않은 전체NULL 색인키를 반환한다. 이와같은식으로PRIMARY_CURRENCY=‘Y’레코드만 고유하게 색인화 한다.
나는이색인을“불량해킹”이라고간주한다. 수행되기는 하지만 썩훌륭한 것은 아니다. 그리고 존재할 수도있는 다른 제약조건은 실행할 수 없다. 요구사항을 지원하기 위해 설계했을 때 올바른 데이터 모델은 선언적명령문으로 훌륭하게 모든 것을 수행한다.
코드가 적을 수록 버그도 적어진다. 적은 수의 코드를 작성하는 방법을 찾 아보아야한다.
결론
트리거는 회의론자의 눈에 띄어야 한다. 트리거가 엔티티 무결성을 실행하는 곳에 있는 경우 의심해보고 다중 사용자 조건에 대해 생각해보자. 두명 이나 세 명이 유사한 데이터에서 동시에 작업할 때 어떤 일이 발생할지 생 각해야 한다. 머리 속이나 화이트보드에서 모든 조합을 실행하자. 다중 세 션을 사용하여 스키마를 실행한다. 동시 액세스할 때 어떤 일이 일어나는지 확인해야한다. 열에값을 제공하는 트리거가 있는경우, 가능한 유지보수 문제와 “예상치못한”부작용을 인식 해야한다.
트리거는 규칙이 아니라 예외가 되어야 한다. 다른 방식으로 무엇인가 할 수없을 때만 사용해야 한다. 동일성문제, 비트랜 잭션작업을 수행하는 문제 및 유지보수 문제가 있는 경우 트리거는 그 사용을 자제해야한다.
필자소개
출처 : 한국 오라클
제공 : DB포탈사이트 DBguide.net
2010년 6월 3일 목요일
Oracle 10g RAC의 Load Balancing과 Failover
Oracle 10g RAC의 Load Balancing과 Failover
여기서는 DBMS의 고가용성(HA)을 보장해주는 여러 제품군들 가운데 Oracle RAC를 살펴보기로 한다. Oracle RAC의 특징 중 하나는 Load Balancing과 FailOver 기능으로, 이중에서도 RAC를 이용하는 가장 큰 이유는 바로 Application FailOver 기능 때문일 것이다. Application FailOver는 하나의 노드에 장애가 발생했을 때 다른 살아 있는 노드에서 이 Application의 서비스를 대신해주는 것으로, 실제 사용자는 서비스 중이던 노드에 장애가 일어났는지를 감지하지 못하고 정상적으로 서비스를 이용할 수 있는 기능을 의미한다.
Oracle RAC에서 Fail-Over를 가능하게 해주는 것이 Oracle 9i부터 소개된 방법인 CTF(Connection Time Failover)와 TAF(Transparent Application Failover)이다. CTF는 클라이언트가 해당 데이터베이스로 접속을 시도했으나, 해당 서버에 장애가 발생하여 접속하지 못할 경우 살아 있는 다른 서버로 접속할 수 있게 해주는 기능이고 TAF는 클라이언트가 RAC에서 어느 한 노드에 접속하여 작업하던 도중 장애가 발생했을 경우 살아 있는 다른 노드에서 수행 중이던 작업을 계속할 수 있게 해주는 기능이다. 먼저 Oracle RAC의 Load Balancing 기능을 먼저 살펴보자.
Client-Side Connect-Time Load Balancing
Client-Side Connect-Time Load Balancing의 특징은 이용 가능한 Listener 리스트 중에서 랜덤(random)하게 접속 요청을 하는 것이다. 이는 클라이언트의 tnsnames.ora에 “LOAD_ BALANCE=ON”을 설정함으로써 가능하다.
| -- TNSNAMES.ORA ghlee = (DESCRIPTION = (LOAD_BALANCE=ON) (ADDRESS = (PROTOCOL = TCP)(HOST = 192.128.25.18)(PORT= 1521)) #ghlee1 (ADDRESS = (PROTOCOL = TCP)(HOST = 192.128.25.19)(PORT= 1521)) #ghlee2 (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = GHLEE))) -- 첫 번째 접속 시도 [/oracle/product/10.2.0/network/admin> sqlplus scott/tiger@ghlee SCOTT@ghlee1 >@in.sql INSTANCE_NUMBER INSTANCE_NAME HOST_NAME -------------- -------------- -------------- 1 ghlee1 ghlee1 -- 두 번째 접속 시도 [/oracle/product/10.2.0/network/admin> sqlplus scott/tiger@ghlee INSTANCE_NUMBER INSTANCE_NAME HOST_NAME --------------- --------------- -------------- 1 ghlee1 ghlee1 -- 세 번째 접속 시도 [/oracle/product/10.2.0/network/admin> sqlplus scott/tiger@ghlee INSTANCE_NUMBER INSTANCE_NAME HOST_NAME --------------- --------------- -------------- 1 ghlee1 ghlee1 -- 네 번째 접속 시도 [/oracle/product/10.2.0/network/admin> sqlplus scott/tiger@ghlee INSTANCE_NUMBER INSTANCE_NAME HOST_NAME --------------- --------------- -------------- 2 ghlee2 ghlee2 -- 다섯 번째 접속 시도 [/oracle/product/10.2.0/network/admin> sqlplus scott/tiger@ghlee INSTANCE_NUMBER INSTANCE_NAME HOST_NAME --------------- --------------- -------------- 1 ghlee2 ghlee2 ghlee2 |
위의 결과에서 볼 수 있는 것처럼 랜덤하게 접속을 시도한다. 만약 “LOAD_BALANCE=OFF”로 설정되어 있다면 List에 명시된 처음 Address (HOST = 192.128.25.18)로 먼저 접속을 시도하고 응답이 없을 경우 TCP TIMEOUT까지 대기한 후 다음 Address list (HOST = 192.128.25.19 )로 접근을 시도한다.
Server-Side Connect-Time Load Balancing
Server-Side Connect-Time Load Balancing이라는 것은 서버 측에 설정하는 것으로서 동작 방식은 다음과 같다.
<그림 1> Server-Side Connect-Time Load Balancing
① PMON Process가 Listener에 Performance 정보와 함께 Service를 동적으로 등록한다. <표 1>과 같은 정보들이 주기적으로 기록된다.
<표 1> 주기적으로 기록된 정보
② 클라이언트가 Connection을 요청하면, 클라이언트의 tnsnames.ora 파일에 기술된 Protocol address 중에 랜덤으로 하나의 접속에 성공한다. 그 Listener가 ghlee1의 Listener이다. ghlee1의 Listener는 ghlee1과 ghlee2 instance의 load의 양을 비교한다. 여기서는 ghlee2의 load의 양이 더 적으므로 ghlee2를 선택한다.
③ Ghlee1의 Listener는 클라이언트에게 ghlee2의 Listener로 connection하도록 redirect 한다.
④ 클라이언트는 ghlee2의 Listener로 접속한다. Listener는 server process를 생성하고 Listener로부터 connection 요청을 상속받아 이제부터는 Listener를 거치지 않고 바로 클라이언트와 통신한다.
이와 같은 방식으로 실행하기 위해서는 ghlee1의 Listener에 ghlee2의 Node의 상태 정보나, Service의 정보들이 PMON에 의해 Listener에 주기적으로 갱신해줘야 하는데 이를 위해서는 Instance가 Listener에 자동으로 등록되는 방식을 사용해야 한다. 이를 Dynamic Register 방식이라 하고, 이를 위해서는 몇 가지 설정이 필요하다.
Dynamic Register
8i 이전까지는 Database Instance의 정보를 listener.ora 파일에 수동으로 기술했지만, 8i 이후부터는 스스로 Listener에 등록할 수 있다. PMON process가 현재의 상태와 Instance의 load 정보를 Listener에게 보내는 방식이다. 8i 이전까지는 아래와 같이 listener.ora 파일에 Instance에 대한 사항을 기술해주었다.
위의 정보에서 보면 지금 Listener에는 “GHLEE”와 “GHLEE1”이라는 두 개의 서비스가 등록되어 있고, 각 서비스는 “GHLEE1” Instance에 등록되어 있음을 확인할 수 있다. 그리고 “GHLEE” 서비스는 상태 정보가 “READY”인 반면, “GHLEE1” 서비스의 상태 정보는 “UNKNOWN”임을 확인할 수 있다. “GHLEE”라는 서비스는 동적으로 등록된 서비스이므로 PMON에 의해 상태를 지속적으로 Listener에게 업데이트해줌으로써 상태 정보를 확인할 수 있어 “READY”라고 보이며, GHLEE1이라는 서버는 위의 listener.ora 파일에서 기술한 정보이므로 동적으로 등록되지 않아 상태 정보를 확인할 수 없어 “UNKNOWN”이라고 보이는 것이다. Dynamic Register의 구성 방법은 다음과 같다.
● Service Registration 구성
이것은 초기화 파라미터 중 “SERVICE_NAMES” 파라미터에 값을 설정함으로써 가능하다. 이 이름이 바로 Listener에 등록하는 서비스 이름으로 Default 이름은 DB Install 시에 설정한 dbname.domain이다. 여기서는 SERIVCE_NAMES 파라미터를 설정하지 않았어도, dbname.domain에 의해 서비스 이름이 “GHLEE”가 되었다.
● Default Local Listener Registration 방법
Default로 PMON process는 TCP/IP Port 1521을 사용하는 local Listener에 자동으로 서비스 정보를 등록한다. 위의 예제에서는 Default Port를 사용하고 dbname.domain에 의해서 “GHLEE”라는 서비스가 자동으로 등록되었다.
● NON-Default Listener Registration 방법
PMON에 의해 Local Listener에 등록을 원하고 TCP/IP가 Port 1521을 사용하지 않을 경우는 다음과 같이 LOCAL_ LISTENER PARAMETER를 설정하면 된다.
LOCAL_LISTENER =
Listener_alias =
(DESCRIPTION= (ADDRESS=(PROTOCOL=TCP_(HOST= node1_vip)(PORT=1524))
→ 서버의 tnsnames.ora 파일에 기술
or
LOCAL_LISTENER =“(ADDRESS=(PROTOCOL=TCP)(HOST =node1_vip)(PORT=1524))” → Parameter 변경
● Remote Listener Registration 방법
PMON process에 의해 remote Listener를 등록하고 싶다면, 다음과 같이 REMOTE_LISTENER 파라미터를 설정해야 한다.
REMOTE_LISTENER=< listener_alias> → Parameter file 기술
Listener_alias =
(DESCRIPTION= (ADDRESS=(PROTOCOL=TCP_(HOST =node2_vip)(PORT=1521))
or
REMOTE_LISTENER=“(ADDRESS=(PROTOCOL=TCP_(HOST =node2_vip)PORT=1521))”
위와 같이 등록했을 때 LISTENER SERVICES를 확인하면 다음과 같다.
위와 같이 Server에 Dynamic Register 방식으로 구성하게 되면 각 Listener는 Server의 상태 정보를 가지고 있어 좀 더 Idle한 서버에서 클라이언트의 요청을 받을 수 있는 Load Balancing을 구현할 수 있다. 이제부터 Oracle RAC의 강력한 기능인 Fail-Over에 대해 살펴보도록 하겠다.
Fail-Over
CTF는 앞에서 언급했던 것처럼, connection할 당시 해당 서버에 장애가 발생했을 경우 이용 가능한 서버로 connection을 요청하는 방법으로 클라이언트의 tnsnames.ora 파일에 아래와 같이 설정하면 된다.
TAF는 서버에 접속하여 작업 중일 때 작업 중이던 데이터베이스에 문제가 발생했을 경우에 이용 가능한 다른 Node에서 진행 중이던 작업을 계속 해주는 것으로서, 엔드유저는 이 데이터베이스에 장애를 감지하지 못하는 Fail-Over 방식이다. TAF의 Mode에는 현재 접속 중이던 Session을 Fail-Over시키는 Session 방식과 실행 중이던 Select 문을 다른 Node에서 장애가 발생한 시점 이후부터 다시 Select를 실행해주는 Select 방식이 있다.
Fail-Over Method에는 Session이 맺어 있던 Node를 이용할 수 없을 경우 Oracle이 자동으로 살아있는 Instance 쪽으로 다시 접속하는 BASIC 방식과 Instance에 미리 Session을 접속해 Fail-Over 시에 세션을 새로 맺는 오버헤드를 줄이는 방식인 Preconnect 방식이 존재한다. 설정 방법은 다음과 같다.
1. TYPE=SELECT, METHOD=BASIC
| -- 클라이언트의 TNSNAMES.ORA test_basic1 = (DESCRIPTION = (FAILOVER=ON) (LOAD_BALANCE=ON) (ADDRESS = (PROTOCOL = TCP)(HOST =192.128.25.18)(PORT =1521)) (ADDRESS = (PROTOCOL = TCP)(HOST =192.128.25.19)(PORT =1521)) (CONNECT_DATA = (SERVICE_NAME = GHLEE) (FAILOVER_MODE = (TYPE=SELECT) (METHOD=BASIC)) ) ) -- Session접속 [/oracle/product/10.2.0/network/admin>sqlplus scott/tiger@test_basic1 SQL>@se INST_ID USERNAME SID SERIAL# FAILOVER_M FAILOVER_TYPE FAI SERVICE_NAME ---------- --------------- ------- ---------- ---------- 1 SCOTT 2162 13814 BASIC SELECT NO GHLEE -- 접속한 Session에서 Long Query 실행 SQL> select * from t1; -- instance kill(접속된 Instance인 ghlee1을 Kill한다) [ghlee1@/oracle/product/10.2.0/network/admin>srvctl stop instance -d ghlee -i ghlee1 -o abort Sun Apr 12 10:30:55 KST 2009 -- 위의 접속되어 있는 Session의 상태는 INSTANCE가 죽을 당시 잠시 멈춰 있다 가 다시 실행되는 현상을 확인할 수 있다. FAILED_OVER Column의 상태를 보면 "YES" 로 되어서 Fail-Over되었음을 알 수 있다. SQL> @se INST_ID USERNAME SID SERIAL# FAILOVER_M FAILOVER_TYPE FAI SERVICE_NAME ---------- --------------- ------- ---------- ---------- 2 SCOTT 2177 10 BASIC SELECT YES GHLEE |
2. TYPE=SELECT, METHOD=PRECONNECT
| -- 클라이언트의 TNSNAMES.ORA. Preconnect 방식을 이용할 때는 반드시 BACKUP을 명시해야 한다. test_preconn1 = (DESCRIPTION = (FAILOVER=ON) (LOAD_BALANCE=ON) (ADDRESS = (PROTOCOL = TCP)(HOST = 192.128.25.18)(PORT= 1521)) (ADDRESS = (PROTOCOL = TCP)(HOST = 192.128.25.19)(PORT= 1521)) (CONNECT_DATA = (SERVICE_NAME = CAFE4DB) (FAILOVER_MODE = (TYPE=SELECT) (METHOD=PRECONNECT) (BACKUP=PRECONNECT2) ))) PRECONNECT2 = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 192.128.25.19)(PORT= 1521)) (CONNECT_DATA = (service_name = GHLEE) (instance_name = GHLEE2) ) ) -- Session 접속 (미리 2개의 Session이 연결되어 있는 것을 확인할 수 있다) [/oracle/product/10.2.0/network/admin>sqlplus scott/tiger@test_preconn1 SQL>@se INST_ID USERNAME SID SERIAL# FAILOVER_M FAILOVER_TYPE FAI SERVICE_NAME ---------- --------------- ------- ---------- ---------- 1 SCOTT 2162 13814 PRECONNECT SELECT NO GHLEE 2 SCOTT 1065 10351 NONE NONE NO GHLEE --접속한 Session에서 Long Query 실행 SQL> select * from t1; -- instance kill(접속된 Instance인 ghlee1을 Kill한다) [ghlee1@/oracle/product/10.2.0/network/admin>srvctl stop instance -d ghlee -i ghlee1 -o abort Sun Apr 12 10:35:25 KST 2009 -- 위의 접속되어 있는 Session의 상태는 INSTANCE가 죽을 당시 잠시 멈춰 있다가 다시 실행되는 현상을 확인할 수 있다. FAILED_OVER Column의 상태를 보면 "YES"로 되어서 Fail-Over되었다는 것을 알 수 있다. SQL> @se INST_ID USERNAME SID SERIAL# FAILOVER_M FAILOVER_TYPE FAI SERVICE_NAME ---------- --------------- ------- ---------- ---------- 2 SCOTT 1065 10351 PRECONNECT YES NO GHLEE |
<표 2> 결과 정보
3. TYPE=SESSION, METHOD=BASIC
<표 3> 결과 정보
4. TYPE=SESSION, METHOD=PRECONNECT
<표 4> 결과 정보
아쉬운 점은 Instance가 종료된 이후 Node가 다시 정상적으로 이용 가능하다고 하더라도 RAC 내에서 자동으로 Fail-Over된 Session을 다시 원래의 Node로 돌려주는 기능은 아직 구현되어 있지 않다는 것이다. DBA가 원래 Node의 Instance로 돌려주어야 한다.
| 본문에 사용한 스크립트 |
in.sql col host_name format a15 col INSTANCE_NAME format a15 select INSTANCE_NUMBER,INSTANCE_NAME,HOST_NAME from v$instance; se.sql col service_name format a15 select instance_id, username, sid, serial#, failover_method, failover_type, failed_over, service_name from gv$session where program='sqlplus.exe' |
OLTP 환경에서는 수행 시간이 긴 Select 문은 거의 존재하지 않으므로 보통은 Session 방식으로 설정하여 사용하며, DW 환경과 같이 수행시간이 긴 Select 문에서는 Select 방식을 많이 선호한다. 아쉽게도 DML은 아직 지원되고 있지 않다. Preconnect 방식은 오버헤드는 줄여줄 수 있지만 하나의 Session을 맺을 때 미리 하나의 Session을 더 맺어두는 방식으로 한번 연결에 총 두 개의 Session을 맺으므로 시스템에 Idle한 Server Process가 하나 더 생성되는 방식이므로 리소스의 낭비를 초래할 수 있다. 각자의 환경에 맞게 Fail-Over 환경을 구성함으로써 효율적인 HA를 구성해 사용하길 바란다.
| 참고자료 1. Oracle Net Services Administrator's Guide 2. http://metalink.oracle.com |
필자소개
출처 : 한국 마이크로 소프트웨어 [2009년 5월호]
제공 : DB포탈사이트 DBguide.net
Transparent Application Failover(TAF)를 통한 장애 차단(mask)
가장 좋은 장애는 어느 누구도 알아차리지 못한 장애입니다. 불행하게도, Oracle은 매우 신속하게 복구할 수 있는 구조를 갖추고는 있지만, 장애는 데이타베이스에 대한 접속을 해제시킴으로써 사용자들에게 매우 심한 혼란을 줄 수도 있습니다. 사용자들은 가끔 그들의 애플리케이션을 재시작하여, 데이타베이스에 재접속해야만 합니다. 그리고, 장애가 발생했을 때 진행 중인 작업들은 잃어버리기가 가장 쉽습니다. Oracle8 은 명백한 애플리케이션 페일오버를 통해 장애를 차단(mask) 하여 대부분의 사용자들을 위해 이러한 혼란을 제거할 수가 있습니다.
데이타베이스 접속들은 stateless이기 때문에 Transparent Application Failover의 구현은 매우 복잡합니다. 데이타베이스 접속은 다음과 같이 다양한 변수들을 포함하고 있습니다:
패스워드와 다른 사용자-인증 정보
접속이 사용하는 언어와 문자 집합 등과 같이 환경적 상태의 암시적 또는 명시적 집합
오픈되었거나 현재 실행 중인 커서들의 집합
오픈되었거나 부분적으로 스크롤되는 “SELECT” 커서들의 집합
사용자가 현재 실행 중인 트랜잭션에 의해 만들어진 수정
Oracle8의 애플리케이션 페일오버-기능은 다양한 작업들을 명확하게 자동 수행하고 있습니다. Oracle8은 사용자-당 기준으로 지정될 수 있는 잔존 노드의 사용자들을 재접속한 다음 재인증을 하는데, 모든 암시적 그리고 명시적 환경 상태를 재전송할 수가 있습니다. 또한 장애 시 운영되는 모든 커서들을 오픈하여 실행하는 것도 가능합니다. 이 기능은 Oracle Fail Safe, Oracle Parallel Server, 또는 협력 업체 클러스터 제품 등과 같이 페일오버를 실행하는 클러스터 환경에서 가장 잘 동작하지만, 단일-노드 (이 경우에는 복구 동안 사용자 지연이 매우 길 수 있습니다) 또는 복제된 대기 데이타베이스와 함께 동작하도록 구성될 수도 있습니다. 아래 그림은 Oracle Parallel Server 와 TAF 의 일반적인 구성을 보여주고 있습니다.
만약 사용자 또는 애플리케이션이 SELECT 문의 중간에 위치하고 있었다면, 결과 집합의 일부는 장애 전에 반환되었을 것입니다. Oracle은 자동적으로 SELECT 문을 재실행하여 이전에 반환되었던 행들은 모두 폐기한 다음, 그것이 중단되었던 순간 부터 계속적으로 결과 집합을 반환할 것입니다. 선택된 데이타는 고장 시간과 재접속 시간 사이에 변경될 수도 있기 때문에, 사용자가 이미 확인한 데이타는 변경되지 않도록 검증되어야만 합니다. Oracle8은 이 작업을 자동적으로 수행하고 있습니다. 또한, Oracle8의 다중-버전, 읽기-지속성 (read-consistency) 기능, 그리고 원본 시간 기록 (original time stamp) 을 사용하는 데이타 읽기 등을 사용하여, 이 검증은 거의 항상 성공적으로 수행되고 있습니다..
TAF 가 보호하거나 또는 페일-오버합니다:
- 클라이언트/서버 접속
- 사용자 세션 상태
- 준비된 명령문
- 결과들의 반환을 시작하는 활성화된 커서들 (SELECT 문)
- 활성화된 업데이트 트랜잭션들
- PL/SQL 서버-사이드 패키지 변수들
- OCI8 를 사용하지 않는 애플리케이션들 (테이블 참조)
또한 Oracle8 애플리케이션-페일오버 기능은 사용자들을 두 번째 노드로 사전-접속시킬 수가 있습니다. 만약 기본 노드가 죽는다면, 두 번째 접속은 재접속 기간에 네트워크 보틀넥을 통과하여 이미 그 역할을 담당하고 있을 것입니다.
이 작업들이 완료된 후에, 데이타베이스 사용자들은 데이타베이스에 무슨 일이 발생하는지 상관하지 않고, 계속적으로 그들의 현재 애플리케이션을 실행할 것입니다. 이 자동 페일오버 기능은 Oracle8 Program Interfaces(OCI8) 내부에 구축되어 있어서, 그것으로부터 이점을 얻기 위해 애플리케이션들을 변경할 필요는 거의 없습니다. 그러나, 기존의 애플리케이션들은 업데이트가 되어야만 TAF를 지원하는 OCI8을 활용할 수가 있습니다. 다음 테이블은 TAF의 지원 상태을 보여주고 있습니다.
| 애플리케이션 또는 툴 | TAF를 지원하는 최초 릴리스 |
| Oracle Call Interface (Oracle Call Interface) Release 8 | Release 8 |
| ODBC 드라이버 | Release 8.0.5 |
| JDBC 드라이버 | Release 8.1.5 (Thick 드라이버만) |
| Pro* Pre-compilers | 명백한 재접속을 위한 Release 8.0.6완전히 명백한 기능을 위한Release 8.1.5 |
| SQL*Plus | Release 8.0.4 |
| Oracle Objects?for OLE | Release 8i |
TAF의 사용자 정의
클라이언트 애플리케이션은 OCI 라이브러리가 페일오버 과정 동안 자동적으로 호출할 수 있는 함수를 제공하여, TAF 를 사용자 정의할 수 있는데, 이것은 페일오버가 현재 진행 중이고 지연이 있을 수 있다는 것을 사용자에게 통보하기 위해서 사용되고 있습니다. 또한 NLS 지원과 같은 사용자 세션 환경 정보의 복원을 위해서도 사용될 수가 있습니다.TAF의 다른 사용
Transparent Application Failover는 다른 사용 방법도 갖고 있습니다. 관리자는 계획된 다운타임 시 시스템을 유연하게 종료하기 위해 TAF를 사용할 수 있고, 트랜잭션이 완료되자마자 사용자들을 백업 시스템으로 페일오버할 수 있도록 시스템을 구성할 수도 있는데, 이것은 데이타의 손실이 없다는 것을 확실히 하면서, 백업 시스템으로의 이전(migration)을 최종 사용자에게 투명하게 할 수도 있습니다.또한 TAF는 수동적으로 시스템을 로드 밸런싱하기 위해서도 사용될 수 있는데, 관리자는 현재 트랜잭션이 종료되자마자 사용자들이 다량으로 로드되는 시스템에서 다른 시스템으로 투명하게 자동 이전할 수 있도록 사용자들의 일정을 잡을 수가 있습니다.
TAF 구성
TAF 는 tnsnames.ora 파일의 Net8 매개변수를 사용하여 구성이 됩니다.TAF 는 커넥트 디스크립터의 CONNECT_DATA 섹션에 FAILOVER_MODE 매개변수를 포함하여 구성할 수 있습니다. 다음은 TAF 가 지원하는 하위-매개변수들입니다:
TYPE: 이것은 NONE, SESSION, 또는 SELECT 중 하나가 사용됩니다. 사용 해제을 위해서는 TYPE=SESSION 로 설정하고, 세션과 오픈 커서의 페일오버를 위해서는 TYPE=SELECT 로 설정하십시오. 그리고 TAF 를 해제하기 위해서는 TYPE=NONE 으로 설정하시면 됩니다.
METHOD: 이것은 BASIC 또는 PRECONNECT 중 하나가 사용됩니다. BASIC 방식을 사용하면, 기존 접속이 실패할 때까지, TAF 는 접속의 재설정을 시도하지 않을 것입니다. PRECONNECT 방식을 사용하면, TAF 는 백업 접속을 위해 필요한 메모리 구조를 사전-설정하지만, 기존 접속이 실패할 때까지 백업 접속은 활성화되지 않을 것입니다.
BACKUP: 이 하위-매개변수는 백업 접속의 설정을 위해 사용되는 네트 서비스 이름을 지정합니다. BACKUP 지정은 PRECONNECT 방식을 사용할 때 필요한데, BASIC 방식에서 강력하게 추천되고 있습니다; 그렇지 않다면, 클라이언트가 재접속을 할 때까지 추가적으로 지연을 시키면서 방금 실패한 인스턴스에 최초로 재접속을 시도할 것입니다. 그러나, 사용자는 LOAD_BALANCING=ON 인 상태에서는 BACKUP 을 지정할 수가 없습니다.
DELAY: TAF 가 장애 후에 BACKUP 에 연결하려는 시도 사이에서 기다리는 몇 초간의 지연 시간입니다.
RETRIES: 포기하기 전, TAF 가 장애 후에 BACKUP 에 연결하기 위해 시도하는 횟수입니다. RETRIES 와 DELAY 는 TAF 가 백업 접속을 포기하기 전에 콜드 페일오버가 완료될 수 있는 시간을 갖게 해줍니다.
Oracle9i Real Application Clusters의 페일오버 기능 - CTF vs TAF
아마도 독자들이 Oracle9i Real Application Clusters를 도입하는 가장 큰 이유는 Oracle9i Real Application Clusters가 고가용성(HA)을 보장해 주기 때문일 것인데, 실제로 HA를 구현하기 위해서 가장 중요한 부분이 애플리케이션 페일오버(application failover)이다. 애플리케이션 페일오버를 간단히 설명하면, Oracle9i Real Application Clusters 환경에서 하나의 노드에 장애가 발생했을 때 애플리케이션은 나머지 살아 있는 노드들 중의 하나로부터 서비스를 받게 되므로, 엔드유저들은 장애가 발생했는지조차 모르는 것이다. 이것이 바로 오라클에서 Oracle9i Real Application Clusters를 "난공불락(Unbreakable)"이라고 내세우는 이유이다.
최근에는 HA에 대한 관심이 높아지면서 플랫폼 벤더에서도 여러 가지 HA 솔루션 - 이를테면, IP 스위치오버 - 을 제공한다. Oracle9i Real Application Clusters 사용 고객이 이러한 플랫폼 솔루션을 훌륭하게 함께 사용하는 경우도 많이 있지만, 이번 호에서는 혼돈을 피하기 위해 100% 오라클이 제공하는 솔루션만을 다루도록 한다.
Oracle9i Real Application Clusters에서 제공하는 페일오버 기능에는 CTF(Connection Time Failover)와 TAF(Transparent Application Failover)가 있다. 이 두 가지 기능 모두는 Oracle8i Parallel Server에서부터 제공되던 기능이다. 그런데, 많은 고객들이 CTF와 TAF가 상호 배타적인 기능이라고 여기는 것 같은데, TAF는 CTF 기능 위의 부가적인 기능이다. 따라서, CTF를 사용할 것인지 TAF를 사용할 것인지 고민할 필요 없이 TAF를 사용할 것인지 아닌지의 여부만 선택하면 된다.
이 글의 기반이 되는 필자의 테스트 환경은 다음과 같다. 앞으로 이어지는 테스트에서는 항상 이전의 설정을 기본으로 변경사항만 추가하도록 하겠다.
데이타베이스 서버 ‥ 2노드의 Oracle9i Real Application Clusters
운영체제 : Sun Solaris 8
오라클 데이타베이스 버전 : Oracle9i Database Release 2(9.2.0.3) 64비트
호스트명(공용 네트워크) : krrac1, krrac2
ORACLE_SID : RAC1(krrac1), RAC2(krrac2)
init.ora 파라미터
local_listener=(지정 안함) remote_listener=(지정 안함) service_names=(지정 안함) db_name="rac" rac1.instance_name="rac1" rac2.instance_name="rac2"
listener.ora(krrac1. krrac2는 HOST와 SID_NAME만 각각 krrac2, RAC2이며 나머지는 동일)
LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = krrac1) (PORT = 1521)) ) ) ) SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (ORACLE_HOME = /data/oracle) (SID_NAME = RAC1) ) )
Oracle*Net 클라이언트(SQL*Plus)
운영체제 : Windows 2000 Professional
오라클 클라이언트 버전 : Oracle9i Database Release 2(9.2.0.3)
tnsnames.ora
RAC1 = (description= (address=(protocol=tcp)(host=krrac1)(port=1521)) (connect_data= (service_name=RAC)) )
자, 이제 클라이언트에서 rac1이라는 TNS alias로 RAC1에 접속할 수 있다면 모든 준비가 되었다. RAC1 인스턴스에 접속되었는지 확인해 보도록 하자<리스트 1>.
<리스트 1> RAC1 인스턴스의 접속 확인
$ sqlplus scott/tiger@rac1 SQL*Plus: Release 9.2.0.3.0 - Production on 화 Jul 8 12:20:46 2003 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved. 다음에 접속됨: Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production With the Partitioning, Real Application Clusters, OLAP and Oracle Data Mining options JServer Release 9.2.0.3.0 - Production 세션이 변경되었습니다. SQL> select userenv("instance") from dual; USERENV("INSTANCE") -------------------------- 1
그러면, 이제 각각의 기능이 어떻게 작동하는지 자세히 알아보도록 하자.
CTF
CTF(Connection Time Failover)는 가장 기본적인 페일오버 설정 방법으로, 클라이언트의 TNS alias만 아래와 같이 수정해주면 된다.
RAC_CTF = (description= (load_balance=off) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) ) )
로드 밸런싱(load balancing)은 이후에 별도로 자세히 설명하므로, 일단 여기서는 로드 밸런싱에 대한 언급은 하지 않기로 하자. 이와 같이 설정한다면, 일단 RAC_CTF를 사용하여 접속하는 클라이언트들은
krrac1의 1521 포트로 기동된 리스너를 컨택한다.
그 리스너가 RAC라는 서비스를 하고 있다면 RAC라는 서비스에 등록되어 있는 인스턴스에 접속한다.
만일 1에서 리스너가 응답이 없거나, 2에서 RAC라는 서비스가 없다면, 다음에 지정된 어드레스인 krrac2의 1521 포트로 기동된 리스너를 컨택한다. 여기서 리스너가 응답이 없다면 에러를 리턴하며 접속을 하지 못하게 된다.
2와 마찬가지 과정으로 RAC 서비스에 등록되어 있는 인스턴스를 접속한다. 여기서 RAC 서비스를 찾을 수 없다면, 마찬가지로 에러를 리턴하며 접속을 하지 못하게 된다.
따라서, 새로이 접속하는 클라이언트에 대하여는 krrac1에서 어떤 장애가 일어나더라도 문제없이 krrac2로 페일오버가 된다. 그렇다면, 기존에 이미 접속되어 있던 클라이언트들은 어떻게 될까? 리스너에만 장애가 있다면 당연히 영향이 없을 것이지만, 만일 인스턴스가 중단되었다면? 물론 클라이언트는 ORA-3113/3114 에러를 만나게 되며 기존의 접속 상태가 끊어지고, 새로 접속하면 위와 같은 과정을 거쳐 krrac2로 접속하게 된다. 실행되고 있던 DML은 살아 있는 RAC2 인스턴스에서 트랜잭션 복구를 수행하여 모두 롤백된다.
여기서 각 호스트의 리스너가 어떤 포트로 기동되어 있는지, 어떤 서비스에 어떤 인스턴스가 등록되어 있는지는 <리스트 2>와 같은 방법으로 알 수 있다.
<리스트 2> 각 호스트의 리스너 포트와 서비스별 인스턴스 등록 확인
krrac1|/data/oracle> lsnrctl services LSNRCTL for Solaris: Version 9.2.0.3.0 - Production on 08-JUL-2003 14:09:47 Copyright (c) 1991, 2002, Oracle Corporation. All rights reserved. Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=krrac1)(PORT=1521))) Services Summary... Service "RAC" has 1 instance(s). Instance "RAC1", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:1 refused:0 state:ready LOCAL SERVER Service "RAC1" has 1 instance(s). Instance "RAC1", status UNKNOWN, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:0 refused:0 LOCAL SERVER The command completed successfully
여기서 독자들은 뭔가 좀 특이한 점을 볼 수 있을 것이다. 현재 리스너에는 RAC와 RAC1 두 가지 서비스가 등록되어 있으며, 각각의 서비스에는 RAC1 인스턴스가 등록되어 있다. 그렇다면, 왜 두 가지 서비스가 등록되어 있을까? 이것을 이해하기 위해서는 Oracle8i Database에서 도입된 동적 등록(dynamic registration) 방식에 대하여 우선 알아야 한다. 위에서 status 항목을 자세히 살펴보면, RAC 서비스에 대하여는 READY인 반면 RAC1 서비스에 대하여는 UNKNOWN으로 나와 있는 것을 볼 수 있다. 즉, 리스너는 현재 RAC 서비스를 통해서는 RAC1 인스턴스가 살아 있다(READY)는 사실을 알 수 있지만, RAC1 서비스를 통해서는 RAC1 인스턴스의 상태를 알 수 없다(UNKNOWN). 여기서 한 번 RAC1 인스턴스를 셧다운한 후 똑 같은 테스트를 해 보자<리스트 3>.
<리스트 3> RAC1 인스턴스의 셧다운 후 테스트 실행
krrac1|/data/oracle> lsnrctl services listener_rac1 LSNRCTL for Solaris: Version 9.2.0.3.0 - Production on 08-JUL-2003 14:25:51 Copyright (c) 1991, 2002, Oracle Corporation. All rights reserved. Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=krrac1)(PORT=1521))) Services Summary... Service "RAC1" has 1 instance(s). Instance "RAC1", status UNKNOWN, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:0 refused:0 LOCAL SERVER The command completed successfully
그러면, RAC 서비스가 사라진 것을 볼 수 있다. 반면에, RAC1 서비스는 여전히 RAC1 인스턴스의 상태를 모르고 있다. 즉, RAC 서비스는 동적으로 등록된 서비스이며, 이러한 서비스에 등록된 인스턴스는 지속적으로 자신의 상태를 리스너에 업데이트시켜 준다.
동적 등록을 구성하는 방법은 다음과 같다.
SERVICE_NAMES 파라미터 값을 설정해 준다. 이것이 바로 리스너에 등록하는 서비스의 이름으로, 디폴트 값은 db_name.db_domain이 된다. 즉, 예제에서는 db_domain 값이 설정되어 있지 않으므로 그냥 rac가 된다. 주의할 점은 service_names를 임의로 설정하는 경우 Oracle9i Real Application Clusters 데이타베이스 내의 페일오버 설정에 참여하는 모든 인스턴스들은 같은 값으로 설정해야 한다는 것이다. 앞서 설명한 RAC_CTF TNS alias를 보면 service_name 값을 RAC로 사용하여 서로 다른 리스너 어드레스를 등록한 것을 확인할 수 있다.
LOCAL_LISTENER 파라미터 값을 설정해 준다. 즉 어느 리스너에 자신의 상태를 업데이트시켜 줄 것인지를 정해주는 부분으로, 디폴트 값은 (ADDRESS = (PROTOCOL=TCP)(HOST=)(PORT=1521))이 된다. 즉, 로컬 호스트의 1521 포트로 기동되는 리스너에 대하여는 LOCAL_LISTENER 값을 설정하지 않더라도 자동으로 동적 등록이 설정된다.
이 예제에서는 SERVICE_NAMES, LOCAL_LISTENER 값을 둘 다 설정하지 않았지만 lsnrctl services에서 동적 등록이 되어 있는 것을 확인할 수 있다. 모두 디폴트 값을 취했기 때문이다. 즉, db_name을 SERVICE_NAMES로 사용하고, 1521 포트의 리스너를 사용하고자 한다면 이 예제와 같이 아무것도 설정할 필요가 없다.
LOCAL_LISTENER를 설정할 때는 디폴트 값처럼 TNS alias 구문으로 설정해 줄 수도 있지만 다른 방법도 있다. 서버의(클라이언트가 아니라 서버임에 유의하자) tnsnames.ora 파일에 다음과 같은 항목을 추가해 보자.
LISTENER_RAC1 = (description= (address=(protocol=tcp)(host=krrac1)(port=1521)) )
그리고 init.ora 파일에는 LOCAL_LISTENER=LISTENER_RAC1라고 지정해 보자. lsnrctl services에서와 마찬가지로 동적 등록이 이루어진 것을 확인할 수 있다. 즉, 클라이언트에서 tnsnames.ora 파일에 TNS alias를 설정하여 사용하듯이, 서버에서도 LOCAL_LISTENER에 대하여 TNS alias를 설정하여 사용할 수 있다. 물론 여기서 설정하는 값은 listener.ora 파일에 지정하는 리스너 이름과는 아무런 상관이 없다. Oracle9i Database Release 2(V9.2)부터는 LOCAL_LISTENER 값을 인스턴스가 기동중인 상태에서도 아래와 같이 동적으로 바꿀 수 있다.
SQL>alter system set local_listener="LISTENER_RAC1"; System altered.
이 때 물론 서버의 tnsnames.ora에는 LISTENER_RAC1이라는 항목이 미리 지정되어 있어야 하며, 만일 이것이 없을 때에는 오라클 에러가 나타나며 실패하게 된다. 또한 SPFile(Server Parameter File)이 아닌 텍스트 방식의 init.ora를 사용하는 경우 인스턴스를 내렸다 올리면 위 설정은 없어지고 원래대로 돌아가므로, 설정을 지속시키려면 init.ora 파일에 값을 명시적으로 설정해야 한다.
자, 그럼 여기서 한 가지 의문이 생길 수 있다. "그렇다면, 정적으로 등록된 서비스(즉 위 예제에서 status UNKNOWN으로 등록된 RAC1 서비스)는?" 결론적으로 말하면, 동적 등록 방식이 정적 등록 방식을 오버라이드(override)하게 되므로 정적 등록 방식은 다음과 같이 listener.ora 파일을 수정하여 제거하는 것이 좋다.
LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = krrac1) (PORT = 1521)) ) ) )
여기서 또 한 가지. 바로 위에 설정한 내용이 listener.ora의 디폴트 값이다. 결국 listener.ora 파일에 아무런 내용이 없어도 된다는 이야기가 된다. 사실 1521 포트를 사용하면서 LISTENER라는 이름의 리스너를 사용한다면 실제로 listener.ora 파일 자체가 필요가 없다. lsnrctl 명령어는 디폴트로 LISTENER라는 이름의 리스너에 대하여 수행되므로, 시동/중단하는 데에도 아무런 지장이 없다. 한 번 listener.ora 파일을 제거하고 테스트해 보도록 하자. 여태까지와 마찬가지 결과를 얻을 것이다.
물론, LISTENER라는 이름이 아닌 다른 이름의 리스너를 사용하고자 한다면 1521 포트를 사용하더라도 이름을 지정하기 위해서 listener.ora 파일이 필요하다. 이미 눈치 빠른 독자들은 알아챘겠지만, 동적 등록 방식이 우수하므로 앞으로는 항상 동적 등록 방식을 쓰도록 하자는 것이다. 동적 등록 방식은 세 가지 측면에서 과거의 정적 등록 방식보다 우수하다.
주기적으로 인스턴스가 리스너에 자신의 상태를 알려주므로, CTF 설정에서 인스턴스에 장애가 있을 경우 앞서 설정한 페일오버 과정에서 장애가 발생한 인스턴스에 접속을 시도하는 오버헤드가 없다. 즉, 리스너까지만 가면 인스턴스의 상태를 알 수 있다.
Oracle9i Database의 새로운 기능인 리스너(커넥션) 로드 밸런싱을 사용할 수 있다.
TAF를 사용할 수 있다.
그렇다면 TAF란 무엇인가? 이것에 대해 자세히 알아보도록 하자.
TAF
앞서 잠깐 설명한 대로 CTF의 약점은 기존에 접속되어 있던 클라이언트들에 대해서는 다시 한 번 접속을 시도해야 페일오버가 이루어진다는 점이다. 바로 이것을 극복할 수 있는 솔루션이 바로 TAF(Transparent Application Failover)이다. 앞서 설정한 클라이언트쪽의 TNS alias를 다음과 같이 수정해 보자. 물론 리스너에는 반드시 인스턴스가 동적으로 등록되어 있어야 한다.
RAC_TAF = (description= (load_balance=off) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) (failover_mode=(type=select)(method=basic)) ) )
CTF와 비교하여 다른 점은 failover_mode라는 항목이 설정되었다는 점이다. 이것을 설정하는 방법을 설명하기에 앞서, 일단 위와 같이 TAF가 설정되어 있는 경우에 이미 접속되어 있던 클라이언트들은 장애상황에서 어떻게 되는지부터 알아보자. 예를 들어, 클라이언트에서 100건의 레코드를 리턴하는 Select를 수행중이었고, 현재 50건이 Fetch된 상태에서 RAC1 인스턴스가 중단되었다고 가정한다면,
살아 있는 인스턴스인 RAC2에서 새로이 Oracle Server Process (Foreground Process)를 띄운다.
이렇게 새로이 만들어진 프로세스로 50건을 Fetch하던 클라이언트의 세션 정보가 옮겨진다. 이때 복구되는 내용은 다음과 같다.
SQL을 실행하던 사용자의 로그인 정보
실행하던 SQL 명령어
Fetch하던 커서
서버측 프로그램 변수(예를 들면, PL/SQL package states)
이렇게 세션 정보가 복구되면 수행되던 Select 문장이 Fetch하던 나머지 50건이 Fetch된다. 따라서 클라이언트는 RAC1 인스턴스에 장애가 발생했다는 사실을 전혀 모르게 된다(Transparent Application). 물론 2번 과정에서 세션 정보를 복구하는 과정이 있으므로 50건 Fetch 후 다시 나머지 50건의 Fetch가 시작되기까지 약간 지연될 수 있다.
TAF와 CTF의 가장 큰 차이점은 재접속이 필요 없다는 점이다. 앞서 CTF 방식에서는 접속되어 있던 클라이언트들은 장애 발생 시 일단 ORA-3113/3114 에러를 만나게 되며, 재접속을 해야 페일오버가 되었다는 점을 기억하자. 이 때 기존에 접속되어 있던 클라이언트와 별도로 새로 접속하는 클라이언트들은 앞서 설명한 CTF가 작동하여 페일오버가 이루어진다.
그러면, failover_mode의 각각의 항목에 대하여 알아보도록 하자.
TYPE
SESSION : 이 방식은 재접속은 필요 없지만, 예제와는 달리 Select가 Fetch하던 도중에 장애를 만나면 Fetch가 도중에 실패하게 된다. 데이타 웨어하우스와 같이 다량의 레코드를 한 번에 Select하는 경우가 아니라면 이 방식을 쓰는 것이 좋다.
SELECT : 예제와 같은 방식으로, 재접속도 필요 없을 뿐더러 Fetch하던 레코드까지 복구해 준다. 다량의 레코드를 Select하는 애플리케이션에 적절한 방법이다.
NONE : 디폴트 값으로 TAF가 작동하지 않는다.
METHOD
BASIC : On-demand 방식으로 페일오버가 필요할 때 살아있는 인스턴스쪽으로 Oracle Server Process를 기동시킨다.
PRECONNECT : 페일오버가 이루어질 인스턴스에 미리 Oracle Server Process를 기동시켜 페일오버시 발생하는 오버헤드를 미리 줄여 놓는 방식이다. 물론 페일오버 속도는 향상되지만, 자원을 낭비할 수 있다.
그렇다면, 여기서 또 의문점이 생긴다. PRECONNECT 메소드를 사용하는 경우 미리 어느 인스턴스로 페일오버할지를 결정해 놓아야 한다. 다음과 같이 클라이언트의 tnsnames.ora를 수정해 보자.
RAC_TAF = (description= (load_balance=off) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) (failover_mode=(type=select)(method=preconnect)(backup=RAC2)) ) ) RAC2 = (description= (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC)) )
즉 PRECONNECT 메소드를 사용하는 경우에는 반드시 backup이라는 항목이 함께 설정되어야 한다. 이 때 backup에는 TNS alias로 지정하며, 여기에 지정할 TNS alias는 반드시 미리 설정되어 있어야 한다(예제에서는 RAC2). 특히, 이 때 지정한 TNS alias가 올바로 설정된 값이 아니어도 접속할 때 따로 에러를 리턴하지 않는다는 점에 주의하자.
물론, 이 예제에서는 2노드 환경이므로 한 쪽에서 장애가 발생하면 나머지 살아 있는 쪽은 하나밖에 없으므로 왜 이것이 필요할까 궁금해 하는 독자들이 있을 것이다. 위와 같이 설정해 놓고 RAC_TAF라는 TNS alias로 접속한 후 <리스트 4>와 같이 SQL을 실행해 보자. 참고로 <리스트 4>의 SQL은 PRECONNECT 메소드뿐만 아니라 모든 경우의 TAF 설정이 제대로 작동하는지 알아보기 위해서 사용할 수 있다.
<리스트 4> TAF 설정이 올바로 되었는지 확인하는 SQL
SQL> SELECT 2 INST_ID, SID, MACHINE, FAILOVER_TYPE, FAILOVER_METHOD, FAILED_OVER
3 FROM GV$SESSION 4 WHERE USERNAME="SYSTEM" 5 AND PROGRAM NOT LIKE "%(P%";
INST_ID SID MACHINE FAILOVER FAILOVER_METHOD FAILED
1 20 ORACLE\leesb-kr SELECT PRECONNECT NO
2 19 ORACLE\leesb-kr NONE NONE NO
이렇게 PRECONNECT 메소드를 사용하는 경우에는 최초에 접속하는 주(primary) 인스턴스와 백업 인스턴스에 동시에 접속한다는 것을 알 수 있다. 이때 RAC1 인스턴스를 셧다운시키면 다음과 같은 결과가 나와야 한다.
INST_ID SID MACHINE FAILOVER FAILOVER_METHOD FAILED
2 19 ORACLE\leesb-kr SELECT PRECONNECT YES
여기서 주목해야 할 점은 PRECONNECT 메소드를 사용할 때는 항상 주 인스턴스와 백업 인스턴스를 지정해야 하므로 로드 밸런싱을 할 수 없다는 사실이다. 따라서 PRECONNECT 메소드는 각 인스턴스에 접속하는 클라이언트가 업무별/지역별로 분할되어 있는 경우에 사용할 수 있다.
페일오버 테스트 시나리오
자, 이렇게 페일오버를 설정했다면 실제로 장애 상황별로 제대로 작동하는지 살펴보아야 할 것이다. 보통 페일오버 테스트에서 고려하는 장애 상황은 다음과 같다.
오라클 인스턴스 장애 : 간단하게 shutdown abort 명령으로 인스턴스 장애 상황을 만들 수 있다.
노드 장애(네트워크 장애) : 클라이언트 입장에서는 노드 자체가 장애가 있는 경우(reboot 또는 halt 명령)나 네트워크 장애가 있는 경우(네트워크 디바이스의 셧다운 또는 네트워크 라인의 unplug)나 같은 상황이므로 똑 같은 결과가 나와야 한다.
리스너 장애 : 간단하게 리스너 프로세스를 중단하든지 lsnrctl stop으로 셧다운해서 장애 상황을 만들 수 있다. 인스턴스는 살아 있으면서 리스너만 장애가 있는 경우에는 항상 기존에 접속되어 있던 클라이언트는 페일오버 설정과 관계 없이 아무런 문제가 없어야 하며, 오직 새로이 접속하는 클라이언트만 페일오버가 이루어진다는 점을 기억하자.
이 세 가지 장애 상황에서 페일오버가 모두 정상적으로 이루어진다면 설정이 성공적으로 이루어졌다고 볼 수 있다.
마지막으로 클라이언트 페일오버를 테스트할 때, 오라클 인터커넥트(Oracle9i Real Application Clusters 노드 간의 개별 네트워크)에 대한 장애는 클라이언트 페일오버와 전혀 상관이 없으며, 이 인터커넥트의 장애 발생시에는 인스턴스의 장애가 당연히 뒤따르므로 이를 고려하여 테스트하여야 한다. 오라클 인터커넥트의 장애 발생시 어떤 결과가 일어나는가에 대해서는 본지 2003년 봄호에 실린 필자의 "Oracle9i Real Application Clusters - Cache Fusion의 작동 원리와 활용"을 참고하기 바란다.
클라이언트 로드 밸런싱 vs 리스너 로드 밸런싱
자, 이제는 Oracle9i Real Application Clusters의 로드 밸런싱에 대해 알아보도록 하자. "로드 밸런싱(load balancing)"이란 말 그대로 공평하게 부하(load)를 나누어 준다는 뜻이다. 즉, 100개의 클라이언트가 있다면 2노드 Oracle9i Real Application Clusters 환경에서는 각 인스턴스별로 50개씩 똑같이 나누어 주고 싶을 것이고, 차후에 다시 50개의 새로운 클라이언트가 접속한다면 다시 각각 25개씩 나누어 주어 언제나 공평하게 부하를 유지하고 싶을 것이다.
하지만, 이제까지의 예제와 같이 TNS alias에서 load_balance=off로 설정한 경우에는 어느 클라이언트가 어느 인스턴스로 접속할지 수동으로 모두 설정해 주어야 하는 번거로움이 있었다. 만일 이것이 자동으로 된다면? 즉, 100개의 클라이언트가 무작위로 접속할 때 자동으로 50개씩 나누어진다면 훨씬 더 편리할 것이다.
사실 Oracle9i Real Application Clusters가 나오기 이전에 Oracle Parallel Server를 사용하는 고객들은 로드 밸런싱을 잘 사용하지 못했다고 할 수 있다. 왜냐하면, Oracle Parallel Server에서 발생하는 핑(ping) 현상으로 인해 보통 불가피하게 노드별로 데이타를 분할하여 데이타베이스를 구성하므로, 업무별/지역별로 클라이언트를 나누어 특정 인스턴스를 지정해서 접속해야 했기 때문이다.
하지만, Oracle9i Real Application Clusters에서 완벽하게 이루어지는 Cache Fusion의 기능으로 더 이상 데이타의 분할이 필요 없게 되었으므로, 클라이언트는 Oracle9i Real Application Clusters로 구성된 데이타베이스 내의 어떤 인스턴스에 접속하더라도 성능에는 아무런 영향을 주지 않는다.
오라클의 로드 밸런싱 설정은 다음 두 가지가 있다.
클라이언트 로드 밸런싱 : 클라이언트가 같은 SERVICE_NAME을 사용하는 인스턴스들 중에 무작위로 선택하여 접속하는 방법이다.
리스너(커넥션) 로드 밸런싱 : Oracle9i Real Application Clusters의 새로운 기능으로 리스너가 각 인스턴스의 부하에 대한 정보를 가지고 있어서 항상 부하가 덜 걸린 인스턴스쪽으로 클라이언트를 접속시켜 주는 방법이다.
그러면 이제 각각의 방법을 어떻게 구현할 수 있는지 차례로 살펴보자.
클라이언트 로드 밸런싱
앞서 설정했던 BASIC 메소드의 TAF 설정을 다음과 같이 수정해 보자.
RAC_TAF = (description= (load_balance=on) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) (failover_mode=(type=select)(method=basic)) ) )
다른 것은 그대로 둔 채로 load_balance 부분만 on으로 설정하면 클라이언트 로드 밸런싱이 구성된다. 이제 반복적으로 접속하여 결과를 보도록 하자.
SQL> conn scott/tiger@rac_taf Connected. SQL> select userenv("instance") from dual; USERENV("INSTANCE") -------------------------- 2 SQL> conn scott/tiger@rac_taf Connected. SQL> select userenv("instance") from dual; USERENV("INSTANCE") -------------------------- 1
위와 같이 클라이언트는 인스턴스를 바꿔가며 RAC1, RAC2에 무작위로 접속하는 것을 볼 수 있다. 라운드로빈(round-robin) 방식이 아닌 무작위(random) 방식이므로, 특별한 패턴을 찾을 수는 없지만, 접속 횟수가 많아질수록 균일하게 분배되는 것을 알 수 있을 것이다. 즉, 예제와 같이 2노드의 Oracle9i Real Application Clusters 환경에서 100회 반복하여 접속한다면, 거의 50:50으로 나누어지는 것을 확인할 수 있을 것이다.
클라이언트 로드 밸런싱을 하는 경우 특별히 동적 등록이 필요하지는 않으나, 예제와 같이 TAF와 함께 사용하는 경우에는 반드시 동적 등록이 구성되어 있어야 한다.
리스너(커넥션) 로드 밸런싱
최초에 RAC1 인스턴스에는 100개의 클라이언트, RAC2 인스턴스에는 10개의 클라이언트가 접속되어 있었다고 가정하자. 이후에 다시 100개의 새로운 클라이언트를 클라이언트 로드 밸런싱을 설정하여 접속시킨다면? 결과는 새로운 100개의 클라이언트들은 50:50으로 균일하게 분배되어 결국 최종적으로는 RAC1 인스턴스는 150개, RAC2 인스턴스에는 60개의 클라이언트가 붙을 것이다.
이렇게 되면 RAC1 인스턴스 입장에서는 불공평한 결과가 될 수 있다. 즉 RAC2 인스턴스가 훨씬 더 여유가 있다는 것을 미리 알아서, 새로이 들어오는 100개의 클라이언트를 5:95로 분배하여 결국 105:105가 되도록 해준다면 각각의 인스턴스 입장에서는 언제나 공평한 결과를 만들 수 있다.
바로 이것이 Oracle9i Real Application Clusters에서 새로 등장한 리스너(커넥션) 로드 밸런싱이며, 이것을 위해서는 서버쪽의 tnsnames.ora와 init.ora에 다음과 같은 설정을 추가해야 한다. krrac1의 tnsnames.ora에 다음과 같은 항목을 추가해 보자.
LISTENER_RAC2 = (description= (address=(protocol=tcp)(host=krrac2)(port=1521)) )
그리고 SYSDBA 사용자로 다음과 같이 remote_listener 파라미터 값을 변경해 보자.
SQL> alter system set remote_listener="LISTENER_RAC2"; System altered.
이제 krrac2(krrac1이 아님)의 리스너를 확인해 보자. <리스트 5>와 같이 RAC 서비스에 기존에 등록되어 있던 RAC2 인스턴스와 함께, RAC1 인스턴스도 추가로 동적으로 등록되어 있는 것을 볼 수 있다.
<리스트 5> 리스너 로드 밸런싱에서 리스너 확인
krrac2|/data/oracle> lsnrctl services LSNRCTL for Solaris: Version 9.2.0.3.0 - Production on 14-JUL-2003 15:29:55 Copyright (c) 1991, 2002, Oracle Corporation. All rights reserved. Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=krrac2)(PORT=1521))) Services Summary... Service "RAC" has 2 instance(s). Instance "RAC1", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:0 refused:0 state:ready REMOTE SERVER (DESCRIPTION=(address=(protocol=tcp)(host=krrac1)(port=1521))) Instance "RAC2", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:25 refused:0 state:ready LOCAL SERVER The command completed successfully
그러면, 이제 krrac2의 tnsnames.ora, RAC2 인스턴스의 remote_listener 값을 마찬가지로 올바르게 설정했다면 krrac1의 리스너에도 <리스트 6>과 같이 RAC1, RAC2 인스턴스가 모두 등록되어 있는 것을 확인할 수 있을 것이다.
<리스트 6> 리스너 로드 밸런싱에서 인스턴스의 등록 확인
krrac1|/data/oracle> lsnrctl services LSNRCTL for Solaris: Version 9.2.0.3.0 - Production on 14-JUL-2003 15:33:52 Copyright (c) 1991, 2002, Oracle Corporation. All rights reserved. Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=krrac2)(PORT=1521))) Services Summary... Service "RAC" has 2 instance(s). Instance "RAC1", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:0 refused:0 state:ready REMOTE SERVER (DESCRIPTION=(address=(protocol=tcp)(host=krrac1)(port=1521))) Instance "RAC2", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:25 refused:0 state:ready LOCAL SERVER The command completed successfully
앞서 설명한 동적 등록이 무엇을 의미하는가를 기억한다면, 여기서 각각의 인스턴스가 remote_listener 파라미터를 사용함으로써 상대방 노드의 리스너에 자신의 상태를 지속적으로 업데이트해 주고 있다는 것을 눈치챌 수 있다.
즉, 예제와 같이 리스너(커넥션) 로드 밸런싱을 사용하는 경우 클라이언트는 접속할 때 다음과 같은 과정을 거치게 된다.
클라이언트는 일단 TNS alias의 address_list의 첫 번째 항목인 krrac1의 1521 포트로 기동된 리스너에 컨택한다.
krrac1의 리스너는 RAC1, RAC2 인스턴스가 모두 등록되어 있으며, 각 인스턴스는 자신이 서비스가 가능한 상태인지의 여부와, 자신이 가지고 있는 부하(접속해 있는 클라이언트의 개수)를 지속적으로 업데이트해 주고 있으므로, 1에서 리스너에 컨택한 클라이언트는 RAC1과 RAC2 중 어느 것이 적은 수의 클라이언트를 가지고 있는지 알 수 있다.
만일 RAC1의 클라이언트 수가 적다면, 로컬 노드(krrac1)에서 Oracle Server Process(Foreground Process)를 만들어서 커넥션을 맺어 준다.
만일 RAC2의 클라이언트 수가 적다면, address_list의 다음에 나오는 항목(리모트 노드 : krrac2의 1521 포트)에 등록되어 있는 리스너로 리다이렉트시켜 준다.
krrac2의 리스너는 이제 Oracle Server Process(Foreground Process)를 만들어서 커넥션을 맺어 준다.
따라서, 리스너의 서비스에 remote_listener를 사용하여 Oracle9i Real Application Clusters의 모든 인스턴스가 등록되어 있기만 하다면, 언제나 공평한 분배(로드 밸런싱)가 이루어질 수 있다.
설정 후 테스트는 다음과 같이 해볼 수 있다.
RAC1 인스턴스에만 로드 밸런싱을 사용하지 않고 미리 상당수의 커넥션을 맺어 놓는다(이를테면 10개라고 하자.)
이제 RAC_TAF TNS alias를 가지고 반복적으로 접속해 보자. 10개까지는 RAC2에만 접속되어 각 인스턴스에 10개씩의 클라이언트가 만들어질 것이다.
이어서 지속적으로 RAC_TAF TNS alias를 가지고 반복적으로 접속해 본다면, 이제는 각 인스턴스에 거의 번갈아서 접속하면서 클라이언트 개수의 총합이 언제나 50:50으로 유지되는 것을 볼 수 있을 것이다.
마지막으로 주의할 점은, 리스너(커넥션) 로드 밸런싱은 항상 클라이언트 로드 밸런싱을 오버라이드하므로, remote_listener가 설정되어 리스너에 여러 인스턴스가 등록되어 있는 경우, 아래와 같이 TNS alias에서 클라이언트 로드 밸런싱을 사용하지 않더라도, 리스너(커넥션) 로드 밸런싱은 계속 이루어진다는 사실이다. 즉, 아래와 같이 TNS alias를 설정한 경우에 로드 밸런싱이 일어나고 있다면, 일단 remote_listener가 설정되어 있는지 우선 확인해 보도록 하자.
RAC_TAF = (description= (load_balance=off) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) (failover_mode=(type=select)(method=basic)) )
remote_listener 값은 Oracle9i Database Release 2(9.2)부터는 아래와 같이 동적으로 해제할 수 있다.
SQL> alter system set remote_listener=""; System altered.
이제까지 로드 밸런싱을 설명하면서 계속 TAF를 예로 들었지만, CTF 설정(failover_mode 부분 제고)으로도 똑 같은 로드 밸런싱을 구현할 수 있으며, 예제와 똑 같은 테스트를 할 수 있다.
난공불락의 Oracle9i Real Application Clusters
필자가 Oracle9i Real Application Clusters를 도입하고자 하는 독자들에게 하고 싶은 말은, 일단 싱글 인스턴스 환경에서 시스템을 문제 없이 사용해 왔다면, Oracle9i Real Application Clusters로의 전환 이후에도 문제 없는 시스템을 유지할 수 있으며, 나아가 오라클이 자랑하는 "난공불락 Oracle9i Real Application Clusters" 놀라운 기능을 마음껏 누릴 수 있다는 것이다.
마지막으로 필자가 3회의 연재를 통해 언급한 몇 가지 주의사항을 다시 한 번 정리하며 끝을 맺기로 한다.
싱글 인스턴스로 운영중이던 애플리케이션은 Oracle9i Real Application Clusters로 전환 시 Cache Fusion이 완벽하게 작동하므로 별도의 데이타 분할이 필요 없다.
Oracle9i Real Application Clusters에서 고속 인터커넥트는 Cache Fusion을 위해 필수이며, HA로 구성되어 있는 것이 바람직하다.
싱글 인스턴스 환경에서 튜닝 되지 않은 SQL들은 Oracle9i Real Application Clusters 환경에서 더욱 성능이 악화될 수 있으니, Oracle9i Real Application Clusters 전환 전에 애플리케이션을 반드시 점검하도록 하자.
global cache cr request가 과다하게 보이는 경우, 무엇보다도 SQL 튜닝이 되어 있는지 먼저 확인하고, 읽기 액세스가 대부분인 테이블스페이스에 한하여 gc_files_to_locks 파라미터를 신중하게 고려해 본다.
페일오버 설정은 각 장애 유형별로 철저히 테스트해 보아야 하며, 애플리케이션 분할이 되어 있지 않다면 로드 밸런싱을 하는 것이 바람직하다.
제공 : DB포탈사이트 DBguide.net
최근에는 HA에 대한 관심이 높아지면서 플랫폼 벤더에서도 여러 가지 HA 솔루션 - 이를테면, IP 스위치오버 - 을 제공한다. Oracle9i Real Application Clusters 사용 고객이 이러한 플랫폼 솔루션을 훌륭하게 함께 사용하는 경우도 많이 있지만, 이번 호에서는 혼돈을 피하기 위해 100% 오라클이 제공하는 솔루션만을 다루도록 한다.
Oracle9i Real Application Clusters에서 제공하는 페일오버 기능에는 CTF(Connection Time Failover)와 TAF(Transparent Application Failover)가 있다. 이 두 가지 기능 모두는 Oracle8i Parallel Server에서부터 제공되던 기능이다. 그런데, 많은 고객들이 CTF와 TAF가 상호 배타적인 기능이라고 여기는 것 같은데, TAF는 CTF 기능 위의 부가적인 기능이다. 따라서, CTF를 사용할 것인지 TAF를 사용할 것인지 고민할 필요 없이 TAF를 사용할 것인지 아닌지의 여부만 선택하면 된다.
이 글의 기반이 되는 필자의 테스트 환경은 다음과 같다. 앞으로 이어지는 테스트에서는 항상 이전의 설정을 기본으로 변경사항만 추가하도록 하겠다.
데이타베이스 서버 ‥ 2노드의 Oracle9i Real Application Clusters
운영체제 : Sun Solaris 8
오라클 데이타베이스 버전 : Oracle9i Database Release 2(9.2.0.3) 64비트
호스트명(공용 네트워크) : krrac1, krrac2
ORACLE_SID : RAC1(krrac1), RAC2(krrac2)
init.ora 파라미터
local_listener=(지정 안함) remote_listener=(지정 안함) service_names=(지정 안함) db_name="rac" rac1.instance_name="rac1" rac2.instance_name="rac2"
listener.ora(krrac1. krrac2는 HOST와 SID_NAME만 각각 krrac2, RAC2이며 나머지는 동일)
LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = krrac1) (PORT = 1521)) ) ) ) SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (ORACLE_HOME = /data/oracle) (SID_NAME = RAC1) ) )
Oracle*Net 클라이언트(SQL*Plus)
운영체제 : Windows 2000 Professional
오라클 클라이언트 버전 : Oracle9i Database Release 2(9.2.0.3)
tnsnames.ora
RAC1 = (description= (address=(protocol=tcp)(host=krrac1)(port=1521)) (connect_data= (service_name=RAC)) )
자, 이제 클라이언트에서 rac1이라는 TNS alias로 RAC1에 접속할 수 있다면 모든 준비가 되었다. RAC1 인스턴스에 접속되었는지 확인해 보도록 하자<리스트 1>.
<리스트 1> RAC1 인스턴스의 접속 확인
$ sqlplus scott/tiger@rac1 SQL*Plus: Release 9.2.0.3.0 - Production on 화 Jul 8 12:20:46 2003 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved. 다음에 접속됨: Oracle9i Enterprise Edition Release 9.2.0.3.0 - 64bit Production With the Partitioning, Real Application Clusters, OLAP and Oracle Data Mining options JServer Release 9.2.0.3.0 - Production 세션이 변경되었습니다. SQL> select userenv("instance") from dual; USERENV("INSTANCE") -------------------------- 1
그러면, 이제 각각의 기능이 어떻게 작동하는지 자세히 알아보도록 하자.
CTF
CTF(Connection Time Failover)는 가장 기본적인 페일오버 설정 방법으로, 클라이언트의 TNS alias만 아래와 같이 수정해주면 된다.
RAC_CTF = (description= (load_balance=off) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) ) )
로드 밸런싱(load balancing)은 이후에 별도로 자세히 설명하므로, 일단 여기서는 로드 밸런싱에 대한 언급은 하지 않기로 하자. 이와 같이 설정한다면, 일단 RAC_CTF를 사용하여 접속하는 클라이언트들은
krrac1의 1521 포트로 기동된 리스너를 컨택한다.
그 리스너가 RAC라는 서비스를 하고 있다면 RAC라는 서비스에 등록되어 있는 인스턴스에 접속한다.
만일 1에서 리스너가 응답이 없거나, 2에서 RAC라는 서비스가 없다면, 다음에 지정된 어드레스인 krrac2의 1521 포트로 기동된 리스너를 컨택한다. 여기서 리스너가 응답이 없다면 에러를 리턴하며 접속을 하지 못하게 된다.
2와 마찬가지 과정으로 RAC 서비스에 등록되어 있는 인스턴스를 접속한다. 여기서 RAC 서비스를 찾을 수 없다면, 마찬가지로 에러를 리턴하며 접속을 하지 못하게 된다.
따라서, 새로이 접속하는 클라이언트에 대하여는 krrac1에서 어떤 장애가 일어나더라도 문제없이 krrac2로 페일오버가 된다. 그렇다면, 기존에 이미 접속되어 있던 클라이언트들은 어떻게 될까? 리스너에만 장애가 있다면 당연히 영향이 없을 것이지만, 만일 인스턴스가 중단되었다면? 물론 클라이언트는 ORA-3113/3114 에러를 만나게 되며 기존의 접속 상태가 끊어지고, 새로 접속하면 위와 같은 과정을 거쳐 krrac2로 접속하게 된다. 실행되고 있던 DML은 살아 있는 RAC2 인스턴스에서 트랜잭션 복구를 수행하여 모두 롤백된다.
여기서 각 호스트의 리스너가 어떤 포트로 기동되어 있는지, 어떤 서비스에 어떤 인스턴스가 등록되어 있는지는 <리스트 2>와 같은 방법으로 알 수 있다.
<리스트 2> 각 호스트의 리스너 포트와 서비스별 인스턴스 등록 확인
krrac1|/data/oracle> lsnrctl services LSNRCTL for Solaris: Version 9.2.0.3.0 - Production on 08-JUL-2003 14:09:47 Copyright (c) 1991, 2002, Oracle Corporation. All rights reserved. Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=krrac1)(PORT=1521))) Services Summary... Service "RAC" has 1 instance(s). Instance "RAC1", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:1 refused:0 state:ready LOCAL SERVER Service "RAC1" has 1 instance(s). Instance "RAC1", status UNKNOWN, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:0 refused:0 LOCAL SERVER The command completed successfully
여기서 독자들은 뭔가 좀 특이한 점을 볼 수 있을 것이다. 현재 리스너에는 RAC와 RAC1 두 가지 서비스가 등록되어 있으며, 각각의 서비스에는 RAC1 인스턴스가 등록되어 있다. 그렇다면, 왜 두 가지 서비스가 등록되어 있을까? 이것을 이해하기 위해서는 Oracle8i Database에서 도입된 동적 등록(dynamic registration) 방식에 대하여 우선 알아야 한다. 위에서 status 항목을 자세히 살펴보면, RAC 서비스에 대하여는 READY인 반면 RAC1 서비스에 대하여는 UNKNOWN으로 나와 있는 것을 볼 수 있다. 즉, 리스너는 현재 RAC 서비스를 통해서는 RAC1 인스턴스가 살아 있다(READY)는 사실을 알 수 있지만, RAC1 서비스를 통해서는 RAC1 인스턴스의 상태를 알 수 없다(UNKNOWN). 여기서 한 번 RAC1 인스턴스를 셧다운한 후 똑 같은 테스트를 해 보자<리스트 3>.
<리스트 3> RAC1 인스턴스의 셧다운 후 테스트 실행
krrac1|/data/oracle> lsnrctl services listener_rac1 LSNRCTL for Solaris: Version 9.2.0.3.0 - Production on 08-JUL-2003 14:25:51 Copyright (c) 1991, 2002, Oracle Corporation. All rights reserved. Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=krrac1)(PORT=1521))) Services Summary... Service "RAC1" has 1 instance(s). Instance "RAC1", status UNKNOWN, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:0 refused:0 LOCAL SERVER The command completed successfully
그러면, RAC 서비스가 사라진 것을 볼 수 있다. 반면에, RAC1 서비스는 여전히 RAC1 인스턴스의 상태를 모르고 있다. 즉, RAC 서비스는 동적으로 등록된 서비스이며, 이러한 서비스에 등록된 인스턴스는 지속적으로 자신의 상태를 리스너에 업데이트시켜 준다.
동적 등록을 구성하는 방법은 다음과 같다.
SERVICE_NAMES 파라미터 값을 설정해 준다. 이것이 바로 리스너에 등록하는 서비스의 이름으로, 디폴트 값은 db_name.db_domain이 된다. 즉, 예제에서는 db_domain 값이 설정되어 있지 않으므로 그냥 rac가 된다. 주의할 점은 service_names를 임의로 설정하는 경우 Oracle9i Real Application Clusters 데이타베이스 내의 페일오버 설정에 참여하는 모든 인스턴스들은 같은 값으로 설정해야 한다는 것이다. 앞서 설명한 RAC_CTF TNS alias를 보면 service_name 값을 RAC로 사용하여 서로 다른 리스너 어드레스를 등록한 것을 확인할 수 있다.
LOCAL_LISTENER 파라미터 값을 설정해 준다. 즉 어느 리스너에 자신의 상태를 업데이트시켜 줄 것인지를 정해주는 부분으로, 디폴트 값은 (ADDRESS = (PROTOCOL=TCP)(HOST=)(PORT=1521))이 된다. 즉, 로컬 호스트의 1521 포트로 기동되는 리스너에 대하여는 LOCAL_LISTENER 값을 설정하지 않더라도 자동으로 동적 등록이 설정된다.
이 예제에서는 SERVICE_NAMES, LOCAL_LISTENER 값을 둘 다 설정하지 않았지만 lsnrctl services에서 동적 등록이 되어 있는 것을 확인할 수 있다. 모두 디폴트 값을 취했기 때문이다. 즉, db_name을 SERVICE_NAMES로 사용하고, 1521 포트의 리스너를 사용하고자 한다면 이 예제와 같이 아무것도 설정할 필요가 없다.
LOCAL_LISTENER를 설정할 때는 디폴트 값처럼 TNS alias 구문으로 설정해 줄 수도 있지만 다른 방법도 있다. 서버의(클라이언트가 아니라 서버임에 유의하자) tnsnames.ora 파일에 다음과 같은 항목을 추가해 보자.
LISTENER_RAC1 = (description= (address=(protocol=tcp)(host=krrac1)(port=1521)) )
그리고 init.ora 파일에는 LOCAL_LISTENER=LISTENER_RAC1라고 지정해 보자. lsnrctl services에서와 마찬가지로 동적 등록이 이루어진 것을 확인할 수 있다. 즉, 클라이언트에서 tnsnames.ora 파일에 TNS alias를 설정하여 사용하듯이, 서버에서도 LOCAL_LISTENER에 대하여 TNS alias를 설정하여 사용할 수 있다. 물론 여기서 설정하는 값은 listener.ora 파일에 지정하는 리스너 이름과는 아무런 상관이 없다. Oracle9i Database Release 2(V9.2)부터는 LOCAL_LISTENER 값을 인스턴스가 기동중인 상태에서도 아래와 같이 동적으로 바꿀 수 있다.
SQL>alter system set local_listener="LISTENER_RAC1"; System altered.
이 때 물론 서버의 tnsnames.ora에는 LISTENER_RAC1이라는 항목이 미리 지정되어 있어야 하며, 만일 이것이 없을 때에는 오라클 에러가 나타나며 실패하게 된다. 또한 SPFile(Server Parameter File)이 아닌 텍스트 방식의 init.ora를 사용하는 경우 인스턴스를 내렸다 올리면 위 설정은 없어지고 원래대로 돌아가므로, 설정을 지속시키려면 init.ora 파일에 값을 명시적으로 설정해야 한다.
자, 그럼 여기서 한 가지 의문이 생길 수 있다. "그렇다면, 정적으로 등록된 서비스(즉 위 예제에서 status UNKNOWN으로 등록된 RAC1 서비스)는?" 결론적으로 말하면, 동적 등록 방식이 정적 등록 방식을 오버라이드(override)하게 되므로 정적 등록 방식은 다음과 같이 listener.ora 파일을 수정하여 제거하는 것이 좋다.
LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = krrac1) (PORT = 1521)) ) ) )
여기서 또 한 가지. 바로 위에 설정한 내용이 listener.ora의 디폴트 값이다. 결국 listener.ora 파일에 아무런 내용이 없어도 된다는 이야기가 된다. 사실 1521 포트를 사용하면서 LISTENER라는 이름의 리스너를 사용한다면 실제로 listener.ora 파일 자체가 필요가 없다. lsnrctl 명령어는 디폴트로 LISTENER라는 이름의 리스너에 대하여 수행되므로, 시동/중단하는 데에도 아무런 지장이 없다. 한 번 listener.ora 파일을 제거하고 테스트해 보도록 하자. 여태까지와 마찬가지 결과를 얻을 것이다.
물론, LISTENER라는 이름이 아닌 다른 이름의 리스너를 사용하고자 한다면 1521 포트를 사용하더라도 이름을 지정하기 위해서 listener.ora 파일이 필요하다. 이미 눈치 빠른 독자들은 알아챘겠지만, 동적 등록 방식이 우수하므로 앞으로는 항상 동적 등록 방식을 쓰도록 하자는 것이다. 동적 등록 방식은 세 가지 측면에서 과거의 정적 등록 방식보다 우수하다.
주기적으로 인스턴스가 리스너에 자신의 상태를 알려주므로, CTF 설정에서 인스턴스에 장애가 있을 경우 앞서 설정한 페일오버 과정에서 장애가 발생한 인스턴스에 접속을 시도하는 오버헤드가 없다. 즉, 리스너까지만 가면 인스턴스의 상태를 알 수 있다.
Oracle9i Database의 새로운 기능인 리스너(커넥션) 로드 밸런싱을 사용할 수 있다.
TAF를 사용할 수 있다.
그렇다면 TAF란 무엇인가? 이것에 대해 자세히 알아보도록 하자.
TAF
앞서 잠깐 설명한 대로 CTF의 약점은 기존에 접속되어 있던 클라이언트들에 대해서는 다시 한 번 접속을 시도해야 페일오버가 이루어진다는 점이다. 바로 이것을 극복할 수 있는 솔루션이 바로 TAF(Transparent Application Failover)이다. 앞서 설정한 클라이언트쪽의 TNS alias를 다음과 같이 수정해 보자. 물론 리스너에는 반드시 인스턴스가 동적으로 등록되어 있어야 한다.
RAC_TAF = (description= (load_balance=off) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) (failover_mode=(type=select)(method=basic)) ) )
CTF와 비교하여 다른 점은 failover_mode라는 항목이 설정되었다는 점이다. 이것을 설정하는 방법을 설명하기에 앞서, 일단 위와 같이 TAF가 설정되어 있는 경우에 이미 접속되어 있던 클라이언트들은 장애상황에서 어떻게 되는지부터 알아보자. 예를 들어, 클라이언트에서 100건의 레코드를 리턴하는 Select를 수행중이었고, 현재 50건이 Fetch된 상태에서 RAC1 인스턴스가 중단되었다고 가정한다면,
살아 있는 인스턴스인 RAC2에서 새로이 Oracle Server Process (Foreground Process)를 띄운다.
이렇게 새로이 만들어진 프로세스로 50건을 Fetch하던 클라이언트의 세션 정보가 옮겨진다. 이때 복구되는 내용은 다음과 같다.
SQL을 실행하던 사용자의 로그인 정보
실행하던 SQL 명령어
Fetch하던 커서
서버측 프로그램 변수(예를 들면, PL/SQL package states)
이렇게 세션 정보가 복구되면 수행되던 Select 문장이 Fetch하던 나머지 50건이 Fetch된다. 따라서 클라이언트는 RAC1 인스턴스에 장애가 발생했다는 사실을 전혀 모르게 된다(Transparent Application). 물론 2번 과정에서 세션 정보를 복구하는 과정이 있으므로 50건 Fetch 후 다시 나머지 50건의 Fetch가 시작되기까지 약간 지연될 수 있다.
TAF와 CTF의 가장 큰 차이점은 재접속이 필요 없다는 점이다. 앞서 CTF 방식에서는 접속되어 있던 클라이언트들은 장애 발생 시 일단 ORA-3113/3114 에러를 만나게 되며, 재접속을 해야 페일오버가 되었다는 점을 기억하자. 이 때 기존에 접속되어 있던 클라이언트와 별도로 새로 접속하는 클라이언트들은 앞서 설명한 CTF가 작동하여 페일오버가 이루어진다.
그러면, failover_mode의 각각의 항목에 대하여 알아보도록 하자.
TYPE
SESSION : 이 방식은 재접속은 필요 없지만, 예제와는 달리 Select가 Fetch하던 도중에 장애를 만나면 Fetch가 도중에 실패하게 된다. 데이타 웨어하우스와 같이 다량의 레코드를 한 번에 Select하는 경우가 아니라면 이 방식을 쓰는 것이 좋다.
SELECT : 예제와 같은 방식으로, 재접속도 필요 없을 뿐더러 Fetch하던 레코드까지 복구해 준다. 다량의 레코드를 Select하는 애플리케이션에 적절한 방법이다.
NONE : 디폴트 값으로 TAF가 작동하지 않는다.
METHOD
BASIC : On-demand 방식으로 페일오버가 필요할 때 살아있는 인스턴스쪽으로 Oracle Server Process를 기동시킨다.
PRECONNECT : 페일오버가 이루어질 인스턴스에 미리 Oracle Server Process를 기동시켜 페일오버시 발생하는 오버헤드를 미리 줄여 놓는 방식이다. 물론 페일오버 속도는 향상되지만, 자원을 낭비할 수 있다.
그렇다면, 여기서 또 의문점이 생긴다. PRECONNECT 메소드를 사용하는 경우 미리 어느 인스턴스로 페일오버할지를 결정해 놓아야 한다. 다음과 같이 클라이언트의 tnsnames.ora를 수정해 보자.
RAC_TAF = (description= (load_balance=off) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) (failover_mode=(type=select)(method=preconnect)(backup=RAC2)) ) ) RAC2 = (description= (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC)) )
즉 PRECONNECT 메소드를 사용하는 경우에는 반드시 backup이라는 항목이 함께 설정되어야 한다. 이 때 backup에는 TNS alias로 지정하며, 여기에 지정할 TNS alias는 반드시 미리 설정되어 있어야 한다(예제에서는 RAC2). 특히, 이 때 지정한 TNS alias가 올바로 설정된 값이 아니어도 접속할 때 따로 에러를 리턴하지 않는다는 점에 주의하자.
물론, 이 예제에서는 2노드 환경이므로 한 쪽에서 장애가 발생하면 나머지 살아 있는 쪽은 하나밖에 없으므로 왜 이것이 필요할까 궁금해 하는 독자들이 있을 것이다. 위와 같이 설정해 놓고 RAC_TAF라는 TNS alias로 접속한 후 <리스트 4>와 같이 SQL을 실행해 보자. 참고로 <리스트 4>의 SQL은 PRECONNECT 메소드뿐만 아니라 모든 경우의 TAF 설정이 제대로 작동하는지 알아보기 위해서 사용할 수 있다.
<리스트 4> TAF 설정이 올바로 되었는지 확인하는 SQL
SQL> SELECT 2 INST_ID, SID, MACHINE, FAILOVER_TYPE, FAILOVER_METHOD, FAILED_OVER
3 FROM GV$SESSION 4 WHERE USERNAME="SYSTEM" 5 AND PROGRAM NOT LIKE "%(P%";
INST_ID SID MACHINE FAILOVER FAILOVER_METHOD FAILED
1 20 ORACLE\leesb-kr SELECT PRECONNECT NO
2 19 ORACLE\leesb-kr NONE NONE NO
이렇게 PRECONNECT 메소드를 사용하는 경우에는 최초에 접속하는 주(primary) 인스턴스와 백업 인스턴스에 동시에 접속한다는 것을 알 수 있다. 이때 RAC1 인스턴스를 셧다운시키면 다음과 같은 결과가 나와야 한다.
INST_ID SID MACHINE FAILOVER FAILOVER_METHOD FAILED
2 19 ORACLE\leesb-kr SELECT PRECONNECT YES
여기서 주목해야 할 점은 PRECONNECT 메소드를 사용할 때는 항상 주 인스턴스와 백업 인스턴스를 지정해야 하므로 로드 밸런싱을 할 수 없다는 사실이다. 따라서 PRECONNECT 메소드는 각 인스턴스에 접속하는 클라이언트가 업무별/지역별로 분할되어 있는 경우에 사용할 수 있다.
페일오버 테스트 시나리오
자, 이렇게 페일오버를 설정했다면 실제로 장애 상황별로 제대로 작동하는지 살펴보아야 할 것이다. 보통 페일오버 테스트에서 고려하는 장애 상황은 다음과 같다.
오라클 인스턴스 장애 : 간단하게 shutdown abort 명령으로 인스턴스 장애 상황을 만들 수 있다.
노드 장애(네트워크 장애) : 클라이언트 입장에서는 노드 자체가 장애가 있는 경우(reboot 또는 halt 명령)나 네트워크 장애가 있는 경우(네트워크 디바이스의 셧다운 또는 네트워크 라인의 unplug)나 같은 상황이므로 똑 같은 결과가 나와야 한다.
리스너 장애 : 간단하게 리스너 프로세스를 중단하든지 lsnrctl stop으로 셧다운해서 장애 상황을 만들 수 있다. 인스턴스는 살아 있으면서 리스너만 장애가 있는 경우에는 항상 기존에 접속되어 있던 클라이언트는 페일오버 설정과 관계 없이 아무런 문제가 없어야 하며, 오직 새로이 접속하는 클라이언트만 페일오버가 이루어진다는 점을 기억하자.
이 세 가지 장애 상황에서 페일오버가 모두 정상적으로 이루어진다면 설정이 성공적으로 이루어졌다고 볼 수 있다.
마지막으로 클라이언트 페일오버를 테스트할 때, 오라클 인터커넥트(Oracle9i Real Application Clusters 노드 간의 개별 네트워크)에 대한 장애는 클라이언트 페일오버와 전혀 상관이 없으며, 이 인터커넥트의 장애 발생시에는 인스턴스의 장애가 당연히 뒤따르므로 이를 고려하여 테스트하여야 한다. 오라클 인터커넥트의 장애 발생시 어떤 결과가 일어나는가에 대해서는 본지 2003년 봄호에 실린 필자의 "Oracle9i Real Application Clusters - Cache Fusion의 작동 원리와 활용"을 참고하기 바란다.
클라이언트 로드 밸런싱 vs 리스너 로드 밸런싱
자, 이제는 Oracle9i Real Application Clusters의 로드 밸런싱에 대해 알아보도록 하자. "로드 밸런싱(load balancing)"이란 말 그대로 공평하게 부하(load)를 나누어 준다는 뜻이다. 즉, 100개의 클라이언트가 있다면 2노드 Oracle9i Real Application Clusters 환경에서는 각 인스턴스별로 50개씩 똑같이 나누어 주고 싶을 것이고, 차후에 다시 50개의 새로운 클라이언트가 접속한다면 다시 각각 25개씩 나누어 주어 언제나 공평하게 부하를 유지하고 싶을 것이다.
하지만, 이제까지의 예제와 같이 TNS alias에서 load_balance=off로 설정한 경우에는 어느 클라이언트가 어느 인스턴스로 접속할지 수동으로 모두 설정해 주어야 하는 번거로움이 있었다. 만일 이것이 자동으로 된다면? 즉, 100개의 클라이언트가 무작위로 접속할 때 자동으로 50개씩 나누어진다면 훨씬 더 편리할 것이다.
사실 Oracle9i Real Application Clusters가 나오기 이전에 Oracle Parallel Server를 사용하는 고객들은 로드 밸런싱을 잘 사용하지 못했다고 할 수 있다. 왜냐하면, Oracle Parallel Server에서 발생하는 핑(ping) 현상으로 인해 보통 불가피하게 노드별로 데이타를 분할하여 데이타베이스를 구성하므로, 업무별/지역별로 클라이언트를 나누어 특정 인스턴스를 지정해서 접속해야 했기 때문이다.
하지만, Oracle9i Real Application Clusters에서 완벽하게 이루어지는 Cache Fusion의 기능으로 더 이상 데이타의 분할이 필요 없게 되었으므로, 클라이언트는 Oracle9i Real Application Clusters로 구성된 데이타베이스 내의 어떤 인스턴스에 접속하더라도 성능에는 아무런 영향을 주지 않는다.
오라클의 로드 밸런싱 설정은 다음 두 가지가 있다.
클라이언트 로드 밸런싱 : 클라이언트가 같은 SERVICE_NAME을 사용하는 인스턴스들 중에 무작위로 선택하여 접속하는 방법이다.
리스너(커넥션) 로드 밸런싱 : Oracle9i Real Application Clusters의 새로운 기능으로 리스너가 각 인스턴스의 부하에 대한 정보를 가지고 있어서 항상 부하가 덜 걸린 인스턴스쪽으로 클라이언트를 접속시켜 주는 방법이다.
그러면 이제 각각의 방법을 어떻게 구현할 수 있는지 차례로 살펴보자.
클라이언트 로드 밸런싱
앞서 설정했던 BASIC 메소드의 TAF 설정을 다음과 같이 수정해 보자.
RAC_TAF = (description= (load_balance=on) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) (failover_mode=(type=select)(method=basic)) ) )
다른 것은 그대로 둔 채로 load_balance 부분만 on으로 설정하면 클라이언트 로드 밸런싱이 구성된다. 이제 반복적으로 접속하여 결과를 보도록 하자.
SQL> conn scott/tiger@rac_taf Connected. SQL> select userenv("instance") from dual; USERENV("INSTANCE") -------------------------- 2 SQL> conn scott/tiger@rac_taf Connected. SQL> select userenv("instance") from dual; USERENV("INSTANCE") -------------------------- 1
위와 같이 클라이언트는 인스턴스를 바꿔가며 RAC1, RAC2에 무작위로 접속하는 것을 볼 수 있다. 라운드로빈(round-robin) 방식이 아닌 무작위(random) 방식이므로, 특별한 패턴을 찾을 수는 없지만, 접속 횟수가 많아질수록 균일하게 분배되는 것을 알 수 있을 것이다. 즉, 예제와 같이 2노드의 Oracle9i Real Application Clusters 환경에서 100회 반복하여 접속한다면, 거의 50:50으로 나누어지는 것을 확인할 수 있을 것이다.
클라이언트 로드 밸런싱을 하는 경우 특별히 동적 등록이 필요하지는 않으나, 예제와 같이 TAF와 함께 사용하는 경우에는 반드시 동적 등록이 구성되어 있어야 한다.
리스너(커넥션) 로드 밸런싱
최초에 RAC1 인스턴스에는 100개의 클라이언트, RAC2 인스턴스에는 10개의 클라이언트가 접속되어 있었다고 가정하자. 이후에 다시 100개의 새로운 클라이언트를 클라이언트 로드 밸런싱을 설정하여 접속시킨다면? 결과는 새로운 100개의 클라이언트들은 50:50으로 균일하게 분배되어 결국 최종적으로는 RAC1 인스턴스는 150개, RAC2 인스턴스에는 60개의 클라이언트가 붙을 것이다.
이렇게 되면 RAC1 인스턴스 입장에서는 불공평한 결과가 될 수 있다. 즉 RAC2 인스턴스가 훨씬 더 여유가 있다는 것을 미리 알아서, 새로이 들어오는 100개의 클라이언트를 5:95로 분배하여 결국 105:105가 되도록 해준다면 각각의 인스턴스 입장에서는 언제나 공평한 결과를 만들 수 있다.
바로 이것이 Oracle9i Real Application Clusters에서 새로 등장한 리스너(커넥션) 로드 밸런싱이며, 이것을 위해서는 서버쪽의 tnsnames.ora와 init.ora에 다음과 같은 설정을 추가해야 한다. krrac1의 tnsnames.ora에 다음과 같은 항목을 추가해 보자.
LISTENER_RAC2 = (description= (address=(protocol=tcp)(host=krrac2)(port=1521)) )
그리고 SYSDBA 사용자로 다음과 같이 remote_listener 파라미터 값을 변경해 보자.
SQL> alter system set remote_listener="LISTENER_RAC2"; System altered.
이제 krrac2(krrac1이 아님)의 리스너를 확인해 보자. <리스트 5>와 같이 RAC 서비스에 기존에 등록되어 있던 RAC2 인스턴스와 함께, RAC1 인스턴스도 추가로 동적으로 등록되어 있는 것을 볼 수 있다.
<리스트 5> 리스너 로드 밸런싱에서 리스너 확인
krrac2|/data/oracle> lsnrctl services LSNRCTL for Solaris: Version 9.2.0.3.0 - Production on 14-JUL-2003 15:29:55 Copyright (c) 1991, 2002, Oracle Corporation. All rights reserved. Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=krrac2)(PORT=1521))) Services Summary... Service "RAC" has 2 instance(s). Instance "RAC1", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:0 refused:0 state:ready REMOTE SERVER (DESCRIPTION=(address=(protocol=tcp)(host=krrac1)(port=1521))) Instance "RAC2", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:25 refused:0 state:ready LOCAL SERVER The command completed successfully
그러면, 이제 krrac2의 tnsnames.ora, RAC2 인스턴스의 remote_listener 값을 마찬가지로 올바르게 설정했다면 krrac1의 리스너에도 <리스트 6>과 같이 RAC1, RAC2 인스턴스가 모두 등록되어 있는 것을 확인할 수 있을 것이다.
<리스트 6> 리스너 로드 밸런싱에서 인스턴스의 등록 확인
krrac1|/data/oracle> lsnrctl services LSNRCTL for Solaris: Version 9.2.0.3.0 - Production on 14-JUL-2003 15:33:52 Copyright (c) 1991, 2002, Oracle Corporation. All rights reserved. Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=krrac2)(PORT=1521))) Services Summary... Service "RAC" has 2 instance(s). Instance "RAC1", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:0 refused:0 state:ready REMOTE SERVER (DESCRIPTION=(address=(protocol=tcp)(host=krrac1)(port=1521))) Instance "RAC2", status READY, has 1 handler(s) for this service... Handler(s): "DEDICATED" established:25 refused:0 state:ready LOCAL SERVER The command completed successfully
앞서 설명한 동적 등록이 무엇을 의미하는가를 기억한다면, 여기서 각각의 인스턴스가 remote_listener 파라미터를 사용함으로써 상대방 노드의 리스너에 자신의 상태를 지속적으로 업데이트해 주고 있다는 것을 눈치챌 수 있다.
즉, 예제와 같이 리스너(커넥션) 로드 밸런싱을 사용하는 경우 클라이언트는 접속할 때 다음과 같은 과정을 거치게 된다.
클라이언트는 일단 TNS alias의 address_list의 첫 번째 항목인 krrac1의 1521 포트로 기동된 리스너에 컨택한다.
krrac1의 리스너는 RAC1, RAC2 인스턴스가 모두 등록되어 있으며, 각 인스턴스는 자신이 서비스가 가능한 상태인지의 여부와, 자신이 가지고 있는 부하(접속해 있는 클라이언트의 개수)를 지속적으로 업데이트해 주고 있으므로, 1에서 리스너에 컨택한 클라이언트는 RAC1과 RAC2 중 어느 것이 적은 수의 클라이언트를 가지고 있는지 알 수 있다.
만일 RAC1의 클라이언트 수가 적다면, 로컬 노드(krrac1)에서 Oracle Server Process(Foreground Process)를 만들어서 커넥션을 맺어 준다.
만일 RAC2의 클라이언트 수가 적다면, address_list의 다음에 나오는 항목(리모트 노드 : krrac2의 1521 포트)에 등록되어 있는 리스너로 리다이렉트시켜 준다.
krrac2의 리스너는 이제 Oracle Server Process(Foreground Process)를 만들어서 커넥션을 맺어 준다.
따라서, 리스너의 서비스에 remote_listener를 사용하여 Oracle9i Real Application Clusters의 모든 인스턴스가 등록되어 있기만 하다면, 언제나 공평한 분배(로드 밸런싱)가 이루어질 수 있다.
설정 후 테스트는 다음과 같이 해볼 수 있다.
RAC1 인스턴스에만 로드 밸런싱을 사용하지 않고 미리 상당수의 커넥션을 맺어 놓는다(이를테면 10개라고 하자.)
이제 RAC_TAF TNS alias를 가지고 반복적으로 접속해 보자. 10개까지는 RAC2에만 접속되어 각 인스턴스에 10개씩의 클라이언트가 만들어질 것이다.
이어서 지속적으로 RAC_TAF TNS alias를 가지고 반복적으로 접속해 본다면, 이제는 각 인스턴스에 거의 번갈아서 접속하면서 클라이언트 개수의 총합이 언제나 50:50으로 유지되는 것을 볼 수 있을 것이다.
마지막으로 주의할 점은, 리스너(커넥션) 로드 밸런싱은 항상 클라이언트 로드 밸런싱을 오버라이드하므로, remote_listener가 설정되어 리스너에 여러 인스턴스가 등록되어 있는 경우, 아래와 같이 TNS alias에서 클라이언트 로드 밸런싱을 사용하지 않더라도, 리스너(커넥션) 로드 밸런싱은 계속 이루어진다는 사실이다. 즉, 아래와 같이 TNS alias를 설정한 경우에 로드 밸런싱이 일어나고 있다면, 일단 remote_listener가 설정되어 있는지 우선 확인해 보도록 하자.
RAC_TAF = (description= (load_balance=off) (failover=on) (address=(protocol=tcp)(host=krrac1)(port=1521)) (address=(protocol=tcp)(host=krrac2)(port=1521)) (connect_data= (service_name=RAC) (failover_mode=(type=select)(method=basic)) )
remote_listener 값은 Oracle9i Database Release 2(9.2)부터는 아래와 같이 동적으로 해제할 수 있다.
SQL> alter system set remote_listener=""; System altered.
이제까지 로드 밸런싱을 설명하면서 계속 TAF를 예로 들었지만, CTF 설정(failover_mode 부분 제고)으로도 똑 같은 로드 밸런싱을 구현할 수 있으며, 예제와 똑 같은 테스트를 할 수 있다.
난공불락의 Oracle9i Real Application Clusters
필자가 Oracle9i Real Application Clusters를 도입하고자 하는 독자들에게 하고 싶은 말은, 일단 싱글 인스턴스 환경에서 시스템을 문제 없이 사용해 왔다면, Oracle9i Real Application Clusters로의 전환 이후에도 문제 없는 시스템을 유지할 수 있으며, 나아가 오라클이 자랑하는 "난공불락 Oracle9i Real Application Clusters" 놀라운 기능을 마음껏 누릴 수 있다는 것이다.
마지막으로 필자가 3회의 연재를 통해 언급한 몇 가지 주의사항을 다시 한 번 정리하며 끝을 맺기로 한다.
싱글 인스턴스로 운영중이던 애플리케이션은 Oracle9i Real Application Clusters로 전환 시 Cache Fusion이 완벽하게 작동하므로 별도의 데이타 분할이 필요 없다.
Oracle9i Real Application Clusters에서 고속 인터커넥트는 Cache Fusion을 위해 필수이며, HA로 구성되어 있는 것이 바람직하다.
싱글 인스턴스 환경에서 튜닝 되지 않은 SQL들은 Oracle9i Real Application Clusters 환경에서 더욱 성능이 악화될 수 있으니, Oracle9i Real Application Clusters 전환 전에 애플리케이션을 반드시 점검하도록 하자.
global cache cr request가 과다하게 보이는 경우, 무엇보다도 SQL 튜닝이 되어 있는지 먼저 확인하고, 읽기 액세스가 대부분인 테이블스페이스에 한하여 gc_files_to_locks 파라미터를 신중하게 고려해 본다.
페일오버 설정은 각 장애 유형별로 철저히 테스트해 보아야 하며, 애플리케이션 분할이 되어 있지 않다면 로드 밸런싱을 하는 것이 바람직하다.
제공 : DB포탈사이트 DBguide.net


