-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Include all runtime errors in transaction result #1010
Conversation
1a0baed
to
3c9d8c0
Compare
Some transaction run time errors like WRONGTYPE will get QUEUED, and therefore the error is included in the final response array. Other errors however like READONLY dont get queued but are rejected straight away, and Redis will therefore exclude them from the exec result. This change reinserts such errors into the right position in the exec() result.
3c9d8c0
to
8e84557
Compare
The |
e0ebf43
to
5d2e1c8
Compare
Confirmed the new prettier version is what was breaking CI. I had to bump prettier to |
I've quickly looked through the code, but certainly need more time to fully understand it. One thing I've noticed is that new test was added for multi, but not for as for multi itself, based on the docs:
maybe a better case would be to actually check |
Thanks for taking a look! The callback is definitely a workaround available now for catching such errors, but to leverage it would mean giving up much of the benefits of this lib's promise support. I think a workaround would look something like this:
And this would only allow us to catch the errors, I would have to do some more work to figure out which commands actually succeeded. At this point it starts to feel like ioredis could be doing some of this legwork for us, hence this PR. Let me know what you think. (I can certainly add a test for pipeline.exec if we agree on the overall approach.) |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 7 days if no further activity occurs, but feel free to re-open a closed issue if needed. |
Happy new year dear maintainers! @AVVS have you had a chance to take a closer look at this? I can certainly add additional tests for pipeline.exec but I'd first like to get a sanity check on the overall approach. Once can certainly check for a successful QUEUED state in each individual command callback as you suggest but I think this is a bit cumbersome (see the code sample in my previous comment) |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 7 days if no further activity occurs, but feel free to re-open a closed issue if needed. |
Hi Andrew, Sorry for the late response! The pull request works for this specified issue. However, I'm wondering whether it's a ticky way to handle this issue by merging results from different places. According to Redis official documentation (https://redis.io/topics/transactions#errors-inside-a-transaction), I think the proper behavior for a server to deal with this kind of error would be just discarding the whole transaction and returning an error directly, instead of returning a partial result. |
Hey @luin thanks for looking into this!
This sounds good to me. All I really want is to know that the transaction has failed without jumping through these hoops. If I have a syntax error in my transaction Also, is there any back-compat concern? Do you think we should make this an opt-in setting? |
By "I think the proper behavior for a server" I meant I think what AWS Elasticache currently does doesn't seem right. So it leaves us a gray area to deal with this kind of error. The code seems deal with the issue reasonably so I'm going to merge this after I do more tests. However, I failed to reproduce the behavior you mentioned. Here's my steps:
It just closed the connection instead of allowing me enter further commands. Also for the following code: const Redis = require('ioredis');
const redis = new Redis(HOST);
async function run () {
const res = await redis.multi()
.set('foo', 42)
.get('bar')
.exec();
console.log(res);
}
setInterval(() => {
run();
}, 100); It just reject the whole transaction because of the READONLY error.
Did I missing anything? |
That's odd. I've been testing against a localhost replica. E.g:
On the wire:
The behavior you're showing with elasticache is what I would have expected to see when targeting a local read only replica. I'll run some additional tests against elasticache. |
@luin You actually might have just stumbled on that bug I mentioned in the PR description that only happens with Elasticache. If instead you run your transaction against the readonly replica twice (instead of in a setInterval), and add a .then() to log the error, you'll notice you only see that error once. The reason is when elasticache detects that READONLY error it forces us to reconnect (unlike what I see when testing against localhost replica). But when this happens, transaction commands in the offlineQueue get left in a corrupted state. Here's a repro that makes this clearer:
Run with debugging turned we can see what's going wrong:
After our first reconnection we send offline queue commands from the middle of a failed transaction ( I have a work in progress fix for this here that marks transaction commands with an |
Ah! That makes a lot of senses. I was under impression that this issue only happens in AWS Elasticache. I suspect it's a bug on Redis side so I create an issue for that to double check. |
I've tested the fix and I believe it resolves the core issue I was trying to address here. If I run the PR description code now,
So I'll close this PR. Thanks @luin ! |
I'm not sure yet if this will resolves the desync issue caused by Elasticache immediately disconnecting the client upon readonly errors, so a further fix to clear out |
Thanks for pointing this out Andrew! That fix would benefit all Redis users, which is a great thing. Besides waiting for AWS, I think we can also emulate what Elasticache does by adding |
That's a great idea, I'll give it a try! |
READONLY errors are common with AWS Elasticache failovers, but there's a problem when it comes to ioredis transactions. Consider this contrived transaction:
If you run it against an Elasticache endpoint which has just failed over under your feet, a READONLY error is reported, but your
res
contains an unexpected result:The transaction has partially failed, but I don't see the error in my exec result like I would if it was a WRONGTYPE error. On the wire, what I actually got back is this:
Redis excluded the error from the exec result because it was rejected upfront rather than QUEUED. This PR will re-insert such errors back into the
exec()
result in the order they were sent so that we insteadres
looks this:I'm not sure if what I'm doing is kosher, e.g if
exec()
is meant to exactly represent the last element sent on the wire. But I imagine there may be other redis runtime errors that behave like READONLY. I'm open to alternative ideas on how to clearly communicate to the caller which individual commands succeeded and which failed in a partially failed transaction.Note: The readme proposes to handle such ElastiCache READONLY errors with
reconnectOnError
. This works most of the time however mixing this with transactions introduces a different problem which I think was first reported here. I'm hoping do address that in a further PR.