연결
연결 (Connect)
Go 클라이언트는 표준 database/sql 타입을 통해 동작해요. *sql.DB가 DuckDB 데이터베이스에 대한 커넥션 풀이죠. 이 페이지에서는 인메모리/파일 기반 데이터베이스를 여는 방법, DuckDB 설정 옵션을 넘기는 방법, Connector로 초기화 단계를 실행하는 방법, 클라이언트의 스레드 안전성과 커넥션 수명 규칙, 그리고 데이터베이스를 깔끔하게 닫는 방법을 다뤄볼게요.
출처: 문서
본문
데이터베이스 열기
sql.Open()은 드라이버 이름 duckdb와 데이터 소스 이름(DSN)을 받아요. 빈 DSN이나 :memory: DSN은 인메모리 데이터베이스를 열고, 파일 경로는 영구 데이터베이스를 여는데 파일이 없으면 새로 만들어요.
// In-memory database: nothing is persisted to disk.
// An empty DSN and ":memory:" are equivalent.
db, err := sql.Open("duckdb", "")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// File-backed database: created if it does not exist.
db, err := sql.Open("duckdb", "/path/to/foo.db")
sql.Open()이 반드시 커넥션을 여는 건 아니에요. 인자만 검증하고, 커넥션을 지연해서(lazily) 여는 *sql.DB를 반환하죠. 데이터베이스에 도달 가능한지 확인하려면 db.Ping()을 호출해 보세요.
데이터 소스 이름 (DSN)
DuckDB [설정 옵션]({% link docs/current/configuration/overview.md %})은 URL 스타일의 name=value 쿼리 파라미터로 DSN에 붙이는데, &로 구분해요.
db, err := sql.Open("duckdb", "/path/to/foo.db?access_mode=read_only&threads=4")
access_mode=read_only 옵션은 데이터베이스 파일을 여러 프로세스가 동시에 읽을 수 있게 열어줘요. 전체 옵션 목록은 [Configuration 페이지]({% link docs/current/configuration/overview.md %})에서 볼 수 있고, 커넥션 후에도 [SET 문]({% link docs/current/sql/statements/set.md %})으로 변경할 수 있어요.
Connector로 초기화 단계 실행하기
어떤 쿼리보다 먼저 설정 문을 실행해야 하거나, DSN이 아니라 코드에서 DuckDB를 설정하고 싶다면 duckdb.NewConnector()로 Connector를 만들고 sql.OpenDB()로 데이터베이스를 열면 돼요. 커넥터의 두 번째 인자는 각 새 커넥션에 대해 실행되는 콜백이에요. 부트 쿼리가 바로 여기 들어가요.
connector, err := duckdb.NewConnector("/path/to/foo.db?threads=4", func(execer driver.ExecerContext) error {
bootQueries := []string{
"SET schema = 'main'",
"SET search_path = 'main'",
}
for _, query := range bootQueries {
if _, err := execer.ExecContext(context.Background(), query, nil); err != nil {
return err
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
defer connector.Close()
db := sql.OpenDB(connector)
defer db.Close()
초기화가 필요 없으면 콜백에 nil을 넘기면 돼요. Connector는 그로부터 얻은 개별 커넥션에서 동작하는 [Appender]({% link docs/current/clients/go/data_import.md %}#appender)와 [Arrow]({% link docs/current/clients/go/result_handling.md %}) 인터페이스, 그리고 Connector 자체에 등록되는 [replacement scan]({% link docs/current/clients/go/functions.md %}#replacement-scans)과 [로그 저장소]({% link docs/current/clients/go/profiling.md %}#log-storage)의 진입점이기도 해요.
커넥션 로컬 작업
일부 기능은 풀이 아니라 단일 커넥션에 한정돼요. 프로파일링, 사용자 정의 함수 등록, Appender가 모두 한 커넥션에서 동작하죠. db.Conn()으로 풀에서 커넥션을 체크아웃할 수 있는데, 닫기 전까지 다른 goroutine과 공유되지 않는 *sql.Conn을 반환해요.
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
클라이언트는 *sql.Conn을 받는 헬퍼 두 개도 제공해요. duckdb.GetTableNames()는 쿼리가 참조하는 테이블 이름(선택적으로 정규화)을 반환하고, duckdb.ConnId()는 기본 DuckDB 커넥션 ID를 반환해요.
스레드 안전성과 동시성
*sql.DB는 여러 goroutine의 동시 사용에 안전해요. 자체 커넥션 풀을 관리하고, 진행 중인 각 쿼리에 커넥션 하나씩을 할당하죠. db.Conn()으로 체크아웃한 단일 *sql.Conn과, 그 위에 만들어진 DuckDB [Appender]({% link docs/current/clients/go/data_import.md %}#appender)·[Arrow]({% link docs/current/clients/go/result_handling.md %}) 핸들은 동시 사용에 안전하지 않아요. DuckDB는 자체 네이티브 스레드 풀에서 쿼리를 실행하는데, 그 규모는 threads 옵션으로 정해지고 풀 전체가 공유해요. 커넥션과 스레드의 상호작용은 [DuckDB 동시성 문서]({% link docs/current/connect/concurrency.md %})를 참고해 주세요.
커넥션 수명
[임시 테이블]({% link docs/current/sql/statements/create_table.md %}#temporary-tables) 같은 임시 객체는 커넥션에 한정돼요. 코드가 커넥션을 닫으면 database/sql은 실제로 닫는 대신 풀에 유휴 커넥션으로 반환할 수 있어서, 임시 테이블이 그것을 만든 코드보다 오래 살 수 있어요. 커넥션을 닫으면 실제로 정리되게 하려면 유휴 커넥션을 비활성화하세요.
db.SetMaxIdleConns(0)
데이터베이스 닫기
DuckDB는 Go 애플리케이션과 같은 프로세스에서 실행되므로 모든 메모리가 그 프로세스에 속해요. database/sql 객체를 닫아야 DuckDB의 메모리가 해제되고, 영구 데이터베이스의 경우 [write-ahead log]({% link docs/current/internals/storage.md %})가 디스크로 플러시돼요. Close() 메서드가 있는 객체는 모두 닫는 게 좋은데, defer로 편하게 처리할 수 있어요.
db, _ := sql.Open("duckdb", "")
defer db.Close()
conn, _ := db.Conn(context.Background())
defer conn.Close()
rows, _ := conn.QueryContext(context.Background(), "SELECT 42")
defer rows.Close() // or drain the rows until Next() returns false
connector, _ := duckdb.NewConnector("", nil)
defer connector.Close() // if the connector is passed to sql.OpenDB
영구 데이터베이스를 닫지 않으면 변경 사항이 write-ahead log에 남아 영구 저장소와 동기화되지 않을 수 있어요. 오래 실행되는 애플리케이션에서는 메모리를 관리하기 위해 결과·커넥션·데이터베이스를 제때 닫는 게 필수적이에요.
더 알아보기 (Learn more)
- [쿼리 실행]({% link docs/current/clients/go/querying.md %}) — 여기서 연 데이터베이스와 커넥션으로 쿼리를 보내고 결과를 읽는 방법.
- [Configuration]({% link docs/current/configuration/overview.md %}) — DSN이나 커넥터 콜백으로 넘길 수 있는 DuckDB 설정 전체 목록.
- [동시성]({% link docs/current/connect/concurrency.md %}) — DuckDB가 여러 커넥션과 스레드를 다루는 방식.
- [문제 해결]({% link docs/current/clients/go/troubleshoot.md %}) — 데이터베이스를 열 때 겪는 cgo, 링킹 등 빌드 문제.