이론 정리/Database

대규모 데이터를 배치로 처리할 때에 알아두면 좋은 내용

철매존 2025. 4. 18. 01:59
728x90
반응형

대규모 데이터를 배치로 처리할 때에 알아두면 좋은 내용

배치가 뭐고 왜 쓰는거지?

간단하게 말하자면 데이터나 작업 등을 자동으로 처리하는 방법이다.

사용 용도는 다음과 같다.

  • 대용량 데이터 처리 : 실시간으로 하기 곤란한 대용량 데이터 처리
  • 반복 작업 : 주기적으로 수행되기 때문에 자동으로 되면 좋은 것
  • 복잡하거나 리소스가 많이 드는 작업 : 시간이 오래 걸리거나 리소스 소모가 심하면 사용량이 적을 때에 배치로 처리 가능

즉, 얘는 개발자가 직접 처리하지 않고 뭉탱이로 처리할 수 있는 용도이고 장점은

  • 대용량 처리 가능
  • 작업 용이
  • 자동화
  • 부하 분산

등이 있을 것이다.

대용량 데이터 사용 시 배치 주의점

배치를 사용하면 대용량 데이터를 처리할 수 있다.
그런데... 만약 그 대상이 되는 데이터가 진짜 짱많은면 어떨까?
생각을 해보면

  1. 배치도 결국 데이터를 처리하기 위해서는 리소스를 잡아먹는다.
  2. 그리고, 뭉탱이로 처리하기 위해 보통은 처리하는 데이터를 Transaction 으로 감싸서 처리한다.
  3. 만약 Spring 에서 저 많은 데이터를 로드하고 처리하려고 한다면?
  4. OOM 이 발생할 가능성이 매우 높다.
  5. 그리고, 그렇지 않다고 해도 Transaction 때문에 DB에 부하가 가고 시스템 장애의 원인이 될 수 있다.

해결 방법

결국은 간단하게(말로는) 해결할 수 있어 보인다.
한번에 다 해서 문제라면 그냥 따로 나눠서 하면 되기 때문.

  1. 페이징 기법 활용
  2. 병렬 처리
  3. 합쳐서 처리

1. 페이징 기법 활용

한번에 가져오는게 문제라면?
-> 여러번에 걸쳐 들고오면 된다.

그냥 간단히 말하면 게시글 볼 때 페이지 나누는 거라고 생각하면 된다.
당연하겠지만 offset limit 은 좋지 않고, Cursor 방식을 활용하면 좋다.
마지막으로 처리한 데이터 이후로부터 들고오는 방식이라고 생각하면 되는데, 이 글을 참고해 봐도 좋을듯.

방식을 설명해 보자면, 한 10000개 정도의 데이터를 가져오려 할 때

  1. Transaction 시작
  2. 1000개를 읽어옴
  3. 1000개를 처리함
  4. Transaction 커밋
  5. Transaction 시작
  6. 1000번째 데이터를 통해 그 다음 1000개를 읽어옴
  7. 반복...

이러면 된다.
중간에 실패한다면 그 부분만 다시 시도하면 될 것이다.

2. 병렬 처리

한번에 가져오는게 문제라면?
-> 여러명이 들고오면 된다.

이거는 이제 여러 배치 서버가 같이 일한다고 생각하면 된다.
나누는 것 자체는 위랑 같지만, 혼자서 여러번 하는것과 한꺼번에 나눠서 하는것의 차이다.

근데 그게 방법이 여러개가 있는데

  • 멀티스레딩
    • 여러 스레드가 나눠서 처리하는 방식
    • 장점
      • 메모리를 공유하기 때문에 데이터를 주고받기 쉽고, 그 때문에 I/O 대기가 많은 작업에 효율적이다.
    • 단점
      • 메모리 문제가 터질 수 있다.
      • 메모리 동기화 문제 발생 가능
  • 멀티프로세싱
    • 여러 프로세스가 나눠서 처리하는 방식
    • 장점
      • 메모리 문제가 생길 여지가 적다.
      • 동기화 문제도 발생하지 않는다.(애초에 공유를 못하니)
    • 단점
      • 메모리 공유 자체가 안됨.
      • 프로세스를 매번 만들고 내려야 한다.
  • 분산처리
    • 여러 서버에서 배치 작업 수행
    • 장점
      • 스케일아웃이 매우 자유롭다.
      • 데이터가 많으면 사실상 이게 정답.
    • 단점
      • 구성과 관리가 난감하다.
      • 하나가 아예 죽어버리거나 각각 서버 간의 통신 등.. 고민할 점이 너무 많다.

일단은 셋의 차이는 이렇다.
정말 데이터가 많다면 사실상 분산처리가 강제되기 때문에 이 내용에서는 이거를 생각하면 될듯.

그러면 이게 어떻게 할까? 똑같이 10000개의 데이터를 처리한다고 하면
10개의 서버가 있다고 가정한다.

  1. 1번 서버에서 1~1000 번 데이터 처리
  2. 2번 서버에서 1001번 ~ 2000번 데이터 처리
  3. 10개 서버에서 수행

이러면 간단히 처리된다.
여기서 저거 어떤 값을 처리할지를 어떻게 아냐? 하면

  • Range 파티셔닝
    • 범위를 애초에 나눠서 처리
    • 위의 방식대로 11000 / 10012000 .... 이렇게
    • 장점
      • Index 효율이 좋다.
        • 범위를 나눠서 처리하면 인덱스 내에서 순서대로 위치하기 때문에 모듈러보다 훨씬 효율적이다.
      • 직관적
    • 단점
      • 데이터 쏠림
        • 나눠둔 범위에 데이터가 얼마나 있을지 모른다.
      • 사전 분석 필요
        • 어떤 범위를 어떻게 나눠서 배치 서버에서 수행하게 할지 고민해 봐야 한다.
  • 모듈로 파티셔닝
    • 서버 개수로 나머지를 구해서 처리
    • ID가 1로 끝나면 1번 서버가 처리, 2로 끝나면 2번 서버가 처리 이런 식으로 가는거
    • 장점
      • 데이터 쏠림이 적다.
        • 얘는 위의 범위를 나누는 것에 비해 데이터가 나눠져 있을 여지가 많다.
      • 간단한 구현 가능
        • 어떤 범위로 나눠야 할지 깊은 고민하지 않고 서버 수로 구하면 된다.
    • 단점
      • Index 효율이 낮다.
        • 당연하지만 index를 타도 숫자가 모듈로로 인해 나뉘어 있어 더 많은 데이터의 로드가 필요하기 떄문에 Range 방식에 비하면 훨씬 비효율적이다.

하는 방식이 있다.
DB 부하를 줄이는 것이 좋고 데이터 분포가 균일하다면 Range를, 아니면 Modulo 를 선택하면 될 것이다.

합쳐서 처리

사실 분산 처리의 경우는 합쳐서 처리하는 경우가 많다.
서버를 여러 대 운용하면서 각 서버가 잘 나눠서 처리하는 것이다.

예를 들어 10개의 서버에서 100000개 데이터를 처리해야 한다면

  • 1번 서버
    • 1~10000개의 데이터 처리
    • 1000개씩 나눠서 10번 처리
  • 2번 서버
    • 10001 ~ 20000개의 데이터 처리
    • 1000개씩 나눠서 10번 처리

요런 식으로 가면 된다.
모듈로를 사용한다면

  • 1번 서버
    • 1, 11, 21 ... 해서 1000개까지 처리하고 다음으로
  • 2번 서버
    • 2, 12, 22 .. 해서 1000개까지 처리하고 다음으로

이런 식으로 가면 될 것이다.
어떤 것이 성능이 좋을지를 잘 고민하고 쓰면 좋을 것 같다.

실패 시 재시도?

그러면 저런 작업 중에 일부분이 실패하면 어떻게 할까?
보통은 배치에서는 작업 단위로 Transaction 을 건다.
그렇기 때문에 어떤 작업이 실패하면 그 부분에 해당하는 것들만 롤백될 것이다.

이 경우 재시도를 하면 되는데, 여기서도 좀 애매한게 있다.

  • 재시도 횟수 고려
    • 당연하지만 실패했으니 재시도를 통해 처리해야 하는데, 그게 계속 실패하면 뭔가 일시적인게 아니라 계속되는 실패일 수도 있다.
    • 그러면 이거를 계속 돌리는 것 자체가 리소스의 낭비일 것이다.
    • Threshold 등을 걸어, 재시도 횟수를 제한하는 것도 괜찮을 것이다.
  • 멱등성을 보장해야 한다.
    • 매우 중요한 내용인데, 배치에서 실패했을 떄 재시도를 하는 경우 원했던 결과의 멱등성을 보장해야 한다.
반응형