Python을 배워보자

Python으로 오라클 서버 간 20만 건 데이터 이동 시 타임아웃 해결 방법

_Blue_Sky_ 2025. 2. 25. 23:22
728x90
안녕하세요, 오늘은 Python에서 두 오라클 서버 간 데이터를 이동할 때, 20만 건 정도에서 타임아웃으로 연결이 끊어지는 문제를 해결하는 방법을 다뤄보겠습니다. 순수 Python 코드로만 문제를 풀어보며, 실무에서 바로 적용할 수 있는 최적화 기법을 소개합니다.

문제 상황
두 개의 오라클 서버를 Python(cx_Oracle)로 연결해 테이블 데이터를 이동시키던 중, 약 20만 건에서 타임아웃이 발생하며 연결이 끊어졌습니다. 이 문제는 네트워크, 데이터베이스 설정, 또는 코드 비효율성에서 비롯될 가능성이 높습니다. 아래에서 원인을 분석하고 해결책을 제시합니다.

원인 분석
  1. 오라클 세션 타임아웃: 서버 설정이나 cx_Oracle 연결의 기본 타임아웃이 짧음.
  2. 대량 데이터 부하: 한 번에 20만 건을 처리하며 메모리와 네트워크에 과부하.
  3. 트랜잭션 크기: 대량 삽입 시 트랜잭션이 커져 타임아웃 발생.
  4. 커서 리소스 부족: 단일 커서로 대량 데이터를 처리하며 리소스가 고갈됨.

해결 방법
이 문제를 해결하려면 다음 전략을 적용합니다:
  • 배치 처리: 데이터를 1만 건씩 나눠 처리해 부하 감소.
  • 타임아웃 설정 조정: 연결 유지 시간을 늘리거나 확인 로직 추가.
  • 효율적 삽입: executemany로 여러 행을 한 번에 처리.
  • 에러 처리: 타임아웃 시 재연결로 안정성 확보.
728x90
728x90
아래는 최적화된 샘플 코드입니다.

최적화된 Python 코드
import cx_Oracle
from datetime import datetime

# 배치 크기 설정
BATCH_SIZE = 10000

# 소스 서버 연결
dsn1 = cx_Oracle.makedsn("source_host", 1521, service_name="source_service")
conn1 = cx_Oracle.connect(user="source_user", password="source_password", dsn=dsn1)

# 타겟 서버 연결
dsn2 = cx_Oracle.makedsn("target_host", 1521, service_name="target_service")
conn2 = cx_Oracle.connect(user="target_user", password="target_password", dsn=dsn2)

try:
    # 커서 생성
    cursor1 = conn1.cursor()  # 소스 DB
    cursor2 = conn2.cursor()  # 타겟 DB

    # 소스 데이터 조회
    cursor1.execute("SELECT id, name, created_date FROM source_table")
    
    # 타겟 테이블 준비
    cursor2.execute("""
        CREATE TABLE IF NOT EXISTS target_table (
            id NUMBER,
            name VARCHAR2(100),
            created_date DATE
        )
    """)

    # 배치 단위 처리
    rows = cursor1.fetchmany(BATCH_SIZE)
    total_processed = 0

    while rows:
        start_time = datetime.now()
        
        # 효율적 삽입
        cursor2.executemany("""
            INSERT INTO target_table (id, name, created_date)
            VALUES (:1, :2, :3)
        """, rows)
        
        conn2.commit()
        
        total_processed += len(rows)
        print(f"{total_processed}건 처리 완료 - 소요 시간: {datetime.now() - start_time}")

        # 다음 배치
        rows = cursor1.fetchmany(BATCH_SIZE)

    print("데이터 이동 완료!")

except cx_Oracle.DatabaseError as e:
    print(f"오류 발생: {e}")
    conn2.rollback()

finally:
    # 리소스 정리
    cursor1.close()
    cursor2.close()
    conn1.close()
    conn2.close()

코드 개선 포인트
  1. 배치 크기 조정
    • BATCH_SIZE를 10,000으로 설정했지만, 서버 성능에 따라 5,000~50,000으로 조정 가능합니다.
  2. 타임아웃 늘리기
    • 소켓 타임아웃을 늘려 연결 끊김 방지:
       
      import socket
      socket.setdefaulttimeout(600)  # 10분
  3. 연결 유지 확인
    • 주기적으로 연결 상태 점검:
      if total_processed % 50000 == 0:
          conn1.ping()
          conn2.ping()
          print("연결 상태 확인")
  4. 에러 처리 강화
    • 타임아웃 시 재연결:
      except cx_Oracle.OperationalError as e:
          print(f"연결 오류: {e}, 재연결 시도")
          conn1 = cx_Oracle.connect(user="source_user", password="source_password", dsn=dsn1)
          conn2 = cx_Oracle.connect(user="target_user", password="target_password", dsn=dsn2)
          cursor1 = conn1.cursor()
          cursor2 = conn2.cursor()
          continue
  5. 성능 최적화
    • 소스와 타겟 테이블에 인덱스를 추가해 속도 개선:
      CREATE INDEX idx_source_id ON source_table(id);

기대 효과
  • 타임아웃 해결: 배치 처리로 부하를 줄여 20만 건 이상 처리 가능.
  • 속도 향상: executemany로 네트워크 호출 최소화.
  • 안정성: 에러 처리와 재연결로 중단 없이 실행.

추가 팁
  • 대용량 데이터: 행 크기가 크다면(예: CLOB), BATCH_SIZE를 더 줄임.
  • 네트워크 점검: 서버 간 대역폭이 낮으면 DBA와 협의.
  • 로그 추가: 진행 상황을 파일로 남기려면 logging 모듈 활용.

마무리
이 코드를 적용하면 타임아웃 없이 두 오라클 서버 간 데이터 이동이 가능할 겁니다. 만약 문제가 지속되거나 에러가 발생하면, 구체적인 메시지를 공유해 주시면 더 세밀히 도와드리겠습니다. 데이터 이동 작업이 성공적으로 마무리되길 바랍니다!
728x90