Opening one connection per thread or per request is straightforward, but it comes with overhead: each call toDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/nimdeveloper/better-duck/llms.txt
Use this file to discover all available pages before exploring further.
Connection::open(path) opens a new database file handle, and each call to Connection::open_in_memory() creates an entirely isolated database. The pool feature addresses both of these costs. It provides an r2d2-backed connection pool where every slot in the pool shares one underlying Database — so connections are created quickly (a single duckdb_connect call), and for in-memory databases, all connections observe the same data.
Enabling the pool Feature
r2d2 as a dependency. The DuckDbConnectionManager, Pool, and related type aliases are available under better_duck_core::pool.
Creating a Pool
Create a DuckDbConnectionManager
The manager holds one
Database handle. All connections it creates are derived from that handle via db.connect().Build the pool with r2d2
Pass the manager to
Pool::builder() and configure the pool size, timeout, and other r2d2 settings before calling .build():DuckDbConnectionManager Constructors
| Constructor | Description |
|---|---|
DuckDbConnectionManager::memory() -> Result<DuckDbConnectionManager> | Shared in-memory database |
DuckDbConnectionManager::memory_with_flags(config) -> Result<DuckDbConnectionManager> | Shared in-memory with DuckDB config |
DuckDbConnectionManager::file(path) -> Result<DuckDbConnectionManager> | File-based database |
DuckDbConnectionManager::file_with_flags(path, config) -> Result<DuckDbConnectionManager> | File-based with DuckDB config |
DuckDbConnectionManager::new(database) -> DuckDbConnectionManager | Wrap an already-open Database |
Database from an existing manager with manager.database().
Checking Out Connections
pool.get() returns a PooledConnection — a smart pointer that derefs to Connection. Use it exactly like a regular Connection and drop it when done to return the slot to the pool:
All connections checked out of the same pool share one underlying
Database. For in-memory pools this means they observe consistent data — creating a table on one checkout is immediately visible to the next checkout. This is the key difference from opening N independent connections.Full Example
Pool Types
| Type | Description |
|---|---|
Pool | r2d2::Pool<DuckDbConnectionManager> — the pool itself |
PooledConnection | r2d2::PooledConnection<DuckDbConnectionManager> — a checked-out connection |
PoolBuilder | r2d2::Builder — builder returned by Pool::builder() (re-exported as-is; parameterized by DuckDbConnectionManager at use-site) |
PoolError | r2d2::Error — error type from checkout operations |
PoolState | r2d2::State — idle/active connection counts |
better_duck_core::pool so you don’t need to depend on r2d2 directly.
r2d2 Pool Validation
TheDuckDbConnectionManager implements r2d2::ManageConnection with two validation hooks:
is_valid— runsSELECT 1on a connection before handing it to the caller. Connections that fail this check are discarded and replaced.has_broken— returnstrueifconn.is_open()isfalse. Broken connections are immediately evicted.
Using with AsyncPool
When bothasync and pool features are enabled, AsyncPool wraps an r2d2 Pool and provides async methods. Import AsyncPool from better_duck_core:
AsyncPool::with() is the correct place for transactions — the closure acquires a checkout and runs entirely on a blocking thread before the await resolves, so the connection is never held across an .await point: