연결
연결 (Connect)
Java에서 쓸 수 있는 모든 DuckDB 기능은 Connection에서 시작해요. 이 페이지에서는 드라이버가 받아들이는 JDBC URL 형식, DuckDB와 드라이버 옵션을 넘기는 방법과 어떤 것이 우선하는지, 커넥션이 기본 데이터베이스 인스턴스를 공유(또는 비공유)하는 방식, DuckDB의 스레드가 그 인스턴스와 어떤 관계인지, [DuckLake]({% link docs/current/core_extensions/ducklake.md %})를 여는 법·연결하는 법, 그리고 [Quack]({% link docs/current/quack/overview.md %})으로 원격 DuckDB 서버에 도달하는 법을 다뤄볼게요.
출처: 문서
본문
커넥션 열기
JDBC에서 데이터베이스 커넥션은 표준 java.sql.DriverManager 클래스를 통해 만들어요. 최신 JVM에서는 JDBC JAR가 클래스패스에 있으면 드라이버가 DriverManager에 자동으로 등록돼요(Java의 service-provider 메커니즘 덕분). 그래서 별도 설정이 필요 없어요. 어떤 이유로 등록이 안 되면, 커넥션을 열기 전에 DuckDBDriver 클래스를 명시적으로 로드해서 강제 등록하면 돼요.
Class.forName("org.duckdb.DuckDBDriver");
Connection conn = DriverManager.getConnection("jdbc:duckdb:");
[GraalVM Native Image]({% link docs/current/clients/java/deploy_native_image.md %}) 실행 파일에서는 항상 이렇게 명시적으로 로드해야 해요. service-provider 탐색은 Native Image의 정적 분석에 신뢰성 있게 보이지 않기 때문이에요.
DuckDB 커넥션을 만들려면 jdbc:duckdb: JDBC URL 프리픽스로 DriverManager를 호출하면 돼요.
import java.sql.Connection;
import java.sql.DriverManager;
Connection conn = DriverManager.getConnection("jdbc:duckdb:");
[Appender]({% link docs/current/clients/java/data_import.md %}#appender) 같은 DuckDB 전용 기능을 쓰려면 객체를 DuckDBConnection으로 캐스팅하세요.
import java.sql.DriverManager;
import org.duckdb.DuckDBConnection;
DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
추가 커넥션도 DriverManager로 만들 수 있어요. DuckDBConnection#duplicate() 메서드는 설정을 다시 읽지 않고 같은 데이터베이스 인스턴스에 다른 커넥션을 열어줘요.
DuckDBConnection conn2 = ((DuckDBConnection) conn).duplicate();
이 메서드의 주된 목적은 커넥션 전용 인메모리 데이터베이스 인스턴스에 도달하는 거예요. 그건 일반 jdbc:duckdb: URL이 만드는 인스턴스라 다른 어떤 URL로도 주소를 지정할 수 없거든요. 이름 붙은 인메모리 또는 파일 기반 커넥션에서는 이 인스턴스들이 어차피 인스턴스 캐시를 통해 공유되므로, duplicate()는 같은 URL로 새 커넥션을 여는 것과 사실상 같아요.
같은 인스턴스에 여러 커넥션을 여는 건 허용되지만, 모두 같은 옵션으로 열어야 해요. 옵션은 첫 커넥션을 열 때 인스턴스가 시작되면서 적용되기 때문에, 나중에 다른 옵션(예: 다른 access_mode)을 요청하는 커넥션은 거부돼요. Data베이스 인스턴스와 인스턴스 캐싱을 참고하세요.
JDBC URL 문법
DuckDB JDBC URL은 jdbc:duckdb: 프리픽스 + 선택적 데이터베이스 이름 + 세미콜론으로 구분된 선택적 옵션 목록으로 구성돼요.
jdbc:duckdb:⟨database⟩;⟨key⟩=⟨value⟩;⟨key⟩=⟨value⟩
데이터베이스 부분이 커넥션이 무엇을 여는지 결정해요.
| JDBC URL | 열리는 것 |
|---|---|
jdbc:duckdb: |
커넥션 전용 인메모리 데이터베이스. |
jdbc:duckdb:memory: |
위와 동일, 명시적으로 적은 것. |
jdbc:duckdb:memory:⟨label⟩ |
다른 커넥션이 공유할 수 있는 이름 붙은 인메모리 데이터베이스. |
jdbc:duckdb:⟨path⟩ |
디스크의 데이터베이스 파일. 예: jdbc:duckdb:/tmp/my_database. |
jdbc:duckdb:ducklake:⟨metadata_path⟩ |
[DuckLake]({% link docs/current/core_extensions/ducklake.md %}) 카탈로그. |
jdbc:duckdb: URL만 사용하면 커넥션 전용 인메모리 데이터베이스가 만들어져요. 인스턴스는 그 단일 커넥션에 속해 duplicate() 외에는 공유할 수 없어요. 인메모리 데이터베이스는 디스크에 아무것도 저장되지 않으니(즉 Java 프로그램을 종료하면 모든 데이터가 사라져요) 이 점을 기억하세요. 영구 데이터베이스에 접근하거나 만들려면 경로 뒤에 파일 이름을 붙이면 돼요.
읽기 전용 커넥션
DuckDB 데이터베이스 파일을 읽기 전용 모드로 열 수 있어요. 여러 Java 프로세스가 같은 데이터베이스 파일을 동시에 읽고 싶을 때 유용하죠. 기존 데이터베이스 파일을 읽기 전용으로 열려면 커넥션 속성 duckdb.read_only를 설정하면 돼요.
Properties readOnlyProperty = new Properties();
readOnlyProperty.setProperty("duckdb.read_only", "true");
Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database", readOnlyProperty);
읽기 전용 모드는 DuckDB 표준 access_mode 속성으로도 설정할 수 있어요. 예: access_mode=READ_ONLY. duckdb.read_only와 access_mode가 둘 다 제공됐는데 서로 다르면 드라이버가 에러를 던져요.
설정 옵션
DuckDB 설정
커넥션을 열 때 어떤 DuckDB 설정 옵션이든 제공할 수 있어요. 드라이버는 자기 것으로 인식하지 못하는 모든 옵션을 DuckDB에 넘겨줘요. 참고로 많은 설정은 나중에 [SET(또는 SET GLOBAL) 문]({% link docs/current/sql/statements/set.md %})이나 동등한 [PRAGMA]({% link docs/current/configuration/pragmas.md %})로도 바꿀 수 있어요.
Properties connectionProperties = new Properties();
connectionProperties.setProperty("temp_directory", "/path/to/temp/dir/");
connectionProperties.setProperty("memory_limit", "4GB");
Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database", connectionProperties);
사용 가능한 옵션은 [Configuration 페이지]({% link docs/current/configuration/overview.md %})에 나열돼 있고, 실행 중에 duckdb_settings()로 조회할 수도 있어요. 드라이버는 표준 Driver.getPropertyInfo() 호출로 같은 목록을 노출하는데, DuckDB 설정마다 하나씩 DriverPropertyInfo를 드라이버 자체 옵션과 함께 반환해요.
Note 커넥션 시점에 넘긴 옵션은 데이터베이스 인스턴스의 전역 설정으로 적용돼요. 세션 스코프만 가진 설정은 커넥션 후
SET문으로 적용해야 해요. 커넥션 시점에 설정하면Could not set option "⟨name⟩" as a global option오류가 나요. 어떤 설정인지 찾으려면duckdb_settings()를 조회하고scope = 'LOCAL'로 필터링하세요.search_path,schema,enable_profiling/profiling_mode계열이 흔한 예예요.
URL에서 옵션 설정하기
같은 옵션을 세미콜론으로 구분해 JDBC URL에 붙일 수도 있어요. 커넥션 문자열은 받지만 Properties 객체는 받지 않는 도구에서 DuckDB를 설정하는 유일한 방법이에요.
jdbc:duckdb:/tmp/my_database;threads=4;memory_limit=4GB;jdbc_stream_results=true
각 항목은 key=value 쌍이고 주변 공백은 잘라내요. 드라이버는 URL을 ;로, 각 항목을 =로 분리하므로, 값 자체에 세미콜론이나 등호가 들어가면 대신 Properties 객체로 넘겨야 해요.
옵션 우선순위
같은 옵션이 두 곳에 모두 있으면 URL의 값이 이겨요.
Properties props = new Properties();
props.setProperty("threads", "8");
// The connection is opened with threads = 4.
Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database;threads=4", props);
지원되지 않는 옵션
DuckDB 설정도 드라이버 옵션도 아닌 옵션 이름이면 커넥션이 실패해요. jdbc_ignore_unsupported_options를 true로 설정하면 드라이버가 그런 이름을 조용히 버려요. 자체 JDBC 속성을 추가하는 프레임워크/BI 도구에 유용하죠. 드라이버는 이미 실수로 넘겨지는 것으로 알려진 두 옵션(Apache Spark의 path, LibreOffice Base의 Type)을 버려요.
JDBC 전용 옵션
DuckDB 자체 설정과 함께, 드라이버는 DuckDB 전용 JDBC 커넥션 속성 몇 가지를 인식해요.
| Property | Description |
|---|---|
duckdb.read_only |
데이터베이스를 읽기 전용 모드로 엽니다. |
access_mode |
표준 DuckDB 접근 모드: READ_ONLY, READ_WRITE, 또는 AUTOMATIC. |
custom_user_agent |
DuckDB에 보고되는 user agent에 사용자 문자열을 덧붙입니다. |
jdbc_stream_results |
결과 집합을 실체화하지 않고 스트리밍합니다. [Streaming Results]({% link docs/current/clients/java/result_handling.md %}#streaming-results) 참고. |
jdbc_auto_commit |
새 커넥션의 기본 auto-commit 모드를 설정합니다. |
jdbc_pin_db |
마지막 커넥션이 닫힌 뒤에도 데이터베이스 인스턴스를 유지합니다. 기본 비활성화. ducklake: URL에서는 jdbc_stream_results와 함께 기본 활성화되어, Metabase 같은 BI 도구가 인스턴스를 반복해서 닫고 다시 만들거나 대용량 DuckLake 데이터셋을 실수로 실체화하지 않게 합니다. |
jdbc_instance_cache |
같은 데이터베이스에 대해 프로세스 전역 인스턴스를 재사용합니다. 기본 활성화. Database Instances and Instance Caching 참고. |
jdbc_ignore_unsupported_options |
지원되지 않는 커넥션 옵션을 에러 대신 조용히 무시합니다. |
jdbc_jfr_memory_monitor |
커넥션의 데이터베이스 인스턴스에 대한 JFR 메모리 모니터링을 켭니다. [Memory Monitoring with JFR]({% link docs/current/clients/java/profiling.md %}#memory-monitoring-with-jfr) 참고. |
session_init_sql_file |
커넥션을 넘겨주기 전에 주어진 파일의 SQL을 실행합니다. URL 전용. Running SQL at Connection Startup 참고. |
session_init_sql_file_sha256 |
세션 init SQL 파일의 예상 SHA-256 다이제스트. URL 전용. |
커넥션 시작 시 SQL 실행
session_init_sql_file 옵션은 커넥션이 호출자에게 반환되기 전에 실행할 로컬 파일시스템의 SQL 파일을 가리켜요. 커넥션 문자열은 제어하지만 커넥션을 여는 코드는 제어하지 못하는 환경(예: JDBC URL 필드만 제공하는 BI 도구)을 위한 거예요.
jdbc:duckdb:/tmp/my_database;session_init_sql_file=/path/to/init.sql
파일은 마커 주석으로 데이터베이스 부분과 커넥션 부분으로 나눌 수 있어요.
-- Runs once, when the database instance is created.
SET memory_limit = '4GB';
CREATE OR REPLACE VIEW recent_events AS
SELECT *
FROM events
WHERE ts > now() - INTERVAL 7 DAYS;
/* DUCKDB_CONNECTION_INIT_BELOW_MARKER */
-- Runs for every connection.
SET search_path = 'analytics';
마커 위의 모든 것은 JVM 프로세스에서 데이터베이스를 처음 열 때 실행되고, 아래의 모든 것은 매 커넥션마다 실행돼요. 마커가 없는 파일은 전체가 데이터베이스 초기화로 처리돼요. 이름 없는 인메모리 데이터베이스의 경우 커넥션마다 각자 인스턴스를 가지므로 데이터베이스 부분도 매 커넥션마다 실행돼요.
이 옵션은 커넥션 문자열이 임의의 SQL을 실행하게 만들기 때문에 드라이버가 제약을 둬요.
- JDBC URL에서만 읽히고
Properties객체에서는 받지 않아요. - 커넥션 문자열의 첫 번째 옵션이어야 하고,
session_init_sql_file_sha256이 있다면 두 번째여야 해요. 둘 다 한 번 이상 나타나면 안 돼요. - 파일은 1MB보다 클 수 없어요.
session_init_sql_file_sha256이 주어졌는데 파일의 다이제스트와 일치하지 않으면 커넥션 열기가 실패해요. 배포 후 수정된 파일을 감지하는 데 쓰세요.DuckDBConnection.getSessionInitSQL()은 실행된 텍스트를 반환하므로 애플리케이션이 기록하거나 감사할 수 있어요.
Warning 파일의 SQL은 커넥션의 전체 권한으로 실행돼요. 커넥션 문자열과 그가 가리키는 파일을 신뢰할 수 있는 입력으로 취급하세요.
데이터베이스 인스턴스와 인스턴스 캐싱
JDBC URL이 커넥션이 단일 기본 데이터베이스 인스턴스를 공유할지, 아니면 각자 가질지를 결정해요. 기본적으로 드라이버는 데이터베이스 인스턴스를 캐시해서 같은 데이터베이스를 가리키는 커넥션이 그것을 재사용해요.
동작은 URL 형태에 따라 달라져요.
| JDBC URL | 동작 |
|---|---|
jdbc:duckdb: 또는 jdbc:duckdb:memory: |
커넥션 전용, 캐시되지 않는 인메모리 데이터베이스. 각 커넥션이 별도 인스턴스를 갖고 데이터는 다른 커넥션과 공유되지 않음. |
jdbc:duckdb:memory:⟨label⟩ |
이름 붙은 인메모리 데이터베이스. 같은 label을 쓰는 커넥션이 :memory:⟨label⟩ 키로 캐시된 인스턴스 하나를 공유함. |
jdbc:duckdb:⟨path⟩ |
파일 기반 데이터베이스. 인스턴스는 데이터베이스 파일의 절대 경로를 캐시 키로 사용해 캐시되므로, 같은 파일을 여는 커넥션은 인스턴스 하나를 공유함. Windows와 macOS에서는 키가 대소문자를 구분하지 않음. |
// Two connections to the same named in-memory database point to the same database instance.
Connection conn1 = DriverManager.getConnection("jdbc:duckdb:memory:shared_db");
Connection conn2 = DriverManager.getConnection("jdbc:duckdb:memory:shared_db");
인스턴스 캐싱은 기본 활성화된 jdbc_instance_cache 커넥션 속성으로 제어돼요. false로 설정하면 커넥션당 격리된 인스턴스가 만들어져요. 파일 기반 데이터베이스에서 캐시를 비활성화하면 다른 커넥션이 같은 파일을 열어둔 경우 로컬 파일 잠금 충돌이 생길 수 있고, 캐시되지 않은 인스턴스를 jdbc_pin_db로 고정해도 재사용 가능해지진 않아요.
설정 옵션은 커넥션이 아니라 인스턴스에 적용되기 때문에, 인스턴스를 만드는 커넥션만 설정할 수 있어요. 캐시된 인스턴스에 나중에 도달한 커넥션이 다른 구성을 요청하면 다음과 같이 실패해요.
Connection Error:
Can't open a connection to same database file with a different configuration than existing connections
주어진 데이터베이스에 대한 모든 커넥션에 같은 옵션을 넘기거나, 첫 커넥션을 의도한 구성으로 열고 나머지는 duplicate()로 만들면 돼요.
스레드와 스레드 풀
DuckDB는 자체 네이티브 스레드 풀에서 쿼리를 실행해요. 이 풀은 JVM 스레드와 독립적이에요. 단일 Statement.executeQuery() 호출이 풀의 모든 스레드를 차지할 수 있고, 많은 애플리케이션 스레드의 블로킹 JDBC 호출이 DuckDB의 병렬성을 늘리진 않아요.
풀은 Connection이 아니라 데이터베이스 인스턴스에 속해요. 같은 인스턴스에 대한 모든 커넥션은 DriverManager로 만들었든 duplicate()로 만들었든 같은 풀에 작업을 제출해요. Data베이스 인스턴스와 인스턴스 캐싱에서처럼 다른 인스턴스로 해석되는 두 커넥션은 각자 자체 풀을 가져요.
풀 크기는 DuckDB의 threads 옵션으로 정해지는데, 기본값은 CPU 코어 수예요. DuckDB는 그 예산 중 external_threads(기본 1)를 라이브러리에 호출하는 스레드용으로 예약하고, threads - external_threads개의 백그라운드 스레드를 시작해요. 16코어 머신이라면 데이터베이스 인스턴스당 네이티브 스레드 15개예요.
스레드 수를 제한하려면 인스턴스 생성 시점에 threads를 설정하세요.
jdbc:duckdb:/tmp/my_database;threads=4
이미 실행 중인 인스턴스는 SET 문으로 크기를 조절할 수 있는데, 그 인스턴스의 모든 커넥션에 영향을 줘요.
SET threads = 4;
Java 애플리케이션에는 두 가지 결과가 짚어볼 만해요.
- 부착된 데이터베이스는 스레드를 추가하지 않아요. [
ATTACH]({% link docs/current/sql/statements/attach.md %})는 커넥션이 이미 속한 데이터베이스 인스턴스에 카탈로그를 추가할 뿐이에요. 모든 부착 데이터베이스가 그 인스턴스의 스레드 풀·메모리 한도·임시 디렉터리를 공유해요. 다른 데이터베이스 이름에 대한 새 JDBC 커넥션을 열어야 두 번째 인스턴스, 즉 두 번째 풀이 생겨요. - 여러 인스턴스는 스레드 수를 배가시킵니다. 16코어 머신에서
jdbc:duckdb:로 열린 전용 인메모리 데이터베이스 12개를 여는 애플리케이션은 각자 스택과 메모리 한도 몫을 가진 네이티브 스레드 약 180개를 시작해요.
Warning 각 전용(캐시되지 않은) 인메모리 데이터베이스는 독립 스레드 풀을 가진 자체 인스턴스를 만들어요. 기본
jdbc:duckdb:URL로 많은 커넥션을 여는 애플리케이션은 운영체제 스레드를 대량 생성할 수 있어요. 이름 붙은 인메모리 데이터베이스를 쓰거나, 인스턴스 캐시로 인스턴스 하나를 공유하거나,threads옵션을 설정해 스레드 수를 제한하세요.
Note 곧 나올 DuckDB v2.0은 블로킹 I/O에 전용인
ASYNC스레드 두 번째 풀을 추가하는데, 크기는 시스템 스레드 수의 4배, 상한 256이에요. 16코어 머신에서 기본 인스턴스당 스레드 수가 약 15에서 약 80으로 늘어나므로, 인스턴스 수를 제한하고threads를 명시적으로 설정하는 게 더 중요해져요. 자세한 내용은 [asynchronous I/O 블로그 글]({% post_url 2026-07-31-asynchronous-io %})을 참고하세요.
DuckDB의 풀과 별개로, 드라이버는 처음 로드될 때 JVM에 duckdb-query-cancel-scheduler-thread라는 단일 데몬 스레드를 시작해요. 전체 프로세스가 공유하며 Statement.setQueryTimeout() 취소를 발화하는 데 쓰여요. 애플리케이션 서버에서 드라이버를 내리는 애플리케이션은 DuckDBDriver.shutdownQueryCancelScheduler()로 멈출 수 있어요.
DuckLake에 연결
[DuckLake]({% link docs/current/core_extensions/ducklake.md %})는 테이블 데이터를 Parquet 파일로 저장하고 그 메타데이터를 카탈로그 데이터베이스(DuckDB 파일·SQLite 파일·PostgreSQL 서버 가능)에 저장해요. JDBC에서 레이크에 도달하는 방법은 두 가지예요. 일반 커넥션에 부착하거나, ducklake: URL 형태로 커넥션의 기본 데이터베이스로 직접 여는 거예요.
DuckLake 부착
[ATTACH]({% link docs/current/sql/statements/attach.md %}) 문은 어떤 JDBC 커넥션에서든 동작하며, DATA_PATH 같은 DuckLake 옵션을 넘기는 유일한 방법이에요. 레이크를 처음 만들 때나, 애플리케이션이 자체 로컬 데이터베이스도 필요할 때 사용하세요.
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:");
Statement stmt = conn.createStatement()) {
stmt.execute("ATTACH 'ducklake:metadata.ducklake' AS lake (DATA_PATH 'lake_files/')");
stmt.execute("USE lake");
stmt.execute("CREATE TABLE IF NOT EXISTS events (id INTEGER, payload VARCHAR)");
stmt.execute("INSERT INTO events VALUES (1, 'hello'), (2, 'world')");
try (ResultSet rs = stmt.executeQuery("SELECT count(*) FROM events")) {
rs.next();
System.out.println(rs.getLong(1));
}
}
ducklake 확장은 첫 ATTACH 때 자동 로드돼요. 명시적 INSTALL ducklake; LOAD ducklake;는 자동 로드가 비활성화된 시스템에서만 필요해요.
DuckLake 직접 열기
레이크가 존재하면 DuckLake 경로를 jdbc:duckdb: 프리픽스 뒤에 두어 커넥션의 기본 데이터베이스로 열 수 있어요. 그러면 테이블을 카탈로그 프리픽스 없이 주소 지정할 수 있고 USE 문도 필요 없어요.
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:ducklake:metadata.ducklake");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, payload FROM events ORDER BY id")) {
while (rs.next()) {
System.out.println(rs.getInt(1) + " " + rs.getString(2));
}
}
ducklake: 뒤의 부분이 카탈로그 데이터베이스를 선택해요.
| JDBC URL | 카탈로그 데이터베이스 |
|---|---|
jdbc:duckdb:ducklake:⟨path⟩.ducklake |
DuckDB 파일. |
jdbc:duckdb:ducklake:sqlite:⟨path⟩.sqlite |
SQLite 파일. |
jdbc:duckdb:ducklake:postgres:⟨connection_string⟩ |
PostgreSQL 서버 (예: postgresql://user:***@host:5432/lake_catalog). |
ATTACH 옵션은 JDBC URL로 제공할 수 없으므로, 이런 방식으로 연 레이크는 생성 시점에 기록된 DATA_PATH를 사용해요. 첫 ; 뒤의 모든 것은 여전히 커넥션 옵션으로 파싱되므로, PostgreSQL 커넥션 문자열 자체에 세미콜론이 들어가면 안 돼요.
ducklake: URL에서는 드라이버가 기본값 두 개를 바꿔서, 커넥션을 자주 열고 닫는 Metabase 같은 BI 도구가 매 요청마다 데이터베이스 인스턴스를 재시작하거나 큰 테이블을 실수로 실체화하지 않게 해요.
jdbc_pin_db가 활성화되어 마지막 커넥션이 닫힌 뒤에도 인스턴스를 유지해요. 애플리케이션 종료 시DuckDBDriver.releaseDB(url)로 해제하세요.jdbc_stream_results가 활성화돼요. 결과는 [Streaming Results]({% link docs/current/clients/java/result_handling.md %}#streaming-results)를 참고하세요.
둘 중 어떤 기본값이든 옵션을 명시적으로 설정해 덮어쓸 수 있어요. 예: jdbc:duckdb:ducklake:metadata.ducklake;jdbc_pin_db=false.
객체 스토리지 자격 증명
데이터 파일이 S3, GCS, 또는 다른 원격 파일시스템에 있으면 자격 증명은 커넥션을 연 후 만드는 [secret]({% link docs/current/configuration/secrets_manager.md %})으로 제공해요.
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:");
Statement stmt = conn.createStatement()) {
stmt.execute("CREATE SECRET (TYPE s3, KEY_ID '⟨key⟩', SECRET '⟨secret⟩', REGION 'us-east-1')");
stmt.execute("ATTACH 'ducklake:postgres:postgresql://user:***@host:5432/lake_catalog' AS lake "
+ "(DATA_PATH 's3://my-bucket/lake/')");
stmt.execute("USE lake");
// ...
}
JDBC URL 필드만 제공하는 도구에서는 같은 문을 session_init_sql_file 옵션으로 파일에서 실행할 수 있어요. 레이크는 init 파일이 실행되기 전에 열리는데, 그래도 괜찮아요. 레이크를 여는 것은 카탈로그 데이터베이스만 읽고, 데이터 파일은 테이블을 쿼리할 때 처음 접촉되거든요. 임시 secret은 인스턴스의 모든 커넥션에 보이므로 파일 어느 부분에든 넣을 수 있어요. 마커 아래에 두면 인스턴스가 이미 초기화됐는지와 무관하게 동작해요.
/* DUCKDB_CONNECTION_INIT_BELOW_MARKER */
CREATE OR REPLACE TEMPORARY SECRET s3_lake (
TYPE s3,
KEY_ID '⟨key⟩',
SECRET '⟨secret⟩',
REGION 'us-east-1'
);
jdbc:duckdb:ducklake:postgres:postgresql://user:***@host:5432/lake_catalog;session_init_sql_file=/etc/duckdb/lake_init.sql
Warning JDBC URL과 init 파일 모두 자격 증명을 담아요. 데이터베이스 비밀번호와 같은 방식으로 기밀로 취급하세요.
객체 스토리지의 읽기 전용 레이크
카탈로그 데이터베이스와 데이터 파일이 모두 객체 스토리지에 있으면, 로컬 파일 없이 전체 레이크를 읽기 전용으로 부착할 수 있어요. 흔히 "frozen"(얼어붙은) 레이크라고 불러요. [s3://]({% link docs/current/core_extensions/httpfs/s3api.md %}), [gcs://]({% link docs/current/guides/network_cloud_storage/gcs_import.md %}), [https://]({% link docs/current/core_extensions/httpfs/https.md %})로 제공되는 DuckDB 또는 SQLite 카탈로그 파일에서 동작해요. 카탈로그 파일은 TYPE ducklake로 ATTACH 대상으로 주고, 원격 파일은 쓸 수 없으므로 READ_ONLY가 필수예요.
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:");
Statement stmt = conn.createStatement()) {
stmt.execute("CREATE SECRET (TYPE s3, KEY_ID '⟨key⟩', SECRET '⟨secret⟩', REGION 'us-east-1')");
stmt.execute("ATTACH 's3://my-bucket/lake/catalog.duckdb' AS lake (TYPE ducklake, READ_ONLY)");
stmt.execute("USE lake");
try (ResultSet rs = stmt.executeQuery("SELECT id, payload FROM events ORDER BY id")) {
while (rs.next()) {
System.out.println(rs.getInt(1) + " " + rs.getString(2));
}
}
}
SQLite 카탈로그도 같은 방식으로 부착하며, 파일 이름이 .sqlite로 끝나요. https://로 제공되는 공개 호스팅 레이크는 secret이 전혀 필요 없어서 카탈로그를 직접 부착할 수 있어요. 예: ATTACH 'https://⟨host⟩/catalog.duckdb' AS lake (TYPE ducklake, READ_ONLY). 카탈로그에 기록된 DATA_PATH는 읽는 쪽에서도 도달할 수 있는 위치(같은 버킷이나 공개 호스트 등)를 가리켜야 해요.
부착하거나 열면 DuckLake 테이블은 로컬 테이블처럼 동작해요. 따라서 [Appender]({% link docs/current/clients/java/data_import.md %}#appender)를 포함해 JDBC API 전체가 적용돼요. 단, DuckLake는 다른 lakehouse 포맷처럼 인덱스, 기본 키, UNIQUE·CHECK 제약 조건을 지원하지 않아요. 차이점 전체 목록은 DuckLake 문서를 참고하세요.
Quack 서버에 연결
[Quack 원격 프로토콜]({% link docs/current/quack/overview.md %})은 DuckDB 인스턴스를 다른 DuckDB 인스턴스가 HTTP로 쿼리할 수 있는 서버로 바꿔줘요. JDBC 드라이버에는 전용 URL 스킴이 없어요. 평소처럼 로컬(보통 인메모리) DuckDB 커넥션을 열고 원격 서버를 카탈로그로 부착하면 돼요.
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:");
Statement stmt = conn.createStatement()) {
stmt.execute("ATTACH 'quack:⟨hostname⟩' AS remote_db (TOKEN '⟨MY_QUACK_TOKEN⟩')");
try (ResultSet rs = stmt.executeQuery("SELECT count(*) FROM remote_db.events")) {
rs.next();
System.out.println(rs.getLong(1));
}
}
토큰은 secret으로 저장할 수도 있는데, 그러면 ATTACH 문에 토큰이 드러나지 않아요.
stmt.execute("CREATE SECRET (TYPE quack, TOKEN '⟨MY_QUACK_TOKEN⟩')");
stmt.execute("ATTACH 'quack:⟨hostname⟩' AS remote_db");
부착하면 원격 테이블이 로컬처럼 동작하므로 JDBC API 전체가 적용돼요. [USE]({% link docs/current/sql/statements/use.md %}) remote_db를 실행해 원격 카탈로그를 기본으로 만들면, 자격 없는(정규화되지 않은) 테이블 이름이 서버에 대해 해석돼요. 로컬 인스턴스는 여전히 자체 스레드 풀과 메모리 한도를 갖고, 커넥션은 다른 JDBC 커넥션과 같은 인스턴스 규칙을 따르므로, 서버로 쿼리만 전달하는 클라이언트는 작은 threads 값으로 열 수 있어요.
Warning Quack은 활발히 개발 중이며 프로토콜, 함수 이름, 설정, 기본값이 아직 바뀔 수 있어요. 현재 상태는 [Quack 문서]({% link docs/current/quack/overview.md %})를 참고하세요.
커넥션 닫기
DuckDB는 마지막 열린 커넥션이 닫힐 때 데이터베이스를 종료해요. 마지막 커넥션을 닫으면 데이터베이스가 체크포인트되어 write-ahead log(.wal 파일)를 데이터베이스 파일에 병합한 뒤 제거해요. 이 파일들에 대한 자세한 내용은 [Files Created by DuckDB]({% link docs/current/operations_manual/footprint_of_duckdb/files_created_by_duckdb.md %})를 참고하세요.
JDBC 드라이버에서 "정상 종료"란 Java 프로그램이 끝나기 전에 데이터베이스의 모든 Connection이 Connection.close()로 닫혔다는 뜻이에요. 호출할 별도의 종료 메서드는 없어요. 모든 커넥션을 닫는 것으로 충분하죠. 예외가 던져져도 커넥션이 닫히도록 try-with-resources 블록을 사용하세요.
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database")) {
// work with the connection
}
커넥션을 닫지 않고 프로세스가 종료되면 write-ahead log가 그 자리에 남아요. 다음에 데이터베이스 파일을 열 때 재생되므로 커밋된 데이터가 유실되진 않지만, 파일은 깨끗한 종료 시 체크포인트되어야만 압축돼요. 커넥션을 닫지 않고 체크포인트를 강제하려면 [CHECKPOINT 문]({% link docs/current/sql/statements/checkpoint.md %})을 실행하세요.
데이터베이스의 마지막 커넥션을 반복적으로 열고 닫는 것은 인스턴스(스레드 풀 포함)를 매번 시작·종료하는 것을 의미해요. 루프에서 이런 작업을 하는 애플리케이션은 jdbc_pin_db 옵션으로 커넥션 사이에 인스턴스를 유지하고 나중에 DuckDBDriver.releaseDB(url)로 해제할 수 있어요.
더 알아보기 (Learn more)
- [쿼리 실행]({% link docs/current/clients/java/querying.md %}) —
Connection으로 쿼리를 보내고 결과 집합을 읽는 방법. - [결과 처리]({% link docs/current/clients/java/result_handling.md %}) —
jdbc_stream_results옵션과 커넥션 시점에 정하는 다른 결과 처리 선택지. - [Configuration]({% link docs/current/configuration/overview.md %}) — 커넥션 옵션으로 넘길 수 있는 DuckDB 설정 전체 목록.
- [Files Created by DuckDB]({% link docs/current/operations_manual/footprint_of_duckdb/files_created_by_duckdb.md %}) — 커넥션 종료가 체크포인트하고 정리하는 데이터베이스, WAL, 임시 파일.
- [문제 해결]({% link docs/current/clients/java/troubleshoot.md %}) — 흔한 커넥션·드라이버 문제의 해결 방법.