개발노트

25.04.13 Spring Redis hashOps.keys()로 패턴 조회 안 되는 이유와 SCAN을 사용한 해결법 본문

TroubleShooting

25.04.13 Spring Redis hashOps.keys()로 패턴 조회 안 되는 이유와 SCAN을 사용한 해결법

ddong-kka 2025. 4. 13. 01:24

문제

Redis 명령어 CLI에서 다음과 같이 입력하면 예상대로 잘 작동한다.

그래서 Java 코드에서도 아래와 같이 패턴을 넣으면 잘 작동할 줄 알았다.

하지만 내 예상과는 다르게 아무것도 조회가 되지않았다.

Redis에 저장되어있는 키

 

// Redis에서 모든 리뷰의 좋아요 수를 가져와 HashMap으로 반환
	public Map<String, Integer> getAllReviewIdsWithLikes() {
		hashOps = likeCountRedisTemplate.opsForHash();

		// 예시: 모든 리뷰의 Redis 키 패턴을 가져오고 해당하는 값들을 조회
		Set<String> reviewIds = hashOps.keys("review:like:*");  // "review:like:"로 시작하는 모든 키들
		
		Map<String, Integer> reviewLikesMap = new HashMap<>();

		// 각 reviewId에 대해 좋아요 수를 가져와서 Map에 저장
		for (String reviewId : reviewIds) {
			// "review:like:" 접두어 제거
			String plainReviewId = reviewId.replace(LIKE_KEY_PREFIX, "");

			// count 값은 Integer로만 저장된다고 가정하고 바로 캐스팅
			Integer likeCount = (Integer)hashOps.get(reviewId, "count");
			if (likeCount != null) {
				reviewLikesMap.put(plainReviewId, likeCount);
			}
		}

		return reviewLikesMap;
	}

 

 

 

원인 분석

hashOps.keys("pettern") 자바 코드는 내가 생각한 것 처럼  Redisdml KEYS 명령어 처럼 동작하지 않는다.

이 메서드는 단일 해시 키 안의 필드들만들 조회하는 기능이다.

즉, HKEYS review:like:* 이런 식으로는 동작하지 않음.

HKEYS review:like:*

 

review:like:1, review:like:2와 같이 여러 개의 해시 키에 대해 반복적으로 조회해야 할 때는 SCAN을 써야 한다는 사실을 알게되었다.

 

해결 방법 

검색해보니 여러 키를 패턴으로 조회하고 싶으면 SCAN  명령어를 써야 한다.
SCAN은 Redis에서 전체 키 중 패턴에 맞는 키들을 반복적으로 검색하는 명령이고 성능상 KEYS 보다 안전하고 비차단 방식이라 대용량에서도 안전하게 사용 가능하다는 장점이있다.

 

Spring에서는 RedisConnection.scan()을 사용하면된다고한다.

RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
ScanOptions options = ScanOptions.scanOptions()
        .match("review:like:*")
        .count(1000)
        .build();

Cursor<byte[]> cursor = connection.scan(options);

 

cursor를 통해 하나씩 키를 순회하며 원하는 데이터를 꺼낼 수 있다. 커서는 반복이 끝나면 반드시 close() 해줘야하고,

커넥션도 닫아줘야한다.

 

// Redis에서 모든 리뷰의 좋아요 수를 가져와 HashMap으로 반환
	public Map<String, Integer> getAllReviewIdsWithLikes() {
		hashOps = likeCountRedisTemplate.opsForHash();
		String pattern = "review:like:*";

		RedisConnection connection = likeCountRedisTemplate.getConnectionFactory().getConnection();
		ScanOptions options = ScanOptions.scanOptions()
			.match(pattern)
			.count(1000)  // 한 번에 가져올 키 개수
			.build();

		// SCAN을 통해 Redis에서 키를 가져옴
		Cursor<byte[]> keys = connection.scan(options);

		// 결과를 저장할 맵
		Map<String, Integer> reviewLikesMap = new HashMap<>();

		// SCAN을 통해 가져온 키들에 대해 처리
		while (keys.hasNext()) {
			// Redis에서 가져온 키 (byte[])를 문자열로 변환
			String key = new String(keys.next());

			// Redis에서 해당 키에 대한 좋아요 수를 가져옴
			Integer likeCount = getLikeCountFromRedis(key);

			key = key.replace(LIKE_KEY_PREFIX, "");

			// reviewLikesMap에 키와 좋아요 수를 추가
			reviewLikesMap.put(key, likeCount);
		}

		// SCAN이 끝난 후, 연결 자원을 닫음
		connection.close();
		return reviewLikesMap;
	}

	// Redis에서 특정 키에 대한 좋아요 수를 가져오는 메서드
	private Integer getLikeCountFromRedis(String key) {
		// HashOperations을 사용하여 해당 키에서 "count" 값을 가져옴
		hashOps = likeCountRedisTemplate.opsForHash();

		Integer count = (Integer)hashOps.get(key, "count");
		
		// "count" 값이 있으면 Integer로 변환하여 반환, 없으면 기본값 0 반환
		if (count != null) {
			return count;
		} else {
			return 0; // 기본값 0 반환
		}
	}

 

이렇게 코드를 변경함으로 써 키를 가져와 정상적으로 다룰 수 있게되었다!