Skip to content

연결과 드라이버 설정 (한국어)

🌐 CONNECTION.md의 번역입니다. 영어 원문이 표준이며, CI가 영어 원문과의 구조 일치를 검사합니다.

이 가이드는 CUBRID Python 드라이버 설치, SQLAlchemy 연결 문자열 구성, 연결 수명 주기의 이해를 다룹니다.


목차


사전 준비

요구사항 버전
Python 3.11+
SQLAlchemy 2.0 – 2.1
CUBRID 서버 10.2 – 11.4
CUBRID Python 드라이버 pycubrid (권장) 또는 cubrid-python v11.3.0.51+에서 빌드한 CUBRIDdb (지원되는 C 확장)

CUBRID Python 드라이버 설치

권장: 순수 Python 드라이버 (pycubrid)

새 프로젝트에는 순수 Python pycubrid 드라이버를 사용하세요. 드라이버는 CUBRID 네이티브 라이브러리가 필요 없습니다. 권장 extra를 설치하세요:

pip install "sqlalchemy-cubrid[pycubrid]"

이 extra는 pycubrid의 동기·비동기 URL을 모두 지원하고 SQLAlchemy의 asyncio 의존성(greenlet)을 포함합니다. 드라이버 자체는 순수 Python이며, 호환 wheel이 없으면 greenlet 설치에 빌드 도구가 필요할 수 있습니다.

또는 따로 설치:

pip install sqlalchemy-cubrid pycubrid

그리고 cubrid+pycubrid:// URL 스킴을 사용하세요:

engine = create_engine("cubrid+pycubrid://dba@localhost:33000/testdb")

팁: pycubrid 드라이버 자체는 순수 Python이며 네이티브 라이브러리가 필요 없습니다. [pycubrid] extra에 포함된 greenlet은 설치 시 빌드 도구가 필요할 수 있습니다. GitHub의 pycubrid를 참고하세요.

CUBRIDdb C 확장 드라이버

cubrid-python의 공식 CUBRIDdb C 확장 드라이버는 cubrid:// URL에 묶여 있으며, cubrid+cubriddb:// URL 스킴으로 명시적으로 선택할 수 있습니다. 지원되는 설치 방법은 cubrid-python v11.3.0.51 이상을 소스에서 빌드하는 것입니다. 소스에서 CUBRIDdb 빌드를 참고한 뒤 방언만 설치하세요:

pip install sqlalchemy-cubrid

경고: 드라이버를 PyPI에서 설치하지 마세요(pip install CUBRID-Python 또는 폐기 예정인 [cubrid] / [cubriddb] extra). PyPI에는 CUBRID-Python 9.3.x만 있으며, 이 방언과 함께 테스트되지 않았습니다. BIGINT를 str로 반환하고 통합 테스트 일부가 실패합니다. cubrid:// 엔진은 로드된 CUBRIDdb가 11.3보다 오래되었으면 첫 연결 시 SAWarning을 냅니다. PyPI CUBRID-Python 9.3.x는 지원하지 않음을 참고하세요.


연결 문자열 형식

SQLAlchemy는 표준 URL 방식 연결 문자열을 사용합니다:

cubrid://user:password@host:port/database

예제

from sqlalchemy import create_engine

# 기본 연결
engine = create_engine("cubrid://dba:password@localhost:33000/demodb")

# 비밀번호 없이 (CUBRID는 기본적으로 dba의 비밀번호 없는 접근을 허용)
engine = create_engine("cubrid://dba@localhost:33000/testdb")

# 명시적 드라이버 이름 (C 확장)
engine = create_engine("cubrid+cubrid://dba:password@localhost:33000/demodb")

# pycubrid 순수 Python 드라이버 사용
engine = create_engine("cubrid+pycubrid://dba:password@localhost:33000/demodb")

URL 구성 요소

구성 요소 기본값 설명
user (필수) 데이터베이스 사용자 이름 (보통 dba)
password (비어 있음) 데이터베이스 비밀번호
host localhost CUBRID 서버 호스트명 또는 IP
port 33000 CUBRID 브로커 포트
database (필수) 데이터베이스 이름

URL 쿼리 옵션

cubrid+pycubrid://와 cubrid+aiopycubrid://는 다음 URL 쿼리 옵션을 쿼리 문자열에서 pycubrid가 기대하는 타입으로 변환해 pycubrid.connect() / pycubrid.aio.connect()에 전달합니다:

engine = create_engine(
    "cubrid+pycubrid://dba@localhost:33000/demodb?connect_timeout=5&read_timeout=30"
)
옵션 값 설명
connect_timeout 양수 초 브로커 연결을 여는 동안의 타임아웃
read_timeout 양수 초 연결 후 소켓 읽기 타임아웃
fetch_size 1 이상의 정수 서버 측 fetch 배치 크기 (pycubrid 기본값 100)
charset 코덱 이름 Python 코덱 또는 CUBRID utf8 / euckr / iso88591. 데이터베이스 문자셋으로 설정합니다. pycubrid 1.9.0 이상이 필요합니다 (cubrid-lab/pycubrid#510). pycubrid 1.8.0에서는 create_engine()이 설치된 버전을 알려 주는 ArgumentError를 발생시킵니다
ssl 불리언 true이면 pycubrid 기본 컨텍스트(TLS 1.2+)로 TLS를 사용합니다. 그 밖의 설정은 connect_args로 ssl.SSLContext를 전달합니다
decode_collections 불리언 SET / MULTISET / SEQUENCE 값을 Python 컬렉션으로 디코딩
no_backslash_escapes 불리언 pycubrid 자체의 문자열 이스케이프 모드 (생략하면 자동 감지). 방언의 인라인 리터럴 렌더링을 고정하지 않습니다. 방언은 첫 연결 시 프로브한 서버 모드나 아래의 create_engine(no_backslash_escapes=...) 옵션을 따릅니다
enable_timing 불리언 pycubrid 타이밍 통계

불리언은 true/false, yes/no, on/off, 1/0을 받습니다. 잘못된 값, 반복된 옵션, host / port / database / user / password (URL 자체에 지정), autocommit (isolation_level="AUTOCOMMIT" 사용), json_deserializer (connect_args로 전달)는 create_engine()에서 sqlalchemy.exc.ArgumentError를 발생시킵니다. 그 밖의 키는 가장 가까운 지원 옵션을 제안하는 pycubrid의 UnknownConnectionOptionWarning과 함께 무시됩니다. warnings.simplefilter("error", pycubrid.UnknownConnectionOptionWarning)로 오류로 바꿀 수 있습니다. connect_args 값이 URL 쿼리 옵션보다 우선합니다.

cubrid:// / cubrid+cubriddb:// (CUBRIDdb) 방언은 URL 쿼리 문자열을 읽지 않습니다. CUBRIDdb의 connect(url, user, password)는 키워드 옵션을 받지 않으며, 쿼리 옵션은 무시됩니다.


엔트리 포인트

방언은 다음 SQLAlchemy 엔트리 포인트를 등록합니다:

URL 스킴 드라이버 설명
cubrid:// CUBRIDdb 기본 CUBRIDdb C 확장 드라이버
cubrid+cubrid:// CUBRIDdb 명시적 CUBRIDdb C 확장 드라이버
cubrid+cubriddb:// CUBRIDdb 명시적 CUBRIDdb C 확장 드라이버
cubrid+pycubrid:// pycubrid 순수 Python 드라이버 (CUBRID 네이티브 라이브러리 불필요)
cubrid+aiopycubrid:// pycubrid.aio 비동기 순수 Python 드라이버

새 프로젝트는 cubrid+pycubrid://를 권장합니다 (순수 Python 드라이버, CUBRID 네이티브 라이브러리 불필요). [pycubrid] extra의 greenlet 의존성은 호환 wheel이 없으면 빌드 도구가 필요할 수 있습니다. cubrid:// URL은 cubrid-python v11.3.0.51 이상에서 빌드한 지원되는 CUBRIDdb C 확장 드라이버에 묶입니다. 명시적으로 선택하려면 cubrid+cubriddb://를 사용하세요.


비동기 연결

비동기 애플리케이션에서는 create_async_engine과 함께 cubrid+aiopycubrid:// URL 스킴을 사용하세요. pycubrid>=1.8.0,<2.0이 필요합니다.

sqlalchemy-cubrid[pycubrid]를 설치하면 드라이버와 SQLAlchemy의 비동기 브리지가 포함됩니다. 패키지를 따로 설치한다면 SQLAlchemy[asyncio]도 설치하세요.

from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text

engine = create_async_engine("cubrid+aiopycubrid://dba@localhost:33000/testdb")

async with engine.connect() as conn:
    result = await conn.execute(text("SELECT 1"))
    print(result.scalar())

연결은 async with나 await conn.close()로 반환하세요. 애플리케이션이 체크아웃한 연결을 닫지 않고 잃어버리면, 방언이 사용하는 종료 지원을 제공하는 SQLAlchemy 2.0.44 이상에서는 가비지 컬렉터의 정리 과정이 그 연결을 종료합니다 (#872). 방언은 연결의 이벤트 루프가 이미 닫힌 뒤에도 소켓을 동기적으로 닫고 pycubrid 세션을 정리하므로 "cannot be safely terminated" 메시지와 닫히지 않은 스트림의 ResourceWarning이 없습니다. SQLAlchemy는 연결이 반환되지 않았다는 경고를 여전히 냅니다. 무효화된 연결은 일반적인 CLOSE_DATABASE 종료로 닫힙니다. SQLAlchemy 2.0.0부터 2.0.43까지는 풀이 이전 동작을 유지해 그런 연결을 종료하지 않고 버립니다.

Python 3.15와 SQLAlchemy 2.1: Python의 순환 수집기(cycle collector)가 회수하는 연결에 대해서는 SQLAlchemy 풀이 이 정리 과정을 실행하지 않습니다. 자동 시작된 트랜잭션이 열린 채로 잃어버린 연결이 일반적으로 이 경우에 해당합니다. CPython 3.15는 가비지 컬렉션 중 약한 참조를 더 늦게 지우고(python/cpython#136189), SQLAlchemy 2.1의 풀 종료자(2.1.0부터 2.1.4까지 확인)는 그 연결이 아직 참조되고 있다고 판단해 바로 반환합니다. 방언은 호출되지 않습니다. Python 자체의 스트림 종료자가 ResourceWarning과 함께 소켓을 닫고, 풀은 그 슬롯을 계속 체크아웃된 것으로 세므로 이런 누수가 쌓이면 풀이 고갈됩니다(TimeoutError). 이는 SQLAlchemy의 동작이며 동기 풀을 포함한 모든 방언에 영향을 줍니다. 트랜잭션을 이미 커밋하거나 롤백한 연결처럼 참조 순환이 붙잡고 있지 않은 연결은 위에서 설명한 대로 계속 종료됩니다. Python 3.15의 SQLAlchemy 2.0과 Python 3.14 이하는 영향을 받지 않습니다. 연결은 명시적으로 닫으세요.

PK 조회를 동반한 비동기 삽입

CUBRID에는 RETURNING 절이 없습니다. 비동기 ORM 삽입 후 auto-increment 기본 키를 얻으려면, 트랜잭션 블록 안에서 await session.flush()를 호출해 커밋 전에 객체의 PK를 채우세요:

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(100))

engine = create_async_engine("cubrid+aiopycubrid://dba@localhost:33000/testdb")

async with AsyncSession(engine) as session:
    async with session.begin():
        user = User(name="Alice")
        session.add(user)
        await session.flush()  # 커밋 없이 user.id 채움
        print(f"Inserted id={user.id}")

PK가 필요한 Core 수준 삽입에서는 문장 후에 SELECT LAST_INSERT_ID()를 실행하세요 (README → 알려진 한계 참고).

방언의 URL 변환 방식

내부적으로 방언은 SQLAlchemy URL을 CUBRID 네이티브 연결 형식으로 변환합니다:

SQLAlchemy URL:  cubrid://dba:password@myhost:33000/mydb
                        ↓
CUBRID native:   CUBRID:myhost:33000:mydb:::

create_connect_args() 메서드가 CUBRID Python 드라이버의 connect() 함수에 위치 인자로 (connect_url, username, password)를 반환합니다.

변환 상세

# CUBRIDdb (C 확장 드라이버):
connect_url = f"CUBRID:{host}:{port}:{database}:::"
args = (connect_url, username, password)
# → CUBRIDdb.connect("CUBRID:myhost:33000:mydb:::", "dba", "password")

CUBRID 연결 문자열 끝의 :::는 세 개의 빈 선택 파라미터입니다 (CUBRID가 향후 사용을 위해 예약).

pycubrid 변환

pycubrid 방언은 키워드 인자를 직접 전달합니다:

# pycubrid (순수 Python 드라이버):
kwargs = {"host": host, "port": port, "database": database, "user": user, "password": password}
# → pycubrid.connect(host="myhost", port=33000, database="mydb", user="dba", password="password")

URL 쿼리 옵션이 이 키워드 인자에 추가됩니다.


연결 옵션

엔진 수준 옵션

engine = create_engine(
    "cubrid://dba@localhost:33000/testdb",

    # 모든 연결의 기본 격리 수준 설정
    isolation_level="REPEATABLE READ",

     # SQLAlchemy 2.0–2.1 커넥션 풀 설정
    pool_size=5,
    max_overflow=10,
    pool_timeout=30,

    # SQL 로깅 활성화
    echo=True,
)

연결별 격리 수준

with engine.connect().execution_options(
    isolation_level="SERIALIZABLE"
) as conn:
    # 이 연결은 SERIALIZABLE 격리 사용
    result = conn.execute(text("SELECT * FROM accounts"))

지원되는 모든 수준은 격리 수준을 참고하세요.

백슬래시 이스케이프 (no_backslash_escapes)

CUBRID의 no_backslash_escapes 시스템 파라미터 기본값은 yes입니다. 즉 백슬래시는 문자열 리터럴에서 리터럴 문자입니다 (MySQL과 반대). 서버가 no_backslash_escapes=no이면 백슬래시는 이스케이프 문자이므로 인라인 리터럴에서 두 번 써야 합니다. 그렇지 않으면 \' OR 1=1 -- 같은 값이 리터럴을 일찍 끝냅니다.

방언은 서버의 실제 모드를 따릅니다. 옵션을 전달하지 않으면 각 엔진의 첫 연결에서 SELECT CHAR_LENGTH('\\') (pycubrid도 쓰는 프로브)를 실행하고, literal_binds=True로 렌더링된 리터럴, 컬럼과 테이블 주석, 문자열 서버 기본값, 네이티브 ENUM 레이블이 그 결과를 따릅니다. 프로브 결과가 1이나 2가 아니면 추측하지 않고 첫 연결이 실패합니다.

# 모드는 첫 연결 시 서버에서 가져옵니다
engine = create_engine("cubrid+pycubrid://dba@localhost:33000/testdb")

모드를 고정하고 프로브를 건너뛰려면 no_backslash_escapes를 전달하세요. 예를 들어 연결하지 않는 서버용 SQL을 오프라인으로 컴파일할 때입니다:

# 서버가 no_backslash_escapes=no로 구성됨
engine = create_engine(
    "cubrid+pycubrid://dba@localhost:33000/testdb",
    no_backslash_escapes=False,
)

아직 연결하지 않은 방언(예: stmt.compile(dialect=CubridDialect()))은 서버 기본값인 True를 가정합니다. 같은 이름의 URL 쿼리 옵션은 pycubrid 자체의 이스케이프만 설정하며 방언을 고정하지 않습니다. 파라미터 바인딩 값은 이 설정과 무관하게 항상 드라이버가 올바르게 이스케이프합니다.


오토커밋 동작

드라이버 기본값과 방언 오버라이드

두 CUBRID Python 드라이버 모두 기본적으로 autocommit=True입니다. 방언은 SQLAlchemy가 트랜잭션을 올바르게 관리할 수 있도록 모든 새 연결에서 이를 오버라이드합니다. CUBRIDdb는 conn.set_autocommit(False)를, pycubrid는 속성 세터 conn.autocommit = False를 사용합니다.

SQL 텍스트 기반 오토커밋 없음

방언은 SQL 텍스트를 검사해 커밋 여부를 결정하지 않습니다. SQLAlchemy 2.x는 1.x 시절의 should_autocommit_text() 훅을 호출하지 않으므로, DML과 DDL (INSERT, REPLACE, MERGE, CREATE, DROP 포함)은 연결의 트랜잭션 안에서 실행되며 SQLAlchemy 2.x Connection API(conn.commit(), engine.begin() 블록, Session 커밋)를 통해서만 커밋됩니다. 커밋하지 않고 닫은 연결은 롤백됩니다.


서버 버전 감지

방언은 초기화 시 서버 버전을 조회합니다:

SELECT VERSION()

결과(예: 11.2.0.0374)는 내부 버전 검사를 위해 튜플 (11, 2, 0, 374)로 파싱됩니다.


문제 해결

흔한 연결 오류

ImportError: Could not import CUBRIDdb

cubrid:// URL이 사용하는 CUBRIDdb C 확장 드라이버가 설치되지 않았습니다. 권장 순수 Python 드라이버로 전환하세요:

pip install "sqlalchemy-cubrid[pycubrid]"   # 이후 cubrid+pycubrid:// 사용

또는 cubrid-python v11.3.0.51 이상에서 CUBRIDdb를 빌드하세요. 소스에서 CUBRIDdb 빌드를 참고하세요. PyPI의 CUBRID-Python 9.3.x 릴리스는 이 방언과 함께 테스트되지 않았습니다.

포트 33000에서 Connection refused

  1. CUBRID 브로커 실행 확인:

    cubrid broker status
    

  2. cubrid_broker.conf의 브로커 포트 확인 — 기본값은 33000.

  3. Docker 사용 시:

    docker compose up -d
    docker compose logs cubrid
    

Authentication failed

CUBRID 기본 dba 사용자에게는 비밀번호가 없습니다. 설정했다면 연결 문자열과 일치하는지 확인하세요:

# dba에 비밀번호가 없으면
engine = create_engine("cubrid://dba@localhost:33000/testdb")

# dba에 비밀번호가 있으면
engine = create_engine("cubrid://dba:mypassword@localhost:33000/testdb")

Docker 퀵스타트

로컬 개발에는 제공되는 docker-compose.yml을 사용하세요:

# CUBRID 11.2 시작 (기본)
docker compose up -d

# 특정 버전 시작
CUBRID_VERSION=11.4 docker compose up -d

# 실행 확인
docker compose ps

# 연결
python -c "
from sqlalchemy import create_engine, text
engine = create_engine('cubrid://dba@localhost:33000/testdb')
with engine.connect() as conn:
    print(conn.execute(text('SELECT VERSION()')).scalar())
"

커넥션 풀 튜닝

SQLAlchemy는 기본적으로 커넥션 풀을 관리합니다. CUBRID가 풀과 상호작용하는 방식을 이해하는 것이 프로덕션 배포에 중요합니다.

핵심 풀 파라미터

from sqlalchemy import create_engine

engine = create_engine(
    "cubrid://dba@localhost:33000/testdb",

    # 풀 크기: 유지할 지속 연결 수
    pool_size=5,           # 기본: 5

    # 오버플로: pool_size를 넘어 허용되는 추가 연결
    max_overflow=10,       # 기본: 10

    # 타임아웃: 풀에서 연결을 얻기까지 대기 초
    pool_timeout=30,       # 기본: 30

    # 재활용: 연결이 교체되기까지의 초
    # CUBRID 브로커의 SESSION_TIMEOUT보다 낮게 설정
    pool_recycle=1800,     # 권장: 1800 (30분)

    # 사전 핑: 체크아웃 전 연결 생존 테스트
    pool_pre_ping=True,    # 프로덕션 권장: True
)

pool_pre_ping (권장)

pool_pre_ping=True이면 SQLAlchemy는 연결을 애플리케이션에 넘기기 전에 do_ping()을 호출합니다. CUBRIDdb 방언은 CUBRID Python 드라이버의 네이티브 connection.ping() 메서드를 사용합니다. pycubrid 방언 둘 다 이제 pycubrid의 네이티브 Connection.ping(False) / AsyncConnection.ping(False) CHECK_CAS 경로를 사용해, 추가 SELECT 1 왕복 없이 끊어진 연결 오류를 방지합니다.

이것은 다음 상황에서 발생하는 "끊어진 연결" 오류를 방지합니다:

  • CUBRID 브로커 재시작
  • 네트워크 중단
  • 브로커의 SESSION_TIMEOUT 만료
# 프로덕션 권장 구성
engine = create_engine(
    "cubrid://dba@localhost:33000/mydb",
    pool_pre_ping=True,
    pool_recycle=1800,
)

pool_recycle과 CUBRID 브로커 타임아웃

CUBRID 브로커에는 SESSION_TIMEOUT 설정이 있습니다 (버전에 따라 다르지만 보통 300초). 풀링된 연결이 이 타임아웃보다 오래 유휴 상태이면 브로커가 서버 측에서 닫습니다.

항상 pool_recycle을 SESSION_TIMEOUT보다 낮게 설정해 끊어진 연결을 피하세요:

# CUBRID 브로커 SESSION_TIMEOUT이 300(5분)이면
engine = create_engine(
    "cubrid://dba@localhost:33000/mydb",
    pool_recycle=240,  # 브로커 타임아웃 전에 재활용
)

연결 해제 감지

방언은 드라이버 오류 메시지 문구 변경에 강건한 계층적 전략으로 연결 실패를 감지하는 is_disconnect()를 구현합니다:

  1. 숫자 오류 코드 매칭 (주) — CUBRIDdb가 끊겼거나 쓸 수 없는 연결에 대해 내는 CCI·CAS 코드 CCI_ER_COMMUNICATION(-20004), CAS_ER_COMMUNICATION(-10003), CCI_ER_CON_HANDLE(-20002), CCI_ER_CONNECT(-20016), CAS_ER_NO_MORE_MEMORY(-10002; CAS가 이 코드를 보낸 뒤 연결을 닫음)와, CAS의 cub_server 세션이 사라져 CUBRID 브로커가 CAS를 리셋하는 서버 코드 ER_TM_SERVER_DOWN_UNILATERALLY_ABORTED(-111), ER_NET_SERVER_CRASHED(-199), ER_OBJ_NO_CONNECT(-224), ER_BO_CONNECT_FAILED(-677)를 검사. CUBRIDdb는 코드를 args[0]에, pycubrid는 errno에 담으며, errno는 이 네 서버 코드에 더해 -1002(pycubrid가 실제로 받는 CAS_ER_NO_MORE_MEMORY의 레거시 번호. pycubrid처럼 갱신된 오류 코드 프로토콜을 이해한다고 알리지 않는 드라이버에게 CUBRID의 CAS는 CAS 코드에 9000을 더해 보냅니다)와도 비교합니다. 코드는 이 구조화된 필드에서만 읽고 메시지 텍스트에서는 읽지 않습니다. 애플리케이션 데이터를 인용하는 서버 오류(-20004 rows rejected ...)처럼 숫자로 시작하는 메시지에는 코드가 없습니다(#608). 다른 서버 코드는 연결 끊김이 아닙니다. -4는 KILL QUERY로 중단된 쿼리를 뜻하는 ER_INTERRUPTED이며 연결은 계속 쓸 수 있습니다. 문제 해결 — cub_server 재시작 또는 장애 후 오류 참고.
  2. 명시적 OSError 원인 체인 (문구 무관) — 예외의 명시적 __cause__ 체인(raise ... from)에 OSError(예: 소켓 오류)이 있으면 메시지 문구와 무관하게 연결이 끊긴 것으로 간주. 무관한 진행 중 OSError가 살아있는 연결을 오탐 무효화하지 않도록 암시적 __context__는 의도적으로 무시.
  3. 메시지 매칭 (폴백) — 알려진 연결 해제 패턴("connection is closed", "broker is not available", "connection reset" 등)을 오류 메시지에서 검사. CUBRIDdb 드라이버와 코드도 OSError 원인도 없는 pycubrid의 클라이언트 측 문자열 전용 오류(예: "connection lost during receive", pycubrid가 잃어버린 CAS 세션을 교체하지 못했을 때의 "reconnecting failed", 해석할 수 없는 응답을 받은 연결을 끊을 때의 "malformed response from broker")를 커버. 패턴은 드라이버 자체 메시지(pycubrid의 args[0])와 비교하며, -671에 Communication error 같은 errno 설명을 덧붙이는 pycubrid의 str()과는 비교하지 않습니다(pycubrid 1.13.0은 -4, 즉 서버의 ER_INTERRUPTED를 Interrupted로 설명하며, 이전 릴리스는 통신 오류로 보고했습니다). 서버 오류 코드(CUBRIDdb는 -10000보다 큰 args[0], pycubrid는 레거시 CAS 코드가 오는 -1000 .. -1200 범위 밖의 errno)가 있는 오류에는 이 폴백을 적용하지 않습니다. 서버 메시지는 애플리케이션 데이터를 그대로 포함하기 때문입니다. 예를 들어 SELECT 1 FROM [connection refused]는 -493으로 실패하면서 메시지에 해당 문구가 들어갑니다. 메시지 자체가 데이터베이스 서버 연결 실패를 알리는 -190, -191, -368만 예외입니다 (#624).

감지는 의도적으로 보수적입니다: 해제 코드도, OSError 원인도, 해제 메시지도 아닌 데이터베이스 오류(예: 잘못된 격리 수준 오류, 닫힌 커서 오용)는 연결 해제로 취급하지 않아 오탐 풀 무효화를 피합니다.

연결 해제가 감지되면 SQLAlchemy가 자동으로 연결을 무효화하고 풀에서 새 연결을 만듭니다.

오류 코드 매핑

CUBRID 드라이버 예외는 적절한 SQLAlchemy 예외 타입으로 매핑됩니다. 두 드라이버 모두 PEP 249 예외 계층 전체를 노출합니다:

CUBRID 드라이버 예외 SA 예외 매핑
Error (기본) DBAPIError
InterfaceError InterfaceError
DatabaseError DatabaseError
DataError DataError
OperationalError OperationalError
IntegrityError IntegrityError
InternalError InternalError
ProgrammingError ProgrammingError
NotSupportedError NotSupportedError

참고: CUBRIDdb 11.3.0.51과 pycubrid는 위의 모든 PEP 249 예외 클래스를 제공하며, SQLAlchemy는 드라이버가 발생시킨 클래스를 감쌉니다. 특정 서버 오류가 어떤 클래스가 되는지는 드라이버에 따라 다릅니다. 예를 들어 릴리스된 pycubrid의 NOT NULL 및 외래 키 위반이 그렇습니다. 드라이버 호환성을 참고하세요.

풀 구성 권장사항

시나리오 pool_size pool_recycle pool_pre_ping
개발 2 -1 (비활성) False
웹 애플리케이션 5–10 1800 True
고동시성 10–20 900 True
백그라운드 워커 2–5 600 True

단기 스크립트용 NullPool

스크립트나 일회성 작업에는 풀링을 완전히 끄세요:

from sqlalchemy.pool import NullPool

engine = create_engine(
    "cubrid://dba@localhost:33000/testdb",
    poolclass=NullPool,
)

연결 URL 파싱 흐름

flowchart TD
    input[SQLAlchemy URL string] --> parse[SQLAlchemy URL parser]
    parse --> check{Driver name}
    check -->|cubrid or cubrid+cubrid or cubrid+cubriddb| cext[Build CUBRID native DSN]
    check -->|cubrid+pycubrid| pykw[Build pycubrid kwargs]
    check -->|cubrid+aiopycubrid| aio[Build pycubrid.aio kwargs]
    cext --> connect1["CUBRIDdb.connect(url, user, password)"]
    pykw --> connect2["pycubrid.connect(host, port, database, user, password)"]
    aio --> connect3["pycubrid.aio.connect(...)"]
    connect1 --> session[Dialect on_connect sets autocommit=False]
    connect2 --> session
    connect3 --> session

올바른 방언 접두사 사용

pycubrid://...는 유효한 SQLAlchemy URL 스킴이 아닙니다. 항상 cubrid+pycubrid://...를 사용하세요.

브로커 포트를 쿼리 문자열로 전달 금지

...?port=33000이 아니라 ...@host:33000/dbname을 사용하세요.

프로덕션에서는 pool_pre_ping=True 권장

브로커 재시작이나 유휴 타임아웃 만료 후 끊어진 연결 실패를 방지합니다.


참고: 격리 수준 · 타입 매핑 · 기능 지원