Skip to content

SCAN Command Infinite Loop - Cursor has Non-Zero Value for Empty Keys in the Response #2879

Closed

Description

@sivakumar-74

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: [] }

Activity

  1. aarifkhan7 commented on Mar 16, 2025

    @aarifkhan7

    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.

    const activeUserQueryPattern = "users:*";
    let cursor = "0";
    let activeUsers: string[] = [];
    
    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);
    } while (cursor !== "0");
    
    console.log('Active Users :: ', activeUsers);
    return activeUsers;

    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!

  2. midoushitongtong commented on Jul 21, 2025

    @midoushitongtong

    upgrade redis to latest version should resolve the issue

    "redis": "^5.6.0",
  3. vasanthanp commented on Sep 27, 2025

    @vasanthanp

    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?

  4. hiteshwadhwani commented on Jan 8, 2026

    @hiteshwadhwani

    Hi @sivakumar-74 @vasanthanp any update here because we are also facing the same issue with AWS ElastiCache (OSS Redis 7.1)

  5. UgaTheDev commented on Aug 25, 2026

    @UgaTheDev
    Contributor

    The loop reported here on 4.7.0 is SCAN's transformReply coercing the cursor with Number(cursor): ElastiCache returns a 64-bit cursor above Number.MAX_SAFE_INTEGER, so the value gets rounded to a float64 approximation (hence the trailing zeros in 9283678325492940000) and the rounded cursor can never round-trip back to 0. That is fixed on v5+, where the cursor stays a BlobStringReply and 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-0 explanation isn't the cause on 4.7.0: the cursor really was a number there, 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 on while (cursor !== '0'), so under a Blob String type mapping the cursor is a Buffer, never compares equal to '0', and the iterator scans forever — the Sentinel scanIterator already guards this exact case with cursor.toString() !== '0'. Fix and regression test in #3428.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions