site logo

Marico's space

比较 Glue Data Catalog 与 S3 Tables 提供的 Iceberg REST API 权限模型

服务器技术 2026-07-28 11:29:02 9

最近折腾了 Glue Data Catalog 和 S3 Tables 的 Iceberg REST API(应用程序接口),把两边的权限模型对照着看了一遍,踩了几个坑,这篇把发现整理出来。

之前写过两篇文章,直接访问 Iceberg REST Catalog,对比了 Glue 端点和 S3 Tables 端点的设计差异:

  • Hitting the Iceberg REST Catalog Directly: Understanding the Differences Between Glue Data Catalog and S3 Tables
  • Verifying How IAM and Lake Formation Behave for the Glue REST Catalog and S3 Tables

那两篇都是从"同一个表,两个入口"的角度来看。这次换个角度:把官方文档里每个操作对应的权限映射表并列摆在一起。

两个端点的文档都提供了 Iceberg REST 操作到 IAM(身份和访问管理)操作、Lake Formation 权限以及 CloudTrail 事件名称的映射表。把这两个表放在一起看,发现了一些有意思的差异,下面一一说明。

这次不是动手测试,而是基于文档的对比分析。凡是和我之前实测结果吻合的地方,会单独注明。

参考的官方文档:

  • Glue 侧:https://docs.aws.amazon.com/glue/latest/dg/iceberg-rest-apis.html
  • S3 Tables 侧:https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-integrating-open-source.html

按操作对齐权限映射

Operation Glue: IAM action Glue: LF permission S3 Tables: IAM action
getConfig glue:GetCatalog Not required s3tables:GetTableBucket
listNamespaces glue:GetDatabase ALL, DESCRIBE, SELECT s3tables:ListNamespaces
createNamespace glue:CreateDatabase ALL, DESCRIBE, SELECT s3tables:CreateNamespace
loadNamespaceMetadata glue:GetDatabase ALL, DESCRIBE, SELECT s3tables:GetNamespace
updateNamespaceProperties glue:UpdateDatabase ALL, ALTER (not listed)
dropNamespace glue:DeleteDatabase ALL, DROP s3tables:DeleteNamespace
namespaceExists (not listed) - s3tables:GetNamespace
listTables glue:GetTables ALL, SELECT, DESCRIBE s3tables:ListTables
createTable glue:CreateTable ALL, CREATE_TABLE s3tables:CreateTable + s3tables:PutTableData
loadTable glue:GetTable ALL, SELECT, DESCRIBE s3tables:GetTableMetadataLocation + s3tables:GetTableData
updateTable glue:UpdateTable ALL, ALTER s3tables:UpdateTableMetadataLocation + s3tables:PutTableData + s3tables:GetTableData
dropTable glue:DeleteTable ALL, DROP s3tables:DeleteTable
renameTable (not listed; only possible via the extension API's UpdateTable) - s3tables:RenameTable
tableExists glue:GetTable ALL, SELECT, DESCRIBE s3tables:GetTable

就这么把两张表摆在一起,已经能看出好几个差异了。

数据操作的 IAM 动作数量不同

首先注意到的是这个。对于涉及数据的操作,比如 loadTablecreateTableupdateTable,两个端点要求的 IAM 动作数量不一样。

Glue 端点的 loadTable 只需要一个动作 glue:GetTable,而 S3 Tables 端点的 loadTable 需要两个:s3tables:GetTableMetadataLocations3tables:GetTableDatacreateTableupdateTable 也是同样的模式:S3 Tables 侧把权限拆成了"元数据操作动作"和"数据体操作动作"。

这和我在之前一篇文章里实测的结果一致:调用 S3 Tables 的 LoadTable 会在 CloudTrail 里记录一个 GetTableMetadataLocation 管理事件。

这说明 S3 Tables 在 API 层面把元数据访问和数据体访问清晰分离了,而 Glue 端点把一切整合到单个 glue:GetTable 动作里,实际的存储访问权限则委托给 Lake Formation 颁发的凭证来处理。我认为这就是两者设计理念的核心差异。

注意

上面表格里 s3tables:CreateTable + s3tables:PutTableData 是 Iceberg REST API 参考文档(s3-tables-integrating-open-source.html)中定义的 createTable 操作对应的权限集。但另一份关于 SQL 语义的官方文档(https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-sql.html)指出,CreateTable API 只在表 bucket 里创建一个空表,要设置 schema 还需要两个额外动作 UpdateTableMetadataLocationGetTableMetadataLocation,总共四个。

也就是说,"调用 API 创建表"和"真正得到一个可用的、已设置 schema 的表"需要不同数量的权限。如果只看 REST API 的权限表,以为两个动作就够了,在设置 schema 阶段就会踩坑。

renameTable 和 namespaceExists 的差异

下一个差异:renameTable 在 S3 Tables 的支持 API 列表里,但不在 Glue 的列表里。

Glue 的官方文档没有独立的 renameTable 操作。AWS Glue 扩展 API(/extensions/v1/...)的 StartUpdateTableTransaction 说明里提到:"重命名操作也可以通过这个 API 来执行。这种情况下,调用者还必须拥有目标表的 glue:CreateTable 或 Lake Formation 的 CREATE_TABLE 权限。"所以在 Glue 侧,重命名不是独立操作,而是被视为一种更新事务。

namespaceExists 在 S3 Tables 侧是独立操作(HEAD /v1/{prefix}/namespaces/{namespace}),但我在 Glue 的标准 Iceberg REST Catalog API 操作列表里找不到它。两个实现的是同一个 Iceberg REST 规范,但"什么应该被拆成独立的 API"这个设计决策明显不同。

updateNamespaceProperties 的差异

updateNamespaceProperties 只出现在 Glue 侧,这也是值得注意的差异。Glue 有明确定义的 updateNamespaceProperties 操作,对应 IAM 动作 glue:UpdateDatabase,但这个操作在 S3 Tables 的操作列表里完全不存在。S3 Tables 的已知限制说明里提到"命名空间只支持 owner 属性",但官方操作表里没有对应的独立操作。

这个在将来可能会补上,但就目前而言,又是两者之间的一个差异点。

dropTable 的 purge 行为差异

根据官方文档,S3 Tables 的 dropTable 如果指定 purge=false 会返回 400 Bad Request,也就是说必须为 true。而 Glue 的 DeleteTable(对于 S3 上的表)文档说明是:如果指定 purge=true,操作会失败且不删除数据。

同一个"删除表"操作,但文档里 purge 的默认值要求看起来是反过来的。

这看起来很容易搞错,所以我决定实际动手验证一下。

S3 Tables 的 purge 行为

先建了一张表,然后调用 dropTable 不带任何参数:

python -m awscurl --service s3tables --region ap-northeast-1 --profile your-profile \ -X DELETE \ "https://s3tables.ap-northeast-1.amazonaws.com/iceberg/v1/${BUCKET_ARN_PATH}/namespaces/analytics/tables/purge_test"

结果(节选):

{ "error": { "code": 400, "message": "DropTable operation failed. S3 Tables only supports dropping tables with purge enabled.", "type": "purge_enabled" }
}

和文档一致,返回了 400,表依然在列表里。

然后加上查询参数 purge=true 再跑一次,结果得到了一模一样的错误。原来按照 Iceberg REST Catalog 规范,dropTable 指定 purge 的查询参数不是 purge,而是 purgeRequested

python -m awscurl --service s3tables --region ap-northeast-1 --profile your-profile \ -X DELETE \ "https://s3tables.ap-northeast-1.amazonaws.com/iceberg/v1/${BUCKET_ARN_PATH}/namespaces/analytics/tables/purge_test?purgeRequested=true"

这次成功执行,没有返回体,通过列表确认表确实被删除了。

注意

文档里"purge 必须为 true"这个大方向是对的,但以为查询参数叫 purge 就会踩坑。实际上叫 purgeRequested

如果同时用两个端点,这就足够让人实现出 bug 了。

Glue Data Catalog 的 purge 行为

然后在 Glue 侧做了同样的测试。

我的测试环境中,Glue 端点通过 s3tablescatalog 联邦目录访问 S3 Tables 后端的表。所以先通过 Glue 端点调用 DeleteTable,带上 purgeRequested=true,执行成功了,没有任何问题。

python -m awscurl --service glue --region ap-northeast-1 --profile your-profile \ -X DELETE \ "https://glue.ap-northeast-1.amazonaws.com/iceberg/v1/catalogs/123456789012:s3tablescatalog:penguin-rest-test/namespaces/analytics/tables/purge_test_glue?purgeRequested=true"

这和官方文档的说法"Glue 的 DeleteTablepurge=true 时会失败"矛盾了。于是又建了一张表,这次通过 Glue 端点删除时不带 purgeRequested 参数。

python -m awscurl --service glue --region ap-northeast-1 --profile your-profile \ -X DELETE \ "https://glue.ap-northeast-1.amazonaws.com/iceberg/v1/catalogs/123456789012:s3tablescatalog:penguin-rest-test/namespaces/analytics/tables/purge_test_glue2"

结果(节选):

{ "error": { "code": 400, "message": "PurgeRequested must be true for S3 federated iceberg tables.", "type": "InvalidInputException" }
}

错误信息说得很清楚:"S3 federated iceberg tables"。所以 Glue 端点内部会判断表的底层实体是不是 S3 Tables 后端(联邦)的,如果是,就强制要求 purgeRequested=true

这说明 DeleteTablepurge 行为不是由"你访问的是哪个端点"决定的,而是由"表的底层实体是什么"决定的:底层是 S3 Tables 联邦表还是普通通用 S3 bucket 上的表。

文档里说"Glue 的 DeleteTable 在 S3 后端表且 purge=true 时失败",大概描述的是真正 Glue 原生的、放在通用 S3 bucket 上的 Iceberg 表。对于 S3 Tables 后端的表,即使通过 Glue 端点访问,S3 Tables 自己的约束(必须 purgeRequested=true)也会直接透传过来。

为了验证这个假设,我通过 Athena 在一个通用 S3 bucket 上建了一张 Iceberg 表,然后通过 Glue 端点做同样的测试。

先不带 purgeRequested 删除,成功了:

python -m awscurl --service glue --region ap-northeast-1 --profile your-profile \ -X DELETE \ "https://glue.ap-northeast-1.amazonaws.com/iceberg/v1/catalogs/123456789012/namespaces/general_iceberg_db/tables/purge_test_general"

然后在另一张表上带 purgeRequested=true 删除,失败了:

python -m awscurl --service glue --region ap-northeast-1 --profile your-profile \ -X DELETE \ "https://glue.ap-northeast-1.amazonaws.com/iceberg/v1/catalogs/123456789012/namespaces/general_iceberg_db/tables/purge_test_general2?purgeRequested=true"

结果(节选):

{ "error": { "code": 400, "message": "PurgeRequested cannot be true for Glue iceberg tables.", "type": "InvalidInputException" }
}

这和之前 S3 Tables 后端表的错误("PurgeRequested must be true for S3 federated iceberg tables.")几乎是镜像的。AWS 自己的代码里明确区分了"Glue iceberg tables"和"S3 federated iceberg tables",对两者施加了相反的 purgeRequested 要求。

各场景 purge 行为汇总

四个场景的完整测试结果:

表实体 不带 purgeRequested purgeRequested=true
S3 Tables 后端(通过 Glue 端点访问) 400 "PurgeRequested must be true for S3 federated iceberg tables" 成功
S3 Tables 后端(直连 S3 Tables 端点) 400 "S3 Tables only supports dropping tables with purge enabled" 成功
通用 S3 bucket,Glue 原生表(通过 Glue 端点访问) 成功 400 "PurgeRequested cannot be true for Glue iceberg tables"

三个不带 purgeRequested 失败的情况都返回 400 状态码。错误性质是统一的,和你访问哪个端点无关,区别只是错误信息里的措辞不同。

决定 purgeRequested 是否必须的不是"通过 Glue 端点还是 S3 Tables 端点",而是"表的底层实体是 S3 Tables 联邦表还是 Glue 原生的通用 S3 表",两者的要求恰好相反,这个结论有四个场景的实测结果和错误信息双重支撑。

说真的,希望官方能统一成一个约定……

CloudTrail 粒度也反映了 API 设计的分化

S3 Tables 的官方文档明确说明:"LoadTable 会产生一个 GetTableMetadataLocation 管理事件和一个 GetTableData 数据事件。"这和上面说的 IAM 动作拆分是一致的。S3 Tables 在审计日志级别也把"元数据"和"数据体"分离了。

Glue 侧,官方文档显示每个操作的 CloudTrail 事件名称和 IAM 动作名称一致(glue:GetTable 记录为 glue:GetTable),没有 S3 Tables 那种拆分。但我之前实测确认过,当 Glue 端点调用 Lake Formation 的 GetDataAccess 时,会作为独立事件单独记录。看来 Glue 的审计日志分成了两条独立的线:一条是"API 操作本身的日志",另一条是"Lake Formation 授权日志"。

结论

这次换了个角度,没有继续动手实测,而是把 Glue Data Catalog 和 S3 Tables 各自的 Iceberg REST API 权限映射表并列摆在一起做文档对比。

总结一下发现:

  • 数据操作权限的粒度:Glue 把每个操作合并成单个 IAM 动作,S3 Tables 则把元数据动作和数据体动作拆开(createTable 作为 REST 操作只需要两个动作,但按 SQL 语义文档,真正得到一个可用的、设置了 schema 的表需要四个)。
  • 重命名操作:Glue 没有独立的 API,而是作为更新事务的一部分处理。S3 Tables 有专属的 renameTable 操作。
  • namespaceExists:S3 Tables 侧有,Glue 的标准 API 列表里没有。
  • updateNamespaceProperties:Glue 侧有,S3 Tables 没有对应操作(只以受限方式支持 owner 属性)。
  • dropTable 的 purge 行为:查询参数是 purgeRequested,不是 purge。purge 是否必须,不是由"Glue 端点还是 S3 Tables 端点"决定的,而是由表的底层实体(S3 Tables 联邦还是 Glue 原生通用 S3)决定的,且两者要求恰好相反,这通过四个场景的实测验证了。
  • CloudTrail 粒度:S3 Tables 对元数据操作和数据操作分别记录事件,Glue 按操作记录一个事件,Lake Formation 授权评估作为独立线索出现。

虽然两者都实现了同一个 Iceberg REST Catalog 规范,但在权限模型粒度和"什么应该被拆成独立 API"的设计决策上,Glue 和 S3 Tables 之间的差异相当大。我认为这就是之前几篇文章里提到的结构性差异——Glue 作为目录中枢,S3 Tables 作为存储之上的轻量翻译层——在权限映射表这个层面也一致地体现出来了。

把实测结果和文档对照着看,能发现只看其中任何一方都会遗漏的东西。以后也想继续做这种对照分析。

希望这篇文章对正在选择两个端点的同学有帮助。