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

fix(cluster): fix autopipeline with keyPrefix or arg array #1391

Merged
merged 1 commit into from
Aug 1, 2021

Conversation

TysonAndre
Copy link
Collaborator

@TysonAndre TysonAndre commented Jul 27, 2021

Previously the building of pipelines ignored the key prefix.
It was possible that two keys, foo and bar, might be set into
the same pipeline. However, after being prefixed by a configured
"keyPrefix" value, they may no longer belong to the same
pipeline.

This led to the error:
"All keys in the pipeline should belong to the same slots
allocation group"

Similarly, args[0] can be a (possibly empty) array which the Command
constructor would flatten. Properly compute the first flattened key when
autopipelining for a Redis.Cluster instance.

Amended version of https://github.com/luin/ioredis/pull/1335/files - see comments on that PR

This may fix #1264 and #1248.

Fixes #1392

@AVVS
Copy link
Collaborator

AVVS commented Jul 27, 2021

Keys will hash to different nodes and, therefore, wouldnt allow them to be used inside the same pipeline or am I missing something?

@TysonAndre
Copy link
Collaborator Author

TysonAndre commented Jul 27, 2021

Keys will hash to different nodes and, therefore, wouldnt allow them to be used inside the same pipeline or am I missing something?

I'm not quite sure what you mean. If there are 16385 different keys and 16384 different nodes, at least two of the keys will go to the same node because of https://en.wikipedia.org/wiki/Pigeonhole_principle

The issue is that when a cluster was used, we need to use the prefixed key (prefixed + args[0]) to compute the destination node, not the unprefixed key (args[0])

@TysonAndre
Copy link
Collaborator Author

TysonAndre commented Jul 27, 2021

https://github.com/invertase/cluster-key-slot/blob/62a7bce30c668f83c89f331943e882d2c92899c0/lib/index.js#L147 in this case, there's 16384 possible values from 0 to 0x3FFF and there would be collisions if there were more than 16384 keys, and even with a few hundred keys there are likely to be false positive hash collisions due to https://en.wikipedia.org/wiki/Birthday_problem

EDIT: client.slots[ ... ] then narrows down the keys from 0 to 0x3FFF to an even smaller number of distinct server configurations

@TysonAndre
Copy link
Collaborator Author

An example of the failure seen running this without the patch. With this patch, it succeeds

const IORedis = require('./built');
const process = require('process');
async function main() {
    // Using instructions from https://redis.io/topics/cluster-tutorial
    // redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 --cluster-replicas 0
    const redis = new IORedis.Cluster([{ host: '127.0.0.1', port:7000 }], { keyPrefix: 'test:', enableAutoPipelining: true });
    const requests = [];
    for (let i = 0; i < 10; i++) {
        requests.push(redis.get(`k${i}`));
    }
    const results = await Promise.all(requests);
    console.log(results);
    process.exit(0);
}
main();
(node:25595) UnhandledPromiseRejectionWarning: Error: All keys in the pipeline should belong to the same slots allocation group
    at Pipeline.exec (/path/to/ioredis/built/pipeline.js:255:29)
    at Immediate.executeAutoPipeline (/path/to/ioredis/built/autoPipelining.js:35:14)
    at processImmediate (internal/timers.js:463:21)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:25595) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:25595) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

@TysonAndre TysonAndre changed the title fix(cluster): autopipeline when prefix is used fix(cluster): autopipeline when keyPrefix is used Jul 28, 2021
Comment on lines 107 to 139
const slotKey = client.isCluster
? client.slots[
calculateSlot(
client.options.keyPrefix
? `${client.options.keyPrefix}${args[0]}`
: args[0]
)
].join(",")
: "main";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
const slotKey = client.isCluster
? client.slots[
calculateSlot(
client.options.keyPrefix
? `${client.options.keyPrefix}${args[0]}`
: args[0]
)
].join(",")
: "main";
const prefix = client.options.keyPrefix || '';
const slotKey = client.isCluster
? client.slots[
calculateSlot(`${prefix}${args[0]}`)
].join(",")
: "main";

this should likely be cheaper. we can also avoid assigning empty string to keyPrefix if we set it in the constructor

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

redis.get([], [], ['key']) is technically allowed by ioredis because all of the args get passed to lodash flatten. I added getFirstValueInFlattenedArray and some unit tests to work as a fallback for more common cases.

Less common cases (_.isArguments) may benefit from https://github.com/lodash/lodash/blob/master/.internal/isFlattenable.js but that seems brittle, and I don't know ioredis's policy on using internal implementation details like that

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Did something similar, but accounting for the fact that args[0] can be an array

@AVVS
Copy link
Collaborator

AVVS commented Jul 28, 2021

finally realized this is only related to auto pipelining, change makes sense to me

lib/autoPipelining.ts Outdated Show resolved Hide resolved
@TysonAndre TysonAndre force-pushed the autopipeline-fix branch 4 times, most recently from 2e10f8a to e1cab95 Compare July 28, 2021 17:57
@TysonAndre
Copy link
Collaborator Author

This is ready for another look

@TysonAndre TysonAndre changed the title fix(cluster): autopipeline when keyPrefix is used fix(cluster): fix autopipeline when keyPrefix is used Jul 28, 2021
@TysonAndre TysonAndre changed the title fix(cluster): fix autopipeline when keyPrefix is used fix(cluster): fix autopipeline with keyPrefix or arg arrray Jul 28, 2021
@TysonAndre TysonAndre changed the title fix(cluster): fix autopipeline with keyPrefix or arg arrray fix(cluster): fix autopipeline with keyPrefix or arg array Jul 28, 2021
@TysonAndre TysonAndre force-pushed the autopipeline-fix branch 2 times, most recently from ca23dc5 to 36f6387 Compare July 31, 2021 17:39
@AVVS
Copy link
Collaborator

AVVS commented Jul 31, 2021

@luin this LGTM, what do you think?

Previously, the building of pipelines ignored the key prefix.
It was possible that two keys, foo and bar, might be set into
the same pipeline. However, after being prefixed by a configured
"keyPrefix" value, they may no longer belong to the same
pipeline.

This led to the error:
"All keys in the pipeline should belong to the same slots
allocation group"

Similarly, `args[0]` can be a (possibly empty) array which the Command
constructor would flatten. Properly compute the first flattened key when
autopipelining for a Redis.Cluster instance.

Amended version of https://github.com/luin/ioredis/pull/1335/files - see comments on that PR

This may fix redis#1264 and redis#1248.

Fixes redis#1392
@TysonAndre
Copy link
Collaborator Author

My changes since the last review are minor:

  1. Fixed remaining lint errors, including for prefix.
  2. Updated package-lock.json because I'd accidentally used an npm registry mirror globally

@luin
Copy link
Collaborator

luin commented Aug 1, 2021

@luin this LGTM, what do you think?

LGTM! Thanks @TysonAndre

@luin luin merged commit d7477aa into redis:master Aug 1, 2021
@TysonAndre TysonAndre deleted the autopipeline-fix branch August 1, 2021 15:19
ioredis-robot pushed a commit that referenced this pull request Aug 1, 2021
## [4.27.7](v4.27.6...v4.27.7) (2021-08-01)

### Bug Fixes

* **cluster:** fix autopipeline with keyPrefix or arg array ([#1391](#1391)) ([d7477aa](d7477aa)), closes [#1264](#1264) [#1248](#1248) [#1392](#1392)
@ioredis-robot
Copy link
Collaborator

🎉 This PR is included in version 4.27.7 🎉

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.27.7](redis/ioredis@v4.27.6...v4.27.7) (2021-08-01)

### Bug Fixes

* **cluster:** fix autopipeline with keyPrefix or arg array ([#1391](redis/ioredis#1391)) ([d7477aa](redis/ioredis@d7477aa)), closes [#1264](redis/ioredis#1264) [#1248](redis/ioredis#1248) [#1392](redis/ioredis#1392)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
4 participants