일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 서버사이드렌더링
- gitbash
- github markdown
- 카우치코딩 #couchcoding #6주포트폴리오 #6주협업프로젝트
- Anaconda
- 자바파이썬
- PID
- taskkill
- SSR
- 카우치코딩 #couchcoding #6주포트폴리오 #6주협업프로젝트v
- 파이썬
- Technical Writing
- 필사
- Kakao
- 클라이언트사이드렌더링
- khaiii
- 파이콘
- 비동기
- #스파르타코딩클럽후기 #내일배움캠프후기
- 코딩온라인
- 마크다운
- address
- 플젝후체크
- github
- 모바일웹스킨
- Morphological analysis #Corpus
- terminate
- 출처: 자바의 신 8장
- expression statement is not assignment or call html
- Machine Learning
- Today
- Total
개발 일기
Insert가 많을 때 소요 시간 개선하기 - (1) myBatis forEach 적용 본문
문제
판매자의 제품 등록 기능에서 Insert 쿼리가 다수 발생한다는 것을 발견했습니다.
Disk I/O는 웹 서비스 성능에 많은 영향을 미치는 중요 모니터링 지표입니다. 디스크의 데이터 처리 속도가 메모리나 CPU에 비해 너무 느리기 때문입니다. DISK I/O를 줄이는 것은 DB 성능 개선의 핵심 이기도 합니다.
이번 글에서는 쿼리를 여러번 날렸을 때, 나타나는 지연 현상을 개선 해보겠습니다.
개요
네이버 쇼핑, 배달의 민족 등 평소 이용하는 이커머스 서비스를 보면, 소비자가 다양한 옵션을 구성할 수 있도록 제품이 제공됩니다. 이를 충족하기 위해, 판매자가 제품을 등록할 때, 다양한 옵션 그룹과 옵션을 설정할 수 있어야 합니다.
제품 등록 POST 요청 값의 예시입니다.
제품(샐러드) 에 옵션이 있을 경우 optionSet에 옵션 그룹을 등록하는 DTO와 이에 속하는 옵션 DTO 가 리스트 형태로 들어갑니다.
{
"name": "샐러드",
"price": 2000,
"status": "AVAILABLE",
"description": "맛나요",
"singleType": false,
"categoryId": 1,
"optionSet": [
{
"optionGroupRegister": {
"optionGroupName": "중량",
"requiredOption": true,
"exclusive": true,
"minimumOrder": 1,
"maximumOrder": 1
},
"optionDetailRegisterList": [
{
"name": "30g",
"price": 0
},
{
"name": "60g",
"price": 2000
}
]
},
{
"optionGroupRegister": {
"optionGroupName": "토핑",
"requiredOption": false,
"exclusive": false,
"minimumOrder": 1,
"maximumOrder": 2
},
"optionDetailRegisterList": [
{
"name": "견과류",
"price": 1000
},
{
"name": "크랜베리",
"price": 500
}
]
}
]
}
참고 - DB 스키마
아래 스키마에서 참고하실 수 있듯, 옵션 그룹은 제품 Id를 외래키로 참조하고,
옵션은 옵션 그룹 Id를 외래키로 참조하는 상황입니다.
소개
위의 테이블 관계에서 볼 수 있듯, 제품 - 옵션 그룹 (ex. 맛) - 옵션 (ex. 딸기맛, 포도맛 )은 일대 다 관계 입니다.
옵션 그룹을 저장하려면 제품을 포함해야 하고, 옵션을 저장하려면 옵션 그룹 ID 가 필요합니다. .
결과적으로, 옵션 그룹이 N개 이고 옵션이 M개 일 경우 N+N*M , 즉 N(1+M)개의 쿼리가 추가로 발생하는 것을 알 수 있습니다.
Elastic APM으로 모니터링 시, 아래와 같이 N(1+M)개 쿼리가 날아감을 알 수 있습니다.
ProductService
기존 코드
public void addProduct(LoginUser user, ProductRegisterRequest request) {
Product product = request.toProduct(user);
productRepository.save(product);
if (request.getOptionSet() != null) {
for (OptionSet optionSet : request.getOptionSet()) {
OptionGroupRegister optionGroupRegister = optionSet.getOptionGroupRegister();
OptionGroup optionGroup = optionGroupRegister.toOptionGroup(user, product);
optionGroupRepository.save(optionGroup);
List<OptionDetailRegister> optionDetailRegisterList = optionSet.getOptionDetailRegisterList();
List<Option> options = optionSet.toOptions(optionDetailRegisterList, optionGroup);
for(Option option: options){
optionRepository.save(option);
}
}
}
}
변경
*변경 코드는 마지막 for 문을 돌지 않고, list를 한번에 저장합니다. 이외에는 동일합니다.
public void addProduct(LoginUser user, ProductRegisterRequest request) {
Product product = request.toProduct(user);
productRepository.save(product);
if (request.getOptionSet() != null) {
for (OptionSet optionSet : request.getOptionSet()) {
OptionGroupRegister optionGroupRegister = optionSet.getOptionGroupRegister();
OptionGroup optionGroup = optionGroupRegister.toOptionGroup(user, product);
optionGroupRepository.save(optionGroup);
List<OptionDetailRegister> optionDetailRegisterList = optionSet.getOptionDetailRegisterList();
List<Option> options = optionSet.toOptions(optionDetailRegisterList, optionGroup);
optionRepository.save(options);
}
}
}
OptionMapper.xml
기존
<insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into `option` (name, price, option_group_id, seller_id)
values (#{name}, #{price}, #{optionGroupId}, #{sellerId});
</insert>
변경
-option은 mysql 예약어기 때문에 `` 로 닫아 줍니다.
-여러 개의 옵션을 list 에 담아 전달하므로, collection = "list"로 지정합니다. item은 전달받은 인자 값을 지칭할 이름입니다.
list 내에는 , 구분자가 사용되므로, separator 에는 ","를 넣어줍니다.
<insert id="insert" keyProperty="id" parameterType="java.util.List" useGeneratedKeys="true">
insert into `option` (name, price, option_group_id, seller_id)
values
<foreach collection="list" index="index" item="option" separator=",">
(#{option.name}, #{option.price}, #{option.optionGroupId}, #{option.sellerId})
</foreach>
</insert>
결론
forEach를 적용해서 옵션의 개수 만큼 날아가는 쿼리를 한 개로 줄일 수 있었습니다.
기존에는 옵션 그룹이 N개 이고 옵션이 M개 일 경우 N+N*M , 즉 N(1+M)개의 쿼리가 추가로 발생하던 상황이었습니다.
forEach를 활용해 collection 형태로 insert 함으로써, N*M -> N으로 쿼리 개수를 줄여, 현재는 총 2N개의 쿼리가 발생합니다.
참고
https://www.whatap.io/ko/blog/41/
'Tech > 배워서 남주기' 카테고리의 다른 글
Redis Clustering 를 통해 FailOver 구현하기 (도커 컴포즈 환경) (0) | 2023.01.25 |
---|---|
[JPA/MYSQL] 재고에 동시 접근할 때 일어나는 갱신 분실 문제 해결하기 (0) | 2023.01.09 |
협업에 용이한 작업 환경 구성하기 (0) | 2022.11.30 |
Redis로 다중 서버 환경에서 로그인 정보 불일치 문제 해결하기 (0) | 2022.10.08 |
[객체 지향적 설계] 디자인 패턴과 레이어드 아키텍처 적용하기 (0) | 2022.09.29 |