티스토리 뷰

대량 데이터 삽입 로직

데이터 입력시 보통 단건 또는 10건 미만으로 입력이 들어오기 때문에 입력되는 ROW수 만큼 INSERT쿼리 호출이 이루어진다. 그러나 1만 8천 건 정도 되는 데이터를 한 번에 INSERT 하니, 수행 시간이 5분 이상 소요되었고 이대로는 사용할 수 없다고 판단했다.(오류는 나지 않더라도 예외 메세지가 뜨면서 통신이 끊어진것 처럼 보였다.)

 

일반적으로 사용했던 INSERT 로직 (대용량 INSERT에 부적합)

사용자가 이용할 수 없는 속도를 보여줬기 때문에 삽입하는 ROW 수만큼 쿼리를 호출해 수행하는 기존 INSERT 로직을 이용할 순 없었다.  나중에 들어보니 AS-IS(기존 시스템)에선 10분넘게 걸렸었다고한다...

//paramList : 화면단에서 넘겨받은 Data
for (Map<String, Object> paramMap : paramList) { 
    dao.insert();
    //쿼리호출 : INSERT INTO TABLE (...) VALUES (...) x row 수 만큼 수행
}

 

UNION ALL을 이용한 INSERT

INSERT INTO [TABLE] (
    COL1, COL2, COL3
)
SELECT 'VALUE1', 'VALUE2', 'VALUE3' FROM DUAL UNION ALL
SELECT 'VALUE1', 'VALUE2', 'VALUE3' FROM DUAL UNION ALL
SELECT 'VALUE1', 'VALUE2', 'VALUE3' FROM DUAL UNION ALL
...
SELECT 'VALUE1', 'VALUE2', 'VALUE3' FROM DUAL

SELECT문과 UNION ALL을 활용하면 INSERT 쿼리 한 번에 다건의 ROW를 삽입하면서 속도 향상을 기대해 볼 수 있었기에 해당 쿼리를 사용해 하나의 요청마다 200건씩 INSERT를 수행하기로 헀다. 여기에 사용될 SELECT문은 Service단에서 만들어서 dao 쿼리 호출 시에 인자로 전달한다.

 

*INSERT ALL 쿼리도 있었지만 UNION ALL을 사용하는 방법이 속도면에서 더 빠르다기에 이 방법을 선택했다.

Mybatis xml

 <insert id="insertQueryId" parameterType="java.util.HashMap">
    INSERT INTO [TABLE] (
        COL1, COL2, COL3
    )
    ${QUERY}
</insert>

SELECT 문에 작은 따옴표('VALUE')를 사용해야 했기 때문에 파라미터 사용 시 #{} 대신 ${}을 선택했다.  ${}을 사용하면 SQL Injection 공격에 취약해질 수 있지만, 이 경우 데이터가 다수의 사용자에 의해 입력/수정/삭제되지 않으며, 한정된 사용자(1~2명)의 파일 업로드를 통해 생성되므로 보안적인 측면에서는 상대적으로 낮은 위험성을 가진다고 판단했다.

 

 

Java - Service단

// Save 로직 일부

List<Map<String, Object>> dataListSlice = new ArrayList<>();

for (int i = 0; i < paramList.size(); i++) {
    Map<String, Object> element = paramList.get(i);
    dataListSlice.add(element);
    
    // 200개씩 끊어서 입력
    if ((i + 1) % 200 == 0 || i == paramList.size() - 1) {
        Map<String, Object> insertParams = new HashMap<>();
        String insertQuery = generateInsertQuery(dataListSlice);
        insertParams.put("QUERY", insertQuery);
        dao.insert("namespace.insertQueryId", insertParams);
        dataListSlice.clear();
    }
}
String generateInsertQuery(List<Map<String, Object>> data) {
    StringBuilder queryBuilder = new StringBuilder();
    String insertValueList;
    
    for (int i = 0; i < data.size(); i++) {
        Map<String, Object> dataMap = data.get(i);
        insertValueList = "'" + dataMap.get("COL1") + "'" +
                          ", '" + dataMap.get("COL2") + "'" +
                          ", '" + dataMap.get("COL3") + "'";

        if (i == data.size() - 1) {
            queryBuilder.append("SELECT " + insertValueList + " FROM DUAL");
        } else {
            queryBuilder.append("SELECT " + insertValueList + " FROM DUAL UNION ALL\n");
        }
    }
    return queryBuilder.toString();
}

${QUERY} 파라미터에 전달할 값(SELECT ... FROM DAUL UNION ALL)을 Java단에서 만들어주는 로직이다. 

입력된 데이터를 200개씩 끊어서 INSERT 쿼리를 호출하니 18000건 정도의 데이터를 입력하는데 5분 → 30초 정도로 시간이 단축됐다.

 

 

대량 데이터를 INSERT 하는 방법2

INSERT 처리속도를 의미있게 향상시켰지만 ${} 태그를 사용하여 개발한 것이 만족스럽진 않았다. 그러나 Dao단을 추가하여 작업할 정도로 많은 시간을 투자할만한 화면이 아니었고 ${} 태그를 사용하는 데 있어 상대적으로 덜 위험한 환경이라 판단했기 때문에 ${} 태그를 이용했다.

 

[추가] Java단에서 #{COL1_1} ~ #{COL1_200} 까지 인자만 생성해주고 xml에 200개의 SELECT UNION ALL을 작성해주면 ${} 태그를 사용하지 않아도 된다.

 

조금 더 정석적인 방법으로는 다음과 같다. 

 

1. Map을 대체할 객체 생성 : Map의 KEY 항목에 해당하는 필드를 가지는 객체 생성

public class Data {
    private String COL1;
    private String COL2;
    private String COL3;
    
    // getter and setter 
    
    // 생성자
}

 

2. 데이터 처리 : Data 항목에 컬럼들을 할당하고 List<Data>에 해당 Data를 add

List<Data> dataList = new ArrayList<>();

for (Map<String, Object> paramMap : paramList) {
    Data data = new Data();
    data.setCOL1((String) paramMap.get("COL1"));
    // ... 다른 필드들 설정
    
    dataList.add(data);
}

 

3. DAO 매서드 수정 : DAO 메서드의 파라미터를 List<Data> 형태로 수정하여 전달

public int insert_NEW(List<Data> dataList) {
    // ...
}

 

4. Mybatis XML 수정 : Mybatis XML 상에선 foreach 태그를 이용해 다중 INSERT

<insert id="insertQueryId" parameterType="java.util.List">
    INSERT INTO [테이블] (
        COL1, COL2, COL3
    )
    VALUES
    <foreach collection="list" item="item" separator=",">
        (
            #{item.COL1}, #{item.COL2}, #{item.COL3}
        )
    </foreach>
</insert>

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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 31
글 보관함