Skip to content

Commit

Permalink
GETP-324 feat: 프로젝트 테이블 인덱스 설계를 위한 배치 프로그램 작성 (#181)
Browse files Browse the repository at this point in the history
* GETP-324 feat: 프로젝트 배치 삽입 기능 구현

* GETP-324 feat: 프로젝트 배치 삽입 기능을 ExecutorService을 이용해 약 3.17배 개선

* GETP-324 feat: 프로젝트 해시태그, 첨부 파일 배치 삽입 기능 구현 및 속도 약 24.34배 개선

* GETP-324 feat: 프로젝트 지원 배치 삽입 기능 구현
  • Loading branch information
scv1702 authored Nov 13, 2024
1 parent 33b1a3e commit cb99a51
Show file tree
Hide file tree
Showing 27 changed files with 967 additions and 37 deletions.
24 changes: 24 additions & 0 deletions get-p-batch/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
dependencies {
implementation(project(":get-p-domain"))
implementation(project(":get-p-persistence"))
implementation(testFixtures(project(":get-p-domain")))
testImplementation(testFixtures(project(':get-p-domain')))

// Spring Data
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:3.3.5'

// Flyway
implementation 'org.flywaydb:flyway-core:9.16.3'
implementation 'org.flywaydb:flyway-mysql:9.16.3'

// JDBC MySQL 드라이버
runtimeOnly 'com.mysql:mysql-connector-j:9.0.0'
}

bootJar {
enabled = true
}

jar {
enabled = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package es.princip.getp.batch;

public class BatchInsertionException extends RuntimeException {

public BatchInsertionException(final String message) {
super(message);
}

public BatchInsertionException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package es.princip.getp.batch;

import es.princip.getp.batch.project.commission.BatchDeleteProjectService;
import es.princip.getp.batch.project.commission.ParallelBatchInsertProjectService;
import es.princip.getp.batch.project.apply.BatchDeleteProjectApplicationService;
import es.princip.getp.batch.project.apply.BatchInsertProjectApplicationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GetpBatchApplication implements CommandLineRunner {

@Autowired private BatchDeleteProjectApplicationService batchDeleteProjectApplicationService;
@Autowired private BatchDeleteProjectService batchDeleteProjectService;

@Autowired private BatchInsertProjectApplicationService batchInsertProjectApplicationService;
@Autowired private ParallelBatchInsertProjectService batchInsertProjectService;

public static void main(String[] args) {
SpringApplication.run(GetpBatchApplication.class, args);
}

private static final int PROJECT_SIZE = 100_000;
@Override
public void run(final String... args) {
batchDeleteProjectApplicationService.delete();
batchDeleteProjectService.delete();

batchInsertProjectService.insert(PROJECT_SIZE);
batchInsertProjectApplicationService.insert(PROJECT_SIZE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package es.princip.getp.batch;

import java.util.concurrent.atomic.AtomicLong;

public class UniqueLongGenerator {
private static final AtomicLong counter = new AtomicLong();

public static long generateUniqueLong() {
return counter.incrementAndGet();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package es.princip.getp.batch.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class ExecutionTimer {

@Pointcut("@annotation(es.princip.getp.batch.config.ExtendsWithExecutionTimer)")
private void timer() {}

@Around("timer()")
public Object AssumeExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
final long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
final long finish = System.currentTimeMillis();
final long executionTime = finish - start;
final String signature = joinPoint.getSignature().toShortString();
log.info("execution time of {}: {}ms", signature, executionTime);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package es.princip.getp.batch.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtendsWithExecutionTimer {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package es.princip.getp.batch.parallel;

import es.princip.getp.batch.BatchInsertionException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
@Service
public class ParallelBatchInsertService {

@Getter
private final int numThreads = Runtime.getRuntime().availableProcessors();

public void insert(final int size, final ParallelBatchInserter batchInserter) {
final ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
final List<CompletableFuture<Void>> futures = new ArrayList<>();
final int batchSize = size / numThreads;
for (int i = 0; i < numThreads; i++) {
final int start = i * batchSize + 1;
final int end = (i == numThreads - 1) ? size : start + batchSize - 1;
final CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
final String threadName = Thread.currentThread().getName();
log.info("Thread {} is inserting projects from {} to {}", threadName, start, end);
try {
batchInserter.insert(start, end);
} catch (final Exception exception) {
throw new BatchInsertionException(
String.format(
"Thread %s encountered an error during batch insert for range %d to %d: ",
threadName,
start,
end
),
exception
);
}
}, executorService);
futures.add(future);
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
executorService.shutdown();
log.info("All threads completed. Executor service shutdown.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package es.princip.getp.batch.parallel;

@FunctionalInterface
public interface ParallelBatchInserter {
void insert(int start, int end);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package es.princip.getp.batch.project.apply;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class BatchDeleteProjectApplicationService {

private final JdbcTemplate jdbcTemplate;

public void delete() {
jdbcTemplate.execute("delete from team_project_application_teammate");
jdbcTemplate.execute("delete from team_project_application");
jdbcTemplate.execute("delete from individual_project_application");
jdbcTemplate.execute("delete from project_application_attachment_file");
jdbcTemplate.execute("delete from project_application");
log.info("Table \"team_project_application_teammate\" is dropped");
log.info("Table \"team_project_application\" is dropped");
log.info("Table \"individual_project_application\" is dropped");
log.info("Table \"project_application_attachment_file\" is dropped");
log.info("Table \"project_application\" is dropped");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package es.princip.getp.batch.project.apply;

import es.princip.getp.domain.project.apply.model.IndividualProjectApplication;
import es.princip.getp.domain.project.apply.model.ProjectApplication;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

@Service
@RequiredArgsConstructor
class BatchInsertIndividualProjectApplicationJdbcService {

private final JdbcTemplate jdbcTemplate;
private static final String sql =
"""
insert into individual_project_application (
project_application_id
) values (?);
""";

public void batchUpdate(final List<ProjectApplication> applications) {
final List<IndividualProjectApplication> individuals = applications.stream()
.filter(IndividualProjectApplication.class::isInstance)
.map(IndividualProjectApplication.class::cast)
.toList();

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(final PreparedStatement ps, final int i) throws SQLException {
final IndividualProjectApplication application = individuals.get(i);
ps.setLong(1, application.getId().getValue());
}

@Override
public int getBatchSize() {
return individuals.size();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package es.princip.getp.batch.project.apply;

import es.princip.getp.domain.common.model.AttachmentFile;
import es.princip.getp.domain.project.apply.model.ProjectApplication;
import es.princip.getp.domain.project.apply.model.ProjectApplicationId;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

@Service
@RequiredArgsConstructor
class BatchInsertProjectApplicationAttachmentFileJdbcService {

private record ProjectApplicationIdAttachmentFile(
ProjectApplicationId applicationId,
AttachmentFile attachmentFile
) {
}

private final JdbcTemplate jdbcTemplate;
private static final String sql =
"""
insert into project_application_attachment_file(
project_application_id,
attachment_files
) values (?, ?);
""";

public void batchUpdate(final List<ProjectApplication> applications) {
final List<ProjectApplicationIdAttachmentFile> attachmentFiles = applications.stream()
.flatMap(application -> application.getAttachmentFiles().stream()
.map(attachmentFile -> new ProjectApplicationIdAttachmentFile(
application.getId(),
attachmentFile
)))
.toList();

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(final PreparedStatement ps, final int i) throws SQLException {
final ProjectApplicationId applicationId = attachmentFiles.get(i).applicationId();
final AttachmentFile attachmentFile = attachmentFiles.get(i).attachmentFile();
ps.setLong(1, applicationId.getValue());
ps.setString(2, attachmentFile.getUrl().getValue());
}

@Override
public int getBatchSize() {
return attachmentFiles.size();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package es.princip.getp.batch.project.apply;

import es.princip.getp.domain.project.apply.model.IndividualProjectApplication;
import es.princip.getp.domain.project.apply.model.ProjectApplication;
import es.princip.getp.domain.project.apply.model.TeamProjectApplication;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

@Service
@RequiredArgsConstructor
class BatchInsertProjectApplicationJdbcService {

private final BatchInsertProjectApplicationAttachmentFileJdbcService attachmentFileJdbcService;
private final BatchInsertTeamProjectApplicationJdbcService teamJdbcService;
private final BatchInsertIndividualProjectApplicationJdbcService individualJdbcService;
private final JdbcTemplate jdbcTemplate;
private static final String sql =
"""
insert into project_application (
project_application_id,
expected_end_date,
expected_start_date,
description,
status,
people_id,
project_id,
dtype
) values (?, ?, ?, ?, ?, ?, ?, ?);
""";

public void batchUpdate(final List<ProjectApplication> applications) {
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(final PreparedStatement ps, final int i) throws SQLException {
final ProjectApplication application = applications.get(i);
final String dtype = (application instanceof TeamProjectApplication) ?
TeamProjectApplication.TYPE : IndividualProjectApplication.TYPE;
ps.setLong(1, application.getId().getValue());
ps.setDate(2, Date.valueOf(application.getExpectedDuration().getEndDate()));
ps.setDate(3, Date.valueOf(application.getExpectedDuration().getStartDate()));
ps.setString(4, application.getDescription());
ps.setString(5, application.getStatus().toString());
ps.setLong(6, application.getApplicantId().getValue());
ps.setLong(7, application.getProjectId().getValue());
ps.setString(8,dtype);
}

@Override
public int getBatchSize() {
return applications.size();
}
});

attachmentFileJdbcService.batchUpdate(applications);
individualJdbcService.batchUpdate(applications);
teamJdbcService.batchUpdate(applications);
}
}
Loading

0 comments on commit cb99a51

Please sign in to comment.