fix(rate-limiter): return Iceberg ErrorResponse JSON body on HTTP 429#4404
fix(rate-limiter): return Iceberg ErrorResponse JSON body on HTTP 429#4404travis-bowen wants to merge 2 commits into
Conversation
RateLimiterFilter previously returned HTTP 429 with an empty body. Iceberg REST clients cannot parse the empty response and surface an opaque failure. Add an Iceberg-compatible ErrorResponse body consistent with IcebergExceptionMapper's response shape, including Content-Type, response code, type, and message fields. Fixes apache#4402 Signed-off-by: Travis Bowen <travismbowen@gmail.com>
dimas-b
left a comment
There was a problem hiding this comment.
Thanks for your contribution, @travis-bowen !
Technically, changing from a no-body response to a JSON response is a non-breaking change, I think.
| Response.status(Response.Status.TOO_MANY_REQUESTS) | ||
| .type(MediaType.APPLICATION_JSON_TYPE) | ||
| .entity( | ||
| ErrorResponse.builder() |
There was a problem hiding this comment.
This response form is specific to the Iceberg REST API, but the filter covers all Polaris REST API, IIRC... I'm not sure using this response form is relevant to all of them.
... especially considering that changing the response payload after we release with some payload is going to be very difficult.
Is there a way to restrict this to IRC endpoints?
There was a problem hiding this comment.
So, it should be possible with changes in the PreMatching filter.
However, it seems that we already have expectations in polaris of ErrorResponse being built even for management APIs.
Here's what I found.
https://github.com/apache/polaris/blob/main/runtime/service/src/main/java/org/apache/polaris/service/exception/PolarisExceptionMapper.java#L44-L83
What do you think about leaving this to be universal? If not I can try the filtering to Iceberg?
There was a problem hiding this comment.
🤦 TBH I'm not sure that applying PolarisExceptionMapper to non-IRC APIs was intentional 😅
There was a problem hiding this comment.
I did a quick wire capture of a duplicate "create catalog" response (Management API) and got:
{
"error": {
"message": "Cannot create Catalog polaris. Catalog already exists or resolution failed",
"type": "AlreadyExistsException",
"code": 409
}
}
There was a problem hiding this comment.
So, that does look like an IRC error response payload... while I do not agree with this approach for Polaris' own API, this is the state of the project ATM.
Therefore, it makes sense to use the same payload structure for 429 responses too.
Thanks for the pointers, @travis-bowen 👍
Align the ErrorResponse type string with PyIceberg's existing TooManyRequestsError class and the Iceberg exception naming convention, rather than a non-existent RateLimitExceededException type. Signed-off-by: Travis Bowen <travismbowen@gmail.com>
|
I'll add this to the Community sync agenda on May 14... will merge after the call unless other reviewers raise concerns. |
|
Slight correction: It's |
Motivation
RateLimiterFilterpreviously returned HTTP 429 with an empty body and noContent-Typeheader:Iceberg REST clients (pyiceberg, Spark, and any third-party caller) cannot meaningfully parse this. The Iceberg Java client's
DefaultErrorHandler.parseResponse()attemptsErrorResponseParser.fromJson("")on the empty body, catches the JSON parse failure, and falls back to building anErrorResponsewithmessage = "". Since there is nocase 429:in anyErrorHandlersswitch, the result is always:— an opaque error with no actionable context for the caller.
Changes
Add an Iceberg-compatible
ErrorResponseJSON body to the 429 response.Ecosystem Context
The Iceberg REST OpenAPI spec does not define a 429 response schema — implementations are on their own. Surveying the major catalog implementations:
ErrorModel?{"__type": "ThrottlingException", ...}format){"status": 429, "errorCode": "TOO_MANY_REQUESTS", ...}format){"error_code": "RESOURCE_EXHAUSTED", ...}format)All other catalogs return some JSON, but none follow the Iceberg
ErrorModelschema. With this fix, Polaris becomes the most spec-consistent implementation — clients usingErrorResponseParserwill be able to round-trip the 429 body correctly.Compatibility
ErrorResponse.Tests
Added
testRateLimitedResponseHasIcebergErrorBody— a full integration test that performs a live HTTP request against a rate-limited endpoint, reads the response body, and round-trips it throughErrorResponseParser.fromJson(...)to prove Iceberg client compatibility. AssertsContent-Type: application/json,code == 429,type == "RateLimitExceededException", andmessagecontains"Rate exceeded".Fixes #4402