Skip to content
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

Elasticache: Abort incomplete pipelines and transactions upon reconnect #1084

Merged
merged 1 commit into from
Mar 28, 2020

Conversation

alavers
Copy link
Contributor

@alavers alavers commented Mar 27, 2020

Elasticache severs the connection immediately after it returns a
READONLY error. This can sometimes leave queued up pipelined commands
in an inconsistent state when the connection is reestablished. For
example, if a pipeline has 6 commands and the second one generates a
READONLY error, Elasticache will only return results for the first two
before severing the connection. Upon reconnect, the pipeline still
thinks it has 6 commands to send but the commandQueue has only 4. This
fix will detect any pipeline command sets that only had a partial
response before connection loss, and abort them.

This Elasticache behavior also affects transactions. If reconnectOnError
returns 2, some transaction fragments may end up in the offlineQueue.
This fix will check the offlineQueue for any such transaction fragments
and abort them, so that we don't send mismatched multi/exec to redis
upon reconnection.

  • Introduced piplineIndex property on pipelined commands to allow for later
    cleanup
  • Added a routine to event_handler that aborts any pipelined commands inside
    commandQueue and offlineQueue that were interrupted in the middle of the
    pipeline
  • Added a routine to event_handler that removes any transaction
    fragments from the offline queue
  • Introduced inTransaction property on commands to simplify pipeline logic
  • Added a flags param to mock_server to allow the Elasticache disconnect
    behavior to be simulated
  • Added a reconnect_on_error test case for transactions
  • Added some test cases testing for correct handling of this unique elasticache
    behavior
  • Added unit tests to validate inTransaction and pipelineIndex setting

Fixes #965


You can simulate this Elasticache disconnect behavior on a local redis instance by modifying the source like so:

diff --git a/src/server.c b/src/server.c
index f6faa61a..31721551 100644
--- a/src/server.c
+++ b/src/server.c
@@ -2702,7 +2702,9 @@ int processCommand(client *c) {
         !(c->flags & CLIENT_MASTER) &&
         c->cmd->flags & CMD_WRITE)
     {
+        flagTransaction(c);
         addReply(c, shared.roslaveerr);
+        c->flags |= CLIENT_CLOSE_AFTER_REPLY;
         return C_OK;
     }

(Thanks for the idea @luin)

Elasticache severs the connection immediately after it returns a
READONLY error.  This can sometimes leave queued up pipelined commands
in an inconsistent state when the connection is reestablished. For
example, if a pipeline has 6 commands and the second one generates a
READONLY error, Elasticache will only return results for the first two
before severing the connection. Upon reconnect, the pipeline still
thinks it has 6 commands to send but the commandQueue has only 4. This
fix will detect any pipeline command sets that only had a partial
response before connection loss, and abort them.

This Elasticache behavior also affects transactions. If reconnectOnError
returns 2, some transaction fragments may end up in the offlineQueue.
This fix will check the offlineQueue for any such transaction fragments
and abort them, so that we don't send mismatched multi/exec to redis
upon reconnection.

- Introduced piplineIndex property on pipelined commands to allow for later
cleanup
- Added a routine to event_handler that aborts any pipelined commands inside
commandQueue and offlineQueue that were interrupted in the middle of the
pipeline
- Added a routine to event_handler that removes any transaction
fragments from the offline queue
- Introduced inTransaction property on commands to simplify pipeline logic
- Added a flags param to mock_server to allow the Elasticache disconnect
behavior to be simulated
- Added a reconnect_on_error test case for transactions
- Added some test cases testing for correct handling of this unique elasticache
behavior
- Added unit tests to validate inTransaction and pipelineIndex setting

Fixes redis#965
@alavers alavers force-pushed the abort-interrupted-pipelines branch from 3505d83 to aa5fe68 Compare March 27, 2020 19:16
// the connection close and those pipelined commands must be aborted. For
// example, if the queue looks like this: [2, 3, 4, 0, 1, 2] then after
// aborting and purging we'll have a queue that looks like this: [0, 1, 2]
function abortIncompletePipelines(commandQueue: Deque<ICommandItem>) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do feel like this approach is treating the downstream symptoms rather than the root cause of the problem. The tradeoff is that it isolates connection handling cleanup stuff inside event_handler so that transaction.ts and pipeline.ts can be more decoupled from reconnection behaviors.

I'm open to other ways of approaching this.

@luin luin merged commit 0013991 into redis:master Mar 28, 2020
@luin
Copy link
Collaborator

luin commented Mar 28, 2020

Awesome! Merged 🍻

ioredis-robot pushed a commit that referenced this pull request Mar 28, 2020
## [4.16.1](v4.16.0...v4.16.1) (2020-03-28)

### Bug Fixes

* abort incomplete pipelines upon reconnect ([#1084](#1084)) ([0013991](0013991)), closes [#965](#965)
@ioredis-robot
Copy link
Collaborator

🎉 This PR is included in version 4.16.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

janus-dev87 added a commit to janus-dev87/ioredis-work that referenced this pull request Mar 1, 2024
## [4.16.1](redis/ioredis@v4.16.0...v4.16.1) (2020-03-28)

### Bug Fixes

* abort incomplete pipelines upon reconnect ([#1084](redis/ioredis#1084)) ([0013991](redis/ioredis@0013991)), closes [#965](redis/ioredis#965)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Retry logic for ioredis response incorrect result
3 participants