azure 扩展是一个可加载的扩展,它为 DuckDB 增加了对 Azure Blob Storage 的文件系统抽象,从而支持数据的读取和写入。
安装和加载
azure 扩展在首次使用时会从官方扩展仓库中透明地自动加载。如果您希望手动安装并加载它,请运行
INSTALL azure;
LOAD azure;
用法
一旦设置好身份验证,您就可以按照以下方式查询 Azure 存储
Azure Blob 存储
允许的 URI 方案:az 或 azure
SELECT count(*)
FROM 'az://my_container/path/my_file.parquet_or_csv';
同时也支持使用 Glob 模式
SELECT *
FROM 'az://my_container/path/*.csv';
SELECT *
FROM 'az://my_container/path/**';
或者使用完全限定路径语法
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)
允许的 URI 方案:abfss
SELECT count(*)
FROM 'abfss://my_filesystem/path/my_file.parquet_or_csv';
同时也支持使用 Glob 模式
SELECT *
FROM 'abfss://my_filesystem/path/*.csv';
SELECT *
FROM 'abfss://my_filesystem/path/**';
或者使用完全限定路径语法
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';
写入 Azure Blob Storage
您可以使用 COPY 语句将数据直接写入 Azure Blob 或 ADLSv2 存储。
-- 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_container/path/output.csv';
您也可以使用完全限定路径
COPY my_table
TO 'az://my_storage_account.blob.core.windows.net/my_container/path/output.parquet';
配置
使用以下配置选项来控制扩展读取远程文件的方式
| 名称 | 描述 | 类型 | 默认值 |
|---|---|---|---|
azure_http_stats |
在 EXPLAIN ANALYZE 语句中包含来自 Azure Storage 的 HTTP 信息。 |
BOOLEAN |
false |
azure_read_transfer_concurrency |
Azure 客户端可用于单次并行读取的最大线程数。如果 azure_read_transfer_chunk_size 小于 azure_read_buffer_size,则将此值设置为 > 1 将允许 Azure 客户端执行并发请求以填充缓冲区。 |
BIGINT |
5 |
azure_read_transfer_chunk_size |
Azure 客户端在单次请求中读取的最大字节数。建议将其设置为 azure_read_buffer_size 的约数。 |
BIGINT |
1024*1024 |
azure_read_buffer_size |
读取缓冲区的大小。建议将其设置为 azure_read_transfer_chunk_size 的整数倍。 |
UBIGINT |
1024*1024 |
azure_transport_option_type |
Azure SDK 中使用的底层适配器。有效值为:default 或 curl。 |
VARCHAR |
默认值 (default) |
azure_context_caching |
在执行查询时,启用/禁用 DuckDB 连接上下文中底层 Azure SDK HTTP 连接的缓存。如果您怀疑这导致了某些副作用,可以尝试将其设置为 false 来禁用它(不推荐)。 | BOOLEAN |
true |
将
azure_transport_option_type显式设置为curl会产生以下效果
- 在 Linux 上,这可能解决证书问题(
Error: Invalid Error: Fail to get a new connection for: https://storage_account_name.blob.core.windows.net/. Problem with the SSL CA cert (path? access rights?)),因为指定该选项后,扩展会尝试在不同路径中查找捆绑证书(这是 curl 默认不会做的,且由于静态链接可能会出错)。- 在 Windows 上,这会替换默认适配器(WinHTTP),从而允许您使用所有 curl 功能(例如使用 socks 代理)。
- 在所有操作系统上,它都将遵循以下环境变量
CURL_CA_INFO:包含发送给 libcurl 的证书颁发机构的 PEM 编码文件的路径。请注意,已知此选项仅在 Linux 上有效,在其他平台上设置可能会报错。CURL_CA_PATH:包含发送给 libcurl 的证书颁发机构的 PEM 编码文件所在的目录路径。
示例
SET azure_http_stats = false;
SET azure_read_transfer_concurrency = 5;
SET azure_read_transfer_chunk_size = 1_048_576;
SET azure_read_buffer_size = 1_048_576;
身份验证
Azure 扩展有两种配置身份验证的方式。推荐的方式是使用 Secrets。
使用 Secret 进行身份验证
Azure 扩展提供了多种密钥提供程序 (Secret Providers)
- 如果您需要为不同的存储账户定义不同的密钥,请使用
SCOPE配置。请注意,SCOPE要求结尾处带斜杠(SCOPE 'azure://some_container/')。 - 如果您使用完全限定路径,则
ACCOUNT_NAME属性是可选的。
CONFIG 提供程序
默认提供程序 CONFIG(即用户配置)允许使用连接字符串或匿名方式访问存储账户。例如
CREATE SECRET secret1 (
TYPE azure,
CONNECTION_STRING 'value'
);
如果您不使用身份验证,仍然需要指定存储账户名称。例如
CREATE SECRET secret2 (
TYPE azure,
PROVIDER config,
ACCOUNT_NAME 'storage_account_name'
);
默认的 PROVIDER 是 CONFIG。
credential_chain 提供者
credential_chain 提供程序允许使用 Azure SDK 通过 Azure 凭据链自动获取的凭据进行连接。默认情况下,使用 DefaultAzureCredential 链,它会按照 Azure 文档中指定的顺序尝试凭据。例如
CREATE SECRET secret3 (
TYPE azure,
PROVIDER credential_chain,
ACCOUNT_NAME 'storage_account_name'
);
DuckDB 还允许使用 CHAIN 关键字指定特定的链。这接受一个分号分隔的提供程序列表(a;b;c),系统将按顺序尝试这些提供程序。例如
CREATE SECRET secret4 (
TYPE azure,
PROVIDER credential_chain,
CHAIN 'cli;env',
ACCOUNT_NAME 'storage_account_name'
);
可能的值如下:cli;managed_identity;workload_identity;env;default;
如果未提供明确的 CHAIN,则默认值为 default
托管身份 (Managed Identity)
可以通过 credential_chain 优雅且自动地使用托管身份 (MI)。在通常情况下,如果执行环境只有一个可用的 MI,则无需任何配置。
如果您的执行环境有多个身份,请使用 MANAGED_IDENTITY 提供程序并指定要使用的身份。此提供程序允许通过 CLIENT_ID、OBJECT_ID 或 RESOURCE_ID 其中之一来指定身份,例如
CREATE SECRET secret1 (
TYPE AZURE,
PROVIDER MANAGED_IDENTITY,
ACCOUNT_NAME 'storage account name',
CLIENT_ID 'used-assigned managed identity client id'
);
该提供程序可以在不指定 ID 的情况下使用;如果只有一个 ID 可用,此提供程序的功能将与 credential_chain 提供程序相同,并使用该单一可用 ID。如果存在多个 ID,则行为是未定义的(或者更具体地说,由 Azure SDK 定义)——因此我们建议在这种情况下明确设置身份。
SERVICE_PRINCIPAL 提供程序
SERVICE_PRINCIPAL 提供程序允许使用 Azure 服务主体 (SPN) 进行连接。
可以使用密钥
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'
);
或者使用证书
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'
);
配置代理
在使用密钥时要配置代理信息,您可以在密钥定义中添加 HTTP_PROXY、PROXY_USER_NAME 和 PROXY_PASSWORD。例如
CREATE SECRET secret5 (
TYPE azure,
CONNECTION_STRING 'value',
HTTP_PROXY 'https://:3128',
PROXY_USER_NAME 'john',
PROXY_PASSWORD 'doe'
);
- 当使用密钥时,
HTTP_PROXY环境变量仍将被遵循,除非您为它提供了明确的值。- 当使用密钥时,使用变量进行身份验证会话中的
SET变量将被忽略。- 对于 Azure
credential_chain提供程序,实际的令牌是在查询时获取的,而不是在创建密钥时。
使用变量进行身份验证(已弃用)
SET variable_name = variable_value;
其中 variable_name 可以是以下之一
| 名称 | 描述 | 类型 | 默认值 |
|---|---|---|---|
azure_storage_connection_string |
Azure 连接字符串,用于验证和配置 Azure 请求。 | 字符串 |
- |
azure_account_name |
Azure 账户名称,设置后,扩展将尝试自动检测凭据(如果您传递了连接字符串,则不使用此项)。 | 字符串 |
- |
azure_endpoint |
在使用 Azure 凭据提供程序时覆盖 Azure 终端节点。 | 字符串 |
blob.core.windows.net |
azure_credential_chain |
Azure 凭据提供程序的有序列表,以字符串格式用 ; 分隔。例如:'cli;managed_identity;env'。请参阅 credential_chain 提供程序部分中可能的值列表。如果您传递了连接字符串,则不使用此项。 |
字符串 |
- |
azure_http_proxy |
登录及向 Azure 发送请求时使用的代理。 | 字符串 |
HTTP_PROXY 环境变量(如果已设置)。 |
azure_proxy_user_name |
如果需要,HTTP 代理用户名。 | 字符串 |
- |
azure_proxy_password |
如果需要,HTTP 代理密码。 | 字符串 |
- |
附加信息
日志记录
Azure 扩展依赖 Azure SDK 连接到 Azure Blob 存储,并支持将 SDK 日志打印到控制台。要控制日志级别,请设置 AZURE_LOG_LEVEL 环境变量。
例如,可以在 Python 中启用详细日志,如下所示
import os
import duckdb
os.environ["AZURE_LOG_LEVEL"] = "verbose"
duckdb.sql("CREATE SECRET myaccount (TYPE azure, PROVIDER credential_chain, SCOPE 'az://myaccount.blob.core.windows.net/')")
duckdb.sql("SELECT count(*) FROM 'az://myaccount.blob.core.windows.net/path/to/blob.parquet'")
ADLS 与 Blob Storage 的区别
尽管 ADLS 实现了与 Blob 存储类似的功能,但在使用 Glob 模式(尤其是复杂的 Glob 模式)时,使用 ADLS 终端节点具有一些显著的性能优势。
为了演示,我们来看一个分别使用 Blob 和 ADLS 终端节点在内部执行 Glob 操作的示例。
使用以下文件系统
root
├── l_receipmonth=1997-10
│ ├── l_shipmode=AIR
│ │ └── data_0.csv
│ ├── l_shipmode=SHIP
│ │ └── data_0.csv
│ └── l_shipmode=TRUCK
│ └── data_0.csv
├── l_receipmonth=1997-11
│ ├── l_shipmode=AIR
│ │ └── data_0.csv
│ ├── l_shipmode=SHIP
│ │ └── data_0.csv
│ └── l_shipmode=TRUCK
│ └── data_0.csv
└── l_receipmonth=1997-12
├── l_shipmode=AIR
│ └── data_0.csv
├── l_shipmode=SHIP
│ └── data_0.csv
└── l_shipmode=TRUCK
└── data_0.csv
以下查询通过 Blob 终端节点执行
SELECT count(*)
FROM 'az://root/l_receipmonth=1997-*/l_shipmode=SHIP/*.csv';
它将执行以下步骤
- 列出所有以
root/l_receipmonth=1997-为前缀的文件root/l_receipmonth=1997-10/l_shipmode=SHIP/data_0.csvroot/l_receipmonth=1997-10/l_shipmode=AIR/data_0.csvroot/l_receipmonth=1997-10/l_shipmode=TRUCK/data_0.csvroot/l_receipmonth=1997-11/l_shipmode=SHIP/data_0.csvroot/l_receipmonth=1997-11/l_shipmode=AIR/data_0.csvroot/l_receipmonth=1997-11/l_shipmode=TRUCK/data_0.csvroot/l_receipmonth=1997-12/l_shipmode=SHIP/data_0.csvroot/l_receipmonth=1997-12/l_shipmode=AIR/data_0.csvroot/l_receipmonth=1997-12/l_shipmode=TRUCK/data_0.csv
- 使用请求的模式
root/l_receipmonth=1997-*/l_shipmode=SHIP/*.csv过滤结果root/l_receipmonth=1997-10/l_shipmode=SHIP/data_0.csvroot/l_receipmonth=1997-11/l_shipmode=SHIP/data_0.csvroot/l_receipmonth=1997-12/l_shipmode=SHIP/data_0.csv
同时,相同的查询可以通过 datalake 终端节点执行,如下所示
SELECT count(*)
FROM 'abfss://root/l_receipmonth=1997-*/l_shipmode=SHIP/*.csv';
这将执行以下步骤
- 列出
root/中的所有目录root/l_receipmonth=1997-10root/l_receipmonth=1997-11root/l_receipmonth=1997-12
- 过滤并列出子目录:
root/l_receipmonth=1997-10,root/l_receipmonth=1997-11,root/l_receipmonth=1997-12root/l_receipmonth=1997-10/l_shipmode=SHIProot/l_receipmonth=1997-10/l_shipmode=AIRroot/l_receipmonth=1997-10/l_shipmode=TRUCKroot/l_receipmonth=1997-11/l_shipmode=SHIProot/l_receipmonth=1997-11/l_shipmode=AIRroot/l_receipmonth=1997-11/l_shipmode=TRUCKroot/l_receipmonth=1997-12/l_shipmode=SHIProot/l_receipmonth=1997-12/l_shipmode=AIRroot/l_receipmonth=1997-12/l_shipmode=TRUCK
- 过滤并列出子目录:
root/l_receipmonth=1997-10/l_shipmode=SHIP,root/l_receipmonth=1997-11/l_shipmode=SHIP,root/l_receipmonth=1997-12/l_shipmode=SHIProot/l_receipmonth=1997-10/l_shipmode=SHIP/data_0.csvroot/l_receipmonth=1997-11/l_shipmode=SHIP/data_0.csvroot/l_receipmonth=1997-12/l_shipmode=SHIP/data_0.csv
正如您所见,由于 Blob 终端节点不支持目录的概念,过滤只能在列出文件之后进行,而 ADLS 终端节点可以递归地列出文件。特别是在分区/目录数量较多的情况下,性能差异可能会非常显著。