Bulk Insert

213kky·2024년 2월 28일

문제

프로젝트 진행 중 프로젝트 모집 API에서 반복되는 Insert문이 발생하여 해당 부분을 개선해보며 작성하게 되었다.
프로젝트를 함께 할 인원을 구하는 글을 작성하는데 해당 글에 들어갈 내용으로 작성자, 제목, 본문, 역할, 참여 방식, 모집여부, 필요 스킬과 같은 항목이 있다. 이중에서 문제되는 부분은 필요 스킬 부분인데 요구 하는 스킬을 여러개 선정해서 저장하는 과정에서 스킬의 갯수 만큼 insert문이 발생하고 있었다. 해당 부분을 개선할 방법을 찾다 Bulk Insert에 대해 접하게 되었다.

Bulk Insert

먼저 Bulk Insert에 대해 간단히 알아보자

Bulk Insert는 DB에 insert해야 할 양이 많을 때 하는 방식으로 한번에 다량의 데이터를 DB에 집어넣으려 할 때 유용하다.

예를 들어 기존 쿼리가 아래와 같을 때

단일 쿼리

INSERT INTO table values (1, 'one')
INSERT INTO table values (2, 'two')
INSERT INTO table values (3, 'three')
INSERT INTO table values (4, 'four')

Bulk Insert

INSERT INTO table values 
(1, 'one'),				 
(2, 'two'),
(3, 'three'),
(4, 'four')

이런 식으로 여러 쿼리를 한번의 쿼리에 묶어서 처리 하는 방식이다.

각 레코드를 개별적으로 삽입하면 각 레코드마다 별도의 SQL 쿼리가 실행되어 데이터베이스와의 통신 횟수가 많아져 성능이 저하된다.

Bulk Insert는 한 번의 SQL 쿼리로 여러 레코드를 삽입할 수 있기 때문에 대량의 데이터를 처리할 때는 Bulk Insert를 사용하는 것이 좋다.

기존 코드

기존에는 ProjectSkill 객체를 생성하고 save() 메서드를 반복적으로 호출하는 방식으로 스킬을 저장했는데 이 방식은 매번 데이터베이스에 INSERT를 실행하기 때문에 성능에 큰 영향을 준다.

for (Skill skill : skills) {
	projectSkillRepository.save(
    	ProjectSkill.builder()
                    .skill(skill)
                    .project(project)
                    .build()
	);
}

이 코드에서 save()가 반복적으로 호출되면서, 각각의 ProjectSkill이 데이터베이스에 INSERT될 때마다 쿼리가 실행이 된다. 예를 들어, 스킬이 10개라면 10번의 INSERT가 발생하게 된다.

Bulk Insert를 통한 개선

이 문제를 해결하기 위해, JdbcTemplate을 사용하여 Bulk Insert 방식을 적용했다. JdbcTemplate은 여러 개의 INSERT 문을 하나의 쿼리로 묶어서 실행할 수 있다. 이를 통해 성능을 크게 향상시킬 수 있다.

@Transactional
public void saveAll(List<Skill> skills, Project project){
	String sql = "INSERT INTO project_skill (project_id, skill_id) " +
            	"VALUES (?, ?)";
	Long projectId = project.getId();

	jdbcTemplate.batchUpdate(sql,
		skills,
        skills.size(),
        (PreparedStatement ps, Skill skill) -> {
			ps.setLong(1, projectId);
            ps.setLong(2, skill.getId());
		}
	);
}

JdbcTemplate에는 Batch를 지원하는 batchUpdate() 메서드가 구현되어있다.
여러 종류로 overloading되어 있어 편한 메소드를 사용하면 된다.
나는 인자로 (String sql, Collection <? extends Object> batchArgs, int batchSize, ParameterizedPreparedStatementSetter <? extends Object> pss)를 사용하는 것을 선택했다.

테스트

마지막으로 기존 코드와 성능 비교를 해보려한다.

기존 코드와 성능을 비교하기 위해 Postman으로 각각 여러 번 호출하며 테스트를 진행하려 했으나, 더 높은 정확성을 위해 k6라는 성능 테스트 툴을 활용하기로 결정했다.

이번 테스트는 부하 테스트가 아니라 단순히 성능 비교를 목적으로 하며, 반복 횟수를 늘려 정확도를 높이기 위한 것으로 이를 위해 각 가상 사용자가 1초 간격으로 실행되도록 설정할 예정이다.

위 테스트 내용은 동일하고 save()를 반복하는 코드와 Bulk Insert를 각각 테스트 하는 방식으로 진행하였다.

for문 save()

Bulk Insert

중요한 부분은 http_req_duration 부분으로 요청에 걸린 총 시간을 의미하며 avg 부분이 평균 값이다.

for문 save() -> 37.34ms
Bulk Insert -> 26.07ms
성능이 평균적으로 1.43배 증가하였다.

결론

기존 코드에서 발생하던 불필요한 INSERT 문을 Bulk Insert로 개선함으로써 성능을 향상 시킬 수 있었다. JdbcTemplate의 batchUpdate 메서드를 사용하여 여러 개의 데이터를 한 번에 삽입하는 방식은 대량의 데이터를 처리할 때 매우 유용하며, 쿼리 실행 횟수를 줄여 성능을 최적화하는 데 중요한 역할을 한다.

profile
since 2022

0개의 댓글