-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
[QUERY] How to handle etag mismatch (412 Precondition Failed) caused by client retry #39146
Comments
Thank you for your feedback. Tagging and routing to the team member best able to assist. |
Hi @joelverhagen - For example, if as part of the Retry process, it may look like:
If what you want to do is outside the scope of the pipeline processing of the request, it seems like that would be possible without having to deal with the internal model transformation, assuming you are handling the failure of just the table operation, as in your example code. Is your concern that doing this would be too expensive? The other concern I'd have with any sort of local diff approach is that depending on the nature of the update, there is no guarantee that a local comparison would reflect the intended outcome of the current state of the entity on the service. Perhaps the example above is contrived, but if you were updating a |
Hi @joelverhagen. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue. |
Thanks @christothes for entertaining my edge case.
I think I would like the first idea as step 1, e.g. a The second idea sounds like something I would try if the number of cases got too many and it felt mechanical at the GET -> diff side, i.e. a lot of code duplication. This second idea has the benefit of limiting additional allocations. You don't need to reserialized the domain model to the wire format a second time in the
Yes, I was imagining something like that as a generic solution across all domain models.
I am not so concerned about the compute/memory cost (although it does bother me 😅). I'm more thinking that the True representation of the entity is the wire format and not the domain model, at least for the moment in time when a fraudulent 412/409 occurs. Consider a case where the diff is done the domain model and then later a new property is added. The wire format (and Azure SDK) will pick up the change automatically via reflection but the hand-rolled diff in the
Yes, I take your point. I think this boils down to a CQRS concept. How is the "command" defined? Is it If somehow Azure Table Storage persisted a change token or client generated request ID then the For my application I have an So, I think I could add a "change ID" column to all of my entities and handle the 409/412 with a custom pipeline like you described, i.e. "if this thread performed a retry, and a 409/412 was hit, do a For the sake of a general Azure SDK question, I think it would be great to have access to the wire model or have the capability to hook into the pipeline with a predicate like this: public delegate Task<bool> ShouldSwallowFailedRequestAsync(WireFormat model, RequestFailedException ex, bool hasRetry); And you could call |
Thanks @joelverhagen for the additional context!
I think there are bug trade-offs either way. But if your comparison was targeted for the scenario rather than a complete entity comparison, a model comparison seems safe here. Something else to consider is that the wire format is actually not internal, it's just a But all that said, the only place you'd be able to evaluate and manipulate the request and response would be in a pipeline policy. I think a policy-based solution would be much more complex for limited additional benefit. However, if you decide this is the path you think makes the most sense for your scenario, I'd be happy to look at any implementation ideas you come up with. The easiest path would probably be to supply a custom RetryPolicy and put the logic there. |
Hi @joelverhagen. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue. |
I agree with that.
Yes, that would be ideal. Unfortunately, I don't see an easy way to get access to the dictionary. I would like to call the azure-sdk-for-net/sdk/tables/Azure.Data.Tables/src/Extensions/TableEntityExtensions.cs Line 16 in aa5fe51
Additionally the binder used inside the method is internal: azure-sdk-for-net/sdk/tables/Azure.Data.Tables/src/Extensions/TablesTypeBinder.cs Line 11 in aa5fe51
Would it be possible to make this
Is it possible to interrogate the Maybe I can do a pass-through/no-op custom policy that simply allows the top-level call site (e.g. |
Here is an example of accessing the raw dictionary response: var response = client.GetEntity<UserEntity>(userEntity.PartitionKey, userEntity.RowKey, null, default);
var dict = response.GetRawResponse().Content.ToObjectFromJson<Dictionary<string, object>>();
foreach (var kvp in dict)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
/*
prints:
odata.metadata: https://chrissscratch.table.core.windows.net/$metadata#patchicm/@Element
odata.etag: W/"datetime'2023-10-13T15%3A02%3A07.4628778Z'"
PartitionKey: 23ee47a8-5120-49fb-9064-4e12c60fa939
RowKey: 40419cd3-e3c0-4c32-992f-8355632999ab
Timestamp: 2023-10-13T15:02:07.4628778Z
Value: 476692844
*/
} |
Hi @joelverhagen. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue. |
Thanks @christothes.
I will take you up on this! I ran into pain points that it would be nice to improve on but it's not critical.
Here is the solution I came up with (with a diff from the repro I originally included). |
What if you do the work of |
Hi @joelverhagen. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue. |
I'm not sure why I didn't think of that! Thanks! And yes, that seems works also. Here's my attempt: Challenges of the design:
So the retry occurring inside or outside of the pipeline seems to be a trade-off between which contextual information you want available. I think this is practical but nasty/complex enough that it should only be done if it's really important to no-op on these failures in a best effort -- which I guess is a decision based solely the dev's requirements. So of course, I want to implement it 😈 |
For methods like Update, at least, you can get the RK and PK from the URI
This could probably be optimized to be based on the HTTP verb for Update or Add
But another possible approach to retaining the call site context is to utilize CreateClientRequestIdScope. In theory, you could create some kind of mapping between your custom requestId value and the call site details. |
This is great. Leaving this POC for posterity: https://gist.github.com/joelverhagen/bbf0bdd91cfcdb5784abf135a859a108 The problem gets nasty with It would still be nice to have Thanks for your help. I'll close this since it appears quite feasible with |
Library name and version
Azure.Data.Tables 12.8.1
Query/Question
Consider this scenario:
If-Match: <etag>
)(there are adjacent scenarios like adding an entity for the first time and retrying to get a conflict)
What is the client to do to recover?
There are certainly solutions existing today, for example the client can retry the whole unit of work bounding the table storage operation (perhaps no-oping if the Table state actually went through or using a totally different entity depending on the implementation). However, sometimes the cost to retry the entire operation in the application is high enough that it's worth taking on more complexity to handle the 412 gracefully (maybe there is bunch of other API calls prior to the Azure Table API call and you don't want to repeat those in cases like this).
Simply detecting a retry in the Azure SDK HTTP pipeline is also not sufficient. Some retries will succeed (for example the request fails prior to the data operation persisting on the server side) and others will never succeed (for example another thread updates the entity, causing a legitimate Precondition Failed error).
One idea I wanted to try was performing a client-side diff. So if you hit an HTTP 412 (and optional only if and only if the client performed a retry as an optimization) you can fetch the entity from Table storage and compare the entity with what you were expecting to update. This could be done in a partial manner if you were hoping for a
PATCH
-like operation or for all properties if you were hoping to replace the entire entity state.A Table entity is essentially a property bag of well-defined types so performing a client-side diff is pretty straight-forward. Unfortunately, the intermediate model (
Dictionary<string, object>
fromToOdataAnnotatedDictionary
) that would be ideal for generic comparison isinternal
.I can perhaps call the method with reflection or write my own serialization model used just for this comparison. But I wanted to raise this problem space to the Azure SDK team and see if there any prior art in this area or recommendations.
Here is a sample app that shows how client-driven retries can cause etag mismatches:
Sample C# console app
The output looks like this:
Environment
The text was updated successfully, but these errors were encountered: