tencent cloud

Global SCAN Guide for Cluster Architecture
Last updated: 2025-10-11 14:26:39
Global SCAN Guide for Cluster Architecture
Last updated: 2025-10-11 14:26:39

Basic Introduction

Tencent Cloud TencentDB for Redis® Cluster Edition addresses the limitation that native Redis Cluster does not support global SCAN through an innovative Proxy architecture. In native Redis Cluster, data is distributed across multiple shard nodes, making cross-node scan operations impossible. Tencent Cloud supports two precise scan modes through the extended SCAN command feature: users can achieve targeted scan for specific shard nodes by appending the NODEID parameter to the end of the command. Additionally, starting from Proxy 5.8.9, global SCAN operations without node restrictions are fully supported, enabling complete traversal of all shard data.

Use Limits

When a node switch (change of primary and secondary roles) or shard reduction (cluster scale-in) occurs in the cluster, it may trigger rare invalidation of the SCAN cursor, causing a disconnection of the mapping relationship between the shard index corresponding to the cursor and the actual node. If the original cursor is still used to perform scans, the system will throw an error, for example, -ERR invalid cursor(master node idx out of range)\\r\\n. It indicates that the status of the historical shard bound to the cursor has become invalid. If the error is forcibly ignored and the iteration continues, the valid data cannot be obtained, and a dead loop may occur due to the asynchronization between the cursor and shard logic, severely consuming system resources.

Reference Code

In a TencentDB for Redis® cluster environment, when SCAN operations are performed, if a cursor exception is captured due to changes in the cluster topology, it is required to reset the cursor to the initial position of 0 and restart the global scan process. This policy directly mitigates the risk of cursor dead loops caused by invalid shard indexes, ensuring the integrity and security of data traversal. The following code implements a secure key scan feature based on the Redis SCAN command, specifically addressing the key traversal needs of large Redis databases. For specific connection configuration, see Jedis Connecting to Redis.
package com.example.service.impl;
import com.example.config.RedisConnectionFactory;
import com.example.service.RedisService;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.ScanParams;
import redis.clients.jedis.resps.ScanResult;
import java.util.*;
/**
* TencentDB for Redis® service implementation class.
*/
public class RedisServiceImpl implements RedisService {
//scan Maximum number of retries.
private static final int MAX_RETRIES = 3;
private final RedisConnectionFactory connectionFactory;
/**
* Construction function.
*/
public RedisServiceImpl() {
this.connectionFactory = RedisConnectionFactory.getInstance();
}
/**
* scan Best practices.
* @param pattern Matching mode.
* @param count Number of returned keys for each iteration.
*/
@Override
public void scanKeys(String pattern, int count) {
try (Jedis jedis = connectionFactory.getConnection()) {
int retryCount = 0;
boolean scanCompleted = false;
int totalKeysProcessed = 0;
int totalBatches = 0;
long startTime = System.currentTimeMillis();
// Capture the reported errors during the scan process and restart the scan from 0 upon encountering an error.
while (!scanCompleted && retryCount <= MAX_RETRIES) {
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.count(count);
scanParams.match(pattern);
try {
while (true) {
ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
List<String> keys = scanResult.getResult();
totalBatches++;
if (!keys.isEmpty()) {
totalKeysProcessed += keys.size();
// Process the obtained keys - business logic.
processKeysWithBusinessLogic(keys);
}
cursor = scanResult.getCursor();
// Scan is completed when the cursor is "0".
if (cursor.equals(ScanParams.SCAN_POINTER_START)) {
long endTime = System.currentTimeMillis();
double timeElapsed = (endTime - startTime) / 1000.0;
System.out.println("\\n🎉 Scan completed.");
System.out.println("Total number of batches: " + totalBatches);
System.out.println("Total number of keys: " + totalKeysProcessed);
System.out.println("Scan duration: " + timeElapsed + " Second");
System.out.println("Average speed: " + (totalKeysProcessed / timeElapsed) + " Key/Second");
scanCompleted = true;
break;
}
}
} catch (Exception e) {
retryCount++;
System.err.println("⚠️ An error occurs during scanning, and attempting to rescan from 0. (" + retryCount + "/" + MAX_RETRIES + ")");
e.printStackTrace();
// The maximum number of retries is exceeded, and the retry is terminated.
if (retryCount > MAX_RETRIES) {
throw new RuntimeException("Maximum number of retries is reached. (" + MAX_RETRIES + "),Scan failed.", e);
}
}
}
} catch (Exception e) {
System.err.println("Scanning keys failed.: " + e.getMessage());
e.printStackTrace();
}
}
/**
* Business logic of processing keys.
*/
private void processKeysWithBusinessLogic(List<String> keys) {
// Business processing logic....
System.out.println(keys.size());
}
}
Was this page helpful?
You can also Contact Sales or Submit a Ticket for help.
Yes
No

Feedback