Community研究与数据分析github.com

ZrobDev/etl-pipelines

用于设计、审查或调试Python批量ETL/ELT流程——处理重复/缺失行、回填重复计数、水印间隙、延迟到达数据、重试风暴、Schema漂移导致加载失败、抓取到数仓的管道,以及静默加载零行的任务,或者在选择cron、n8n、Airflow和dlt时提供指导。

etl-pipelines 是什么?

该技能为开发者设计、审查和调试基于Python的批量ETL/ELT管道提供了系统化的参考指南。它涵盖了从数据粒度定义、幂等加载模式、增量加载策略、回填、重试与超时处理、Schema漂移,到抓取到数仓的完整流程。技能文档强调了幂等性的关键原则,并提供了具体的PostgreSQL、polars、DuckDB和pytest实现建议。它支持多种编排器(cron、n8n、Airflow、dlt),并能指导用户根据任务复杂度做出合理选择。此外,该技能还包含了最低限度的可观测性要求(结构化日志、断言、心跳、新鲜度告警)以及一份详细的审查清单,帮助确保管道在生产中可靠运行。用户可以通过引用`references/patterns.md`和`references/gotchas.md`获取具体的SQL和Python骨架以及真实案例教训。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ZrobDev/data-engineering-skills/tree/HEAD/skills/etl-pipelines

Installed? Explore more 研究与数据分析 skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

ETL管道

批量导入到PostgreSQL(默认存储),使用Python(uv, ruff, pytest)。本地转换:polars 2.x 或 DuckDB 2.0。

流程

  1. 声明粒度:一行表示什么,由哪个自然键标识。没有键,就没有幂等加载;先定义一个键(或内容哈希)。
  2. 选择加载模式(见下)。选择增量策略。最后选择编排器。
  3. 编写运行逻辑,使得重新运行任何窗口都能得到相同的最终状态。验证它:在测试中运行两次,断言行数和校验和不变。
  4. 在首次生产运行前添加可观测性检查(见下)。
  5. 对照末尾的清单进行审查。

幂等加载模式

  • 行具有稳定的自然键且可能变化:INSERT ... ON CONFLICT (key) DO UPDATE(Postgres)或 MERGE。添加 WHERE target.hash IS DISTINCT FROM excluded.hash,使未更改的行不会造成更改。
  • 数据作为一个完整的分区(天、批次ID)到达,并作为一个单元重写:在一个事务中删除-插入该分区。提交删除与插入分离会在崩溃时留下空洞。
  • 整个表重建:加载到临时表,验证,然后交换(在一个事务中重命名)。读者永远不会看到半加载的表。
  • 仅追加的事实,无键:添加一个确定性的 batch_id / 源文件哈希,并使加载拒绝或替换已加载的批次ID。
  • 绝不要盲目 INSERT 然后再去重。在去重运行之前,重复项会到达仪表板。
  • 通过 COPY 加载到临时/暂存表,然后进行一次基于集合的更新插入。从Python逐行更新插入的速度慢10-100倍。

增量加载

  • 在源表上使用单调递增且有索引的列作为水印(updated_at, sequence id)。将其存储在状态表中,与加载在同一事务中写入,绝不要放在旁边的文件中。
  • 读取 watermark - lookback(例如1到24小时),而不是 > watermark。源提交不按时间戳排序;lookback加更新插入可以吸收延迟行。根据测量到的延迟大小来设置lookback,而不是猜测。
  • 水印列不可靠(更新时不更新,时区混合,软删除缺失):定期进行全量对账(键集差异或按分区计数+校验和),而不是信任它。
  • 源端的硬删除对水印不可见。明确决定:通过键差异对账,或接受并记录。
  • CDC(Postgres逻辑复制,Debezium风格日志读取器):仅当需要删除、亚秒级延迟或源无法按时间戳查询时使用。它会引入slot、保留风险(空闲slot会填满源磁盘)以及排序/快照处理。否则坚持使用水印。

回填

  • 每个任务接收 --start 和 --end(半开区间 [start, end),UTC),并据此推导所有内容。转换内部不要使用 now()。
  • 回填是对旧窗口的正常任务运行。如果需要不同的代码,则正常任务不是幂等的;修复它。
  • 按分区分割,独立运行分区,记录每个分区的状态,以便在分区40失败时可以从40恢复。
  • 通过相同的更新插入/分区替换回填到同一目标。检查是否重复计数:在每个窗口前后执行 count(*) 和 count(distinct key)。
  • 对源API/DB进行限流;回填是自我造成的负载峰值。

重试、超时、“恰好一次”

  • 跨网络不存在恰好一次的传递。构建至少一次传递加上幂等写入;结果是有效一次。
  • 仅对幂等步骤进行重试,使用指数退避和抖动、尝试次数上限,以及对每个网络调用设置硬超时(连接和读取分别设置)。对429/5xx/超时重试,不对4xx重试。
  • 遵守 Retry-After。没有全局速率限制的重试会放大故障。
  • 发送电子邮件、收费或发布到webhook的步骤不是幂等的:使用在调用前存储的幂等键进行保护。
  • 最终失败时以非零退出并告警;绝不要吞掉异常以保持调度为绿色。

Schema漂移

  • 落地层宽松(将原始负载存储为JSON/文本以及加载元数据)。类型层严格,在类型/必填字段违反时大声失败。
  • 新列:允许添加并发出告警。删除、重命名或重类型的列:使运行失败并显示差异;不要强制转换为NULL。
  • 使用显式schema(polars schema、pydantic或DuckDB DESCRIBE)在边界处进行验证,一次完成,不要分散在转换中。

抓取到数仓

  • 两个阶段。阶段1获取并存储原始响应,不变且不可变(对象路径以源、获取时间、内容哈希为键)。阶段2将原始数据解析成表。
  • 解析器错误随后通过重新解析存储的原始数据来修复,无需重新获取,无封禁风险,也不丢失历史。
  • 记录URL、抓取时间戳、HTTP状态码、解析器版本到每个解析行。解析是原始字节加上解析器版本的纯函数。
  • 抓取失败和空/被阻止的页面(验证码、带有登录墙的200响应)通过内容检查检测,而不仅仅通过状态码。

可观测性(最低要求)

  • 记录每一步:读取行数、写入行数、拒绝行数、持续时间、窗口开始/结束、运行ID。结构化(JSON)行。
  • 断言不变量:输出与输入的行数在容忍范围内;对通常非空的窗口返回零行导致运行失败。静默的零行成功是最常见的未检测故障。
  • 心跳:在成功时和开始时ping外部健康检查;错过ping时告警(捕获cron未触发、主机宕机)。失败也ping失败端点。
  • 在新鲜度上告警(加载的最大时间戳早于阈值),而不仅仅在任务错误上告警。
  • 维护运行日志表(运行ID、窗口、状态、计数);它也是你的回填账本。

选择编排器

  • Cron(或systemd timer)+ 一个脚本:适合一到几个独立任务,无跨任务依赖,团队能读取日志。默认选择。需要上述心跳和避免重叠运行的锁(flock)。
  • n8n:低负载,SaaS API之间的粘合剂,webhook触发,非开发者编辑流程。不适合大批量、重型转换或代码审查;将重逻辑放在它调用的Python脚本中。
  • Airflow 3.x:多个DAG,跨任务依赖,一流的回填、重试和SLA,审计UI。真正的运营成本(调度器、元数据库、worker);对于少量任务不值得。Airflow 2到3的升级是带有破坏性变更的迁移;在复制代码片段前检查版本。对于Airflow上的dbt,使用Astronomer Cosmos。
  • dlt:用于API或数据库摄取,自动处理schema推断、增量状态和合并/替换配置。用它代替手动编写分页提取+水印+更新插入代码;仍然对其配置应用上述规则。
  • 容器:secrets作为Compose secrets从 /run/secrets/<name> 读取,绝不要使用env vars或env_file;将发布的端口绑定到127.0.0.1或保留在内部网络上。

供应商技能

当任务需要dbt或Airflow的特定功能(模型、测试、DAG编写、操作符、Cosmos配置)时,不要临时发挥:使用通过此仓库的 de-vendors 技能拉取进来的官方供应商技能,仅当需要检查哪个版本是当前版本时,才回退到 references/landscape-2026-11.md 中的版本。

审查清单

  • 声明了键和粒度;重新运行相同窗口产生相同的状态(已测试)。
  • 删除和插入在一个事务中;水印随加载一起提交。
  • 存在lookback;对延迟数据和删除有明确答案。
  • 超时、带退避的有界重试、非幂等步骤已保护。
  • 原始落地不可变;解析可分离且可重新运行。
  • 记录了每一步的行数;存在零行和新鲜度告警;已配置心跳。
  • Schema漂移在类型层大声失败。
  • 编排器的选择基于任务数量和依赖关系,而不是习惯。

具体SQL和Python骨架:references/patterns.md。来自实际使用的教训:references/gotchas.md。

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

相关技能