ioredis icon indicating copy to clipboard operation
ioredis copied to clipboard

How does ioredis handle separate commands during a MULTI

Open MicroDroid opened this issue 2 years ago • 1 comments

I'd like to run a transaction as such:

  1. MULTI
  2. <some command>
  3. Run some code based on results, and if no errors are raised:
  4. <some command>
  5. ...
  6. EXEC

Essentially I want to the results of the commands mid-transaction.

It seemed like I could disable pipelining for this:

image

However, my concern now is... since I'm re-using the same ioredis instance across requests, what would happen if another request came in and triggered some redis commands, at the time that MULTI block hadn't been EXECed yet?

Would the commands caused by the other request go inside the MULTI block?

MicroDroid avatar Aug 29 '22 16:08 MicroDroid

Looks like I can't get results mid-transaction even with pipeline: false?

MicroDroid avatar Aug 29 '22 17:08 MicroDroid

EDIT: I wasn't aware of this feature of multi({ pipeline: false }). Looking into it.

TysonAndre avatar Oct 29 '22 23:10 TysonAndre

However, my concern now is... since I'm re-using the same ioredis instance across requests, what would happen if another request came in and triggered some redis commands, at the time that MULTI block hadn't been EXECed yet?

Yes, it'd go in the multi block. But that'd only happen if you did something asynchronous between the multi and await call (so avoid doing that), e.g. await, async operations taking callbacks such as setTimeout, etc.

  • If the code was all in one block (with no awaits) then nothing could interrupt synchronous code

TysonAndre avatar Oct 29 '22 23:10 TysonAndre

@TysonAndre so conclusively the current implementation is very risky and error-prone? Avoiding a await is just non-practical/easy to miss

MicroDroid avatar Oct 30 '22 07:10 MicroDroid

so conclusively the current implementation is very risky and error-prone? Avoiding a await is just non-practical/easy to miss

No, I'd strongly recommend the first way in the readme instead, which is safe (Creating a Pipeline, adding commands to the pipeline, and calling exec() on the pipeline.). In that example, all of the commands on the Pipeline instance return this (the pipeline instance), so you can also call

const multiPipeline = redis.multi();
// other code which may have awaits and be interrupted by other uses of redis
multiPipeline.set('foo', 'bar');
// other code which may have awaits and be interrupted by other uses of redis
multiPipeline.get('foo');
const [[errSet, resultSet], [errGet, resultGet]] = await multiPipeline.exec();

https://github.com/luin/ioredis#transaction

Most of the time, the transaction commands multi & exec are used together with pipeline. Therefore, when multi is called, a Pipeline instance is created automatically by default, so you can use multi just like pipeline:

redis
  .multi()
  .set("foo", "bar")
  .get("foo")
  .exec((err, results) => {
    // results === [[null, 'OK'], [null, 'bar']]
  });

TysonAndre avatar Oct 30 '22 12:10 TysonAndre

Essentially I want to the results of the commands mid-transaction.

You can't by design in redis. Redis is usually very fast except for blocking commands.

See https://redis.io/docs/manual/transactions/

Redis Transactions make two important guarantees:

All the commands in a transaction are serialized and executed sequentially. A request sent by another client will never be served in the middle of the execution of a Redis Transaction. This guarantees that the commands are executed as a single isolated operation.

The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the EXEC command none of the operations are performed, instead if the EXEC command is called, all the operations are performed.

What are you using transactions for? If you're looking for higher throughput instead, consider enabling https://github.com/luin/ioredis#autopipelining instead

TysonAndre avatar Oct 30 '22 12:10 TysonAndre

You can't by design in redis.

Oh, okay then. I was trying to essentially replicate how transactions in relational databases work (in the sense of being able to read data right within a transaction).

MicroDroid avatar Oct 30 '22 16:10 MicroDroid