100만 데이터 마이그레이션 경험 (feat. Zset)

June·2023년 5월 29일
1

실무 문제

목록 보기
7/10

가위바위보 이벤트가 생각보다 잘되었고, 이걸 조금 더 개선해보는 아이디어 중 하나가 가위바위보 몇등을 했는지를 보여주는 리더보드를 만드는 것이었다. 랭킹을 보여주려면 레디스의 zSet을 쓰면 된다고 생각했다. 하지만 문제는 과거 데이터까지도 다 반영해서 랭킹을 보여줘야 한다는 것이었다.

특정 새로운 시점부터 랭킹을 매기기 시작한다면 그냥 레디스의 zSet을 쓰고 이길때마다 레디스에서 점수를 반영하면 되는데 과거 데이터를 반영해야해서 데이터 마이그레이션이 필요했다.

<비즈니스 요구사항>

  • 승수를 기준으로 랭킹 매기기 (비긴 것, 패배한 것은 반영하지 않는다)
  • 같은 등수는 존재하면 안된다. 만약 같은 횟수로 승리한 유저라면 userId가 큰 유저를 등수를 높게 준다.
  • 과거 데이터도 소급적용해서 보여준다.

같은 등수를 허용하지 않기

같은 등수를 허용하지 않는다는데서 어려웠다.

score에는 몇 승 했는지 정보만 넣으려고 했기 때문이다. 고민을 하다가 메이트님의 아이디어로 쉽게 해결할 수 있었다. score를 'n승.userId' 로 넣기로 했다. userId는 유니크하기 때문에 이렇게 하면 같은 승수라 할지라도 같은 등수가 발생하지 않는다. 이 방식을 이용하면 다음에 생성 순서 같은 것도 millisec 을 소수점으로 적용시켜서 가능할 것 같았다.

데이터 마이그레이션

아래와 같은 순서로 배포를 해야 빠지는 데이터 없이 모든 데이터를 반영해서 등수를 반영할 수 있다.

  1. 리더보드 테이블 생성
  2. 테이블에 쌓이도록 배포
  • winner 테이블에 없는 경우
    a. winner 테이블에 생성
    a. 승리시 테이블에 1승 쌓이기
    b. 레디스에 등수 쌓기 시작 (없으면 등록)

  • winner 테이블에 이미 있는 경우
    - 리더보드 테이블에 존재하면 update(+1)

    • 레디스도 1승 추가
    • 리더보드 테이블에 없으면 그냥 스킵 (마이그레이션에서 해준다)
  1. 기존 데이터들 배치 잡으로 마이그레이션
    a. 테이블에 쌓이기 시작한 id를 기록해서 그 이전까지만 배치가 돌게 해야함.
    b. 레디스에 승수 올려주기

배치는 winner를 200개씩 winner id 순으로 끊어서 가져온다. winCount가 0 초과이면, 리더보드 테이블에 winCount를 넣어주는 방식이다.

이렇게 작업 순서를 구상했다. 이렇게만 흘러갔으면 좋았겠지만..

배치 작업 로직에 문제가 있었고, 중간에 실행되다가 그 로직 때문에 예외가 발생해서 처음부터 배치를 돌리게 되었다.

다시 db에 넣는데 unique 제약에 걸리면 안되기 때문에 INSERT IGNORE INTO를 사용했다. 이걸 사용하면 없으면 넣고 있으면 아무 작업을 하지 않는 것이다.

또 당연히 batchUpdate를 사용했다.

이렇게 하니 200개를 가져와서 작업하고 200 밀리세컨씩 sleep을 줬음에도 1초에 200개정도씩 진행이 되어서 100만개를 금방할 수 있었다. 200개씩 가져오고 200 밀리세컨씩 sleep을 준 것은, 당시 프로메테우스로db 평균 qps를 보고 크게 벗어나지 않게 계산해서 결정한 값이다.

기타

대다수의 유저들은 당연히 1승을 하고 만다(1승을 하면 리워드를 받기 때문에). 그래서 대략 40만 정도의 유저가 1승이었다. 제품 스펙에 '만약 1승을 더 한다면 몇등이 오를지'를 보여주는 것이 있었는데, 1승만 한 유저들은 1승을 하면 얼마나 오를지 카운팅할때 시간이 많이 걸리는 문제가 있었다. zrange를 사용해서 발생한 문제였다.

0개의 댓글