dlt 接入 AWS Athena 目标完整指南:S3 Parquet 外部表、Iceberg 与分区适配器详解
【免费下载链接】dltdata load tool (dlt) is an open source Python library that makes data loading easy 🛠️项目地址: https://gitcode.com/GitHub_Trending/dl/dlt
AWS Athena 是dlt(data load tool)官方支持的目标(destination)之一,它把数据以 Parquet 文件的形式写入 S3 桶,并在 Athena 中注册外部表,随后即可用 Athena SQL 直接查询整个文件夹的 Parquet 数据。本文以 Athena 官方文档 为主体,结合仓库内 Athena 目标实现源码 与 配置类,系统讲解安装、凭据配置、Iceberg 表、S3 Tables、LakeFormation 标签、分区适配器等全部细节,帮助你快速搭建一套可落地、可维护的 Athena 数据管道。
Athena 目标的工作原理与适用场景
Athena 目标的运行模型是"S3 存文件 + Glue/Athena 建表":
- 数据以Parquet 文件形式写入 S3 桶的 dataset 目录;
dlt在 Athena 中创建 外部表(external tables),将文件与表结构关联;- 你用 Athena SQL 查询时,Athena 扫描该目录下所有相关 Parquet 文件并返回结果;
dlt自身的元数据表(如_dlt_loads、_dlt_pipeline_state等)以 Iceberg 表形式保存在同一个 S3 桶中;- 数据表也可以按需写成Iceberg 表,便于后续做删除、更新等操作(例如按 GDPR 要求剥离数据)。
从源码看,Athena 的能力声明中有几个关键点:它只支持从 S3 上的 staging 文件加载(preferred_loader_file_format = None),staging 文件格式为 Parquet;标识符不区分大小写;不支持事务(supports_transactions = False);时间戳精度为毫秒(timestamp_precision = 3)。因此在设计管道时,staging 目的地(通常为 filesystem)是必需的(详见下文"Staging 支持")。
安装与项目初始化
1. 安装带 Athena 依赖的 dlt
pip install "dlt[athena]"该安装会一并带入s3fs、pyarrow、pyathena和botocore四个包。如果你的 pip 环境在回溯解析时容易失败,可以手动逐个安装:
pip install dlt pip install s3fs pip install pyarrow pip install pyathena其中pyathena是 dlt 与 Athena 通信的 DB-API 驱动,s3fs负责 S3 文件系统访问,pyarrow用于 Parquet 读写。
2. 初始化 dlt 项目
dlt init chess athena该命令会以chess作为示例 source、athena作为 destination,同时自动配置 filesystem staging 目的地。初始化后请安装项目依赖:
pip install -r requirements.txt配置 S3 存储与 Athena 凭据
编辑项目下的.dlt/secrets.toml,填入以下关键信息:
bucket_url:存放 Parquet 文件的 S3 桶地址;query_result_bucket:Athena 写查询结果的 S3 桶。如果你的 Athena workgroup 启用了 Managed Query Results,可以省略该项;- 凭据:需要对桶有读写权限、对 Athena 有完整访问权限的 AWS 角色。
完整的 TOML 模板如下:
[destination.filesystem] bucket_url = "s3://[your_bucket_name]" # replace with your bucket name, [destination.filesystem.credentials] aws_access_key_id = "please set me up!" # copy the access key here aws_secret_access_key = "please set me up!" # copy the secret access key here [destination.athena] query_result_bucket="s3://[results_bucket_name]" # replace with your query results bucket name [destination.athena.credentials] aws_access_key_id="please set me up!" # same as credentials for filesystem aws_secret_access_key="please set me up!" # same as credentials for filesystem region_name="please set me up!" # set your AWS region, for example "eu-central-1" for Frankfurt注意:
region_name是 Athena 目标必填项之一,dbt 适配也需要它。
如果你把凭据存放在~/.aws/credentials,可以直接删除上面两个credentials段,dlt会自动回退到本地凭据的defaultprofile。要切换 profile,传入 profile 名即可(示例为dlt-ci-user):
[destination.filesystem.credentials] profile_name="dlt-ci-user" [destination.athena.credentials] profile_name="dlt-ci-user"从配置类源码可以看到,AthenaClientConfiguration除继承通用 DWH 配置外,还定义了query_result_bucket、athena_work_group、aws_data_catalog、staging_aws_data_catalog、connection_params、force_iceberg、table_location_layout、table_properties、lakeformation_config、info_tables_query_threshold、db_location等字段;其中query_result_bucket、athena_work_group、aws_data_catalog、info_tables_query_threshold是代码生成文档时重点标注的配置项。
附加目的地配置项
Workgroup 与查询结果桶
[destination.athena] athena_work_group="my_workgroup"to_connector_params(configuration.py)会把query_result_bucket作为s3_staging_dir、athena_work_group作为work_group、aws_data_catalog作为catalog_name传给 pyathena。需要留意:pyathena 的wrap_unload会把s3_staging_dir与"unload/"直接拼接,源码为此专门保证尾部斜杠存在,你配置时注意保持规范格式。
强制 Iceberg 与表位置布局
[destination.athena] force_iceberg=true table_location_layout="{dataset_name}/{table_name}"force_iceberg=true:让所有表都以 Iceberg 格式创建(源码中会相应设置preferred_table_format = "iceberg");table_location_layout:控制 Iceberg 表在桶内的目录布局,dataset_name与table_name会被实际名称替换。默认值为"{dataset_name}/{table_name}"(见 configuration.py)。
冲突规避技巧:如果表被删除后重建,可能遇到com.amazonaws.services.s3.model.AmazonS3Exception: The specified key does not exist错误(并非每次都出现)。此时可以在布局中加入随机 location tag:
[destination.athena] table_location_layout="{dataset_name}/{table_name}_{location_tag}"在建表逻辑中,location_tag会被替换为一个 6 位随机唯一 ID,保证每次重建 Iceberg 表都落在全新目录,从而规避该问题。
数据目录(catalog)
默认目录名为awsdatacatalog,可以修改:
[destination.athena] aws_data_catalog="awsdatacatalog"默认情况下生产表与 staging 表共用同一个 catalog。如果要分离,可显式指定 staging catalog:
[destination.athena] staging_aws_data_catalog="my-staging-catalog"透传 pyathena 连接参数
任何 pyathena 连接设置都可以通过conn_properties传入:
[destination.athena.conn_properties] poll_interval=2注意:TOML 配置里的键conn_properties对应源码中的connection_params字段,它会与原生凭据合并后一起传给 pyathena。
INFORMATION_SCHEMA 查询阈值
dlt在 INFORMATION_SCHEMA 中查表名时,默认阈值是 1000 张表,超过后改为在代码侧过滤更高效。对 Athena 而言更优的默认值是 90:
[destination.athena] info_tables_query_threshold=90指定数据库位置
可以用db_location指定 dataset 的 S3 位置:
[destination.athena] db_location="s3://[your_bucket_name]" # replace with your bucket name该值在CREATE DATABASE ... LOCATION '...'(见 sql_client.py)中使用。
使用 S3 Tables 桶
Athena 目标支持把数据放入 S3 Tables 桶,只需把 catalog 设置为 S3 Tables Catalog:
[destination.athena] aws_data_catalog="s3tablescatalog/[your_table_bucket_name]" # replace with your table bucket name使用前提是:你已经有一个与 AWS 分析服务集成好的 table bucket(创建指引见 AWS 文档 判断 catalog 名是否以s3tablescatalog/开头,以此开启 S3 Tables 模式。
使用 S3 Tables 时隐含以下行为:
- 数据以 Iceberg 表存储;
- 表位置由 S3 Tables Catalog 管理,
table_location_layout设置会被忽略(源码会在自定义布局时打印提示日志); - 使用 s3_tables 命名约定(它在 snake_case 基础上兼容 S3 Tables 命名规则,且把
_dlt_前缀改写为dlt_,见 factory.py 与命名约定文档); - 生产表注册在 S3 Tables Catalog,staging 表注册在普通 catalog。若未指定
staging_aws_data_catalog,staging 默认使用awsdatacatalog,因为 staging 表不是 Iceberg 表、无法注册进 S3 Tables Catalog(见 sql_client.py)。
Write disposition 行为
| 写入策略 | 行为 |
|---|---|
append | 属于该表的文件直接追加到 dataset 文件夹 |
replace | 先删除属于该表的全部文件,再写入当前文件集 |
merge | 回退为append(除非使用 Iceberg 表) |
从源码看,merge 策略选择器只有在table_format == "iceberg"时才返回支持的 merge 策略(delete-insert、upsert、scd2、insert-only),普通 Hive 外部表则返回空列表,因此在加载时 prepare_load_table 会把无可用策略的merge降级为append并输出告警。
数据加载与数据类型
数据加载路径是:先把 Parquet 文件放入 S3,再在 Athena 上定义 schema;查询时 Athena 扫描桶中所有相关 Parquet 文件。dlt内部表一律保存为 Iceberg 表。
关于数据类型,需要注意几个细节:
- 时间戳精度:Athena 表的时间戳精度为毫秒(3 位),
dlt按此精度生成 Parquet 文件;Iceberg 表的精度则为微秒。源码中timestamp_precision = 3、max_timestamp_precision = 3与此对应。 - JSON:Athena 不支持 JSON 字段,JSON 会以字符串存储(类型映射中
json -> string,见 AthenaTypeMapper)。 - TIME 类型(重要警告):Athena不支持 Parquet 文件中的 TIME 列,此类 job 会被永久标记失败。请在数据里把
datetime.time对象转换为str或datetime.datetime。源码在 ensure_supported_type 中会直接抛出TerminalValueError提示用户转换。 - 其他类型映射:
time在 schema 中映射为string,decimal/wei映射为decimal(%i,%i);整数类型按精度映射为tinyint/smallint/int/bigint(Iceberg 表的最小整数为int)。
另外,sql_client.py 中的DLTAthenaFormatter为 pendulum 与 stdlib 的datetime定制了格式化器,统一生成TIMESTAMP '...'字面量,时间戳统一按%Y-%m-%d %H:%M:%S.%f输出——Athena 没有时区类型,所以写入前会先把时刻转换为目标时区再去掉偏移。
表与列标识符
Athena 的标识符不区分大小写,且会把所有标识符小写存储到 INFORMATION_SCHEMA。因此:
- 不要使用 区分大小写的命名约定,否则大小写也会被去除,并可能产生标识符冲突——
dlt会检测到这类冲突并使加载失败。 - 从能力声明看,
casefold_identifier = str.lower、has_case_sensitive_identifiers = False,表名列名最大长度均为 255。
底层机制:Athena 的 DDL(catalog 操作)使用HIVE 转义(反引号`),而其他查询使用PRESTO / 常规 SQL 转义。对应实现见 escape_ddl_identifier(DDL 用escape_hive_identifier)与escape_athena_identifier能力(查询侧)。此外 Athena不支持事务,每条 SQL 语句单独自动提交,rollback_transaction直接抛出NotImplementedError(sql_client.py)。
Staging 支持(强制要求)
使用 Athena 目标必须配置 staging 目的地;如果未把 staging 设为filesystem,dlt会自动帮你设置。这也与能力声明中preferred_loader_file_format = None、只支持从 staging 文件加载的设计一致。
如果你修改了 filesystem 的文件名布局(默认值之外),请遵守以下规则,否则 Athena 无法可靠建表:
- 必须包含
{table_name}占位符,且其后必须紧跟一个正斜杠/; - 必须包含
{file_id}占位符,且位置需在{table_name}之后; {table_name}必须是布局中的第一个占位符。
对应的校验逻辑在 AthenaClient.init中通过path_utils.get_table_prefix_layout完成——如果前缀布局不合法会直接抛错。
Iceberg 数据表
按资源启用 Iceberg
把资源切到 Iceberg 格式只需传入table_format="iceberg":
from typing import Iterable from dlt.common.typing import TDataItem @dlt.resource(table_format="iceberg") def data() -> Iterable[TDataItem]: ...加载流程上,每个 Iceberg 表都会先在 staging dataset(filesystem 与 Athena Glue catalog 两侧)创建一张普通 Athena 表,再把数据拷贝进最终的 Iceberg 表;最终 Iceberg 表与非 Iceberg 表同处一个 dataset。不支持在 Iceberg 与普通表之间来回切换。
对应的 follow-up job 逻辑见 athena.py:Iceberg 表的 append 走SqlStagingCopyFollowupJob、replace 走SqlStagingReplaceFollowupJob,而 AthenaMergeJob 专门实现了 merge——由于 Athena 不支持临时表,它会在 staging schema 中创建insert_<table名>、delete_<table名>辅助表,并用CREATE TABLE ... AS实现中间态。
强制全局 Iceberg 与表属性
[destination.athena] force_iceberg = true还可以调整 Iceberg 表属性:
[destination.athena.table_properties] vacuum_max_snapshot_age_seconds = 86400源码在生成 Iceberg 建表语句时,会把用户属性与强制属性合并:table_type='ICEBERG'、format='parquet'(见 _iceberg_table_properties)。
基于 Iceberg 的 merge 支持
merge写入策略在使用 Iceberg 表时受支持,但有两点务必注意:
- Athena 不支持事务,
dlt用多条 DELETE/UPDATE/INSERT 语句实现 merge,如果管道中途失败,表可能处于不一致状态; - 为绕开 Athena 没有临时表的问题,
dlt会在 staging schema 中创建名为insert_<表名>和delete_<表名>的辅助表(见 AthenaMergeJob)。
LakeFormation 标签
可以在 database 级别为 pipeline 设置 AWS LakeFormation 标签,资源/表会继承父 database 的标签;staging 数据不受影响。
前置条件
- 要应用的 LakeFormation 标签必须已通过 IaC 或 AWS 控制台创建;
- 数据落地的 S3 位置必须在 LakeFormation 中注册为 data location;
- 运行 pipeline 的 IAM 角色/用户除常规 IAM 权限外,还需具备 LakeFormation 权限:
- 对所应用标签的
ASSOCIATE; - 对带标签 database 的
CREATE_TABLE/DESCRIBE/ALTER; - 对表的
ALL。
- 对所应用标签的
(完整权限参考见 AWS Lake Formation 权限参考。)
配置方式
[destination.athena.lakeformation_config] enabled = true [destination.athena.lakeformation_config.tags] my_tag = "my_key"移除标签则设置:
[destination.athena.lakeformation_config] enabled = false源码中 LfTagsManager 负责调用 LakeFormation 客户端的add_lf_tags_to_resource/get_resource_lf_tags/remove_lf_tags_from_resource;enabled同时接受true和false两种可执行状态,标签在每次update_stored_schema时(数据加载前)应用(athena.py)。
dbt 支持
Athena 通过dbt-athena-community适配器获得 dbt 支持。dlt会把aws_access_key_id与aws_secret_access_key写入生成的 dbt profile。使用要点:
- region_name 必须在 Athena 配置中设置;
- 可以通过
aws_data_catalog修改默认目录(默认awsdatacatalog):
[destination.athena] aws_data_catalog="awsdatacatalog"- Iceberg 表受支持,但如果源表是 Iceberg,你的 dbt 模型也必须物化为 Iceberg 表;
- 需要注意日期时间列的精度差异:Iceberg 为纳秒、普通 Athena 表为毫秒,物化时可能出现问题。
dlt 状态同步
Athena 目标完整支持 dlt state 同步:dlt状态保存在 S3 桶中的 Athena Iceberg 表里。
支持的文件格式
- Parquet(默认,也是 staging 文件格式)。
Athena 适配器与分区
athena_adapter用于给 Athena 表添加分区,目前仅支持 Iceberg 表。Iceberg 支持若干分区转换函数(完整列表见 AWS 文档)。
athena_partition辅助类用于生成这些转换函数的分区提示:
| 函数 | 作用 |
|---|---|
athena_partition.year(column_name: str) | 按 date/datetime 列的年份分区 |
athena_partition.month(column_name: str) | 按月份分区 |
athena_partition.day(column_name: str) | 按天分区 |
athena_partition.hour(column_name: str) | 按小时分区 |
athena_partition.bucket(n: int, column_name: str) | 按哈希值分到n个桶 |
athena_partition.truncate(length: int, column_name: str) | 按截断值分区(length对数字是宽度) |
从 athena_adapter.py 源码看,分区提示以x-athena-partition表级 hint 保存,每个项是{列名: 转换模板}的映射;普通列名的模板就是{column_name},转换列则是year({column_name})等模板,最终由 Athena loader 注入转义后的列名生成PARTITIONED BY (...)子句(见 _iceberg_partition_clause)。athena_adapter必须至少指定一个partition值,否则抛出ValueError。
完整示例:
from datetime import date import dlt from dlt.destinations.adapters import athena_partition, athena_adapter data_items = [ (1, "A", date(2021, 1, 1)), (2, "A", date(2021, 1, 2)), (3, "A", date(2021, 1, 3)), (4, "A", date(2021, 2, 1)), (5, "A", date(2021, 2, 2)), (6, "B", date(2021, 1, 1)), (7, "B", date(2021, 1, 2)), (8, "B", date(2021, 1, 3)), (9, "B", date(2021, 2, 1)), (10, "B", date(2021, 3, 2)), ] @dlt.resource(table_format="iceberg") def partitioned_data(): yield [{"id": i, "category": c, "created_at": d} for i, c, d in data_items] # Add partitioning hints to the table athena_adapter( partitioned_data, partition=[ # Partition per category and month "category", athena_partition.month("created_at"), ], ) pipeline = dlt.pipeline("athena_example") pipeline.run(partitioned_data)分区提示的落地效果在 tests/load/athena_iceberg/test_athena_adapter.py 中有对应测试:把month、bucket、truncate等转换与普通列混合传入,验证 hint 是否正确生成。
常见问题与排查要点
The specified key does not exist:Iceberg 表删除重建后偶发,使用table_location_layout中的{location_tag}规避。- TIME 列加载失败:Athena 不支持 Parquet 中的 TIME 列,job 会永久失败,请把
datetime.time转为str或datetime.datetime。 - merge 不生效:确认表为 Iceberg 格式(
table_format="iceberg"或force_iceberg=true),否则自动回退为 append。 - dbt 物化失败:源表是 Iceberg 时模型必须物化为 Iceberg,并注意纳秒/毫秒精度差异。
- staging 布局改动后建表失败:检查 filesystem 布局是否满足
{table_name}/在前、{file_id}在后的规则。
参考实现与测试
- 目标实现:dlt/destinations/impl/athena/athena.py
- 配置定义:dlt/destinations/impl/athena/configuration.py
- 能力声明与类型映射:dlt/destinations/impl/athena/factory.py
- SQL 客户端与转义:dlt/destinations/impl/athena/sql_client.py
- 分区适配器:dlt/destinations/impl/athena/athena_adapter.py
- 加载测试:tests/load/athena_iceberg/
【免费下载链接】dltdata load tool (dlt) is an open source Python library that makes data loading easy 🛠️项目地址: https://gitcode.com/GitHub_Trending/dl/dlt
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考