오라클 이중화 리스너 Failover 스크립트 적용하기
오라클 데이터베이스를 이중화했더라도 애플리케이션이 장애가 발생한 리스너 주소만 계속 바라보고 있다면 실제 서비스 전환은 이루어지지 않는다. 이 글에서는 운영 서버에서 Oracle Listener와 데이터베이스 서비스를 점검한 뒤 애플리케이션용 TNS 접속 정보를 자동으로 전환하는 Failover 스크립트를 구성한다.
- Primary와 Standby 접속 별칭을 각각 구성한다.
- 단순 포트가 아니라 실제 SQL 접속 결과로 정상 여부를 판단한다.
- 정상 노드의 접속 정보를 임시 파일에 생성한 후 원자적으로 교체한다.
flock으로 스크립트 중복 실행을 방지한다.- cron 또는 systemd timer로 주기적인 상태 점검을 수행한다.
먼저 확인할 점: 스크립트보다 Oracle Net Failover가 우선이다
Oracle Net은 하나의 접속 기술자에 여러 리스너 주소를 정의하고
FAILOVER=ON을 설정하는 Connect-Time Failover 기능을 제공한다.
여러 주소가 설정된 경우 첫 번째 주소 연결에 실패하면 다음 주소로 연결을 시도할 수 있다.
LOAD_BALANCE=OFF를 함께 설정하면 주소가 작성된 순서대로 접속을 시도한다.
0
APPDB =
(DESCRIPTION = (CONNECT_TIMEOUT = 5) (TRANSPORT_CONNECT_TIMEOUT = 3) (RETRY_COUNT = 1) (ADDRESS_LIST = (LOAD_BALANCE = OFF) (FAILOVER = ON) (ADDRESS = (PROTOCOL = TCP)(HOST = db-primary.example.com)(PORT = 1521)) (ADDRESS = (PROTOCOL = TCP)(HOST = db-standby.example.com)(PORT = 1521)) ) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = appdb_service) ) )
신규 연결만 전환하면 되는 환경에서는 위와 같은 Oracle Net 설정이 스크립트보다 단순하고 안정적이다.
CONNECT_TIMEOUT과 TRANSPORT_CONNECT_TIMEOUT은 접속 실패를 감지하는 시간을 제한하며,
타임아웃 값은 네트워크 지연과 리스너 처리 시간을 고려해 운영 환경에서 조정해야 한다.
1
다음과 같은 레거시 환경에서는 운영 스크립트를 함께 사용할 수 있다.
- 애플리케이션이 다중
ADDRESS_LIST접속 문자열을 지원하지 않는 경우 - 항상 하나의 고정된 TNS 별칭만 사용해야 하는 경우
- 주 서버 복구 후 자동 원복 여부를 운영 정책으로 제어해야 하는 경우
- 전환 이력과 장애 감지 결과를 별도 로그로 관리해야 하는 경우
구성 시나리오
| 구분 | Primary | Standby |
|---|---|---|
| 호스트 | db-primary.example.com |
db-standby.example.com |
| 리스너 포트 | 1521 |
1521 |
| 서비스 이름 | appdb_service |
appdb_service |
| 점검 별칭 | DB_PRIMARY_CHECK |
DB_STANDBY_CHECK |
| 애플리케이션 별칭 | APPDB |
|
이 스크립트는 애플리케이션 서버에서 실행한다.
Oracle Client와 SQL*Plus가 설치되어 있고, TNS_ADMIN 디렉터리를 애플리케이션이 동일하게 사용한다는
전제다.
1. 작업 디렉터리 생성
sudo mkdir -p /opt/app/oracle/network/admin
sudo mkdir -p /var/log/oracle-failover sudo chown -R appuser:appgroup /opt/app/oracle/network sudo chown -R appuser:appgroup /var/log/oracle-failover sudo chmod 750 /opt/app/oracle/network/admin sudo chmod 750 /var/log/oracle-failover
예제의 실행 계정은 appuser다. 실제 운영 환경에서는 Oracle Client를 사용하는 애플리케이션 계정에
맞게 소유자와 권한을 변경한다.
2. 상태 점검용 TNS 별칭 구성
다음 파일은 스크립트가 유지해야 할 고정 설정이다. 애플리케이션용 APPDB 항목은 스크립트가
이 파일 뒤에 추가한다.
파일: /opt/app/oracle/network/admin/tnsnames.static.ora
DB_PRIMARY_CHECK =
(DESCRIPTION = (CONNECT_TIMEOUT = 5) (TRANSPORT_CONNECT_TIMEOUT = 3) (RETRY_COUNT = 0) (ADDRESS = (PROTOCOL = TCP) (HOST = db-primary.example.com) (PORT = 1521) ) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = appdb_service) ) )
DB_STANDBY_CHECK = (DESCRIPTION = (CONNECT_TIMEOUT = 5) (TRANSPORT_CONNECT_TIMEOUT = 3) (RETRY_COUNT = 0) (ADDRESS = (PROTOCOL = TCP) (HOST = db-standby.example.com) (PORT = 1521) ) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = appdb_service) ) )
lsnrctl status는 리스너 상태와 등록 서비스 정보를 확인할 때 유용하다.
다만 애플리케이션 서버에서 실제 서비스 접속 가능 여부까지 판단하려면 SQL*Plus로
SELECT 1 FROM DUAL을 실행하는 방식이 더 적합하다.
Oracle 문서에서도 STATUS는 리스너 상태를, SERVICES는 등록된 서비스와 인스턴스를
확인하는 명령으로 구분한다. 2
3. 최초 tnsnames.ora 생성
스크립트가 처음 실행되기 전에도 상태 점검 별칭을 찾을 수 있도록 고정 파일을
tnsnames.ora로 복사한다.
cp /opt/app/oracle/network/admin/tnsnames.static.ora \
/opt/app/oracle/network/admin/tnsnames.ora
chmod 640 /opt/app/oracle/network/admin/tnsnames.static.ora chmod 640 /opt/app/oracle/network/admin/tnsnames.ora
/opt/app/oracle/network/admin을 TNS_ADMIN으로 사용해야 한다.
Oracle Client는 TNS_ADMIN이 설정되어 있으면 해당 디렉터리의
tnsnames.ora를 우선 확인한다. 3
4. DB 계정 비밀번호를 스크립트에 작성하지 않는다
운영 스크립트 안에 데이터베이스 사용자명과 비밀번호를 평문으로 저장하면 안 된다. 예제는 Oracle Wallet의 외부 인증 정보를 사용해 다음 형식으로 접속한다고 가정한다.
sqlplus -L -s /@DB_PRIMARY_CHECK
Wallet을 사용하지 않는 환경이라면 보안팀 정책에 맞는 Secret Manager나 자격 증명 공급 방식을 적용해야 한다. 환경 변수에 비밀번호를 직접 노출하거나 명령행 인수로 전달하는 방식도 운영 서버에서는 피하는 것이 좋다.
5. Listener Failover 스크립트 작성
파일: /opt/app/oracle/bin/oracle-listener-failover.sh
#!/usr/bin/env bash
set -Eeuo pipefail umask 027
------------------------------------------------------------
Oracle Client 환경
------------------------------------------------------------
ORACLE_HOME="/opt/oracle/instantclient" TNS_ADMIN="/opt/app/oracle/network/admin" PATH="${ORACLE_HOME}:${PATH}"
export ORACLE_HOME export TNS_ADMIN export PATH
------------------------------------------------------------
TNS 및 상태 파일
------------------------------------------------------------
STATIC_TNS="${TNS_ADMIN}/tnsnames.static.ora" ACTIVE_TNS="${TNS_ADMIN}/tnsnames.ora" STATE_FILE="${TNS_ADMIN}/.active-db" LOCK_FILE="/var/run/oracle-listener-failover.lock" LOG_FILE="/var/log/oracle-failover/failover.log"
PRIMARY_ALIAS="DB_PRIMARY_CHECK" STANDBY_ALIAS="DB_STANDBY_CHECK" APP_ALIAS="APPDB"
PRIMARY_HOST="db-primary.example.com" STANDBY_HOST="db-standby.example.com" LISTENER_PORT="1521" SERVICE_NAME="appdb_service"
SQL*Plus 프로세스 전체 제한 시간
CHECK_TIMEOUT="10"
true: Primary가 복구되면 자동 원복
false: Standby 사용 중에는 Standby 장애 전까지 유지
AUTO_FAILBACK="false"
------------------------------------------------------------
공통 함수
------------------------------------------------------------
log() { local level="$1" shift
printf '%s [%s] %s\n'
"$(date '+%Y-%m-%d %H:%M:%S')"
"${level}"
"$*" | tee -a "${LOG_FILE}" }
validate_environment() { if [[ ! -x "${ORACLE_HOME}/sqlplus" ]]; then log "ERROR" "sqlplus 실행 파일을 찾을 수 없습니다: ${ORACLE_HOME}/sqlplus" exit 10 fi
if [[ ! -r "${STATIC_TNS}" ]]; then log "ERROR" "고정 TNS 파일을 읽을 수 없습니다: ${STATIC_TNS}" exit 11 fi
mkdir -p "$(dirname "${LOG_FILE}")" }
check_database() { local alias="$1"
timeout "${CHECK_TIMEOUT}"
"${ORACLE_HOME}/sqlplus" -L -s "/@${alias}" >/dev/null 2>&1 <<'SQL' WHENEVER OSERROR EXIT FAILURE ROLLBACK WHENEVER SQLERROR EXIT FAILURE ROLLBACK
SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SET VERIFY OFF SET ECHO OFF
SELECT 1 FROM dual;
EXIT SUCCESS SQL }
write_app_alias() { local target="$1" local host="" local temp_file=""
case "${target}" in PRIMARY) host="${PRIMARY_HOST}" ;; STANDBY) host="${STANDBY_HOST}" ;; *) log "ERROR" "지원하지 않는 전환 대상입니다: ${target}" return 1 ;; esac
temp_file="$(mktemp "${TNS_ADMIN}/tnsnames.ora.tmp.XXXXXX")"
{ cat "${STATIC_TNS}"
cat <<EOF
${APP_ALIAS} = (DESCRIPTION = (CONNECT_TIMEOUT = 5) (TRANSPORT_CONNECT_TIMEOUT = 3) (RETRY_COUNT = 1) (ADDRESS = (PROTOCOL = TCP) (HOST = ${host}) (PORT = ${LISTENER_PORT}) ) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ${SERVICE_NAME}) ) ) EOF } >"${temp_file}"
chmod 640 "${temp_file}"
같은 파일시스템에서 mv를 사용해 설정 파일을 원자적으로 교체한다.
mv -f "${temp_file}" "${ACTIVE_TNS}"
printf '%s\n' "${target}" >"${STATE_FILE}" chmod 640 "${STATE_FILE}"
log "INFO" "${APP_ALIAS} 접속 대상을 ${target}(${host})로 설정했습니다." }
read_current_target() { if [[ -r "${STATE_FILE}" ]]; then tr -d '[:space:]' <"${STATE_FILE}" else printf '%s\n' "UNKNOWN" fi }
select_target() { local current_target="$1"
자동 원복을 사용하는 경우 Primary를 항상 먼저 점검한다.
if [[ "${AUTO_FAILBACK}" == "true" ]]; then if check_database "${PRIMARY_ALIAS}"; then printf '%s\n' "PRIMARY" return 0 fi
if check_database "${STANDBY_ALIAS}"; then
printf '%s\n' "STANDBY"
return 0
fi
return 1
fi
자동 원복을 사용하지 않고 현재 Standby인 경우 Standby를 우선 유지한다.
if [[ "${current_target}" == "STANDBY" ]]; then if check_database "${STANDBY_ALIAS}"; then printf '%s\n' "STANDBY" return 0 fi
if check_database "${PRIMARY_ALIAS}"; then
printf '%s\n' "PRIMARY"
return 0
fi
return 1
fi
최초 실행 또는 현재 Primary인 경우 Primary를 먼저 점검한다.
if check_database "${PRIMARY_ALIAS}"; then printf '%s\n' "PRIMARY" return 0 fi
if check_database "${STANDBY_ALIAS}"; then printf '%s\n' "STANDBY" return 0 fi
return 1 }
main() { local current_target="" local selected_target=""
validate_environment
동일 스크립트의 중복 실행을 방지한다.
exec 9>"${LOCK_FILE}"
if ! flock -n 9; then log "WARN" "이전 Failover 점검 프로세스가 실행 중이므로 종료합니다." exit 20 fi
current_target="$(read_current_target)" log "INFO" "점검을 시작합니다. 현재 대상=${current_target}"
if ! selected_target="$(select_target "${current_target}")"; then log "ERROR" "Primary와 Standby 모두 SQL 접속에 실패했습니다."
# 양쪽 모두 실패한 경우 기존 설정을 임의로 변경하지 않는다.
exit 30
fi
if [[ "${selected_target}" == "${current_target}" ]]
&& [[ -s "${ACTIVE_TNS}" ]]; then log "INFO" "현재 대상이 정상입니다. 설정 변경 없이 종료합니다." exit 0 fi
write_app_alias "${selected_target}" log "INFO" "Failover 점검을 정상적으로 완료했습니다." }
main "$@"
스크립트는 두 데이터베이스 모두 접속에 실패했을 때 기존 tnsnames.ora를 유지한다.
장애 판단이 불가능한 상황에서 임의의 접속 대상으로 변경하지 않기 위한 조치다.
6. 실행 권한과 디렉터리 권한 설정
sudo mkdir -p /opt/app/oracle/bin
sudo chown appuser:appgroup
/opt/app/oracle/bin/oracle-listener-failover.sh
sudo chmod 750
/opt/app/oracle/bin/oracle-listener-failover.sh
로그 파일과 TNS 파일에는 데이터베이스 구성 정보가 포함될 수 있으므로 일반 사용자에게 쓰기 권한을 부여하지 않는다.
7. 수동 실행 테스트
구문 검사
bash -n /opt/app/oracle/bin/oracle-listener-failover.sh
실행 테스트
sudo -u appuser \
/opt/app/oracle/bin/oracle-listener-failover.sh
생성된 접속 정보 확인
cat /opt/app/oracle/network/admin/.active-db
grep -A 15 '^APPDB'
/opt/app/oracle/network/admin/tnsnames.ora
애플리케이션 별칭으로 SQL 접속 확인
export TNS_ADMIN=/opt/app/oracle/network/admin
sqlplus -L -s /@APPDB <<'SQL' SET HEADING OFF SET FEEDBACK OFF SELECT sys_context('USERENV', 'DB_UNIQUE_NAME') FROM dual; EXIT SQL
출력된 DB_UNIQUE_NAME을 확인하면 실제로 어느 데이터베이스에 접속했는지 검증할 수 있다.
운영 환경에 따라 DATABASE_ROLE도 함께 확인할 수 있다.
SELECT
name,
db_unique_name,
database_role,
open_mode
FROM v$database; 8. cron으로 주기 실행
예를 들어 1분마다 상태를 점검하려면 애플리케이션 계정의 crontab에 다음 항목을 추가한다.
crontab -e
* * * * * /opt/app/oracle/bin/oracle-listener-failover.sh >/dev/null 2>&1
스크립트 자체에서 로그를 기록하므로 cron 출력은 별도로 저장하지 않았다. 단, 운영 표준에 따라 syslog 또는 중앙 로그 수집 시스템으로 전달하도록 변경할 수 있다.
ORACLE_HOME, TNS_ADMIN,
PATH를 명시해야 한다.
9. systemd timer로 실행하는 방법
장애 처리 로그와 실행 상태를 systemd에서 관리하려면 cron 대신 timer를 사용할 수 있다.
Service 파일
/etc/systemd/system/oracle-listener-failover.service
[Unit]
Description=Oracle Listener Failover Check After=network-online.target Wants=network-online.target
[Service] Type=oneshot User=appuser Group=appgroup ExecStart=/opt/app/oracle/bin/oracle-listener-failover.sh TimeoutStartSec=30
[Install] WantedBy=multi-user.target
Timer 파일
/etc/systemd/system/oracle-listener-failover.timer
[Unit]
Description=Run Oracle Listener Failover Check Every Minute
[Timer] OnBootSec=30s OnUnitActiveSec=60s AccuracySec=5s Persistent=true Unit=oracle-listener-failover.service
[Install] WantedBy=timers.target
등록 및 실행
sudo systemctl daemon-reload
sudo systemctl enable --now oracle-listener-failover.timer
sudo systemctl status oracle-listener-failover.timer sudo systemctl list-timers oracle-listener-failover.timer
실행 로그 확인
sudo journalctl \
-u oracle-listener-failover.service
--since today 10. 실제 장애 전환 테스트
운영 반영 전에는 반드시 별도의 검증 환경에서 장애 상황을 재현한다. 단순히 리스너 프로세스를 중지하는 것뿐 아니라 네트워크 단절, 서비스 미등록, 데이터베이스 미오픈 상태도 구분해서 확인해야 한다.
- Primary와 Standby가 모두 정상인 상태에서 Primary가 선택되는지 확인한다.
- Primary 리스너를 중지하거나 테스트 방화벽으로 1521 포트를 차단한다.
- 스크립트 실행 후
.active-db값이STANDBY인지 확인한다. APPDB별칭으로 새로운 SQL 접속이 가능한지 확인한다.- 애플리케이션 커넥션 풀의 기존 연결이 제거되고 재생성되는지 확인한다.
- Primary를 복구한 후 자동 원복 정책에 맞게 동작하는지 확인한다.
- 양쪽 접속을 모두 차단했을 때 기존 TNS 설정이 유지되는지 확인한다.
리스너의 현재 상태는 lsnrctl STATUS로 확인할 수 있으며,
등록 서비스와 인스턴스 정보는 lsnrctl SERVICES로 확인할 수 있다.
4
lsnrctl status LISTENER
lsnrctl services LISTENER 운영 환경에서 반드시 보완할 항목
| 항목 | 권장 사항 |
|---|---|
| 장애 판정 | 포트 점검만 사용하지 말고 실제 서비스명으로 SQL 접속을 수행한다. |
| 오탐 방지 | 한 번의 실패로 즉시 전환하지 않고 연속 실패 횟수 또는 모니터링 시스템 결과를 반영한다. |
| 자동 원복 | Primary 복구 즉시 원복할지 운영자 승인 후 원복할지 사전에 결정한다. |
| DB 역할 확인 | Standby가 실제 서비스 제공이 가능한 역할과 Open Mode인지 확인한다. |
| 커넥션 풀 | TNS 파일 변경만으로 기존 커넥션이 바뀌지 않으므로 연결 검증과 재생성 정책을 적용한다. |
| 동시 실행 | flock이나 systemd 실행 제어로 중복 파일 변경을 방지한다. |
| 설정 파일 교체 | 임시 파일을 완성한 뒤 동일 파일시스템에서 mv로 교체한다. |
| 인증 정보 | 비밀번호를 스크립트에 넣지 말고 Wallet 또는 승인된 Secret 저장소를 사용한다. |
| 모니터링 | 전환 성공, 양쪽 장애, 자동 원복 발생 시 운영 알림을 전송한다. |
Failover가 적용됐는데도 접속이 바뀌지 않는 원인
기존 커넥션 풀이 남아 있는 경우
tnsnames.ora 변경은 이후 생성되는 연결에 적용된다.
애플리케이션 서버가 기존 커넥션을 계속 재사용하면 접속 대상이 즉시 바뀌지 않을 수 있다.
커넥션 검증 쿼리, 연결 최대 수명, 장애 연결 제거 정책을 함께 설정해야 한다.
TNS_ADMIN 경로가 다른 경우
스크립트가 수정하는 파일과 애플리케이션이 읽는 파일이 다르면 전환 효과가 없다.
애플리케이션 프로세스의 환경 변수와 Oracle Client 설정을 기준으로 실제
tnsnames.ora 위치를 확인한다.
리스너는 정상이나 서비스가 등록되지 않은 경우
TCP 1521 포트가 열려 있더라도 요청한 SERVICE_NAME이 리스너에 등록되지 않으면
애플리케이션 연결은 실패한다. 따라서 단순 포트 점검이 아니라 SQL 접속 또는
lsnrctl services 결과를 확인해야 한다.
Standby가 Read Only 또는 Mounted 상태인 경우
리스너 접속 가능 여부와 업무 수행 가능 여부는 다르다.
Data Guard 구성에서는 데이터베이스 역할과 OPEN_MODE, 서비스 시작 조건을 기준으로
서비스 제공 가능 여부를 검증해야 한다.
자동 원복으로 접속이 반복 전환되는 경우
Primary 상태가 불안정할 때 자동 원복을 활성화하면 Primary와 Standby 사이에서 접속 대상이 반복 변경될 수 있다. 운영 환경에서는 연속 성공 횟수, 안정화 대기 시간 또는 운영자 승인 절차를 추가하는 것이 안전하다.
최종 점검 체크리스트
- Primary와 Standby에 동일한 업무용 서비스 이름이 준비되어 있는가?
- 스크립트가 사용하는 접속 계정에 최소 권한만 부여했는가?
- 비밀번호가 소스와 프로세스 목록에 노출되지 않는가?
- 애플리케이션과 스크립트가 동일한
TNS_ADMIN을 사용하는가? - 양쪽 리스너 장애 시 기존 설정을 유지하는가?
- 전환 로그와 운영 알림이 정상적으로 생성되는가?
- 커넥션 풀이 장애 연결을 폐기하고 새 연결을 생성하는가?
- Primary 복구 후 자동 원복 정책이 명확한가?
- 스위치오버와 비정상 Failover 시나리오를 각각 테스트했는가?
- 롤백 절차와 수동 전환 절차를 운영 문서에 기록했는가?
정리
오라클 이중화 환경에서는 우선 다중 ADDRESS와
FAILOVER=ON을 이용한 Oracle Net Connect-Time Failover 적용을 검토하는 것이 좋다.
Oracle은 고가용성 접속 문자열에서 접속 타임아웃과 재시도 설정을 함께 사용하는 구성을 안내하고 있다.
5
레거시 애플리케이션처럼 하나의 고정 접속 정보만 사용할 수 있다면 상태 점검 스크립트로 애플리케이션용 TNS 별칭을 전환할 수 있다. 이때 장애 판단 기준은 리스너 포트가 아니라 실제 SQL 접속이어야 하며, 설정 파일은 임시 파일 작성 후 원자적으로 교체해야 한다.
마지막으로 TNS 전환은 새로운 데이터베이스 연결의 목적지를 변경하는 작업이다. 기존 세션과 처리 중인 트랜잭션까지 보호하려면 데이터베이스 고가용성 구성과 애플리케이션 재연결 정책을 함께 설계해야 한다.
참고 자료
'지식 공유 > DBMS' 카테고리의 다른 글
| Tibero 5 JDBC-590743 문자셋 변환 오류 점검 방법 (0) | 2026.07.02 |
|---|---|
| PostgreSQL VACUUM FULL 후 relfrozenxid age가 1로 줄어드는 이유 (0) | 2026.07.02 |
| oracle to postgresql ORA-00943 오류, Oracle 테이블 대소문자와 스키마 확인 방법 (0) | 2026.06.28 |
| PostgreSQL invalid page in block 오류 원인과 복구 대응 방법 (0) | 2026.06.28 |
| PostgreSQL archive_mode on과 always 차이, 운영 환경별 선택 기준 (0) | 2026.06.28 |
