DuckDB Go 클라이언트 문제 해결
DuckDB Go 클라이언트 문제 해결 (Troubleshoot)
DuckDB Go 클라이언트를 쓰다 겪는 흔한 문제와 해결 방법을 모아 두었어요. 클라이언트는 cgo로 DuckDB를 내장하기 때문에, 빌드 문제의 대부분은 cgo나 링커 문제예요. 여기 없는 문제는 GitHub의 클라이언트 이슈 트래커를 검색해 보세요.
출처: 문서
본문
DuckDB 오류 분류하기 (Classifying DuckDB Errors)
클라이언트는 DuckDB가 보고한 오류에 대해 *duckdb.Error를 반환해요. errors.As를 쓰면 메시지를 파싱하지 않고도 카테고리별로 오류를 처리할 수 있어요:
_, err := db.Exec(query)
if err != nil {
var duckdbError *duckdb.Error
if errors.As(err, &duckdbError) {
switch duckdbError.Type {
case duckdb.ErrorTypeCatalog:
// 존재하지 않는 테이블, 뷰, 기타 카탈로그 항목 처리.
case duckdb.ErrorTypeConstraint:
// 제약 조건 위반 처리.
}
}
}
duckdb.Error.Type은 duckdb.ErrorType이에요. 다른 카테고리로는 ErrorTypeBinder, ErrorTypeParser, ErrorTypeConversion, ErrorTypeIO, ErrorTypeOutOfRange, ErrorTypeInvalidInput이 있어요. Msg 필드에는 오류 메시지가 들어 있어요.
undefined: conn 및 기타 cgo 오류
빌드 중 undefined: conn 오류는 Go 컴파일러가 cgo를 사용할 수 없다고 판단해 DuckDB 바인딩이 컴파일되지 않았음을 뜻해요. 흔한 원인은 두 가지예요:
-
빌드 도구가 없는 경우. cgo는 C 컴파일러와 툴체인이 필요해요. Debian이나 Ubuntu에서는 다음으로 설치해요:
sudo apt-get update && sudo apt-get install build-essential -
크로스 컴파일이 cgo를 비활성화한 경우. Go 컴파일러는 크로스 컴파일 시 cgo를 자동으로 꺼요. 다시 켜고 올바른 크로스 컴파일러를 지정해요:
CC=⟨c_cross_compiler⟩ CGO_ENABLED=1 go build
Windows 설정
Windows에서는 호환되는 버전의 gcc와 필요한 런타임 라이브러리가 필요해요. 한 방법은 MSYS2예요: 설치 후 MSYS2 셸을 열고 UCRT64 툴체인을 설치해요:
pacman -S mingw-w64-ucrt-x86_64-gcc
gcc를 PATH에 추가해요. 예를 들어 PowerShell에서:
$env:PATH = "C:\msys64\ucrt64\bin;$env:PATH"
그러면 패키지가 Windows에서 컴파일돼요.
DuckDB 링크하기 (Linking DuckDB)
기본적으로 클라이언트는 미리 빌드된 DuckDB 라이브러리를 바이너리에 정적으로 링크해요. 이러면 바이너리 크기는 커지지만 별도의 DuckDB 설치가 필요 없어요. 미리 빌드된 라이브러리는 macOS(amd64, arm64), Linux(amd64, arm64), Windows(amd64)용으로 제공돼요. 미리 빌드된 라이브러리가 맞지 않으면 커스텀 라이브러리를 링크하면 돼요.
커스텀 정적 라이브러리 링크
DuckDB의 bundle-library Makefile 타겟으로 소스에서 DuckDB 정적 라이브러리를 빌드하면 build/release/libduckdb_bundle.a가 생성돼요:
cd /path/to/duckdb
make bundle-library DUCKDB_PLATFORM=any BUILD_EXTENSIONS="icu;json;parquet;autocomplete"
그런 다음 duckdb_use_static_lib 빌드 태그와 아카이브용 링커 플래그로 그 아카이브에 대해 Go 모듈을 빌드해요. 예를 들어 macOS ARM64에서는:
CGO_ENABLED=1 \
CPPFLAGS="-DDUCKDB_STATIC_BUILD" \
CGO_LDFLAGS="-lduckdb_bundle -lc++ -L/path/to/libs" \
go build -tags=duckdb_use_static_lib
이 방식은 Go 클라이언트에서 로컬 DuckDB 체크아웃을 검증하는 방법이기도 하고, 클라이언트 자신의 Makefile과 CI가 빌드하는 방식이기도 해요. 미리 빌드된 라이브러리가 없는 플랫폼, 특히 DuckDB가 번들 라이브러리를 게시하지 않는 FreeBSD를 타게팅할 때 필요해요.
동적 라이브러리 링크
혹은 duckdb_use_lib 빌드 태그로 시스템의 libduckdb 공유 라이브러리에 동적으로 링크할 수도 있어요. DuckDB 릴리스 페이지에서 공유 라이브러리(Linux는 .so, macOS는 .dylib)를 내려받은 뒤, 로더 경로에 라이브러리를 두고 빌드·실행해요:
# On Linux.
CGO_ENABLED=1 CGO_LDFLAGS="-lduckdb -L/path/to/libs" go build -tags=duckdb_use_lib main.go
LD_LIBRARY_PATH=/path/to/libs ./main
# On macOS.
CGO_ENABLED=1 CGO_LDFLAGS="-lduckdb -L/path/to/libs" go build -tags=duckdb_use_lib main.go
DYLD_LIBRARY_PATH=/path/to/libs ./main
TIMESTAMP vs TIMESTAMP_TZ
C API에서 DuckDB는 TIMESTAMP와 TIMESTAMP_TZ를 모두 1970-01-01 UTC 이후 마이크로초 수라는 동일한 인스턴트로 저장하며, 오프셋 정보는 없어요. 클라이언트에 time.Time이 전달되면 UnixMicro()로 그 인스턴트로 변환돼요. TIMESTAMP_TZ도 마찬가지예요. 그리고 SQL 타입이 값별 시간대를 모델링하지 않으므로 두 타입 중 아무거나 스캔해도 인스턴트가 돌아와요.
베어 time.Time은 기본적으로 TIMESTAMP_TZ로 바인딩돼요. TIMESTAMP_NS 열에 바인딩하는 것처럼 다른 타임스탬프 타입이 필요하면 duckdb.Typed()로 타입을 강제해요:
row := db.QueryRow(query,
duckdb.Typed(start, duckdb.TYPE_TIMESTAMP_NS),
duckdb.Typed(end, duckdb.TYPE_TIMESTAMP_NS),
)
전체 예시는 쿼리 실행 (Run Queries)을 참고해요.
JSON 값 스캔하기
v2부터 JSON 값을 string이나 []byte로 직접 스캔하는 것은 더 이상 지원되지 않아요. 쿼리 실행에서 보여 주는 것처럼 any나 duckdb.Composite 래퍼로 스캔해요. 평범한 string/byte 결과가 필요하고 JSON 구조가 필요 없다면, 스캔 전에 열을 SQL에서 ::VARCHAR나 ::BLOB으로 캐스팅해요.
더 읽어보기 (Further Reading)
- Go 클라이언트 — 설치, 빌드 태그, 번들 익스텐션.
- 연결 (Connect) — 데이터베이스 열기와 연결 수명주기. 실패는 대개 빌드나 링크 문제예요.
- 쿼리 실행 (Run Queries) —
duckdb.Typed()힌트와 JSON 스캔을 포함한 파라미터 바인딩.