내 DuckDB Extension PR이 8주 동안 빨간색(실패) 상태로 방치되었던 이유. 버그는 CI가 말한 것이 아니었다.
요약
DuckDB 확장 프로그램 개발 중 CI(지속적 통합) 오류가 플랫폼 문제로 오인되어 8주간 방치된 사례를 다룹니다. 실제 원인은 Makefile 타겟 부재였으며, C-API extension 방식을 통해 독립적인 CMake 프로젝트를 CI 환경에 맞게 해결하는 과정을 설명합니다.
핵심 포인트
- CI 실패 시 플랫폼 이름보다 실제 에러 로그를 우선 확인해야 함
- Fail-fast 기능으로 인해 특정 플랫폼의 오류가 전체 매트릭스 중단 원인이 될 수 있음
- 독립적인 CMake 프로젝트를 DuckDB CI에 통합하려면 C-API extension 방식이 효율적임
- 빌드 하네스와 메타데이터 간의 구조적 일치가 CI 성공의 핵심임
5월 10일, 나는 NVIDIA CUDA와 Apple Silicon Metal 모두에서 실행되는 GPU 가속 DuckDB 확장 프로그램인 gpudb를 DuckDB community-extensions 레지스트리에 추가하기 위해 풀 리퀘스트 (Pull Request)를 보냈다. 그러고 나서 그대로 방치되었다. 하나의 빨간색 체크 표시, 유지 관리자의 코멘트 없음, 8주간의 침묵. 실패한 작업의 이름은 linux_arm64였기에, 당연히 나는 처음 한 시간 동안 내가 ARM 문제를 겪고 있다고 확신하며 시간을 보냈다. 하지만 아니었다. CI (지속적 통합)가 나에게 거짓말을 하고 있었다. 악의적인 것은 아니었고, 단지 구조적인 문제였다. 실제로 무엇이 고장 났었는지, 그리고 어떻게 일련의 AI 에이전트(AI agents)들이 약 70분 만에 모든 4개 플랫폼을 빨간색에서 초록색으로 바꾸었는지 소개한다.
오도(Misdirection): 플랫폼 이름은 진단명이 아니다
유일하게 빨간색이었던 작업은 linux_arm64였다. 모든 본능은 "교차 컴파일 (cross-compilation), ARM 툴체인 (toolchain), 어떤 aarch64의 특이점"을 떠올리게 한다. 하지만 로그 속에 파묻혀 있던 실제 에러는 단 세 단어였다:
make: *** No rule to make target 'set_duckdb_version'
이것은 ARM 에러가 아니다. 빌드 에러조차 아니다. 이것은 make가 해당 이름의 Makefile 타겟이 없다고 알려주는 것이다. 왜냐하면 빌드 하네스 (build harness) 자체가 아예 없었기 때문이다. community-extensions CI는 모든 확장 프로그램 레포지토리(repo)가 extension-template의 Makefile을 포함하고 있을 것을 기대한다. 내 것은 그렇지 않았다. gpudb는 독립적인 CMake 프로젝트였으며, 나는 이를 레지스트리의 메타데이터에 연결만 했을 뿐, CI가 실제로 구동하는 템플릿 빌드 레이어를 스캐폴딩 (scaffolding) 하지 않았다.
그렇다면 왜 arm64만 빨간색이 되었을까? 바로 스케줄링 (Scheduling) 때문이다. 매트릭스 (matrix)는 모든 플랫폼을 동일한 첫 번째 단계인 make set_duckdb_version으로 실행한다. arm64 러너 (runner)가 우연히 그 단계에 가장 먼저 도달했고, 실패했으며, 매트릭스의 fail-fast 기능이 다른 형제 작업들이 동일하게 실패하기 전에 취소해 버린 것이다. 빨간색 플랫폼 라벨은 순전한 노이즈였다. 진짜 메시지는 세 단어 안에 있었다. 내가 계속해서 다시 배우고 있는 교훈은 이것이다: 작업 이름이 아니라 에러를 읽어라.
해결책: 독립적인 CMake 프로젝트가 CI의 언어를 말하게 만들기
gpudb는 DuckDB의 C++ extension 템플릿(template)으로 빌드되지 않습니다. 이는 CUDA 및 Metal 백엔드를 갖춘 자체적인 CMake 프로젝트입니다. 저는 이를 템플릿에 맞춰 다시 작성하고 싶지 않았습니다. 가장 깔끔한 경로는 C-API extension 방식이며, 이 방식을 사용하면 libduckdb를 전혀 링크하지 않고도 어떤 프로젝트든 로드 가능한 .duckdb_extension을 생성할 수 있습니다:
-
빌드가 독립적(hermetic)이 되도록 C API 헤더(
duckdb.h,duckdb_extension.h)를 저장소에 벤더링(Vendor)합니다. -
extension-ci-tools에서c_api_extensions메이크파일(makefiles)을 채택합니다. 이것이 CI의make호출이 실제로 기대하는 하네스(harness)이며,set_duckdb_version타겟을 포함한 모든 구성 요소입니다. -
안정적인 C_STRUCT ABI를 기반으로 빌드합니다. 모든 DuckDB 호출은 로드 시점에 extension에 전달되는 함수 포인터 구조체(function-pointer struct)를 통해 이루어지므로, 결과물(artifact)은 DuckDB 심볼을 전혀 링크하지 않으며 DuckDB의 포인트 릴리스(point releases) 간에도 ABI 안정성을 유지합니다.
-
레지스트리(registry)의
ref를 해당 하네스가 반영된main브랜치의 정확한 커밋으로 고정(Pin)하여, CI가 실제로 메이크파일을 포함하고 있는 트리를 체크아웃하도록 합니다.
이 경로의 유일한 실제 사례는 DuckDB 자체의 C-API 샘플인 capi_quack입니다. 만약 여러분도 같은 작업을 하고 있다면, 그것이 여러분의 참조 구현체(reference implementation)입니다. 문서는 여러분이 템플릿에서 시작했다고 가정하지만 실제로는 그렇지 않으므로, 이를 면밀히 읽어보시기 바랍니다.
아무도 말해주지 않는 비결: 사람이 확인하기 전에 CI를 통과시키는 법
8주간의 기다림을 당일 해결로 바꿔주는 부분은 바로 여기입니다. 공식 체크(official checks)는 유지 관리자가 duckdb/community-extensions에서 워크플로우(workflow)를 승인해야만 실행됩니다. 즉, 처음 기여하는 사람은 자동 실행 혜택을 받지 못합니다. 따라서 수정 사항을 푸시(push)하고 나서, 그것이 컴파일조차 되는지 확인하기 위해 며칠을 기다려야 할 수도 있습니다.
하지만 그럴 필요는 없습니다. 빌드 워크플로우는 일반적인 재사용 가능한 워크플로우(reusable workflow)이므로, 여러분의 포크(fork)에서 동일한 파이프라인을 workflow_dispatch로 실행할 수 있습니다:
gh workflow run "Community Extension Build" \
--repo /community-extensions \
--ref main \
...
7분 반이 지나자 DuckDB v1.5.2 (ci-tools v1.5-variegata)가 제가 직접 마련한 러너 예산으로, 메인테이너들이 실행할 것과 정확히 동일한 매트릭스인 linux_amd64, linux_arm64, osx_amd64, 그리고 osx_arm64에서 빌드되고 녹색(성공) 상태로 배포되었습니다. 이 실행 기록은 공개되어 있습니다: [github.com/singhpratech/community-extensions/actions/runs/28753457602]. 제가 마침내 메인테이너들에게 연락했을 때, 저는
PR #1898은 포크(fork)된 저장소에서 열려 있으며 초록색(성공) 상태입니다. 이제 메인테이너가 공식 실행(official run)을 승인하기만을 기다리고 있습니다. "gpudb가 CUDA 및 Metal에서 어떻게 GPU 메모리 대역폭을 포화시키는지"에 대한 전체 이야기와 INSTALL gpudb FROM community 명령어에 대한 내용은 머지(merge) 당일에 다룰 예정입니다. 이 포스트는 지루하고 솔직한 중간 과정, 즉 8주간의 빨간색(실패) 상태와 로그에 찍힌, 모든 해답을 담고 있었던 세 단어에 대한 이야기입니다.
만약 템플릿 프로젝트가 아닌 곳에서 자신만의 DuckDB 확장(extension)을 배포하고 있다면: C 헤더(C headers)를 벤더링(vendor)하고, c_api_extensions 메이크파일(makefiles)을 채택하며, C_STRUCT를 대상으로 빌드하고, 증명을 위해 포크된 저장소의 CI를 실행하며, 막혔을 때는 capi_quack을 읽으십시오. 그리고 특정 플랫폼이 빨간색(실패)으로 변할 때는—작업(job) 이름이 아니라 에러(error)를 읽으십시오.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기