ADR 0016 — CUBRID 상태 백엔드 + manifest 정본 이전¶
- 상태: 수용됨(Accepted)
- 관련 이슈/문서: ADR 0003(영속 Build 저장소), ADR 0010(ArtifactStore·백엔드 분리, 제안됨), ADR 0012(credential 경계)
- 드라이버:
sqlalchemy-cubrid[pycubrid](v1.7.1+, SQLAlchemy>=2.0,<2.3, 순수 파이썬pycubrid1.7.1, Python 3.10+) [pycubrid]extra 가 필수다.sqlalchemy-cubrid만 설치하면 dialect 만 생기고 드라이버는 없다.- URL 은 반드시
cubrid+pycubrid://다. sqlalchemy-cubrid 는 dialect 를cubrid,cubrid.cubrid,cubrid.cubriddb,cubrid.pycubrid넷으로 등록하는데 앞의 셋은 모두 legacy C-extension(CUBRID-Python, import 이름CUBRIDdb)을 쓴다. 드라이버를 생략한cubrid://는 연결 시점에ImportError: Could not import CUBRIDdb로 죽으므로,store/backend.py가 기동 시 정규화·검증한다.
맥락¶
조직 요구사항으로 Builder 의 영속 상태 백엔드를 CUBRID(한국형 RDBMS)로 전환한다. 스케일링이 목적이 아니다 — 배포는 OCI Compute VM + Docker 단일 인스턴스(단일 replica)이며, CUBRID 사용 자체가 요구사항이다. ADR 0010 이 제안한 백엔드 추상화를 본 ADR 이 실제 구현으로 확정한다.
기존 상태는 세 곳으로 나뉜다:
- BuildIndex(ADR 0003) — 완료 이력 파생 캐시. 단일 파일 SQLite(
_builds.sqlite). - Credential 저장소(ADR 0012) — 암호화 ciphertext. SQLite.
- 산출물 + manifest.json — 로컬 파일시스템. ADR 0003 은
manifest.json을 정본으로 규정한다.
결정¶
1. 백엔드 선택 (기본 무외부의존)¶
KPUBDATA_BUILDER_STORAGE_BACKEND = sqlite(기본) | cubrid, KPUBDATA_BUILDER_CUBRID_URL
= SQLAlchemy URL. 기본값 sqlite/local 은 SQLAlchemy 의존이 없다(AGENTS.md 무외부의존·결정성).
sqlalchemy import 는 store/backend.py 의 cubrid 분기 안에서만 이뤄진다. cubrid 컴포넌트는
프로세스 전역 단일 Engine(커넥션 풀 + pool_pre_ping)을 공유하고, 연산마다 짧은 커넥션을
빌린다(멀티스레드 async job 대비). serve 시작 시 validate_storage_config() 로 cubrid URL·드라이버를
fail-closed 검증한다.
2. BuildIndex / Credential — Protocol 뒤의 CUBRID 구현체¶
BuildIndex를 Protocol 로 승격, 기존 concrete 를SqliteBuildIndex로 리네임(ADR 0010 권고),CubridBuildIndex(SQLAlchemy Core) 추가.make_build_index()가 백엔드로 선택.CredentialRepository(ADR 0012)는 이미 Protocol —CubridCredentialRepository추가.- upsert 는 dialect 독립적으로 단일 트랜잭션 내 delete+insert 로 처리한다(CUBRID dialect 의 MERGE/ON DUPLICATE 지원 여부에 의존하지 않음). BuildIndex 쓰기 예외는 ADR 0003 규칙4대로 삼키고, credential 쓰기(사용자 액션)는 전파한다.
3. manifest 정본을 CUBRID 로 이전 (ADR 0003 supersede)¶
ArtifactStore Protocol(ADR 0010)을 도입한다. 핵심 설계 결정:
- 산출물 바이트는 두 백엔드 모두 로컬 파일시스템(OCI 블록 볼륨)에 둔다. 근거:
(a)
query/engine.py가 별도 서브프로세스에서pl.scan_parquet(경로)로 lazy 스캔하므로 실제 파일 경로가 필요하다; (b) 대용량 parquet/CSV 를 RDBMS BLOB 에 넣는 것은 안티패턴이며 드라이버 성숙도 리스크가 크다; (c) 단일 replica 라 공유 오브젝트 스토어 이점이 없다. - manifest 문서만 백엔드가 다르다.
CubridArtifactStore는 CUBRIDmanifests행을 정본으로 삼고 FS 를 미러(캐시)로 유지한다. 이는 ADR 0003 의 "manifest.json 이 정본" 을 cubrid 백엔드에 한해 뒤집는다 → 본 ADR 이 해당 조항을 supersede 한다.LocalArtifactStore(기본)는 FS 파일 자체가 정본이라 기존 동작과 바이트 동일하다.
FS 미러가 항상 유지되므로 벌크 스캔(datasets/list_builds)·바이트 접근·쿼리는 계속 FS 에서
동작하고, 단일-run manifest 조회(get_manifest)만 CUBRID 정본을 우선(FS 폴백)한다.
마이그레이션 (FS → CUBRID)¶
KPUBDATA_BUILDER_STORAGE_BACKEND=cubrid+KPUBDATA_BUILDER_CUBRID_URL설정.kpubdata-builder rebuild-index— FSmanifest.json스캔으로 CUBRID BuildIndex 재구축 (백엔드 인지: cubrid 는 truncate+reinsert).- 기존 run 의 manifest 문서는 다음 빌드부터
put_manifest로 CUBRID 에 승격된다. 기존 run 을 즉시 CUBRID 정본으로 올리려면 각 run 의manifest.json을put_manifest로 재적재한다 (FS 미러가 있으므로get_manifest는 그 전에도 FS 폴백으로 동작). - credential 은 master key + AAD 바인딩이라 자동 이관되지 않는다 — 재
put필요.
리스크¶
- 정본 이전: manifest 정본이 CUBRID 로 이동(본 ADR). FS 미러로 하위 호환·벌크 스캔·백업을
보존한다.
store/build_index.py·service/datasets.py의 "manifest.json 이 정본" 서술은 local 백엔드 기준이며, cubrid 백엔드에서는 본 ADR 이 우선한다. - 드라이버 성숙도:
sqlalchemy-cubrid/pycubrid는 신생이다. upsert·LOB/CLOB·DDL 타입매핑·LIMIT·트랜잭션 동작을 통합 테스트(docker-compose CUBRID)로 검증한다. 실제 문제 발견 시github.com/cubrid-lab/sqlalchemy-cubrid에 이슈를 낸다. - fail-closed/결정성: cubrid 선택 시 serve 시작에서 조기 검증하되, 인덱스/manifest 승격 쓰기는 best-effort(FS 정본/미러가 durable). 기본(sqlite/local) 경로는 결정적·무외부의존을 유지한다.