Skip to content

Commit cb99a51

Browse files
authored
GETP-324 feat: 프로젝트 테이블 인덱스 설계를 위한 배치 프로그램 작성 (#181)
* GETP-324 feat: 프로젝트 배치 삽입 기능 구현 * GETP-324 feat: 프로젝트 배치 삽입 기능을 ExecutorService을 이용해 약 3.17배 개선 * GETP-324 feat: 프로젝트 해시태그, 첨부 파일 배치 삽입 기능 구현 및 속도 약 24.34배 개선 * GETP-324 feat: 프로젝트 지원 배치 삽입 기능 구현
1 parent 33b1a3e commit cb99a51

27 files changed

+967
-37
lines changed

get-p-batch/build.gradle

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
dependencies {
2+
implementation(project(":get-p-domain"))
3+
implementation(project(":get-p-persistence"))
4+
implementation(testFixtures(project(":get-p-domain")))
5+
testImplementation(testFixtures(project(':get-p-domain')))
6+
7+
// Spring Data
8+
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:3.3.5'
9+
10+
// Flyway
11+
implementation 'org.flywaydb:flyway-core:9.16.3'
12+
implementation 'org.flywaydb:flyway-mysql:9.16.3'
13+
14+
// JDBC MySQL 드라이버
15+
runtimeOnly 'com.mysql:mysql-connector-j:9.0.0'
16+
}
17+
18+
bootJar {
19+
enabled = true
20+
}
21+
22+
jar {
23+
enabled = false
24+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package es.princip.getp.batch;
2+
3+
public class BatchInsertionException extends RuntimeException {
4+
5+
public BatchInsertionException(final String message) {
6+
super(message);
7+
}
8+
9+
public BatchInsertionException(final String message, final Throwable cause) {
10+
super(message, cause);
11+
}
12+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package es.princip.getp.batch;
2+
3+
import es.princip.getp.batch.project.commission.BatchDeleteProjectService;
4+
import es.princip.getp.batch.project.commission.ParallelBatchInsertProjectService;
5+
import es.princip.getp.batch.project.apply.BatchDeleteProjectApplicationService;
6+
import es.princip.getp.batch.project.apply.BatchInsertProjectApplicationService;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.boot.CommandLineRunner;
9+
import org.springframework.boot.SpringApplication;
10+
import org.springframework.boot.autoconfigure.SpringBootApplication;
11+
12+
@SpringBootApplication
13+
public class GetpBatchApplication implements CommandLineRunner {
14+
15+
@Autowired private BatchDeleteProjectApplicationService batchDeleteProjectApplicationService;
16+
@Autowired private BatchDeleteProjectService batchDeleteProjectService;
17+
18+
@Autowired private BatchInsertProjectApplicationService batchInsertProjectApplicationService;
19+
@Autowired private ParallelBatchInsertProjectService batchInsertProjectService;
20+
21+
public static void main(String[] args) {
22+
SpringApplication.run(GetpBatchApplication.class, args);
23+
}
24+
25+
private static final int PROJECT_SIZE = 100_000;
26+
@Override
27+
public void run(final String... args) {
28+
batchDeleteProjectApplicationService.delete();
29+
batchDeleteProjectService.delete();
30+
31+
batchInsertProjectService.insert(PROJECT_SIZE);
32+
batchInsertProjectApplicationService.insert(PROJECT_SIZE);
33+
}
34+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package es.princip.getp.batch;
2+
3+
import java.util.concurrent.atomic.AtomicLong;
4+
5+
public class UniqueLongGenerator {
6+
private static final AtomicLong counter = new AtomicLong();
7+
8+
public static long generateUniqueLong() {
9+
return counter.incrementAndGet();
10+
}
11+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package es.princip.getp.batch.config;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.aspectj.lang.ProceedingJoinPoint;
5+
import org.aspectj.lang.annotation.Around;
6+
import org.aspectj.lang.annotation.Aspect;
7+
import org.aspectj.lang.annotation.Pointcut;
8+
import org.springframework.stereotype.Component;
9+
10+
@Slf4j
11+
@Aspect
12+
@Component
13+
public class ExecutionTimer {
14+
15+
@Pointcut("@annotation(es.princip.getp.batch.config.ExtendsWithExecutionTimer)")
16+
private void timer() {}
17+
18+
@Around("timer()")
19+
public Object AssumeExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
20+
final long start = System.currentTimeMillis();
21+
try {
22+
return joinPoint.proceed();
23+
} finally {
24+
final long finish = System.currentTimeMillis();
25+
final long executionTime = finish - start;
26+
final String signature = joinPoint.getSignature().toShortString();
27+
log.info("execution time of {}: {}ms", signature, executionTime);
28+
}
29+
}
30+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package es.princip.getp.batch.config;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface ExtendsWithExecutionTimer {
11+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package es.princip.getp.batch.parallel;
2+
3+
import es.princip.getp.batch.BatchInsertionException;
4+
import lombok.Getter;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.stereotype.Service;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.concurrent.CompletableFuture;
11+
import java.util.concurrent.ExecutorService;
12+
import java.util.concurrent.Executors;
13+
14+
@Slf4j
15+
@Service
16+
public class ParallelBatchInsertService {
17+
18+
@Getter
19+
private final int numThreads = Runtime.getRuntime().availableProcessors();
20+
21+
public void insert(final int size, final ParallelBatchInserter batchInserter) {
22+
final ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
23+
final List<CompletableFuture<Void>> futures = new ArrayList<>();
24+
final int batchSize = size / numThreads;
25+
for (int i = 0; i < numThreads; i++) {
26+
final int start = i * batchSize + 1;
27+
final int end = (i == numThreads - 1) ? size : start + batchSize - 1;
28+
final CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
29+
final String threadName = Thread.currentThread().getName();
30+
log.info("Thread {} is inserting projects from {} to {}", threadName, start, end);
31+
try {
32+
batchInserter.insert(start, end);
33+
} catch (final Exception exception) {
34+
throw new BatchInsertionException(
35+
String.format(
36+
"Thread %s encountered an error during batch insert for range %d to %d: ",
37+
threadName,
38+
start,
39+
end
40+
),
41+
exception
42+
);
43+
}
44+
}, executorService);
45+
futures.add(future);
46+
}
47+
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
48+
executorService.shutdown();
49+
log.info("All threads completed. Executor service shutdown.");
50+
}
51+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package es.princip.getp.batch.parallel;
2+
3+
@FunctionalInterface
4+
public interface ParallelBatchInserter {
5+
void insert(int start, int end);
6+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package es.princip.getp.batch.project.apply;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.jdbc.core.JdbcTemplate;
6+
import org.springframework.stereotype.Service;
7+
8+
@Slf4j
9+
@Service
10+
@RequiredArgsConstructor
11+
public class BatchDeleteProjectApplicationService {
12+
13+
private final JdbcTemplate jdbcTemplate;
14+
15+
public void delete() {
16+
jdbcTemplate.execute("delete from team_project_application_teammate");
17+
jdbcTemplate.execute("delete from team_project_application");
18+
jdbcTemplate.execute("delete from individual_project_application");
19+
jdbcTemplate.execute("delete from project_application_attachment_file");
20+
jdbcTemplate.execute("delete from project_application");
21+
log.info("Table \"team_project_application_teammate\" is dropped");
22+
log.info("Table \"team_project_application\" is dropped");
23+
log.info("Table \"individual_project_application\" is dropped");
24+
log.info("Table \"project_application_attachment_file\" is dropped");
25+
log.info("Table \"project_application\" is dropped");
26+
}
27+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package es.princip.getp.batch.project.apply;
2+
3+
import es.princip.getp.domain.project.apply.model.IndividualProjectApplication;
4+
import es.princip.getp.domain.project.apply.model.ProjectApplication;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
7+
import org.springframework.jdbc.core.JdbcTemplate;
8+
import org.springframework.stereotype.Service;
9+
10+
import java.sql.PreparedStatement;
11+
import java.sql.SQLException;
12+
import java.util.List;
13+
14+
@Service
15+
@RequiredArgsConstructor
16+
class BatchInsertIndividualProjectApplicationJdbcService {
17+
18+
private final JdbcTemplate jdbcTemplate;
19+
private static final String sql =
20+
"""
21+
insert into individual_project_application (
22+
project_application_id
23+
) values (?);
24+
""";
25+
26+
public void batchUpdate(final List<ProjectApplication> applications) {
27+
final List<IndividualProjectApplication> individuals = applications.stream()
28+
.filter(IndividualProjectApplication.class::isInstance)
29+
.map(IndividualProjectApplication.class::cast)
30+
.toList();
31+
32+
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
33+
@Override
34+
public void setValues(final PreparedStatement ps, final int i) throws SQLException {
35+
final IndividualProjectApplication application = individuals.get(i);
36+
ps.setLong(1, application.getId().getValue());
37+
}
38+
39+
@Override
40+
public int getBatchSize() {
41+
return individuals.size();
42+
}
43+
});
44+
}
45+
}

0 commit comments

Comments
 (0)