python-oracledb 접속 확립: thin/thick, Instant Client, libaio, NLS

이전 · 다음

개요

이관의 첫 관문은 소스 Oracle에 파이썬으로 접속하는 것이다. 겉보기엔 oracledb.connect() 한 줄이지만, 폐쇄망의 오래된 서버 앞에서는 드라이버 모드(thin/thick), 네이티브 클라이언트 라이브러리(Instant Client, libaio), 문자셋(NLS)이 얽혀 좀처럼 한 번에 붙지 않는다.

접속 문제를 진단할 때 가장 중요한 원칙은 “네트워크로 닿는가”와 “인증이 되는가”를 분리하는 것이다. 리스너·방화벽 문제(ORA-12170, ORA-12514, ORA-12505)와 인증·프로토콜 문제(DPY-3010, DPY-3015)는 원인도 해법도 완전히 다르다. 이 둘을 섞으면 방화벽을 뚫어 놓고 패스워드만 계속 바꾸는 헛수고를 하게 된다.

핵심 개념

thin vs thick 모드

python-oracledb는 두 가지 모드로 동작한다.

항목thin 모드thick 모드
네이티브 클라이언트불필요 (순수 파이썬)Oracle Instant Client 필요
지원 서버 버전≥ 12.111g 등 구버전 포함
배포 편의성높음 (pip만으로)낮음 (.so/.dll 배치 필요)
구형 password verifier미지원지원
NLS 처리파이썬 레벨클라이언트 라이브러리 레벨

기본은 thin이다. 배포가 간단하고 의존성이 없기 때문이다. 그러나 소스가 오래된 서버라면 thin이 접속을 거부한다. 이 경우 thick으로 fallback해야 한다.

Instant Client와 버전 선택

thick 모드는 Oracle Instant Client 라이브러리를 로드한다. 구서버(11g)에 붙어야 하는 상황에서도 클라이언트는 19c(LTS) 를 쓰는 것이 안전하다. 19c 클라이언트는 11.2 이상 서버와 호환되며 장기 지원(LTS)이라 폐쇄망에 오래 두기 적합하다. 패키지는 NLS 데이터를 포함하는 Basic 배포를 쓴다(Basic Light는 일부 문자셋이 빠져 한글이 깨질 수 있다).

실전

thin → thick fallback 접속 코드

thin을 먼저 시도하고, 실패하면 thick을 초기화해 재시도하는 패턴이 실전에서 가장 견고하다.

import oracledb
 
def connect(user: str, password: str, dsn: str):
    try:
        return oracledb.connect(user=user, password=password, dsn=dsn)  # thin 우선
    except oracledb.DatabaseError as e:
        (err,) = e.args
        # 구서버/구형 verifier면 thick으로 전환
        if err.full_code in ("DPY-3010", "DPY-3015"):
            oracledb.init_oracle_client(lib_dir="/opt/oracle/instantclient_19")
            return oracledb.connect(user=user, password=password, dsn=dsn)
        raise

init_oracle_client()는 프로세스당 한 번만 호출할 수 있으므로, 예외 처리 안에서 상태를 관리하거나 최초에 조건적으로 초기화한다.

네트워크 도달성부터 확인

접속 코드를 짜기 전에 리스너에 TCP로 닿는지부터 본다. 인증 이전 단계다.

# 리스너 포트 도달성 (인증 이전)
nc -vz oracle-host 1521
 
# tnsping으로 서비스 등록 여부 확인 (Instant Client 포함 도구)
tnsping "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle-host)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=orclpdb)))"

여기서 막히면 인증 문제가 아니라 리스너·방화벽·서비스명 문제다.

함정·트러블슈팅

코드/증상원인해법
DPY-3010thin 모드가 서버 버전(예: 11g)을 미지원thick + Instant Client 19c로 전환. 패스워드 재설정으로는 해결 안 됨
DPY-3015계정이 구형(11g) password verifier 사용DBA가 ALTER USER ... IDENTIFIED BY ...로 verifier 재생성, 또는 thick fallback
libaio.so.1 로드 실패Instant Client가 libaio 의존패키지 설치 + 심볼릭 링크(아래)
한글이 ?/모지바케로 깨짐클라이언트 charset 불일치Basic 배포 사용, KO16MSWIN949AL32UTF8 확인
ORA-12170/12514/12505리스너·타임아웃·서비스명 문제인증이 아니라 네트워크 계층 → tnsping/방화벽 점검

DPY-3010 — thin이 구서버를 거부

이 오류는 “서버가 너무 오래됐다”는 명확한 신호다. 흔한 오해가 패스워드 문제로 착각하고 계정을 리셋하는 것인데, 아무 소용이 없다. thick으로 전환하고 Instant Client 19c를 붙이는 것이 정답이다.

DPY-3015 — 구형 password verifier

계정의 패스워드가 11g 시절 해시(구형 verifier)로만 저장돼 있으면 thin 모드가 인증을 거부한다. 두 가지 길이 있다.

-- (DBA 권한) 새 verifier를 생성하도록 패스워드 재설정
ALTER USER app_reader IDENTIFIED BY "<new-password>";

DBA 협조가 어렵다면 thick fallback으로 우회한다. thick은 구형 verifier를 처리할 수 있기 때문이다.

libaio 누락과 Ubuntu 24.04 리네임

Instant Client는 리눅스에서 libaio 없이 초기화에 실패한다. 문제는 Ubuntu 24.04에서 패키지가 libaio1t64로 리네임되면서, Instant Client가 찾는 libaio.so.1이 존재하지 않는다는 점이다. 심볼릭 링크로 이어준다.

sudo apt-get install -y libaio1t64
# Instant Client가 기대하는 이름으로 링크
sudo ln -sf /usr/lib/x86_64-linux-gnu/libaio.so.1t64 \
            /usr/lib/x86_64-linux-gnu/libaio.so.1

NLS와 인코딩

한글 멀티바이트는 클라이언트 charset이 소스와 어긋나면 곧장 깨진다. 레거시 Oracle은 흔히 KO16MSWIN949(CP949 계열)를 쓰고, PostgreSQL 대상은 AL32UTF8(UTF-8)이다. 접속 시 NLS를 명시해 두면 예측 가능하다.

import os
os.environ["NLS_LANG"] = "KOREAN_KOREA.AL32UTF8"  # 드라이버가 UTF-8로 받도록

핵심은 인코딩을 추출 단계에서 확정하는 것이다. 잘못 디코딩된 한글은 적재 후 되돌리기 어렵다(→ 04.데이터 정제 (NUL 문자·인코딩·암호화 컬럼)).

정리

접속은 “네트워크 도달”과 “인증”을 분리해 진단하는 것이 절반이다. 나머지 절반은 구서버 대응이다. thin을 기본으로 하되 DPY-3010/DPY-3015를 만나면 Instant Client 19c로 thick fallback하고, 리눅스에서는 libaio 링크를, 한글에서는 NLS charset을 반드시 챙긴다. 접속이 안정되면 다음은 대용량 테이블을 안전하게 끌어오는 추출 단계다.