1

For every 200 status code that we're receiving from the gremlin query, I want to check if the result vertex matches with the vertex that needed to be updated. And if it doesn't match then do a retry.

public async Task<string> ExecuteUpdateQueryAsync(string query, bool ignoreConflict = true)
{
    try
    {
        var repsonse = await Policy
                          .Handle<ResponseException>(x => (long)x.StatusAttributes["x-ms-status-code"] != (long)HttpStatusCode.Conflict)
                          .OrResult<ResultSet<dynamic>>(r => r.StatusAttributes["x-ms-status-code"] == 200)
                          .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
                          .ExecuteAsync(() => gremlinClient.SubmitAsync<dynamic>(query));

        logger.LogDebug(query + JsonConvert.SerializeObject(repsonse.StatusAttributes));
        if (repsonse.Count > 0)
        {
            return JsonConvert.SerializeObject(repsonse);
        }
        return default;
    }
    ...
}

EDIT:

The response we get from the Gremlin client looks something like : (hope this helps)

g.V(['1111|2222|3333','1111|2222|3333']).Property('Transaction_Quantity',4500).Property('Transaction_Type','Repack'){"x-ms-status-code":200,"x-ms-activity-id":"a4af8faf-4aa9-4ae2-8dd8-797bdbd80a97","x-ms-request-charge":34.64,"x-ms-total-request-charge":34.64,"x-ms-server-time-ms":7.6626,"x-ms-total-server-time-ms":7.6626}

I want to be able to compare the transaction quantity in this response with the one that we are trying to update.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • Welcome to StackOverflow! What exactly is your question? – Peter Csala Feb 14 '22 at 14:47
  • @PeterCsala I need to know if there is a way to use the Or functionality in polly to add a condition to the response we receive. For eg. If I want to compare some data in the response to the data in the DB – Samaira Mathers Feb 14 '22 at 15:35
  • Well you should do that inside the action which you decorate with the policy. – Peter Csala Feb 14 '22 at 15:44
  • The `Handle`, `HandleResult`, `Or` and `OrResult` were designed to receive synchronous predicate. In other words they are good for simple checks that's why they are implemented most of the time with lambda expressions. If you need a database call to decide whether a new retry is needed then you should do that inside the `ExecuteAsync` part. Shall I leave a post to demonstrate it? – Peter Csala Feb 14 '22 at 16:13
  • Yes. That'll be really helpful. – Samaira Mathers Feb 14 '22 at 16:22
  • Gremlin requests do not fail if they are unable to fetch a vertex or edge that is being updated. The query ops are executed in a pipeline and if there are no results from the previous op in the query (get a vertex) then the next op (update vertex) will be no-op. Customers can check if an update operation in the query was successful by examining the results in the response. If the update had been successful, the results would contain the vertex object with updated content. <----- How do I check if the result contains the updated content – Samaira Mathers Feb 14 '22 at 16:26
  • Okay since I'm unfamiliar with Gremlin I have to do a little research on it to be able to give you actionable advice. So I'll leave a post tomorrow. – Peter Csala Feb 14 '22 at 16:29
  • I've scrutinised [the .NET driver](https://github.com/apache/tinkerpop/tree/7f7d3a485c7f100f98047b71672a0c2c9ab855b4/gremlin-dotnet/src/Gremlin.Net/Driver) and now I can understand the `ResultSet` and `ResponseException` classes. But without a sample response I can only share with you a non-gremlin specific solution proposal. Can you please amend your question and include a sample response and indicate which part interests you? – Peter Csala Feb 15 '22 at 07:22
  • 1
    I've added a sample response in the edit. Hope that helps. @PeterCsala – Samaira Mathers Feb 15 '22 at 07:43
  • 1
    Thank you so much for taking your time out. Ill try to wrap my head around all of this and try to formulate a solution. Much appreciated! – Samaira Mathers Feb 15 '22 at 11:23

1 Answers1

1

Disclaimer: I'm not familiar with CosmosDb's Gremlin API, so my suggestions regarding the API usage might be incorrect. I try to focus on the Polly side in my post.


So, according to my understanding you want to decide whether or not you need to perform further retry attempts based on the response. If one of the fields of the response matches with some value then you shouldn't otherwise you should.

In case of Polly the Handle, HandleResult, Or and OrResult policy builder functions are anticipating synchronous predicates. In other words they are designed to perform simple assertions (like: status code check, existence check, inner exception type check, etc...) Because of this, most of the time they are defined with simple lambda expressions.

If you need to perform more complex / asynchronous logic then you should put that logic next to your to-be-decorated method call. In other words that logic should belong to the to-be-retried function.


As I said inside the disclaimer I'm not familiar with the Gremlin API. According to this SO thread you can deserialize the response into a DTO by first serializing the ResultSet<dynamic> then deserializing that into YourDto.

private async Task<ResultSet<dynamic>> IssueGremlinQueryAsync(string query)
{
    ResultSet<dynamic> results = gremlinClient.SubmitAsync<dynamic>(query);
    var parsedResult = JsonConvert.DeserializeObject<YourDto>(JsonConvert.SerializeObject(results));
    //TODO: perform assertion against the parsedResult
    return results;
}

Here you have several options how you propagate back the need for further retries to the policy:

  • Use a ValueTuple where one of the items is a flag
  • Use a custom exception
  • etc.

ValueTuple

private async Task<(bool, ResultSet<dynamic>)> IssueGremlinQueryAsync(string query)
{
    ResultSet<dynamic> results = gremlinClient.SubmitAsync<dynamic>(query);
    var parsedResult = JsonConvert.DeserializeObject<YourDto>(JsonConvert.SerializeObject(results));

    //Replace this with your own assertion 
    bool shouldRetry = parsedResult.TransactionQuantity % 2 == 0; 
    return (shouldRetry, results);
}

The first item in the tuple is the flag, whereas the second is the unparsed results.

With this method you can refactor the related part of your ExecuteUpdateQueryAsync method like this:

public async Task<string> ExecuteUpdateQueryAsync(string query, bool ignoreConflict = true)
{
    //...

    var retryPolicy = Policy
                        .Handle<ResponseException>(x => (long)x.StatusAttributes["x-ms-status-code"] != (long)HttpStatusCode.Conflict)
                        .OrResult<(bool ShouldRetry, ResultSet<dynamic> Response)>(
                            x => x.ShouldRetry || (long)x.Response.StatusAttributes["x-ms-status-code"] == 200)
                        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

    var response = await retryPolicy.ExecuteAsync(async () => await IssueGremlinQueryAsync(query));
    
    //...
}

Exception

private async Task<ResultSet<dynamic>> IssueGremlinQueryAsync(string query)
{
    ResultSet<dynamic> results = gremlinClient.SubmitAsync<dynamic>(query);
    var parsedResult = JsonConvert.DeserializeObject<YourDto>(JsonConvert.SerializeObject(results));

    //Replace this with your own assertion 
    bool shouldRetry = parsedResult.TransactionQuantity % 2 == 0;
    return !shouldRetry ? results : throw new OperationFailedRetryNeededException("...");
}

If we should not retry then we return with the unparsed results otherwise we throw a custom exception.

With this method you can refactor the related part of your ExecuteUpdateQueryAsync method like this:

public async Task<string> ExecuteUpdateQueryAsync(string query, bool ignoreConflict = true)
{    
    //...

    var retryPolicy = Policy
                        .Handle<ResponseException>(x => (long)x.StatusAttributes["x-ms-status-code"] != (long)HttpStatusCode.Conflict)
                        .Or<OperationFailedRetryNeededException>()
                        .OrResult<ResultSet<dynamic>>( x => (long)x.StatusAttributes["x-ms-status-code"] == 200)
                        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

    var response = await retryPolicy.ExecuteAsync(async () => await IssueGremlinQueryAsync(query));
    
    //...
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75