1

Background

I need a way to create a transaction in REDIS, where if one command fails, I need the entire transaction to fail / rollback.

I tried asking once before (REDIS- pipeline doesnt fail if one of the commands don't work)

But perhaps I didn't ask clearly or something got lost in the wording of my question. Or I didn't understand the answer. But in any case, I've been doing more research and it seems that REDIS does have transactions via the MULTI and EXEC commands. And in my case, since I'm using pyredis, I can create a pipeline with the parameter "transaction" set to True.

Code

To better understand how transactions and pipelines work, I wrote code to "simulate" a failure to prove that commands in my transaction are rolling back. So this is what I have in my my flask app:

@application.route("/test/transactions/<int:trigger>")
def test_transactions(trigger):
    try:
        logging.info('test transactions triggered')
        r = redis.Redis(connection_pool=POOL)
        p = r.pipeline(transaction=True)
        p.hmset('multitest', {'thefield':'thevalue'})
        if trigger==1:  
            assert 1==0, "trigger enabled. this will cause failure."
        p.hmset('multitest2', {'a':'b'})
        retval = p.execute()
        logging.info(retval)
        return "keys created"
    except Exception as e:
        logging.error(e)
        return "keys not created"

When i pass in a 0 as the trigger, it doesn't "fail" so the system creates both hashes. When I do set the trigger, it seems to behaving correctly because I don't have any hashes in the database.

Questions

  1. is this a good test? have proven that transactions work? If not can you suggest a better to test?

  2. In my real code, I'm plannning to check the contents of retval (which, by the way would look something like this: [0,0] if nothing was done, or [1, 1] if I did add / delete something.) If I'm expecting a [1,1] and i get back something different, i'm going to assume that transaction failed and have the method just return a false. Is there a better way to do this?

Thanks in advance for your time and input.

dot
  • 14,928
  • 41
  • 110
  • 218

2 Answers2

0

Redis' transactions do not have "rollback" abilities - once you call p.execute() the operations in it will be executed. You can cancel the transaction at any time before execution by calling p.discard().

When you discard a transaction it isn't rolled back because no operations were actually executed.

@application.route("/test/transactions/<int:trigger>")
def test_transactions(trigger):
    try:
        logging.info('test transactions triggered')
        r = redis.Redis(connection_pool=POOL)
        p = r.pipeline(transaction=True)
        p.hmset('multitest', {'thefield':'thevalue'})
        if trigger==1:  
            p.discard()
            return "discarded"
        else:
            p.hmset('multitest2', {'a':'b'})
            retval = p.execute()
            logging.info(retval)
            return "keys created"
    except Exception as e:
        logging.error(e)

Note: I'm not fully getting your code - you can check trigger before anything and just skip the attempt to transact.

Itamar Haber
  • 47,336
  • 7
  • 91
  • 117
  • I wonder how others accomplish things like this then. If there is no rollback, and only 2 out of 3 commands actually work, how do you undo? you have to manually check each step again and revert? That's pretty painful – dot Jan 21 '19 at 21:30
0

I have the same or a similar issue using transactions/pipelines in redis-py. I think your example covers a failure due to some exception in your code, but not errors in your interactions with redis. I did not test this myself yet, but this is my take from reading the redis documentation on transactions [1]:

Two kinds of errors can happen during a transaction: failure to queue a command, or failure after EXEC. For the first kind of errors the transaction can be aborted. For the second kind it cannot, neither can it be rolled back. The second kind, however, will only happen on syntax errors not detected while queueing or keys holding the wrong data type. These are programming errors, and the argument is that rollback should not be implemented to guard against these.

On failures to queue, the transaction can be aborted explicitly by calling DISCARD (will not work in redis-py, I think, since this only buffers the commands [2]), or it will be done implicitly together with a failing call to EXEC (assuming redis version >= 2.6.5).

References:

[1] Redis transactions: https://redis.io/topics/transactions

[2] redis-py, pipeline transactions (see _execute_transaction): https://redis.readthedocs.io/en/stable/_modules/redis/client.html

Ida
  • 3,417
  • 1
  • 11
  • 11