Redis HINCRBY로 동시성 문제 해결: 실시간 베팅

2024. 11. 19. 19:43· Backend/Redis
목차
  1. 문제 상황
  2. 레디스의 특징과 Redis HINCRBY 명령
  3. 동시성 테스트
  4. 설계
  5. 결과

문제 상황

 실시간으로 베팅이 이루어지는 서비스이다. "만약 많은 사용자가 동시에 베팅을 진행할 경우, 동시성 문제가 일어나 데이터가 부정확해지는 것은 아닐까?" 고민을 하게 되었다.

 

구체적인 상황은 아래와 같다.

1. 베팅을 하면 카운트가 올라간다. (베팅 참여자, 베팅한 금액)

2. 카운트를 올리기 위해 데이터를 조회한다.

3. 조회한 데이터에 증가 연산을 수행한다.

4. 업데이트 된 데이터를 저장한다.

 

위 경우, 만약 두 명의 사용자가 데이터가 1인 시점에 접근해 각각 1씩 올려도 3이 아니라 2가 되는 동시성 문제가 발생하게 되는 것이다.

 

 

 

결론부터 이야기하면, 레디스와 레디스의 HINCRBY 명령어를 통해 원자성을 보장할 수 있었다.

 

HINCRBY 명령어와 가상의 테스트 시나리오를 통해, 어떻게 동시성 문제를 해결했는지 정리해보았다.

 

 

레디스의 특징과 Redis HINCRBY 명령

Redis는 단일 스레드 기반으로 동작하여 모든 명령어를 순차적으로 처리한다. 즉 한번에 하나의 요청을 처리한다.
이런 특성 덕분에 Redis는 많은 동시 요청을 처리할 때에도 원자성을 보장할 수 있다.

 

HINCRBY 명령어는 그 자체로 원자성을 가진다. 아래의 메커니즘이 하나의 원자성으로 실행된다.

1. 키 조회 및 변경
  지정된 해시 키(hash key)와 필드(field)를 조회한다.

2. 증가 연산
  조회한 값을 메모리에서 바로 증가 연산한다.

3. 변경사항 저장
  증가된 값을 다시 지정된 필드에 저장한다.

4. 응답 반환

 

 위 동작이 하나의 명령어로 처리되고, 레디스는 단일 스레드로 동작하기 때문에 다른 명령어가 끼어들거나 같은 키에 대해 동시에 작업하지 못한다.

 

그림으로 쉽게 표현해보면 위와 같다. 하나의 저장소에 동시 접근이 불가능하고 HINCRBY 명령이 원자적으로 수행된다.

 

 

동시성 테스트

설계

이것을 확인하기 위해서 실제 레디스에 접근해 테스트를 진행했다.

베팅 옵션 업데이트

  async updateBetOption(roomId: string, option: string, betAmount: number) {
    await Promise.all([
      this.client.hincrby(`test:${roomId}:${option}`, 'currentBets', betAmount),
      this.client.hincrby(`test:${roomId}:${option}`, 'participants', 1),
    ]);
  }

베팅을 업데이트하는 메서드는 위와 같다. 이것을 promise all을 통해 병렬에 가깝게 호출하여 테스트를 진행했다.

 

Redis.concurrency.ts

async function run() {

  ... 레디스 초기화

  const option = "option1"
  const betAmount = 10;
  const updatePromises:Promise<void>[] = [];
  const updateCount = 1000000;

  // 100만 번
  for (let i = 0; i < updateCount; i++) {
    updatePromises.push(redisManager.updateBetOption(roomID, option, betAmount));
  }
  await Promise.all(updatePromises);

  const result = await redisManager.getChannelData(roomID);
  console.log('Fetching channel data:', result);

  const expectedBets = updateCount * betAmount;
  const expectedParticipants = updateCount;

  if (result && result[option]) {
    const { currentBets, participants } = result[option];

    console.log("Test Result:", {
      expectedBets,
      actualBets: parseInt(currentBets, 10),
      expectedParticipants,
      actualParticipants: parseInt(participants, 10),
    });

    if (
      parseInt(currentBets, 10) === expectedBets &&
      parseInt(participants, 10) === expectedParticipants
    ) {
      console.log("테스트 통과");
      console.log("");
    } else {
      console.error("테스트 실패: 동시성 문제");
    }
  } 
  ... 레디스 삭제 및 종료
}

run().catch(console.error);

10만 번의 카운트를 통해 업데이트를 실행하고 결과를 확인했다.

기대 값은 expectBetAmount = 100,000*10, expectParticipants = 100,000*1 이다

 

 

결과

기대값과 일치하는 것을 확인할 수 있다.

 

'Backend > Redis' 카테고리의 다른 글

Redis 메모리 초과로 서버 다운된 경험과 해결  (2) 2024.12.01
레디스에서 O(N) 관련 커맨드는 주의하기  (0) 2024.11.19
  1. 문제 상황
  2. 레디스의 특징과 Redis HINCRBY 명령
  3. 동시성 테스트
  4. 설계
  5. 결과
'Backend/Redis' 카테고리의 다른 글
  • Redis 메모리 초과로 서버 다운된 경험과 해결
  • 레디스에서 O(N) 관련 커맨드는 주의하기
동구름이
동구름이
동구름이
동구름
동구름이
전체
오늘
어제
  • 분류 전체보기 (178) N
    • Java (63)
      • Java 를 파헤쳐보자 (13)
      • BOJ (45)
      • 프로그래머스 (3)
      • SWEA (1)
      • Java GUI (1)
    • JavaScript (17)
      • JS를 파헤쳐보자 (7)
      • 프로그래머스 (7)
      • JS 학습 정리 (1)
    • Backend (33) N
      • Spring (3)
      • HTTP (7)
      • 프로젝트 (10)
      • MySQL (6) N
      • Redis (3)
      • Elastic Search (1)
      • 인증, 인가 (3)
    • CS (57)
      • 운영체제 (35)
      • Network (22)
    • Git (2)
    • 개발 관련 이것저것 (2)
    • etc (1)
    • 독서 (0)
    • 사설 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Java
  • 자바
  • OS
  • 김영한
  • 백준
  • 네트워크
  • 운영체제
  • BOJ
  • 구현
  • 이석복
  • 스택
  • JCF
  • 프로그래머스
  • 모든 개발자를 위한 HTTP 웹 기본 지식
  • 큐
  • 한양대
  • 레디스
  • 자바스크립트
  • 반효경
  • 인프런

최근 글

hELLO · Designed By 정상우.v4.2.2
동구름이
Redis HINCRBY로 동시성 문제 해결: 실시간 베팅
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.