개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › DB › 확장과 분산

Redis의 한 줄 처리

명령 하나가 느리면 모두가 기다린다

Redis는 명령을 스레드 하나가 한 번에 하나씩 차례로 실행합니다. 그래서 락 없이도 명령 하나하나가 끼어들 틈 없이 실행되지만, 오래 걸리는 명령이 하나 끼면 그 뒤의 모든 명령이 줄을 섭니다.

일부만 실행으로 확인 · Redis 8.10.2에서 원리를 측정해 확인(시간 값은 예시)

이 실습에 나오는 개념

Redis
데이터를 메모리에 저장하는 키-값 저장소다. 문자열뿐 아니라 리스트, 해시, 집합, 정렬된 집합 등의 자료구조를 제공한다. 메모리에서 동작해 매우 빠르며 캐시, 세션 저장소, 분산 락, 순위표, 요청 수 제한 등에 쓰인다. 명령을 하나의 스레드에서 차례로 실행한다.
캐시
자주 사용하는 데이터나 계산 결과를 빠르게 접근할 수 있는 곳에 미리 저장해 두고 재사용하는 것이다. 원본(주로 데이터베이스)을 매번 조회하지 않아 응답이 빨라지고 원본의 부하가 줄어든다. 대신 원본이 바뀌었을 때 캐시의 값이 오래된 상태로 남는 문제를 관리해야 한다.
원자성(Atomicity)과 CAS
원자성은 여러 단계로 이뤄진 연산이 중간에 끼어들 틈 없이 하나처럼 실행되는 성질이다. CAS(Compare-And-Swap)는 "현재 값이 내가 읽었던 값과 같을 때만 새 값으로 바꾼다"를 하나의 원자적 연산으로 수행하는 방법으로, 락 없이 안전한 갱신을 가능하게 한다. AtomicInteger 등이 이를 이용한다.
이벤트 루프
소수의 스레드가 여러 연결에서 발생하는 입출력 이벤트를 감지해 차례로 처리하는 구조다. 연결마다 스레드를 두지 않으므로 하나의 스레드가 많은 연결을 담당한다. 이 스레드에서 오래 걸리는 작업이나 블로킹 호출을 하면 그 스레드가 맡은 모든 연결의 처리가 멈춘다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. Redis는 여러 클라이언트의 명령을 받아 한 줄로 세운 뒤, 스레드 하나가 앞에서부터 하나씩 실행합니다. 명령 하나가 끝나야 다음 명령을 시작합니다.
  2. 서비스 A의 GET product:1: 0.1ms 동안 실행됐습니다.
  3. 서비스 B의 GET product:2: 0.1ms 동안 실행됐습니다.
  4. 관리자의 KEYS user:*: 500ms 동안 실행됐습니다. 이 명령이 실행되는 동안 Redis는 다른 어떤 명령도 처리하지 못합니다.
  5. 서비스 A의 GET product:3: 앞의 명령이 끝나기를 499.2ms 기다린 뒤 0.1ms 동안 실행됐습니다.
  6. 서비스 B의 SET cart:9 ...: 앞의 명령이 끝나기를 499.3ms 기다린 뒤 0.1ms 동안 실행됐습니다.
  7. 서비스 A의 GET product:4: 앞의 명령이 끝나기를 498.4ms 기다린 뒤 0.1ms 동안 실행됐습니다.

실행 결과

코드

// 서비스 A, B: 평범한 캐시 조회
String v = redis.opsForValue().get("product:1");   // 0.1ms

// 관리자 스크립트
Set<String> keys = redis.keys("user:*");          // 전체 키를 한 번에 훑는다

이 모형의 전제

명령의 실행 시간(GET 0.1ms, KEYS 500ms, 큰 리스트 조회 300ms, SCAN 한 번 0.5ms)은 크기의 차이를 보이기 위한 예시입니다. Redis가 명령을 한 스레드에서 차례로 실행한다는 점과 KEYS 대신 SCAN을 쓰라는 권고는 Redis 공식 문서를 따랐습니다. Redis 6부터 네트워크 입출력에는 여러 스레드를 쓸 수 있지만 명령 실행은 여전히 한 스레드입니다. 원리는 실제 Redis 8.10.2에서 확인했습니다(키 100만 개, 원소 50만 개짜리 리스트). KEYS가 도는 동안 다른 클라이언트의 GET이 최대 186ms, 큰 리스트 조회 동안 22ms를 기다렸고(평소에는 3ms 이하), SCAN으로 1,000개씩 나눠 훑는 동안에는 1.5ms를 넘지 않았습니다. 두 클라이언트가 같은 키에 20,000번씩 동시에 보낸 INCR의 최종 값은 정확히 40,000이었습니다. 화면의 시간 값은 이 측정값이 아니라 예시이며, 실제 시간은 데이터 크기와 장비에 따라 달라집니다.

앞 실습: 파티셔닝 · 다음 실습: NoSQL과 문서 모델

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요