Software Architecture Patterns for Serverless Systems by John Gilbert

Software Architecture Patterns for Serverless Systems by John Gilbert

Author:John Gilbert
Language: eng
Format: epub, mobi
Publisher: Packt Publishing Pvt. Ltd.
Published: 2021-06-29T00:00:00+00:00


Inverse optimistic locking

We use the traditional optimistic locking technique to prevent multiple users from updating the same record concurrently. We only allow a user to update a record if the oplock value has not changed since the user retrieved the data. If another user has updated the record and thus the oplock value, then we throw an exception when the current user attempts to update the record and force the user to retrieve the data again before proceeding with the update. This ensures that we perform updates sequentially, and it requires human interaction to resolve any potential conflicts. We can use this traditional technique in BFF services command functions when users modify data.

Conversely, we use the inverse optimistic locking technique in downstream asynchronous stream processors to provide both idempotence and order tolerance. It ensures that older or duplicate events do not overwrite the most recent data. Instead of forcing a transaction to retry when an oplock fails, they simply do the opposite; they drop the older or duplicate event.

The following DynamoDB example shows the typical structure of a mapping step for a stream processing pipeline that creates a materialized view. We are using uow.event.timestamp as the new oplock value. ConditionExpression compares the old oplock value against the new value. If the new value is greater than the old value, then we allow the update to proceed. The attribute_not_exists clause handles the case where the record is new and there is no old oplock to compare against:

export const toUpdateRequest = (uow) => ({

Key: {

pk: uow.event.thing.id,

sk: 'thing',

},

ExpressionAttributeNames: {

'#name': 'name',

'#oplock': 'timestamp',

},

ExpressionAttributeValues: {

':name': 'Thing One',

':timestamp': uow.event.timestamp,

},

UpdateExpression: 'SET #name = :name,

#oplock = :timestamp',

ConditionExpression: 'attribute_not_exists(#oplock)

OR :timestamp > #oplock',

});

The first thing to notice in this example that is different with an inverse oplock is that the comparison is greater than instead of equals. If the value is equal to the previous value, then this indicates that we have already processed this event. If the value is less than the previous value, then this indicates that we are processing an older event out of order. When this value is greater than the previous value, we know we have a newer event, so we can proceed with the update.

The last piece of the puzzle is handling the response value returned from the call to update the database. The AWS SDK will throw an exception when error.code is returned. We don't want to ignore all errors, so we catch the exception and check for the ConditionalCheckFailedException code. We re-throw all other exceptions for fault handling, as we covered in Chapter 4, Trusting Facts and Eventual Consistency. When we do ignore an exception, it is good practice to log a metric so that we can track how frequently this happens and assess whether or not there is a root cause that we should address:

db.update(params).promise()

.catch((err) => {

if (err.code !== 'ConditionalCheckFailedException') {

throw err;

}

});

When using the inverse optimistic locking technique, it is important to ensure that we are not dropping important data when we ignore older events.



Download



Copyright Disclaimer:
This site does not store any files on its server. We only index and link to content provided by other sites. Please contact the content providers to delete copyright contents if any and email us, we'll remove relevant links or contents immediately.