1
votes

I have a custom entity which has near real-time updates (approx 1000 updates per minute). Since the same entity record could get updated in different batch update requests and the requests are received from different async sources, there are chances that a more recent update in the CDS could get overwritten by a slightly older update.

  1. Update-1 (with custom attribute datamodifiedon set to 8.02 PM) for Entity record A is processed by async service S1.
  2. Update-2 (with custom attribute datamodifiedon set to 8.03 PM) for Entity record A is processed by async service S2.

Due to network latency or some other reason, S1 service is slightly slow and service S2 went ahead and fired its update call for Entity record A with datamodifiedon 8.03 PM.

Service-1 now fires Update-1 on the same entity record and the update happened at 8.03PM gets overwritten by update happened at 8.02PM leading to data loss.

I have registered a plugin for my custom entity's Update SDK message at Stage 10(Pre-Validation). This plugin compares datamodifiedon attribute from input parameters with the one set in pre-entity images from plugin context. And throws Invalid Plugin Execution exception if the pre-entity image already has more recent datamodifiedon attribute.

// If ModifiedOn value in pre-image is later than the one received in input parameters, this is an obsolete request and must be rejected.
if (preImage.Contains("datamodifiedon")
    && preImage.GetAttributeValue<DateTime>("datamodifiedon") != DateTime.MinValue
    && entity.Contains("datamodifiedon")
    && entity.GetAttributeValue<DateTime>("datamodifiedon") != DateTime.MinValue
    )
{
    if (DateTime.Compare(preImage.GetAttributeValue<DateTime>("datamodifiedon"), entity.GetAttributeValue<DateTime>("datamodifiedon")) > 0)
    {
        string traceMessage = "PreOperationLiveWorkItemUpdatePlugin: Update request is obsolete. datamodifiedon field found in pre-entity image: "
            + preImage.GetAttributeValue<DateTime>("datamodifiedon")
            + "datamodifiedon field in Input parameters: "
            + entity.GetAttributeValue<DateTime>("datamodifiedon");

        tracingService.Trace(traceMessage);
        throw new InvalidPluginExecutionException(traceMessage);
    }
}

Since the updates happen very frequently, is there a possibility that 1. Update-2 (datamodifiedon 8.03 PM) is in pre-validation stage. Post successful validation, actual update in database is in progress. 2. Now Update-1 enters pre-validation stage. Since Update-2 is not yet committed in the database, will the pre-entity image received in this stage be the old value still and lets the validation to go through?

Would registering this plugin at Pre-Operation or Post-Operation stage help instead of Pre-Validation stage since the other two stages execute in the same database transaction?

Is there any other way to solve this concurrency problem? Since the update is initiated via a batch odata call, eTag pre-condition cannot be used in the request header.

2

2 Answers

0
votes

You should try to use Optimistic concurrency. If you can check the row version before updating it.

The optimistic concurrency feature provides the ability for your applications to detect whether an entity record has changed on the server in the time between when your application retrieved the record and when it tries to update or delete that record.

Code example

0
votes

You want to register this plugin on the pre-operation step. I think you have a valid concern regarding the pre-entity image potentially being dirty (I am uncertain whether the pre-image is retrieved within the transaction), so instead of using the pre-image retrieve a fresh copy of the record and check the timestamp of the retrieved record against the timestamp on your target. Since all of your operations are synchronous transactions against the same table, and the timestamp is a field on that table, I think that this will guarantee that you have no dirty writes.

However:
If your Update1 and Update2 operations update different fields, now you may suffer the reverse data loss. If Update1 sets Field A and update2 sets Field B, then the change made to field A would be disregarded if it is processed after the update to Field B.