If a large number of requests are sent to concurrently access and update the shared resources stored in Tair, an accurate and efficient concurrency control mechanism is required. The mechanism must be able to help you prevent logical failures and data errors. One of the mechanisms is optimistic locking. Compared with open source Redis, the TairString data structure of Tair allows you to implement optimistic locking to deliver higher performance at lower costs.

Concurrency and last-writer-wins

The following figure shows a typical scenario where concurrent requests cause race conditions.

  1. At the initial stage, the value of key_1 is hello. The values of this key are strings.
  2. At the t1 time point, application 1 reads the key_1 value hello.
  3. At the t2 time point, application 2 reads the key_1 value hello.
  4. At the t3 time point, application 1 changes the value of key_1 to world.
  5. At the t4 time point, application 2 changes the value of key_1 to universe.

The value of key_1 is determined by the last write. At the t4 time point, application 1 considers the value of key_1 as world, but the actual value is universe. Therefore, the subsequent operations may become faulty. This process explains what is last-writer-wins. To resolve the issues that are caused by last-writer-wins, you must ensure the atomicity of the access and update operations on string data. In other words, you must convert the string data of the shared resources into atomic variables. To do this, you can implement high-performance optimistic locking by using the TairString data structure.

Implement optimistic locking by using TairString

TairString, also known as an extended string (exString), is a string data structure that carries a version number. Native Redis strings consist of only keys and values. TairStrings consist of keys, values, and version numbers. For this reason, TairString is more suitable for optimistic locking. For more information about TairString commands, see TairString.

Note TairStrings differ from native Redis strings. The commands that are supported by TairStrings and native Redis strings are not interchangeable.

TairString has the following features:

  • A version number is provided for each key. The version number indicates the current version of a key. If you run the EXSET command to create a key, the default version number of the key is 1.
  • If you run the EXGET command for a specified key, you can retrieve the values of two fields: value and version.
  • When you update a TairString value, the version is verified. If the verification fails, the following error message is returned: ERR update version is stale.
  • After the TairString value is updated, the version number is automatically incremented by 1.
  • TairString integrates all the features of Redis String except bit operations.

Due to these features, the locking mechanism is native to TairString data. Therefore, TairString provides an easy method for you to implement optimistic locking. Example:

    {value, version} = EXGET(key);      // Retrieve the value and version number of the key.
    value2 = update(...);               // Save the new value as value 2.
    ret = EXSET(key, value2, version);  // Update the key and assign the return value to the ret variable.
    if(ret == OK)
       break;                           // If the return value is OK, the update is successful and the while loop exits.
    else if (ret.contanis("version is stale"))     
       continue;                        // If the return value contains the "version is stale" error message, the update fails and the while loop is repeated.
  • If you delete a TairString and create a TairString that has the same key as the deleted TairString, the key version of the new TairString is 1. The new TairString does not inherit the key version of the deleted TairString.
  • You can specify the ABS option to skip version verification and forcefully overwrite the current version to update a TairString. For more information, see EXSET.

Reduce resource consumption for optimistic locking

In the preceding sample code, if another client updates the shared resource after you run the EXGET command, you receive an update failure message and the while loop is repeated. The EXGET command is repeatedly run to retrieve the value and version number of the shared resource before the update is successful. As a result, two I/O operations are performed to access Tair in each while loop. However, you need to only send one access request in each while loop by using the EXCAS command of TairString. For more information about the EXCAS command, see EXCAS. This results in a significant decrease in the consumption of system resources and improves service performance in high concurrency scenarios.

When you run the EXCAS command, you can specify a version number in the command to verify the version. If the verification succeeds, the TairString value is updated. If the verification fails, the following elements are returned:

  • update version is stale
  • value
  • version

If the update fails, the command returns the current version number of the TairString. You do not need to run another query to retrieve the current version number, and only one access request is required for each while loop. Sample code:

    {ret, value, version} = excas(key, new_value, old_version)    // Use the CAS command to replace the original value with a new value.
    if(ret == OK)
       break;    // If the return value is OK, the update is successful and the while loop exits.
    else (if ret.contanis("update version is stale"))    // If the return value contains the "update version is stale" error message, the update fails. The values of the value and old_version variables are updated.
       old_version = version;