연결
연결 (Connect)
Rust에서 쓸 수 있는 모든 DuckDB 기능은 Connection에서 시작해요. 인메모리/파일 기반 데이터베이스를 여는 것부터, Config로 데이터베이스 인스턴스를 설정하는 것, 읽기 전용으로 여는 것, 스레드 간 커넥션 풀링, 크레이트의 스레드 안전성 보장, 그리고 커넥션을 닫는 것까지 아래에서 모두 다뤄볼게요.
출처: 문서
본문
커넥션 열기
인메모리 데이터베이스용 커넥션은 Connection::open_in_memory(), 데이터베이스 파일용 커넥션은 Connection::open(path)으로 만들어요. 둘 다 Result를 반환하므로 ? 연산자로 에러를 그대로 전파할 수 있어요.
use duckdb::{Connection, Result};
// In-memory database: nothing is persisted to disk.
let conn = Connection::open_in_memory()?;
// File-backed database: created if it does not exist.
let conn = Connection::open("my_database.duckdb")?;
인메모리 데이터베이스는 디스크에 아무것도 저장되지 않으니 프로세스가 종료되면 모든 데이터가 사라져요. 파일 기반 데이터베이스는 파일이 없으면 새로 만들어져요. 확장자는 .db, .duckdb, 그 외 아무거나 가능해요.
커넥션 설정하기
데이터베이스 인스턴스가 시작될 때 DuckDB 옵션을 설정하려면 Config를 만들어 Connection::open_with_flags()(또는 Connection::open_in_memory_with_flags())로 커넥션을 열면 돼요. Config는 빌더 스타일이에요. 각 메서드는 config를 소비하고 다시 반환하며, DuckDB가 옵션을 검증하므로 각각 Result를 반환해요.
use duckdb::{Config, Connection, Result};
let config = Config::default()
.max_memory("4GB")?
.threads(4)?;
let conn = Connection::open_with_flags("my_database.duckdb", config)?;
Config는 가장 흔한 옵션들에 대해 타입 있는 메서드를 제공해요. access_mode(), max_memory(), threads(), default_order(), default_null_order(), enable_external_access(), enable_object_cache(), custom_user_agent(), allow_unsigned_extensions() 같은 것들이죠. 그 외에 다른 DuckDB 설정은 with()로 이름을 지정해 넘길 수 있어요.
let config = Config::default()
.with("temp_directory", "/path/to/temp/dir/")?
.with("preserve_insertion_order", "false")?;
전체 설정 목록은 [Configuration 페이지]({% link docs/current/configuration/overview.md %})에서 볼 수 있어요. 대부분은 커넥션 후에 [SET 문]({% link docs/current/sql/statements/set.md %})이나 동등한 [PRAGMA]({% link docs/current/configuration/pragmas.md %})로도 바꿀 수 있어요.
읽기 전용 커넥션
데이터베이스 파일을 읽기 전용 모드로 열 수 있는데, 여러 프로세스가 같은 파일을 동시에 읽어야 할 때 유용해요. Config의 접근 모드를 AccessMode 열거형으로 설정하면 돼요.
use duckdb::{AccessMode, Config, Connection, Result};
let config = Config::default().access_mode(AccessMode::ReadOnly)?;
let conn = Connection::open_with_flags("my_database.duckdb", config)?;
AccessMode는 세 가지 변형이 있어요. Automatic(기본값), ReadOnly, ReadWrite예요.
스레드 안전성
Connection은 Send지만 Sync는 아니에요. 즉 다른 스레드로 이동할 수는 있지만, 같은 시점에 여러 스레드가 공유할 수는 없어요. 권장 패턴은 스레드당 커넥션 하나예요. 같은 인메모리/파일 기반 데이터베이스에 커넥션을 여러 개 열려면 Connection::try_clone()을 쓰면 돼요. 이 메서드는 이미 열린 데이터베이스에 새로운 커넥션을 열어줘요.
let conn = Connection::open("my_database.duckdb")?;
let conn2 = conn.try_clone()?; // a second connection to the same database
DuckDB는 자체 네이티브 스레드 풀에서 쿼리를 실행하는데, 그 규모는 threads 옵션으로 정해지고 데이터베이스 인스턴스의 모든 커넥션이 공유해요. 커넥션과 스레드가 어떻게 상호작용하는지는 [DuckDB 동시성 문서]({% link docs/current/connect/concurrency.md %})를 참고해 주세요.
커넥션 풀링
각 워커 스레드에 커넥션 하나씩을 넘기는 애플리케이션에는 r2d2 기능이 DuckDB를 r2d2 커넥션 풀과 통합해 줘요. DuckdbConnectionManager가 커넥션을 만들고, 풀이 그 커넥션을 체크아웃했다가 반환해요.
use duckdb::{params, DuckdbConnectionManager};
let manager = DuckdbConnectionManager::file("my_database.duckdb")?;
let pool = r2d2::Pool::new(manager)?;
// Each worker checks out its own connection from the pool.
let conn = pool.get()?;
conn.execute("INSERT INTO foo (bar) VALUES (?)", params![1])?;
DuckdbConnectionManager::memory()는 인메모리 데이터베이스용 매니저를 만들고, DuckdbConnectionManager::file_with_flags()와 memory_with_flags()는 Config를 받아요. Cargo.toml에서 features = ["r2d2"]로 기능을 켜면 돼요.
커넥션 닫기
Connection은 drop될 때(스코프를 벗어날 때) 자동으로 닫히며, 이때 기본 데이터베이스가 종료돼요. 명시적으로 닫으면서 에러도 처리하려면 close()를 호출하면 돼요.
match conn.close() {
Ok(()) => {}
Err((_conn, err)) => eprintln!("failed to close connection: {err}"),
}
close()는 실패하면 커넥션을 호출자에게 돌려주므로 닫기를 재시도할 수 있어요. 일반적인 경우 명시적 close()와 drop에 맡기는 것 사이 큰 차이는 없지만, close()는 Drop이라면 버려버릴 종료 에러를 관찰하고 처리할 기회를 줘요.
더 알아보기 (Learn more)
- [쿼리 실행]({% link docs/current/clients/rust/querying.md %}) —
Connection으로 쿼리를 보내고 결과를 읽는 방법. - [Configuration]({% link docs/current/configuration/overview.md %}) —
Config에 넘길 수 있는 DuckDB 설정 전체 목록. - [동시성]({% link docs/current/connect/concurrency.md %}) — DuckDB가 여러 커넥션과 스레드를 다루는 방식.
- [문제 해결]({% link docs/current/clients/rust/troubleshoot.md %}) — 커넥션을 열 때 겪는 링킹·빌드 문제.