
最近折腾了 Glue Data Catalog 和 S3 Tables 的 Iceberg REST API(应用程序接口),把两边的权限模型对照着看了一遍,踩了几个坑,这篇把发现整理出来。
之前写过两篇文章,直接访问 Iceberg REST Catalog,对比了 Glue 端点和 S3 Tables 端点的设计差异:
那两篇都是从"同一个表,两个入口"的角度来看。这次换个角度:把官方文档里每个操作对应的权限映射表并列摆在一起。
两个端点的文档都提供了 Iceberg REST 操作到 IAM(身份和访问管理)操作、Lake Formation 权限以及 CloudTrail 事件名称的映射表。把这两个表放在一起看,发现了一些有意思的差异,下面一一说明。
这次不是动手测试,而是基于文档的对比分析。凡是和我之前实测结果吻合的地方,会单独注明。
参考的官方文档:
| 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 |
就这么把两张表摆在一起,已经能看出好几个差异了。
首先注意到的是这个。对于涉及数据的操作,比如 loadTable、createTable、updateTable,两个端点要求的 IAM 动作数量不一样。
Glue 端点的 loadTable 只需要一个动作 glue:GetTable,而 S3 Tables 端点的 loadTable 需要两个:s3tables:GetTableMetadataLocation 和 s3tables:GetTableData。createTable 和 updateTable 也是同样的模式: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)指出,CreateTableAPI 只在表 bucket 里创建一个空表,要设置 schema 还需要两个额外动作UpdateTableMetadataLocation和GetTableMetadataLocation,总共四个。也就是说,"调用 API 创建表"和"真正得到一个可用的、已设置 schema 的表"需要不同数量的权限。如果只看 REST API 的权限表,以为两个动作就够了,在设置 schema 阶段就会踩坑。
下一个差异: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 只出现在 Glue 侧,这也是值得注意的差异。Glue 有明确定义的 updateNamespaceProperties 操作,对应 IAM 动作 glue:UpdateDatabase,但这个操作在 S3 Tables 的操作列表里完全不存在。S3 Tables 的已知限制说明里提到"命名空间只支持 owner 属性",但官方操作表里没有对应的独立操作。
这个在将来可能会补上,但就目前而言,又是两者之间的一个差异点。
根据官方文档,S3 Tables 的 dropTable 如果指定 purge=false 会返回 400 Bad Request,也就是说必须为 true。而 Glue 的 DeleteTable(对于 S3 上的表)文档说明是:如果指定 purge=true,操作会失败且不删除数据。
同一个"删除表"操作,但文档里 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 侧做了同样的测试。
我的测试环境中,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 的 DeleteTable 在 purge=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。
这说明 DeleteTable 的 purge 行为不是由"你访问的是哪个端点"决定的,而是由"表的底层实体是什么"决定的:底层是 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 要求。
四个场景的完整测试结果:
| 表实体 | 不带 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 表",两者的要求恰好相反,这个结论有四个场景的实测结果和错误信息双重支撑。
说真的,希望官方能统一成一个约定……
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 权限映射表并列摆在一起做文档对比。
总结一下发现:
createTable 作为 REST 操作只需要两个动作,但按 SQL 语义文档,真正得到一个可用的、设置了 schema 的表需要四个)。renameTable 操作。owner 属性)。purgeRequested,不是 purge。purge 是否必须,不是由"Glue 端点还是 S3 Tables 端点"决定的,而是由表的底层实体(S3 Tables 联邦还是 Glue 原生通用 S3)决定的,且两者要求恰好相反,这通过四个场景的实测验证了。虽然两者都实现了同一个 Iceberg REST Catalog 规范,但在权限模型粒度和"什么应该被拆成独立 API"的设计决策上,Glue 和 S3 Tables 之间的差异相当大。我认为这就是之前几篇文章里提到的结构性差异——Glue 作为目录中枢,S3 Tables 作为存储之上的轻量翻译层——在权限映射表这个层面也一致地体现出来了。
把实测结果和文档对照着看,能发现只看其中任何一方都会遗漏的东西。以后也想继续做这种对照分析。
希望这篇文章对正在选择两个端点的同学有帮助。