The azure extension lets DuckDB read and write files in Azure Blob Storage and Azure Data Lake Storage (ADLS) Gen2.
Installing and Loading
The azure extension will be transparently autoloaded on first use from the official extension repository.
If you would like to install and load it manually, run:
INSTALL AND LOAD azure;
Usage
Queries need a secret; for a public container, a secret with the storage account name is enough (see the CONFIG provider). Then you can query Azure storage as follows:
Azure Blob Storage
Allowed URI schemes: az or azure
SELECT count(*)
FROM 'az://my_container/path/my_file.parquet_or_csv';
Globs are also supported:
SELECT *
FROM 'az://my_container/path/*.csv';
SELECT *
FROM 'az://my_container/path/**';
Or with a fully qualified path syntax:
SELECT count(*)
FROM 'az://my_storage_account.blob.core.windows.net/my_container/path/my_file.parquet_or_csv';
SELECT *
FROM 'az://my_storage_account.blob.core.windows.net/my_container/path/*.csv';
Azure Data Lake Storage (ADLS)
Allowed URI schemes: abfss or abfs (both connect over HTTPS)
SELECT count(*)
FROM 'abfss://my_filesystem/path/my_file.parquet_or_csv';
Globs are also supported:
SELECT *
FROM 'abfss://my_filesystem/path/*.csv';
SELECT *
FROM 'abfss://my_filesystem/path/**';
Or with a fully qualified path syntax:
SELECT count(*)
FROM 'abfss://my_storage_account.dfs.core.windows.net/my_filesystem/path/my_file.parquet_or_csv';
SELECT *
FROM 'abfss://my_storage_account.dfs.core.windows.net/my_filesystem/path/*.csv';
The fully qualified path can also use the my_filesystem@my_storage_account form:
SELECT count(*)
FROM 'abfss://my_filesystem@my_storage_account.dfs.core.windows.net/path/my_file.parquet_or_csv';
Writing to Azure Blob Storage
You can write data directly to Azure Blob or ADLSv2 Storage using the COPY statement.
-- Write query results to a Parquet file on Blob Storage
COPY (SELECT * FROM my_table)
TO 'az://my_container/path/output.parquet';
-- Write a table to a CSV file on ADLSv2 Storage
COPY my_table
TO 'abfss://my_filesystem/path/output.csv';
You can also use fully qualified paths:
COPY my_table
TO 'az://my_storage_account.blob.core.windows.net/my_container/path/output.parquet';
Since v1.5.4, the extension buffers written data locally and uploads it in blocks of azure_write_block_size bytes (default: 8 MiB).
By default, the uploaded blocks are committed when the file is closed, so other readers see the new content only after the write completes.
To commit earlier, set azure_write_staged_blocks_per_commit to the number of blocks after which an intermediate commit happens.
Warning An intermediate commit makes an incomplete file visible to readers. A Parquet file cannot be read until its footer is written at the end, so keep the default
0for Parquet files and for Delta, Iceberg or DuckLake tables.
Writing Large Files
Azure limits a blob to 50,000 blocks.
With the default block size of 8 MiB, the largest file you can write is about 390 GiB.
To write larger files, increase azure_write_block_size (maximum: 4000 MiB, about 3.9 GiB). For example, to write files up to about 3 TiB:
SET azure_write_block_size = 64 * 1024 * 1024;
Configuration
Use the following configuration options to control how the extension reads and writes remote files:
| Name | Description | Type | Default |
|---|---|---|---|
azure_http_stats |
Include HTTP info from Azure Storage in the EXPLAIN ANALYZE statement. |
BOOLEAN |
false |
azure_http_logging |
Write Azure HTTP requests to the DuckDB HTTP log when it is enabled. See Logging. |
BOOLEAN |
true |
azure_http_logging_redact_query_params |
Semicolon-separated list of URL query parameter names (case-sensitive) whose values are redacted in the HTTP log. | VARCHAR |
sig |
azure_http_logging_redact_headers |
Semicolon-separated list of header names (case-insensitive) whose values are redacted in the HTTP log, for both requests and responses. | VARCHAR |
Authorization |
azure_read_transfer_concurrency |
Maximum number of threads the Azure client can use for a single parallel read. Values above 1 let the client fill one buffer with parallel requests, when the chunk size is smaller than the buffer. | INTEGER |
5 |
azure_read_transfer_chunk_size |
Maximum size in bytes that the Azure client will read in a single request. Should be a divisor of azure_read_buffer_size. |
BIGINT |
8*1024*1024 |
azure_read_buffer_size |
Size of the read buffer. Should be a multiple of azure_read_transfer_chunk_size. |
UBIGINT |
8*1024*1024 |
azure_write_block_size |
Size in bytes of each block for Blob and ADLS writes. Setting it to 0 restores the default. The maximum is 4000 MiB (about 3.9 GiB). See Writing Large Files. |
UBIGINT |
8*1024*1024 |
azure_write_staged_blocks_per_commit |
Number of uploaded blocks after which an intermediate commit happens. With 0, all blocks are committed when the file is closed. Non-zero values make partial writes visible sooner, at the cost of extra requests. This option does not change the 50,000-block limit. |
UBIGINT |
0 |
azure_transport_option_type |
Underlying adapter to use in the Azure SDK. Valid values are: default or curl. |
VARCHAR |
default |
azure_context_caching |
Enable/disable the caching of the underlying Azure SDK HTTP connection in the DuckDB connection context when performing queries. If you suspect that this is causing some side effect, you can try to disable it by setting it to false (not recommended). | BOOLEAN |
true |
Since v1.5.4, azure_read_transfer_chunk_size and azure_read_buffer_size default to 8 MiB (previously 1 MiB).
Tip Setting
azure_transport_option_typetocurlhas the following effects:
- On Linux, it may fix certificate errors such as
Problem with the SSL CA cert (path? access rights?): the extension then looks for the CA bundle in the usual paths, which curl does not do by default and which static linking may get wrong.- On Windows, it replaces the default adapter (WinHTTP), so you can use all curl features, e.g., SOCKS proxies.
- On all operating systems, it honors these environment variables:
CURL_CA_INFO: path to a PEM encoded file with the certificate authorities for libcurl. It is known to work only on Linux and may fail on other platforms.CURL_CA_PATH: path to a directory of PEM encoded files with the certificate authorities for libcurl.
Example:
SET azure_http_stats = false;
SET azure_read_transfer_concurrency = 5;
SET azure_read_transfer_chunk_size = 8_388_608;
SET azure_read_buffer_size = 8_388_608;
Authentication
Authenticate with a secret. The SET variables in Authentication with Variables still work but are deprecated.
Authentication with Secret
Several Secret Providers are available, described below. These rules apply to every provider:
- If you need to define different secrets for different storage accounts, use the
SCOPEconfiguration. Note that theSCOPErequires a trailing slash (SCOPE 'az://my_storage_account.blob.core.windows.net/'). - With fully qualified paths,
ACCOUNT_NAMEis optional. - For paths that are not fully qualified, the
ENDPOINTattribute overrides the default endpoint (blob.core.windows.netforaz://andazure://,dfs.core.windows.netforabfss://andabfs://).
CONFIG Provider
The default provider, CONFIG (i.e., user-configured), allows access to the storage account using a connection string or anonymously. For example:
CREATE SECRET secret1 (
TYPE azure,
CONNECTION_STRING 'connection_string'
);
For anonymous access, give the storage account name:
CREATE SECRET secret2 (
TYPE azure,
PROVIDER config,
ACCOUNT_NAME 'storage_account_name'
);
credential_chain Provider
The credential_chain provider allows connecting using credentials automatically fetched by the Azure SDK via the Azure credential chain.
By default, the provider uses the Azure SDK's DefaultAzureCredential chain. The token is fetched at query time, not when the secret is created. For example:
CREATE SECRET secret3 (
TYPE azure,
PROVIDER credential_chain,
ACCOUNT_NAME 'storage_account_name'
);
DuckDB also allows specifying a specific chain using the CHAIN keyword. This takes a semicolon-separated list (a;b;c) of providers that will be tried in order. For example:
CREATE SECRET secret4 (
TYPE azure,
PROVIDER credential_chain,
CHAIN 'cli;env',
ACCOUNT_NAME 'storage_account_name'
);
The possible values are:
cli: the Azure CLI loginmanaged_identityworkload_identity, e.g., on AKSenv: environment variablesdefault: the full default chain, used when noCHAINis given
MANAGED_IDENTITY Provider
If the machine has one managed identity, the credential_chain provider uses it with no extra options. If it has several, use the MANAGED_IDENTITY provider and pick one with CLIENT_ID, OBJECT_ID or RESOURCE_ID:
CREATE SECRET azure_mi (
TYPE azure,
PROVIDER managed_identity,
ACCOUNT_NAME 'storage_account_name',
CLIENT_ID 'client_id'
);
Without an ID, the provider works like credential_chain. With several identities and no ID, the Azure SDK decides which one is used, so set an ID.
SERVICE_PRINCIPAL Provider
The SERVICE_PRINCIPAL provider allows connecting using an Azure Service Principal (SPN).
With a client secret:
CREATE SECRET azure_spn (
TYPE azure,
PROVIDER service_principal,
TENANT_ID 'tenant_id',
CLIENT_ID 'client_id',
CLIENT_SECRET 'client_secret',
ACCOUNT_NAME 'storage_account_name'
);
Or with a certificate:
CREATE SECRET azure_spn_cert (
TYPE azure,
PROVIDER service_principal,
TENANT_ID 'tenant_id',
CLIENT_ID 'client_id',
CLIENT_CERTIFICATE_PATH 'client_cert_path',
ACCOUNT_NAME 'storage_account_name'
);
ACCESS_TOKEN Provider
The ACCESS_TOKEN provider allows connecting with an OAuth access token (bearer token) that you obtained outside of DuckDB:
CREATE SECRET azure_token (
TYPE azure,
PROVIDER access_token,
ACCESS_TOKEN 'access_token',
ACCOUNT_NAME 'storage_account_name'
);
Warning The extension does not refresh the token. When it expires, run
CREATE OR REPLACE SECRETwith a new token; it takes effect on the next query.
Configuring a Proxy
To configure proxy information when using secrets, you can add HTTP_PROXY, PROXY_USER_NAME and PROXY_PASSWORD in the secret definition. For example:
CREATE SECRET secret5 (
TYPE azure,
CONNECTION_STRING 'connection_string',
HTTP_PROXY 'http://localhost:3128',
PROXY_USER_NAME 'proxy_user_name',
PROXY_PASSWORD 'proxy_password'
);
Note When you use secrets:
- The
HTTP_PROXYenvironment variable is still honored, unless the secret setsHTTP_PROXY.- The
SETvariables of Authentication with Variables are ignored.
Authentication with Variables
Deprecated Use secrets instead.
SET variable_name = value;
Where variable_name can be one of the following:
| Name | Description | Type | Default |
|---|---|---|---|
azure_storage_connection_string |
Azure connection string, used for authenticating and configuring Azure requests. | VARCHAR |
- |
azure_account_name |
Azure account name, when set, the extension will attempt to automatically detect credentials (not used if you pass the connection string). | VARCHAR |
- |
azure_endpoint |
Override the Azure endpoint for when the Azure credential providers are used. | VARCHAR |
blob.core.windows.net |
azure_credential_chain |
Ordered list of Azure credential providers, in string format separated by ;. For example: 'cli;managed_identity;env'. See the list of possible values in the credential_chain provider section. Not used if you pass the connection string. |
VARCHAR |
- |
azure_http_proxy |
Proxy for login and for requests to Azure. | VARCHAR |
HTTP_PROXY environment variable (if set). |
azure_proxy_user_name |
HTTP proxy username if needed. | VARCHAR |
- |
azure_proxy_password |
HTTP proxy password if needed. | VARCHAR |
- |
Additional Information
Logging
Since v1.5.2, the Azure extension writes its HTTP requests to the DuckDB HTTP log. To inspect them:
CALL enable_logging('HTTP');
SELECT * FROM 'az://my_container/path/my_file.parquet';
SELECT request.type, request.url, response.status FROM duckdb_logs_parsed('HTTP');
Logged requests show the DuckDB User-Agent header.
The values of the sig query parameter and the Authorization header are replaced by REDACTED.
To change what is redacted, set azure_http_logging_redact_query_params and azure_http_logging_redact_headers.
A new value replaces the default list, so include sig or Authorization if you still want them redacted:
SET azure_http_logging_redact_headers = 'Authorization;x-ms-date';
Warning The Azure extension applies only these settings, not DuckDB's
redact_http_logssetting. To keep Azure requests out of theHTTPlog, runSET azure_http_logging = false;.
The Azure extension relies on the Azure SDK to connect to Azure Blob storage and supports printing the SDK logs to the console.
To control the log level, set the AZURE_LOG_LEVEL environment variable.
For instance, to start the DuckDB CLI with verbose logs:
AZURE_LOG_LEVEL=verbose duckdb
CREATE SECRET myaccount (
TYPE azure,
PROVIDER credential_chain,
SCOPE 'az://my_storage_account.blob.core.windows.net/'
);
SELECT count(*) FROM 'az://my_storage_account.blob.core.windows.net/my_container/path/my_file.parquet';
Difference between ADLS and Blob Storage
For glob patterns over many directories, prefer abfss://: the ADLS endpoint lists only the directories that match, while the Blob endpoint lists every file under the prefix and filters afterwards. The following example shows how a glob is performed through each endpoint.
Using the following filesystem:
root
├── l_receiptmonth=1997-10
│ ├── l_shipmode=AIR
│ │ └── data_0.csv
│ ├── l_shipmode=SHIP
│ │ └── data_0.csv
│ └── l_shipmode=TRUCK
│ └── data_0.csv
├── l_receiptmonth=1997-11
│ ├── l_shipmode=AIR
│ │ └── data_0.csv
│ ├── l_shipmode=SHIP
│ │ └── data_0.csv
│ └── l_shipmode=TRUCK
│ └── data_0.csv
└── l_receiptmonth=1997-12
├── l_shipmode=AIR
│ └── data_0.csv
├── l_shipmode=SHIP
│ └── data_0.csv
└── l_shipmode=TRUCK
└── data_0.csv
The following query is performed through the Blob endpoint:
SELECT count(*)
FROM 'az://root/l_receiptmonth=1997-*/l_shipmode=SHIP/*.csv';
It will perform the following steps:
- List all the files with the prefix
root/l_receiptmonth=1997-root/l_receiptmonth=1997-10/l_shipmode=SHIP/data_0.csvroot/l_receiptmonth=1997-10/l_shipmode=AIR/data_0.csvroot/l_receiptmonth=1997-10/l_shipmode=TRUCK/data_0.csvroot/l_receiptmonth=1997-11/l_shipmode=SHIP/data_0.csvroot/l_receiptmonth=1997-11/l_shipmode=AIR/data_0.csvroot/l_receiptmonth=1997-11/l_shipmode=TRUCK/data_0.csvroot/l_receiptmonth=1997-12/l_shipmode=SHIP/data_0.csvroot/l_receiptmonth=1997-12/l_shipmode=AIR/data_0.csvroot/l_receiptmonth=1997-12/l_shipmode=TRUCK/data_0.csv
- Filter the result with the requested pattern
root/l_receiptmonth=1997-*/l_shipmode=SHIP/*.csvroot/l_receiptmonth=1997-10/l_shipmode=SHIP/data_0.csvroot/l_receiptmonth=1997-11/l_shipmode=SHIP/data_0.csvroot/l_receiptmonth=1997-12/l_shipmode=SHIP/data_0.csv
Meanwhile, the same query can be performed through the datalake endpoint as follows:
SELECT count(*)
FROM 'abfss://root/l_receiptmonth=1997-*/l_shipmode=SHIP/*.csv';
This will perform the following steps:
- List all directories in
root/root/l_receiptmonth=1997-10root/l_receiptmonth=1997-11root/l_receiptmonth=1997-12
- Filter and list subdirectories:
root/l_receiptmonth=1997-10,root/l_receiptmonth=1997-11,root/l_receiptmonth=1997-12root/l_receiptmonth=1997-10/l_shipmode=SHIProot/l_receiptmonth=1997-10/l_shipmode=AIRroot/l_receiptmonth=1997-10/l_shipmode=TRUCKroot/l_receiptmonth=1997-11/l_shipmode=SHIProot/l_receiptmonth=1997-11/l_shipmode=AIRroot/l_receiptmonth=1997-11/l_shipmode=TRUCKroot/l_receiptmonth=1997-12/l_shipmode=SHIProot/l_receiptmonth=1997-12/l_shipmode=AIRroot/l_receiptmonth=1997-12/l_shipmode=TRUCK
- Filter and list subdirectories:
root/l_receiptmonth=1997-10/l_shipmode=SHIP,root/l_receiptmonth=1997-11/l_shipmode=SHIP,root/l_receiptmonth=1997-12/l_shipmode=SHIProot/l_receiptmonth=1997-10/l_shipmode=SHIP/data_0.csvroot/l_receiptmonth=1997-11/l_shipmode=SHIP/data_0.csvroot/l_receiptmonth=1997-12/l_shipmode=SHIP/data_0.csv
Because the Blob endpoint does not support the notion of directories, the filter can only be performed after the listing, whereas the ADLS endpoint will list files recursively. Especially with higher partition/directory counts, the performance difference can be very significant.