diff --git a/backend/internal/handler/instance_detail.go b/backend/internal/handler/instance_detail.go index 3ff8106..a5d3953 100644 --- a/backend/internal/handler/instance_detail.go +++ b/backend/internal/handler/instance_detail.go @@ -749,7 +749,7 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string args = append(args, p) } - // For KEYS command, use SCAN with pagination (auto-append * if no wildcard) + // For KEYS command, use ScanAllKeys (cluster-aware) with pagination if cmd == "KEYS" { pattern := "*" if len(parts) > 1 { @@ -759,27 +759,17 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string if !strings.Contains(pattern, "*") && !strings.Contains(pattern, "?") && !strings.Contains(pattern, "[") { pattern = pattern + "*" } - var allKeys []string - cursor := uint64(0) - maxScan := offset + limit - for { - keys, nextCursor, err := client.Scan(ctx, cursor, pattern, int64(maxScan-len(allKeys))).Result() - if err != nil { - return nil, fmt.Errorf("SCAN failed: %v", err) - } - allKeys = append(allKeys, keys...) - cursor = nextCursor - if cursor == 0 || len(allKeys) >= maxScan { - break - } + allKeys, err := client.ScanAllKeys(ctx, pattern, limit+offset) + if err != nil { + return nil, fmt.Errorf("SCAN failed: %v", err) } total := len(allKeys) end := offset + limit - if end > len(allKeys) { - end = len(allKeys) + if end > total { + end = total } - if offset > len(allKeys) { - offset = len(allKeys) + if offset > total { + offset = total } pagedKeys := allKeys[offset:end] rows := make([]interface{}, len(pagedKeys)) diff --git a/backend/internal/handler/redis.go b/backend/internal/handler/redis.go index 9e90be8..28a6a66 100644 --- a/backend/internal/handler/redis.go +++ b/backend/internal/handler/redis.go @@ -59,6 +59,53 @@ func (w *redisClientWrapper) Scan(ctx context.Context, cursor uint64, match stri return w.Client.Scan(ctx, cursor, match, count) } +// ScanAllKeys scans keys across ALL master nodes in cluster mode, or single node in standalone. +// Returns merged deduplicated keys. +func (w *redisClientWrapper) ScanAllKeys(ctx context.Context, match string, count int) ([]string, error) { + if !w.isCluster { + // Standalone: iterate SCAN until cursor returns to 0 + var allKeys []string + cursor := uint64(0) + for { + keys, nextCursor, err := w.Client.Scan(ctx, cursor, match, int64(count)).Result() + if err != nil { + return allKeys, err + } + allKeys = append(allKeys, keys...) + cursor = nextCursor + if cursor == 0 { + break + } + } + return allKeys, nil + } + + // Cluster: iterate SCAN on every master node + var allKeys []string + seen := make(map[string]bool) + err := w.ClusterClient.ForEachMaster(ctx, func(ctx context.Context, node *redis.Client) error { + cursor := uint64(0) + for { + keys, nextCursor, err := node.Scan(ctx, cursor, match, int64(count)).Result() + if err != nil { + return err + } + for _, k := range keys { + if !seen[k] { + seen[k] = true + allKeys = append(allKeys, k) + } + } + cursor = nextCursor + if cursor == 0 { + break + } + } + return nil + }) + return allKeys, err +} + func (w *redisClientWrapper) Type(ctx context.Context, key string) *redis.StatusCmd { if w.isCluster { return w.ClusterClient.Type(ctx, key) @@ -321,7 +368,6 @@ func RedisScanKeys(c *gin.Context) { pattern = pattern + "*" } count, _ := strconv.Atoi(c.DefaultQuery("count", "100")) - cursorParam := c.DefaultQuery("cursor", "0") db := 0 if dbParam != "" { @@ -342,8 +388,7 @@ func RedisScanKeys(c *gin.Context) { defer client.Close() ctx := context.Background() - cursor, _ := strconv.ParseUint(cursorParam, 10, 64) - keys, nextCursor, err := client.Scan(ctx, cursor, pattern, int64(count)).Result() + allKeys, err := client.ScanAllKeys(ctx, pattern, count) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()}) return @@ -356,7 +401,7 @@ func RedisScanKeys(c *gin.Context) { TTL int64 `json:"ttl"` } var keyInfos []KeyInfo - for _, key := range keys { + for _, key := range allKeys { keyType, _ := client.Type(ctx, key).Result() ttl, _ := client.TTL(ctx, key).Result() ttlMs := int64(-1) @@ -371,7 +416,7 @@ func RedisScanKeys(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "keys": keyInfos, - "cursor": nextCursor, + "cursor": 0, "total": len(keyInfos), }) } diff --git a/frontend/src/pages/QueriesPage.tsx b/frontend/src/pages/QueriesPage.tsx index 606c54f..da8e2bc 100644 --- a/frontend/src/pages/QueriesPage.tsx +++ b/frontend/src/pages/QueriesPage.tsx @@ -431,25 +431,26 @@ export default function QueriesPage() { )} - {/* -- 编辑器 + 侧边栏 (上半区,不随结果滚动) -- */} -