Every quak connection is a DuckDB database. DuckDB picks its defaults for a single user on their own machine: it uses most of the memory and every CPU core. That works well on a laptop. On a shared server, in a container, or for very large queries, a few settings are worth changing.
This article covers DuckDB’s settings. quak also has its own options,
such as the extension cache directory and whether collect()
prints progress. Those are separate. See
quak_options().
The settings that matter most
| Setting | Default | Change it to |
|---|---|---|
memory_limit |
80% of RAM | Leave memory for R and other programs |
threads |
One per CPU core | Share the machine, or fit a container |
temp_directory |
Inside R’s temporary directory | Put overflow data on a large disk |
max_temp_directory_size |
90% of free disk space | Cap how much disk overflow data uses |
preserve_insertion_order |
true |
Make large writes use less memory |
memory_limit
memory_limit caps the memory DuckDB uses. It does not
include R’s own memory, and the data frames that collect()
returns live in R. With the default of 80%, DuckDB and R together can
run the machine out of memory. On a shared server or in a container, the
operating system may then stop the process.
A lower limit leaves room for R. When a query needs more than the limit, DuckDB writes intermediate data to disk instead of failing. This works for joins, aggregations, sorts and window functions.
threads
threads is the number of threads DuckDB runs queries on.
Lower it on a machine that other people or jobs share. Inside a
container, check the value with
conn_setting(conn, "threads"). Depending on how the
container limits CPU, DuckDB may see more cores than it is allowed to
use. Each thread also holds its own buffers, so fewer threads use less
memory.
There is a trade-off for quak. Reading from Azure is mostly waiting
for the network, and more threads keep more requests going at once.
Lowering threads can make large scans slower.
temp_directory and
max_temp_directory_size
temp_directory is where DuckDB writes intermediate data
when a query goes over memory_limit. By default it is
inside R’s temporary directory. On Linux servers and in containers that
directory is often on a small /tmp disk, and a large join
fails once the disk is full. Point it at a disk with plenty of free
space.
max_temp_directory_size caps how much space that
directory may use.
A low memory_limit only prevents crashes if DuckDB has
somewhere to put the overflow, so set these together.
preserve_insertion_order
By default, DuckDB returns rows in the order they were read, even
when the query has no ORDER BY. To keep that order, it must
hold data back in memory. Setting preserve_insertion_order
to false lets DuckDB process and write data in parallel,
with much less memory. It helps most for large writes with
az_copy_to(), az_write_parquet() and
CREATE TABLE ... AS SELECT.
Queries without ORDER BY can then return rows in any
order. Add dplyr::arrange() wherever order matters.
Set them when you open the connection
Pass the settings to duckdb::duckdb() as a
config list, then give the connection to
az_conn(). Write every value as a string:
Change them on an open connection
conn_setting() changes a setting on a connection that is
already open:
conn <- az_conn()
conn_setting(conn, "memory_limit", "4GB")
#> ✔ Set `memory_limit` = "4GB"
conn_setting(conn, "threads", 4)
#> ✔ Set `threads` = 4
conn_setting(conn, "temp_directory", "/scratch/duckdb")
#> ✔ Set `temp_directory` = "/scratch/duckdb"
conn_setting(conn, "preserve_insertion_order", FALSE)
#> ✔ Set `preserve_insertion_order` = FALSEWith just a name, it returns the current value as a string:
conn_setting(conn, "threads")
#> [1] "4"With no name, it returns every setting, with a description of each:
settings <- conn_setting(conn)
settings[settings$name %in% c("memory_limit", "threads"), c("name", "value")]
#> # A tibble: 2 × 2
#> name value
#> <chr> <chr>
#> 1 memory_limit 3.7 GiB
#> 2 threads 4DuckDB reports sizes in binary units. "4GB" is 4 ×
109 bytes, which it shows as 3.7 GiB.
To go back to the default, reset the setting:
DBI::dbExecute(conn, "RESET threads")Settings apply to the whole database
These settings belong to the database, not to one connection. When
several connections share one duckdb() driver, they share
one database, and a change made on any of them applies to all:
drv <- duckdb::duckdb()
conn1 <- DBI::dbConnect(drv)
conn2 <- DBI::dbConnect(drv)
conn_setting(conn1, "threads", 3)
conn_setting(conn2, "threads")
#> [1] "3"Each call to az_conn() without an existing connection
opens a new database, with its own settings.
Azure read settings
az_tune() sets how DuckDB reads from Azure. Each
argument left as NULL keeps its current value:
az_tune(conn, concurrency = 8, metadata_cache = TRUE)| Argument | DuckDB setting | What it controls |
|---|---|---|
concurrency |
azure_read_transfer_concurrency |
How many transfer requests one read makes at once |
chunk_size |
azure_read_transfer_chunk_size |
The size of each transfer request |
buffer_size |
azure_read_buffer_size |
The size of the read buffer |
transport |
azure_transport_option_type |
The HTTP library used. az_conn() sets
"curl"
|
metadata_cache |
enable_http_metadata_cache |
Whether file metadata is cached between queries |
context_cache |
azure_context_caching |
Whether the Azure connection context is reused |
az_tune() warns when buffer_size is not a
whole multiple of chunk_size.
az_conn_settings() lists every Azure setting and its
current value.
Examples
A large export
Turn off insertion order before writing a large result back to the lake:
conn_setting(conn, "preserve_insertion_order", FALSE)
az_write_parquet(
conn,
tbl_delta(conn, "abfss://container@account/path/events"),
"abfss://container@account/path/events_parquet",
partition_by = "event_date"
)Use the same settings every session
DuckDB settings last as long as the connection. To use the same
settings every time, wrap them in a small function in your project or
.Rprofile:
my_conn <- function() {
conn <- DBI::dbConnect(duckdb::duckdb(config = list(
memory_limit = "8GB",
threads = "4"
)))
conn <- az_conn(conn)
az_set_chain_secret(conn, chain = "cli")
conn
}