Repository navigation
Closed
Labels
Description
Description
I encountered an infinite loop while using the SCAN command with pattern matching in Redis via AWS ElastiCache.
The issue arises when querying Redis for keys using a pattern (users:*). The loop keeps executing even when no more keys match the given pattern.
Below code to reproduce the issue
async function getActiveUsers(redisClient) {
const activeUserQueryPattern = `users:*`;
let cursor = 0;
let activeUsers = [];
console.log("Querying Redis for Active Users pattern :: ", activeUserQueryPattern);
do {
const userQueryData = await redisClient.scan(cursor, {
MATCH: activeUserQueryPattern,
COUNT: 100,
});
console.log("User Query Data :: ", userQueryData);
activeUsers = activeUsers.concat(userQueryData.keys);
cursor = userQueryData.cursor;
} while (cursor !== 0);
console.log('Active Users :: ', activeUsers);
return activeUsers;
}I have fixed with checking keys length for now, but actually the cursor will return with zero, if there is not keys.
async function getActiveUsers(redisClient) {
const activeUserQueryPattern = `users:*`;
let keyLen = 1;
let activeUsers = [];
console.log("Querying Redis for Active Users pattern :: ", activeUserQueryPattern);
do {
const userQueryData = await redisClient.scan(cursor, {
MATCH: activeUserQueryPattern,
COUNT: 100,
});
console.log("User Query Data :: ", userQueryData);
activeUsers = activeUsers.concat(userQueryData.keys);
keyLen = userQueryData.keys.length;
} while (keyLen !== 0);
console.log('Active Users :: ', activeUsers);
return activeUsers;
}Node.js Version
node v18.18.0
Redis Server Version
ElasticCache (Redis Version 7.1)
Node Redis Version
4.7.0
Platform
Windows
Logs
2024-12-07T15:33:22.237+05:30
2024-12-07T10:03:22.237Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.247+05:30
2024-12-07T10:03:22.247Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.249+05:30
2024-12-07T10:03:22.249Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.252+05:30
2024-12-07T10:03:22.252Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.255+05:30
2024-12-07T10:03:22.255Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.267+05:30
2024-12-07T10:03:22.267Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.270+05:30
2024-12-07T10:03:22.270Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.272+05:30
2024-12-07T10:03:22.272Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.275+05:30
2024-12-07T10:03:22.275Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.290+05:30
2024-12-07T10:03:22.290Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.292+05:30
2024-12-07T10:03:22.292Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.294+05:30
2024-12-07T10:03:22.294Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.307+05:30
2024-12-07T10:03:22.307Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.310+05:30
2024-12-07T10:03:22.310Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }
2024-12-07T15:33:22.312+05:30
2024-12-07T10:03:22.312Z 1cb6467a-63e1-41ce-8378-b57b1c365069 INFO User Query Data :: { cursor: 9283678325492940000, keys: [] }Reacted by Sergey Romanchuk and Brett Fernandes
Activity
aarifkhan7 commentedon Mar 16, 2025
It looks like the issue is due to a type mismatch in the cursor comparison. The SCAN command in Redis expects and returns the cursor as a string, but your loop was comparing it to 0 (integer) instead of "0" (string). This can cause the loop to never exit.
This should resolve the issue as the cursor is compared correctly. Let me know if this fixes the problem so we can move forward with closing it!
midoushitongtong commentedon Jul 21, 2025
upgrade redis to latest version should resolve the issue
vasanthanp commentedon Sep 27, 2025
Hi, @sivakumar-74,
I’m running into the same problem — when using SCAN with MATCH on AWS ElastiCache (OSS Redis 7.1), I sometimes get a very large non-zero cursor back (e.g. 9283051603865108000) with no keys. This causes my loop to run indefinitely since the cursor never returns "0".
Could you clarify if this issue has been fixed, or if the root cause is on the Redis/ElastiCache side? Also, is there a recommended workaround (other than adding iteration guards or cursor sanity checks) to safely handle this situation in production?
hiteshwadhwani commentedon Jan 8, 2026
Hi @sivakumar-74 @vasanthanp any update here because we are also facing the same issue with AWS ElastiCache (OSS Redis 7.1)
Fix Redis SCAN infinite loop - use string cursor comparison
UgaTheDev commentedon Aug 25, 2026
The loop reported here on 4.7.0 is
SCAN'stransformReplycoercing the cursor withNumber(cursor): ElastiCache returns a 64-bit cursor aboveNumber.MAX_SAFE_INTEGER, so the value gets rounded to a float64 approximation (hence the trailing zeros in9283678325492940000) and the rounded cursor can never round-trip back to0. That is fixed on v5+, where the cursor stays aBlobStringReplyand is passed back to the server as an opaque string with no numeric coercion — so this issue can likely be closed as fixed-in-v5, which matches @midoushitongtong's suggestion above. Note that @aarifkhan7's"0"-vs-0explanation isn't the cause on 4.7.0: the cursor really was anumberthere, and the reporter's comparison was correct for that version.Worth flagging for anyone landing here from a search, though: while verifying the above I found a distinct infinite loop still live on
master. All six client scan iterators (scanIterator,hScanIterator,hScanValuesIterator,hScanNoValuesIterator,sScanIterator,zScanIterator) end onwhile (cursor !== '0'), so under a Blob String type mapping the cursor is aBuffer, never compares equal to'0', and the iterator scans forever — the SentinelscanIteratoralready guards this exact case withcursor.toString() !== '0'. Fix and regression test in #3428.fix(client): terminate scan iterators when the cursor is a Buffer (#3428