返回

Snowflake托管Iceberg表的事件驱动Glue Catalog注册方案

RSS Snowflake Engineering (Medium) AI 逐段翻译 发布 2026-09-24 03:01 收录于 09-26

有效阅读仅保存在当前浏览器,用于「为你」推荐;暂停或清除记录。

DataHot 速览

Snowflake在2025_01 BCR版本中为托管的Iceberg表引入新写入路径,会在基础路径后追加8位随机后缀以保证表删除重建时的唯一性,导致Glue crawler抓取到 customers_f0xgvx1a 这类杂乱表名,影响用户写SQL探索数据。Glue本身不提供关闭该后缀或重命名的设置,作者在无法采用新Catalog服务的前提下,改用事件驱动方式将近实时地把表直接注册进Glue Catalog,成本仅每天几美分。文章也提示,已采用Horizon Catalog或其他兼容REST API的Catalog的组织不必自建此方案。

原收录理由(待核验):面向受限于既有AWS技术栈、无法引入新Catalog服务的数据团队,给出绕开Glue crawler随机后缀问题的可落地工程做法。

本文目录 9 节
  1. 在没有 Glue 爬虫的情况下,以近乎实时的方式消除随机后缀问题,每天只需几美分。
  2. 在构建此方案之前:请考虑 Horizon Catalog
  3. 解决方案:无需爬虫的事件驱动注册
  4. 驱动此架构的关键原则:
  5. 实现
  6. 已验证的内容
  7. 成本与性能
  8. 何时使用哪种方法
  9. 结论

译文

AI 逐段翻译

在没有 Glue 爬虫的情况下,以近乎实时的方式消除随机后缀问题,每天只需几美分。

我们的每日站会开始了,客户直接开门见山地说:“我把你昨天创建的 Iceberg 表添加到了 Glue Catalog,我想知道你能不能在建表时不要在表名末尾加上那个随机后缀。”

客户解释说,结果并不是简洁的名称,而是像 customers_f0xgvx1a 和 orders_djctgenc 这样的名字。对于最初编写 SQL 来探索数据的用户来说,这些名称既繁琐又令人沮丧。

Snowflake 在 BCR 捆绑版本 2025_01 中为 Snowflake 托管的 Iceberg 表引入了新的写入路径。此功能无法禁用,它会在基础位置后附加一个随机的 8 字符后缀,以便 Snowflake 在表被删除并重新创建时保证唯一性。Glue 爬虫只是从元数据中读取这个名字并将其写入到目录中。

我心里想:“没问题。”Glue 中肯定有一个设置可以关掉这个功能,或者有一条规则可以用来重命名表以去掉后缀。

不幸的是,Glue 中并不存在这些功能。由于没有获得采用新目录服务的批准,也不想重新架构那些对其他所有数据集都已经行之有效的访问模式,我们不得不另辟蹊径。在经历了几次失败的尝试和走入死胡同的变通方案之后,我最终决定直接将表注册到 Glue 目录中。

我们的管道运行良好。由 Snowflake 管理我们的 Iceberg 表和元数据,我们已经完成了成功交付的 95%。我们只需要拿下最后那 5%,而事件驱动的注册就是答案。

在构建此方案之前:请考虑 Horizon Catalog

企业通常受限于他们可以使用的工具和 API,不能仅仅因为想用新工具就用,还需要获得组织内各方人员的批准。如果那些选项不可行,那么这篇文章将对你有所帮助。

本文所述的自定义解决方案适用于 1) 尚未采用 Horizon Catalog 或其他符合 REST API 规范的目录的组织。2) 你在受监管的环境中工作,添加新的目录服务需要漫长的审批流程。3) 你的下游消费者严格锁定使用 AWS Glue 以获取 Lake Formation 权限,或者已有 Athena 工作负载,和/或 4) 你需要在今天就得到可用的东西,而不能等待高层平台决策。

解决方案:无需爬虫的事件驱动注册

扫描表元数据的爬虫可能是一种自然的替代方案,但此解决方案相比使用爬虫有几个好处。其中之一是我们在 Snowflake 将元数据文件写入 S3 的那一刻就直接将表注册到 Glue Data Catalog 中。SQS 位于 S3 和 Lambda 之间,因为它将事件源与处理解耦,支持针对调用失败的死信队列,并避免在多个表同时写入时触及 Lambda 对 S3 的直接并发限制。该架构的关键组件如下:

驱动此架构的关键原则:

  • 无爬虫。我们使用直接 API 注册,近乎实时。
  • 简洁名称。我们严格控制注册到 Glue Catalog 中的表名。
  • 完整模式提取。我们直接从 Iceberg metadata.json 中提取列,而不是硬编码它们。
  • 自动模式演进。新列会在下一次写入时出现在 Glue 中。
  • 幂等性。同一张表的多个事件只会更新指针。

实现

Lambda 函数处理核心逻辑。它通过 SQS 接收 S3 事件,解析路径以获取简洁的表名,从 Iceberg 元数据中读取模式,并将其注册到 Glue 中。

路径解析: 随机后缀正好是 8 个字符的混合大小写字母数字。

PATH_PATTERN = re.compile(
    r'^(?P<prefix>.+)/(?P<base_loc>[^/]+)\.(?P<rand>[A-Za-z0-9]{8})'
    r'/metadata/(?P<file>.+\.metadata\.json)$')

对于像 iceberg/lseg_qa_iceberg/CUSTOMERS.f0XGVx1a/metadata/00042-uuid.metadata.json 这样的路径,基础位置变成了 CUSTOMERS。我们保留这个简洁名称并丢弃随机的 f0XGVx1a 字符串。

def extract_columns_from_metadata(bucket, metadata_key):
    obj = s3.get_object(Bucket=bucket, Key=metadata_key)
    metadata = json.loads(obj['Body'].read())
    # Iceberg v2: find current schema by ID
    current_schema_id = metadata.get('current-schema-id', 0)
    schemas = metadata.get('schemas', [])
    schema = next(
        (s for s in schemas if s.get('schema-id') == current_schema_id),
        schemas[-1] if schemas else None
    )
    columns = []
    for field in schema.get('fields', []):
        columns.append({
            'Name': field['name'].lower(),
            'Type': iceberg_type_to_hive(field['type']),
        })
    return columns

Iceberg metadata.json 包含完整的表模式,我们直接从 S3 读取。我们解析该文件,然后通过 iceberg_type_to_hive 将数据类型映射到 Hive 等效类型。请注意,此函数必须处理基本类型(string、long、double、boolean、date、timestamptz)以及复杂类型——结构体、列表和映射——这些需要递归转换。Decimal 类型必须保留精度和小数位数(例如 decimal(18,2))。Athena v3、Trino 和现代 Spark 都接受这些 Hive 风格的类型字符串。iceberg_type_to_hive 的参考实现可在本文末尾链接的源代码中找到。由于这些文件只有几 KB,我们完全避免了扫描海量数据文件或手动维护任何列列表。

Glue 注册(带重试):

def register_table(table_name, table_location, metadata_location, columns):
    table_input = {
        'Name': table_name,
        'TableType': 'EXTERNAL_TABLE',
        'Parameters': {
            'table_type': 'ICEBERG',
            'metadata_location': metadata_location,
            'classification': 'iceberg',
        },
        'StorageDescriptor': {
            'Columns': columns,
            'Location': table_location,
            'InputFormat': 'org.apache.iceberg.mr.hive.HiveIcebergInputFormat',
            'OutputFormat': 'org.apache.iceberg.mr.hive.HiveIcebergOutputFormat',
            'SerdeInfo': {
                'SerializationLibrary': 'org.apache.iceberg.mr.hive.HiveIcebergSerDe'
            },
        }
    }
    max_retries = 3
    for attempt in range(max_retries):
        try:
            try:
                glue.get_table(DatabaseName=GLUE_DATABASE, Name=table_name)
                glue.update_table(DatabaseName=GLUE_DATABASE, TableInput=table_input)
            except glue.exceptions.EntityNotFoundException:
                glue.create_table(DatabaseName=GLUE_DATABASE, TableInput=table_input)
            return
        except ClientError as e:
            if e.response['Error']['Code'] == 'ConcurrentModificationException' \
            and attempt < max_retries - 1:
                time.sleep((attempt + 1) * 0.5)
            else:
                raise

传入 metadata_location 参数使这一点得以实现。Athena v3、Spark 和 Trino 等查询引擎会从 Glue 表条目中读取此字段,并直接转到 Iceberg metadata.json 以获取模式和快照数据。我们填充 Columns 列表严格来说是为了向后兼容旧工具,如 Hive 3.x、较旧的 Presto 配置,或任何从 Glue 的 StorageDescriptor 而非直接从 Iceberg 元数据读取模式的工具。Athena v3、带 Iceberg 连接器的 Spark 和 Trino 都会忽略 StorageDescriptor 列,而选用原生 Iceberg 模式。 注意:此示例使用旧版 Hive 格式类(HiveIcebergInputFormat / HiveIcebergOutputFormat / HiveIcebergSerDe)以获得与旧工具的最大兼容性。如果你不需要 Hive 3.x 或旧版 Presto 兼容性,AWS Glue 现在支持通过 OpenTableFormatInput / IcebergInput API 原生注册 Iceberg 表,这完全跳过了 Hive SerDe,是未来更现代的方法。

S3 事件配置你必须按 .metadata.json 后缀进行过滤。这是一个关键细节。Snowflake 在每次刷新时都会写出 Parquet 数据文件、Avro 清单文件和 metadata.json 文件。只有 metadata.json 才代表一次已完成的提交。如果不加过滤,你的 Lambda 会在每次文件写入时触发,并用不匹配路径的警告淹没你的日志。

事件名称:iceberg-metadata-notify 前缀:iceberg/my_schema/ 后缀:.metadata.json 事件类型:s3:ObjectCreated:* 目标:SQS 队列

IAM 策略为 Lambda 角色提供最小权限:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": ["glue:GetTable", "glue:CreateTable", "glue:UpdateTable"],
            "Resource": [
                "arn:aws:glue:REGION:ACCOUNT:catalog",
                "arn:aws:glue:REGION:ACCOUNT:database/my_db",
                "arn:aws:glue:REGION:ACCOUNT:table/my_db/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": ["s3:GetObject"],
            "Resource": ["arn:aws:s3:::my-bucket/*"]
        },
        {
            "Effect": "Allow",
            "Action": ["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"],
            "Resource": ["arn:aws:sqs:REGION:ACCOUNT:my-queue"]
        }
    ]
}

已验证的内容

这当然不是一份详尽的列表,但以下是我为确保直接注册成功而运行的验证及其预期结果。

成本与性能

运行此 Lambda 的财务成本可以忽略不计。你为每张表每次 DML 提交支付一次调用费用,而每次调用只读取几 KB 的 JSON。一个每 5 分钟刷新 20 张表的示例管道每天大约会触发 5,760 次调用,而 Lambda、SQS 和 S3 GetObject 费用合计每天大约只需 $0.01–$0.02。

真正的价值来自你无需再付费的部分。

  • 无需 Glue 爬虫计算资源。
  • 无需爬虫调度/管理开销。
  • 爬虫运行之间不存在数据陈旧窗口。
  • 近乎实时的可用性,端到端通常不到一分钟(S3 事件通知投递到 SQS 加上 Lambda 轮询大约增加 30–60 秒),而不是几分钟。

何时使用哪种方法

如果你的组织允许使用 Horizon Catalog,那就是应该走的路。Horizon Catalog 无疑是平台原生的答案,但如果你目前仍困在以 Glue 为中心的环境中,这种事件驱动的 Lambda 模式非常有效。它将提供干净的命名、自动模式同步和近乎实时的可用性,且运维开销很小。

结论

通过使用原生 S3 事件通知来处理你的外部目录更新,并将元数据提交视为从事实来源读取模式的触发器,你可以维持下游工具所需的精确注册。

这个基于 Python 的 Lambda 解决方案部署快速,每月运营成本很低。主要好处在于用户体验:分析师可以执行像 SELECT * FROM customers 这样的简单查询,而无需处理 Snowflake 管理的 Iceberg 表所生成的后缀相关不一致问题。

我们在保留 Snowflake 管理的 Iceberg 优势的同时,也为前面提到的解决方案(如 Horizon Catalog)搭建了一座桥梁,使其能够可用。

详细的设置指南、CloudFormation 模板和完整源代码可按需提供。

针对 Snowflake 管理的 Iceberg 表的事件驱动 Glue Catalog 注册最初发布于 Snowflake Builders Blog: Data Engineers, App Developers, AI, & Data Science 在 Medium 上,人们正在那里通过高亮和回应这个故事来继续对话。

这篇内容对你有用吗?

反馈只用于改善内容筛选,不等同于收藏

分享这条资讯
分享海报
保存图片
iOS 也可以长按图片保存