유저들의 등급을 매월 업데이트하는 배치 프로세스
해당 프로세스를 개발하면서 성능 향상 결과를 정리합니다.
- 100만명 유저 등급 업데이트에 소요되는 시간 변화
- 19m 13s : 커넥션풀 10개, 커널스레드, offset limit 조회
- 8m 15s : 커넥션풀 30개, 커널스레드, offset limit 조회
- 53s : 커넥션풀 30개, VirtualThread, id range 조회
- 바꾼 포인트 설명
- b-tree 시작부터 offset 값 까지 전부 인덱스 스캔하는 과정이 필요한 offset limit 는 sliding window 방식 id range 조회로 변경.
- step 실행 chunk size 를 100 -> 5 로 변경하면서 스레드를 더 많이 사용하도록 변경( + 그에 맞춰 CP 도 증가)
- VirtualThread 를 사용하는 대신 기존 ThreadLocal 로 사용하던 Reader 내부 list 말고 override read() 메서드 내부에 stack 에만 저장하고 즉시 반환하도록 변경
- 업데이트 쿼리 날리는 거 벌크처리
- select 쿼리 날리는 거 필요칼럼만 가져오도록 변경
- sliding window id range 조회 시, 만약 빈 리스트 반환하여도 최대 user.id 값 도달하지 않았으면 window 반복 sliding
id 값이 건너뛸 경우가 있기 때문. e.g. 1,2,3, 1000, 1001, 1002.
- 롤백 시 id 값은 INSERT 시 MYSQL auto_increment 로 증가하고 있습니다. 만약 1000번째 유저가 롤백되면 1000번째 유저는 삭제되지만 id increment 는 TX 외부에서 실행되어 롤백되지 않죠. 즉, 1000번째 유저가 다시 삽입되면 1001 id 값을 갖게 됩니다.
- sequence 로 미리 id 값을 할당받아서 땡겨올 경우, 마찬가지로 롤백 시 or 서버 셧다운 시 db 의 이미 증가된 sequence 값은 그대로라 다음 유저가 그 id 값을 사용합니다.
결과로 19m 13s 걸리던 프로세스가 53s 까지 줄어들었습니다. 21배 빨라진거죠. 만약 insert 쿼리 배치라면 sequence + jdbc bulk insert 를 이용할 수 있을것 같습니다.
Batch process that updates user grades every month
I am summarizing the performance improvement results while developing this process.
- Change in time required to update grades for 1 million users
- 19m 13s: connection pool 10, kernel threads, offset limit lookup
- 8m 15s: connection pool 30, kernel threads, offset limit lookup
- 53s: connection pool 30, VirtualThread, id range lookup
- Explanation of changed points
offset limit, which needs to index-scan everything from the beginning of the b-tree to the offset value, was changed to sliding-window id range lookup.- Changed step execution chunk size from 100 to 5 so more threads are used, and increased the connection pool accordingly.
- Instead of using VirtualThread with the existing
ThreadLocallist inside the Reader, changed it to store only in the stack inside the overriddenread()method and return immediately. - Bulk-processed update queries.
- Changed select queries to fetch only required columns.
- During sliding-window id range lookup, even if an empty list is returned, keep sliding the window until the max
user.idvalue is reached.This is because id values can be skipped. For example: 1, 2, 3, 1000, 1001, 1002.
- On rollback, the id value is increased by MySQL
auto_incrementduring INSERT. If the 1000th user is rolled back, that user is deleted, but the id increment runs outside the transaction and is not rolled back. So if the 1000th user is inserted again, it gets id 1001. - If id values are allocated in advance through a sequence and pulled ahead, the already-increased sequence value in the DB remains the same on rollback or server shutdown, so the next user uses that id value.
- On rollback, the id value is increased by MySQL
As a result, the process that took 19m 13s was reduced to 53s. It became 21x faster. If it were an insert-query batch, sequence + JDBC bulk insert could probably be used.