Как же работают транзакции в Redis

в 12:29, , рубрики: nosql, php, redis, rediska, Веб-разработка, транзакции, метки: , , ,

Работаю с Redis относительно недавно и вот возникла необходимость изменения одного ключа несколькими потоками одновременно. Для работы с Redis в php использую клиент Rediska. Еще когда читал мануал по Rediska видел раздел про транзакции, а сегодня пришло время почитать внимательнее.

Не знаю, что тому виною, то ли мое плохое знание английского, то ли мое тугодумство, то ли непонятность документации, но тем не менее ознакомившись с документацией по транзакциям на сайте Rediska и потом на сайте самого Redis я так и не понял блокируется ли, изменяемый внутри транзакции, ключ на запись до выполнения execute() или нет.
Да, в обоих документациях есть описание и примеры «Optimistic locking using check-and-set» когда используется watch, но при этом в начале официальной документации на сайте Redis-а написано:

All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served in the middle of the execution of a Redis transaction. This guarantees that the commands are executed as a single isolated operation.

После прочтения этого мне показалось (перекрещусь на всякий случай) что тут речь идет как раз о том, что никто не может в середине транзакции изменять значений затронутых ею ключей. Однако я ошибался и что бы это понять пришлось написать скрипт по аналогии с тем, который приведен в доке на сайте клиента Rediska.

$options = array(
    'namespace' => 'Application_',
    'servers'   => array(
       array('host' => '127.0.0.1', 'port' => 6379, 'db' => 5)
    )
);
 
require_once 'Rediska.php';
 
// Get rediska entity
$rediska = new Rediska($options);
 
for ($i=1; $i<=10000; $i++) {
// Start transaction
$transaction = $rediska->transaction();
 
// Get current value
$value = $rediska->get('test_value');
 
// Increment value
$value++;
 
// Store new value
$transaction->set('test_value', $value);
 
// Execute transaction
$transaction->execute();
}
 
echo $rediska->get('test_value');

Это скрипт запускал в два потока из командной строки, т.е. по идее в итоге должен был получить значение у ключа «test_value» равное 20 тысячам, но в реальности там в среднем выходило около 12 тысяч. Т.е. никакой блокировки нет.

Теперь немного модифицирую цикл добавив в него watch:

for ($i=1; $i<=10000; $i++) {
for ($j=1; $j<=5; $j++) {
// Start transaction
$transaction = $rediska->transaction();
 
// Watch
$transaction->watch('test_value');
 
// Get current value
$value = $rediska->get('test_value');
 
// Increment value
$value++;
 
// Store new value
$transaction->set('test_value', $value);
 
// Execute transaction
try {
$transaction->execute();
} catch (Rediska_Transaction_AbortedException $e) {
continue;
  }
 
  break;
}
}

Т.е. по сути при возникновении эксепшена 5-ть раз пытаемся повторить транзакцию. В итоге с 5-тью повторениями в среднем выходило 17 тысяч, если поднять скажем до 10 то будет выходить в среднем 19 тысяч.
Это конечно частные случаи, на практике вряд ли будет такое количество одновременных изменений и 5-ти повторений в принципе должно хватить, но не в этом дело. Дело в том что по факту Redis не имеет пока механизма (задокументированного) для блокировки ключей, затронутых изменениями.
Хорошо это или плохо решать судить не мне, думаю все зависит от задачи, хотел просто показать как оно есть.

Автор: vagrand

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js