2024-01-10-TIL
Today I Learned
Spring Batch
Spring Batch는 대량의 데이터를 정해진 시점에 한꺼번에 처리하는 배치 작업을 위한 프레임워크다. 재시작, 건너뛰기, 실행 이력 관리처럼 배치마다 다시 만들기 쉬운 기능을 제공한다. 먼저 도메인 용어를 잡아 두면 문서가 잘 읽힌다.
Job: 배치 처리 전체를 담는 단위다. 하나 이상의Step을 순서대로 묶는다.JobInstance: 논리적인 실행 한 건이다. “1월 1일 정산”과 “1월 2일 정산”은 다른 인스턴스다.Job과 식별용JobParameters가 합쳐져 인스턴스를 구분한다.JobExecution: 인스턴스를 실제로 실행한 시도 한 번이다. 실패 후 재시작하면 같은 인스턴스에 실행이 하나 더 생긴다.Step: Job 안의 독립적인 단계다. 실행 기록은StepExecution에 남는다.ExecutionContext: 재시작에 필요한 상태를 키-값으로 저장한다. 커밋 시점마다 저장되어서, 실패하면 마지막으로 저장한 지점부터 다시 시작할 수 있다.JobRepository: 위 실행 정보를 DB에 저장하는 저장소다.
인스턴스는 Job과 식별용 파라미터로 정해지므로, 같은 파라미터로 다시 실행하면 새 인스턴스가 아니라 같은 인스턴스를 가리킨다. 날마다 도는 배치에 날짜를 파라미터로 넘기는 이유가 여기에 있다.
- https://spring.io/projects/spring-batch/#samples
- https://docs.spring.io/spring-batch/reference/index.html
- https://spring.io/guides/gs/batch-processing/
- https://github.com/spring-projects/spring-batch
- https://khj93.tistory.com/entry/Spring-Batch%EB%9E%80-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
- https://spring.io/guides/gs/batch-processing/
- https://www.baeldung.com/introduction-to-spring-batch
- https://www.tutorialspoint.com/spring_batch/spring_batch_quick_guide.htm
Tasklet vs Chunk
Step을 구현하는 방식은 두 가지다.
청크 지향 처리는 데이터를 하나씩 읽어 일정 개수가 모이면 한꺼번에 쓰는 방식이다. Spring Batch 레퍼런스에 따르면 ItemReader로 하나씩 읽어 모으다가 읽은 개수가 commit interval에 도달하면 ItemWriter가 청크 전체를 쓰고 트랜잭션을 커밋한다. 중간에 ItemProcessor로 변환이나 필터링을 넣을 수 있다. 100만 건을 처리하다 실패해도 이미 커밋한 청크는 남고, 실패한 청크부터 다시 하면 된다.
Tasklet은 한 덩어리의 작업을 하는 방식이다. 레퍼런스는 저장 프로시저 호출처럼 읽고 쓰는 구조가 맞지 않는 작업을 예로 든다. 이런 작업을 ItemReader로 억지로 만들면 아무것도 안 하는 ItemWriter가 필요해져서 부자연스럽다. Tasklet의 execute 메서드는 RepeatStatus.FINISHED를 돌려주거나 예외를 던질 때까지 반복 호출되고, 호출 한 번이 트랜잭션 하나로 감싸진다. 파일 정리, 테이블 비우기 같은 준비나 마무리 단계에 쓴다.
정리하면 건수가 많은 데이터를 읽고 가공해서 쓰는 단계는 청크, 한 번 실행하고 끝나는 단계는 Tasklet이다.
- https://www.baeldung.com/spring-batch-tasklet-chunk
- https://prodo-developer.tistory.com/164
- https://choisblog.tistory.com/80
ForkJoin in Java
Fork/Join은 큰 작업을 작은 작업으로 쪼개(fork) 병렬로 처리하고 결과를 합치는(join) 분할 정복용 프레임워크다. 실행기는 ForkJoinPool이다. Javadoc은 다른 ExecutorService와의 차이를 작업 훔치기(work-stealing)로 설명한다. 풀의 모든 스레드가 제출된 작업이나 다른 작업이 만든 작업을 찾아 실행하므로, 작업이 하위 작업을 계속 만들어 내는 경우에 효율적이다. 별도 풀을 지정하지 않으면 공용 풀(commonPool())을 쓴다.
결과를 돌려주는 작업은 RecursiveTask, 돌려주지 않는 작업은 RecursiveAction을 상속해 compute()를 구현한다. 배열의 합을 구하는 예는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 10_000;
private final long[] arr;
private final int from, to;
SumTask(long[] arr, int from, int to) {
this.arr = arr; this.from = from; this.to = to;
}
@Override
protected Long compute() {
if (to - from <= THRESHOLD) { // 충분히 작으면 직접 계산
long sum = 0;
for (int i = from; i < to; i++) sum += arr[i];
return sum;
}
int mid = (from + to) / 2;
SumTask left = new SumTask(arr, from, mid);
left.fork(); // 왼쪽은 다른 스레드에 맡기고
long right = new SumTask(arr, mid, to).compute(); // 오른쪽은 직접 계산
return right + left.join();
}
}
long total = ForkJoinPool.commonPool().invoke(new SumTask(arr, 0, arr.length));
RecursiveTask Javadoc의 예제도 같은 구조다. 범위가 너무 작으면 쪼개지 않고 직접 계산한다. 작업을 만들고 나누는 데도 비용이 들기 때문에, 위 코드의 THRESHOLD처럼 직접 계산으로 넘어가는 기준값을 둔다.
- https://www.baeldung.com/java-fork-join
AI 키워드: Mixtral 논문
1월 8일 Mistral AI가 지난달 공개한 Mixtral 8x7B의 논문 “Mixtral of Experts”를 arXiv에 올렸다. 각 층이 8개의 피드포워드 블록(전문가)으로 이루어지고, 라우터가 토큰마다 그중 2개를 고른다. 그래서 전체 470억 파라미터에 접근하지만 추론에 쓰는 것은 토큰당 130억이다(arXiv 2401.04088). 지시를 따르도록 조정한 Mixtral 8x7B - Instruct도 Apache 2.0으로 함께 공개했다.
댓글
아직 댓글이 없습니다