관리 메뉴

피터의 개발이야기

[Redis] KEYS보다 SCAN 명령어를 써야하는 이유? 본문

DevOps/Redis&Redict

[Redis] KEYS보다 SCAN 명령어를 써야하는 이유?

기록하는 백앤드개발자 2022. 5. 28. 21:59
반응형

ㅁ 개요

 Redis slowlog를 모니터링하는 중 KEYS가 롱쿼리로 감지되어,

 SCAN으로 변경하여 Redis Blocking 시간을 최소화한 과정을 정리하였다.

 

 

ㅁ Redis 모니터링의 필요성

 ㅇ 현재 내가 담당하는 시스템은 대량 트래픽을 처리하고 있다. 그래서 메인 디비로 Redis를 사용하고 있다.

 ㅇ 고성능 고가용성을 위해 쿠버네티스 환경으로 구축되었고, 어플리케이션 내의 개별 프로세스도 비동기형태로 구현되어 있다.

 ㅇ RDS의 처리 속도가 보장되지 않아 Redis를 사용하고 있기 때문에
     Redis의 slowlog확인을 통해 10ms 이상인 것은 지속적으로 튜닝을 해야만 전체 서비스의 TPS를 보장할 수 있다.

 

 

ㅁ Grafana Slow log 확인

 ㅇ HKEYS 명령어가 10ms 이상 발생하고 있다. 

 

 

 ㅇ 해당 구간의 문제점을 파악한 결과, 처음 설계 시 예측했던 데이터양(100건 미만)보다 대량의 데이터가 발생하였다.

 ㅇ VIP 고객이 유치되면서 데이터가 급격히 증가하였다.

 

 

ㅁ HKEYS에서 SCAN으로 변경해야하는 이유

Redis는 single thread event loop 구조로 명령어를 처리하는 비동기 방식이다. keys 명령어를 처리하기 위해서 모든 key를 다 찾을 동안 blocking 상태가 되기 때문에 다른 요청들이 다 지연이 된다. Redis 공식사이트에서도 keys 대신에 scan을 사용하도록 권고하고 있다.

 

Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don't use KEYS in your regular application code. If you're looking for a way to find keys in a subset of your keyspace, consider using SCAN or sets.

 

Scan은 명령어 처리의 blocking을 최소화하기 위해여 count를 기준으로 분산호출을 하도록 되어 있다. 기본 count의 값은 10인데, 총 데이터가 10000개이면 count를 10으로 할 경우 1000번으로 나누어 호출하여 cursor가 0이 될때까지 반복적으로 호출하게 된다.

 

 

ㅁ 소스코드

	public HashMap<String, Object> getFieldList( String key, String pattern ) {
		HashMap<String, Object> result = new HashMap<>();
		Cursor<Entry<Object, Object>> rslt = null;
		try {
			ScanOptions options = ScanOptions.scanOptions().match(pattern).count(scanCount).build();
			rslt = template.opsForHash().scan(key, options);
			if (rslt == null) return null;

			while(rslt.hasNext()) {
				Entry<Object, Object> bytes = rslt.next();
				result.put((String) bytes.getKey(), bytes.getKey());
			}

		} catch (Exception e) {
			log.error(e.getMessage(), e);
		} finally {
			if (rslt != null) try { rslt.close(); } catch (Exception e) {}
		}
		return result;
	}

 

ㅁ 함께 보면 좋은 사이트

https://redis.io/commands/keys/

https://tjdrnr05571.tistory.com/11

반응형
Comments