성능테스트를 진행하면서 대용량 데이터를 안전하게 처리할 수 있는 방법, 유량제어를 할 수 있는 방법을 계속 고민해보았다.
이 중 원리가 가장 기본적이고 유용하게 적용이 가능할 것으로 보이는 방안을 찾게 되어 기록한다.
유량 제어 : <배치처리/대용량 데이터 다건 처리>
-> 배치처리 및 대용량 데이터 다건 처리 시, 한번에 bulk처리 혹은 여러 번의 단건 처리를 일괄적으로 진행해야할 경우 하나의 connection pool을 사용하므로 메모리 관점에서도 문제가 많고 대용량 데이터를 처리해야할 경우 그 자체로 시간 소요/부하 등 여러 문제가 발생할 수 있다.
-> 이때 할 수 있는 유량제어방안 중 하나인 처리 size를 나누어 여러번 하는 방법이 있다.
-> 배치처리에서 chunk와 비슷하지만, 위 방법의 경우 모든 데이터 size를 처리할 때 까지 하나의 트랜잭션으로 간주한다는 점, 병렬처리가 아니라는 점, 단일 트랜잭션이면서 분할처리가 가능하다는 점에서 의미가 있다.
JPA에서도 해당 기능을 응용한다면 안전한 처리를 구현할 수 있을 것이다.
<foreach collection = “list” item = “item” separator “,”>(다건처리가능 : 등록)
or <foreach collection = “list” item = “item” separator “;”>(다건처리불가 : 수정)
등을 사용해서 적절한 동적 쿼리를 사용하여 처리하는 로직을 구현한다.INSERT INTO TABLE ( A , B , C , ..) 이처럼 INSERT는 내부적으로 A, B, C 전산 다건 처리가 모두 가능하므로
INSERT INTO ~~~
<foreach collection ="list" item = "item" seperator = ",">
(
#{item.gdsCd, jdbcType = CHAR},
#{item.coNm, jdbcType = CHAR},
....
)
</foreach>
위와 같은 동적쿼리 구성이 가능하며
UPDATE의 경우 다건처리가 불가능하므로, 여러 개의 단건처리를 반복 순회하여 처리하는 방법으로 동적 쿼리를 구성할 수 있다.
<foreach colection = list" item = "item" seperator = ";" open = "DECLARE BEGIN" close = ";" END ;"
UPDATE TABLE
<set>
<if test = "item.gdsCD != null">
GDS_CD = #{item.jdbcType = CHAR}
</if>
</set>
</foreach>
아직 정리해야할 부분이 많으나 일단 기록함
public void doBondKndCdFileList(List<String> fileList){
List<DTbBondKndCdEntity> entityFileList = new ArrayList<>();
ITM00001DTO itm00001DTO = new ITM00001DTO();
//처리해야 할 데이터 리스트
for(String line : fileList){
item00001DTO.setCoscomBondKndCd(substringByByte(line, 2, 4));
item00001DTO.setBondKndNm(substringByByte(line, 6, 30));
item00001DTO.setEtcCn(substringByByte(line, 36, 155));
DTbBondKndCdEntity dTbBondKndCdEntity =
TbBondKndCdMapper.INSTANCE.toDTbBondKndCdEntity(item00001DTO);
entityFileList.add(dTbBondKndCdEntity);
}
//데이터 리스트를 size만큼 분할(=100 = BATCH_SIZE)
List<DTbBondKndCdEntity> selectList = new ArrayList<>();
for(int i = 0 ; i < entryFileList.size() ; i + = BATCH_SIZE){
//BATCH_SIZE만큼 처리하다가 끝 부분은 entityFileList.size()로 처리
int end = Math.min(i + BATCH_SIZE, entityFileList.size());
List<DTbBondKndCdEntity> batchList = entityFileList.subList(i, end); //분할처리할 리스트를 여기서 나눈다. (i는 계속 증가하고 이 만큼 분할하여 batchlist에 보관한다는 점이 핵심)
List<DTbBondKndCdEntity> resultBatch = ditm00001.selectBondKndCdList(batchList);
selectList.addAll(resultBatch); //selectList에는 분할 처리 size만큼 나뉘어진 데이터리스트들이 저장됨
}
List<DTbBondKndCdEntity> updateList = new ArrayList<>();
List<DTbBondKndCdEntity> insertList = new ArrayList<>();
for(DTbBondKndCdEntity fileItem : entityFileList){
boolean found = false;
for(DTbBondKndCdEntity selectItem : selectList){
selectItem.getCoscomBondKndCd().equals(fileItem.getCoscomBondKndCd())){
found = true;
break; //break를 할 경우 for문에서 탈출
}
}
if(found) {
updateList.add(fileItem);
}else {
insertList.add(fileItem);
}
}
//최종적으로 분할 처리 진행 함
for(int i = 0 ; i < updateList.size() ; i += BATCH_SIZE){
int end = Math.min(i + BATCH_SIZE, updateList.size());
ditem00001.update03(new ArrayList<>(updateList.subList(i, end)));
}
//최종적으로 분할 처리 진행 함
for(int i = 0 ; i < insertList.size() ; i += BATCH_SIZE){
int end = Math.min(i + BATCH_SIZE, insertList.size());
ditem00001.insert03(new ArrayList<>(insertList.subList(i, end)));
}
}