728x90
반응형
✨ 들어가며
최근 진행 중인 프로젝트에서 학교 학사일정 데이터를 NEIS API에서 호출해 DB에 저장하는 기능을 만들고 있었습니다.
처음엔 단순하게 for 루프 안에서 save()를 호출하는 방식으로 구현했는데요,
데이터 양이 많아질수록 성능이 뚝뚝 떨어지는 현상이 생기기 시작했습니다.
이 문제를 해결하기 위해 save() 대신 saveAll()을 사용해 벌크 저장 방식으로 리팩토링해봤습니다.
이 글에서는 그 리팩토링 과정을 공유하려고 합니다!
🧨 기존 코드의 문제점
for (SchoolCalendarResponse response : calendarResponses.getSchoolCalendarResponses()) {
for (EventDetailsResponse event : response.getEvents()) {
SchoolCalendar calendar = SchoolCalendar.builder()
.school(school)
.content(event.getContent())
.date(response.getDate())
.oneGradeEventYN(event.isOneGradeEventYN())
.twoGradeEventYN(event.isTwoGradeEventYN())
.threeGradeEventYN(event.isThreeGradeEventYN())
.fourGradeEventYN(event.isFourGradeEventYN())
.fiveGradeEventYN(event.isFiveGradeEventYN())
.sixGradeEventYN(event.isSixGradeEventYN())
.eventName(event.getEventName())
.build();
schoolCalendarRepository.save(calendar);
}
}
이 코드는 이벤트 수만큼 save()가 반복 호출되면서
- 매번 DB와 통신
- 성능 저하
- 트랜잭션 시간 증가
결과적으로 불필요한 DB round-trip이 생겨 성능이 매우 비효율적이었습니다.
🚀 saveAll()을 사용한 개선
SchoolCalendar 객체들을 한 번에 리스트에 담아 saveAll()로 저장하면
- DB 접근 횟수 최소화
- 트랜잭션 시간 단축
- 성능 개선
private void saveOrUpdateSchoolCalendars(School school, TotalSchoolCalendarResponse calendarResponses) {
List<SchoolCalendar> calendarsToSave = calendarResponses.getSchoolCalendarResponses().stream()
.flatMap(response -> response.getEvents().stream()
.map(event -> SchoolCalendar.builder()
.school(school)
.content(event.getContent())
.date(response.getDate())
.oneGradeEventYN(event.isOneGradeEventYN())
.twoGradeEventYN(event.isTwoGradeEventYN())
.threeGradeEventYN(event.isThreeGradeEventYN())
.fourGradeEventYN(event.isFourGradeEventYN())
.fiveGradeEventYN(event.isFiveGradeEventYN())
.sixGradeEventYN(event.isSixGradeEventYN())
.eventName(event.getEventName())
.build()))
.collect(Collectors.toList());
schoolCalendarRepository.saveAll(calendarsToSave);
}
📊 효과는?
Neis Open API를 통해 1년치 학사일정 데이터를 받아와 저장하는 과정에서,
API 응답 파싱 이후 DB 저장 처리 시간 기준으로 비교해본 결과입니다.
- 기존 방식 (save() 반복 호출): 약 72ms
- 개선 방식 (saveAll() 벌크 저장): 약 62ms
동일한 조건에서 단순히 저장 방식만 변경했을 뿐인데도,
저장 처리 시간이 약간 감소하며 보다 안정적으로 동작하는 것을 확인할 수 있었습니다
또한 벌크 저장 방식 도입을 통해 향후 대용량 데이터 처리 시 병목 현상 방지에도 효과를 기대할 수 있습니다.
✅ 정리하며
- save() 반복 호출은 소량의 데이터엔 문제 없지만, 대량의 데이터에는 성능 병목이 생기기 쉬움
- saveAll()은 벌크 저장으로 DB 접근을 줄이고 성능을 높이는 방법
💬 마무리 한마디
작지만 뿌듯한 개선이었어요!
이제는 루프 안에 무의식적으로 save() 쓰는 거... 잠깐 멈추고 생각해보기🧠!
728x90
반응형
'프로젝트 > Wedle' 카테고리의 다른 글
[자주 조회되는 데이터 Redis 캐싱] – 학사 캘린더 캐싱 (1) | 2025.04.09 |
---|---|
[JPA 성능 최적화] @ManyToOne(fetch = FetchType.LAZY) 와 Fetch Join 활용하기 (0) | 2025.04.08 |
[Spring Boot/Kafka/Stomp] 실시간 채팅 구현 – 7. 채팅 전송 및 조회 (0) | 2025.03.19 |
[Spring Boot/Kafka/Stomp] 실시간 채팅 구현 - 6. 채팅방 생성 (0) | 2025.03.19 |
[Spring Boot/Kafka/Stomp] 실시간 채팅 구현 – 5. Redis와 Kafka를 활용한 실시간 채팅 시스템 구현: WebSocket 기반의 고성능 메시징 (1) | 2025.03.19 |