ABOUT ME

Today
Yesterday
Total
  • Redis에서의 KEYS와 SCAN 명령어 비교
    Spring 2024. 4. 16. 07:10

    Redis는 효율적인 키-값 저장소로 널리 사용되고 있으며, 데이터를 조회하는 두 가지 주요 명령어인 KEYS와 SCAN에 대해 자세히 알아보고, 어떤 상황에서 각각을 사용하는 것이 적합한지 살펴보겠습니다.

    KEYS 명령의 이해

    KEYS 명령은 지정된 패턴에 일치하는 모든 키를 한 번의 호출로 반환합니다. 예를 들어, KEYS user:*는 'user:'로 시작하는 모든 키를 찾아 리스트로 반환합니다. 이 방식은 간단하고 직관적이지만, 큰 데이터베이스에서는 몇 가지 문제점을 유발할 수 있습니다.

    장점
    사용이 간단하며 작은 데이터 세트에서 빠르게 결과를 제공합니다.

    단점
    대규모 데이터 세트에서 사용할 경우, 서버를 블로킹하여 다른 모든 연산이 중단될 수 있습니다.
    매우 CPU 및 메모리 집약적이어서 서버의 성능에 부정적인 영향을 줄 수 있습니다.

    SCAN 명령의 이해

    SCAN 명령은 KEYS의 대안으로 개발되었으며, 대용량 데이터를 처리할 때 서버의 블로킹 없이 점진적으로 키를 반환합니다. SCAN은 커서와 함께 사용되며, 커서는 각 호출 후 다음 조회 시작점을 알려줍니다.

    장점
    대규모 데이터베이스에서도 서버의 블로킹 없이 사용할 수 있습니다.
    count 옵션을 조정하여 한 번에 반환하는 키의 수를 통제할 수 있습니다.

    // RedisTemplate을 사용한 SCAN 명령 예시
    Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
        Set<String> keysTemporary = new HashSet<>();
        Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match("testKey:*").count(100).build());
        while (cursor.hasNext()) {
            keysTemporary.add(new String(cursor.next()));
        }
        return keysTemporary;
    });

    성능 비교 및 사용 사례

    실제 테스트에서 KEYS 명령은 작은 데이터 세트에서 빠르게 결과를 반환하지만, 데이터베이스 크기가 증가함에 따라 그 성능이 급격히 저하됩니다. 반면, SCAN 명령은 더 일관된 성능을 보이며, 대용량 데이터 처리에 적합합니다.

    테스트 코드

    @Slf4j
    @RequiredArgsConstructor
    @Component
    public class RedisKeySearch {
        private final RedisTemplate<String, String> redisTemplate;
    
        @PostConstruct
        public void createTestKeys() throws InterruptedException {
    
            final ExecutorService executorService = Executors.newFixedThreadPool(10);
    
            for (int i = 0; i < 10; i++) {
                int threadNum = i;
                executorService.submit(() -> {
                    for (int j = threadNum * 100_000; j < (threadNum + 1) * 10_000; j++) {
                        redisTemplate.opsForSet().add("test:" + j, "value:" + j);
                    }
                });
            }
    
            executorService.shutdown();;
            executorService.awaitTermination(100, TimeUnit.SECONDS);
    
        }
    
        public void findKeysWithKeysCommand() {
            final Set<String> keys = redisTemplate.keys("test:*");
            log(keys);
        }
    
        private static void log(Set<String> keys) {
            log.info("=== key size : {}", Objects.requireNonNull(keys).size());
        }
    
        public void findKeysWithScanCommand() {
            final Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) action -> {
                final Set<String> keyTemporary = new HashSet<>();
                final Cursor<byte[]> cursor = action.scan(ScanOptions.scanOptions()
                        .match("test:*").count(100)
                        .build());
                while (cursor.hasNext()) {
                    keyTemporary.add(new String(cursor.next()));
                }
                return keyTemporary;
            });
            log(keys);
        }
    
    }

    output

    2024-04-16T06:51:26.529+09:00  INFO 54976 --- [redis-playground] [           main] j.s.r.search.RedisKeySearch              : === key size : 1000000
    2024-04-16T06:51:26.538+09:00  INFO 54976 --- [redis-playground] [           main] j.s.r.search.RedisKeySearchStopWatch     : === findKeysWithKeysCommand - runningTime : StopWatch '': 2.250488346 seconds
    ----------------------------------------
    Seconds       %       Task name
    ----------------------------------------
    2.250488346   100%    
    
    2024-04-16T06:51:34.663+09:00  INFO 54976 --- [redis-playground] [           main] j.s.r.search.RedisKeySearch              : === key size : 1000000
    2024-04-16T06:51:34.663+09:00  INFO 54976 --- [redis-playground] [           main] j.s.r.search.RedisKeySearchStopWatch     : === findKeysWithScanCommand - runningTime : StopWatch '': 8.12487944 seconds
    ----------------------------------------
    Seconds       %       Task name
    ----------------------------------------
    8.12487944    100%

    Keys 명령 사용 금지 설정

    Redis에서 KEYS 명령을 사용하지 못하게 하려면, Redis 서버 구성을 수정하여 특정 명령들을 비활성화할 수 있습니다. 이는 보안과 성능을 향상시키기 위해 특히 중요할 수 있습니다. KEYS 명령은 리소스를 매우 집중적으로 사용하며, 대규모 데이터 세트에 미치는 영향 때문에 프로덕션 환경에서의 사용을 제한하거나 금지하는 것이 일반적입니다.

    Redis 구성 파일을 통한 명령 비활성화

    Redis 서버의 구성 파일(redis.conf)에서 rename-command 지시어를 사용하여 KEYS 명령을 다른 이름으로 변경하거나 완전히 비활성화할 수 있습니다. 명령을 비활성화하려면 다음과 같이 설정하면 됩니다

    rename-command KEYS ""

    실행 중인 Redis 서버에서 명령 비활성화

    실행 중인 Redis 인스턴스에서는 CONFIG SET 명령을 사용하여 rename-command 옵션을 동적으로 변경할 수 있습니다. 다음과 같이 실행하세요

    redis-cli CONFIG SET rename-command KEYS ""

    이 방법은 Redis 서버를 재시작하지 않고도 변경을 적용할 수 있습니다. 하지만, 이 설정은 Redis 서버가 재시작될 경우 사라지므로, 영구적으로 적용하고 싶다면 redis.conf 파일을 수정해야 합니다.

    베스트 프랙티스

    작은 데이터 세트와 테스트 환경에서는 KEYS 명령을 사용할 수 있지만, 프로덕션 환경에서는 SCAN 명령을 사용하는 것이 좋습니다.
    SCAN의 count 옵션을 조절하여 네트워크 비용과 성능 사이의 균형을 맞추는 것이 좋습니다.

    댓글

Designed by Tistory.