MyBatis 배치 업데이트 최적화

YuJun Oh·2024년 11월 15일
0

대규모 데이터 처리 성능을 최적화하기 위해 MyBatis를 활용해 한 사용자의 여러 설정을 일괄 업데이트하는 방법을 시도했습니다. 초기에 단순한 반복 쿼리를 사용했으나, 배치 업데이트를 통해 성능을 높이려는 도전이 있었습니다. 다양한 시행착오를 겪으며 최종적으로 ExecutorType.BATCH를 사용해 배치를 구현한 과정을 소개합니다.


문제 상황

시나리오 예제

각 사용자는 다양한 설정을 가지고 있으며, 이 설정들은 주기적으로 변경됩니다. 기본 updateUserSettings 메서드에서는 설정 하나하나를 반복적으로 UPDATE하는 방식으로 구현되어 있었고, 데이터베이스 성능에 큰 영향을 미쳤습니다. 이를 최적화하기 위해 배치 업데이트 방식을 도입하기로 결정했습니다.

초기 설정 예제: 테이블 및 데이터

우선 예제 테이블 구조를 정의합니다.

CREATE TABLE user_settings (
    user_id VARCHAR(50) NOT NULL,
    setting_id VARCHAR(50) NOT NULL,
    setting_value VARCHAR(255),
    last_modified DATETIME,
    PRIMARY KEY (user_id, setting_id)
);

예제 데이터를 추가해 보겠습니다.

INSERT INTO user_settings (user_id, setting_id, setting_value, last_modified) VALUES
('user123', 'theme', 'dark', NOW()),
('user123', 'notifications', 'enabled', NOW()),
('user123', 'language', 'en', NOW());

초기 코드 구현: 단일 쿼리 반복 실행 방식

기존 방식에서는 설정 하나하나를 별도로 UPDATE하는 방식이었습니다. updateUserSetting 메서드는 setting_id에 따라 쿼리를 반복적으로 실행했기 때문에 성능 저하 문제가 발생했습니다.

@Transactional
public void updateUserSettings(List<SettingUpdateRequest> updates, String userId) {
    for (SettingUpdateRequest update : updates) {
        userSettingMapper.updateSetting(userId, update.getSettingId(), update.getSettingValue());
    }
}

이 방식은 설정이 많아질수록 성능이 급격히 저하되는 문제가 있었습니다. 특히, 대량의 데이터를 처리할 때는 배치 업데이트가 필요하다는 판단을 했습니다.


시도 1: MyBatis FOREACHseparator=";" 사용 (실패)

처음에는 MyBatis XML 매퍼에서 FOREACH 구문을 사용하여 separator=";"를 추가하고, 여러 UPDATE 문을 한 번에 실행하려 했습니다.

매퍼 XML 코드 예시

<update id="batchUpdateSettings" parameterType="list">
    <foreach collection="list" item="item" separator=";">
        UPDATE user_settings
        SET setting_value = #{item.settingValue},
            last_modified = NOW()
        WHERE user_id = #{item.userId}
          AND setting_id = #{item.settingId};
    </foreach>
</update>

하지만, 이 방식으로 실행했더니 SQL 구문 오류가 발생했습니다. MyBatis는 FOREACH 구문을 통해 세미콜론으로 구분된 여러 개의 UPDATE 문을 한 번에 실행하려 했는데, MySQL에서는 세미콜론으로 연결된 여러 개의 UPDATE 문을 한꺼번에 실행하는 것이 허용되지 않았습니다.


해결책: Java 코드에서 SqlSessionExecutorType.BATCH 사용

XML 매퍼 방식이 실패한 후, Java 코드에서 MyBatis ExecutorType.BATCH 옵션을 설정하여 배치 업데이트를 시도했습니다. 이 방식은 SqlSessionExecutorType.BATCH로 설정하여 열고, 쿼리를 쌓아 flushStatements() 메서드로 한꺼번에 실행하는 방식입니다.

최종 코드 예시

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

public class UserSettingService {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Transactional
    public void updateUserSettingsBatch(List<SettingUpdateRequest> updates, String userId) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
            UserSettingMapper mapper = sqlSession.getMapper(UserSettingMapper.class);

            // 각 설정을 배치로 쌓음
            for (SettingUpdateRequest update : updates) {
                mapper.updateSetting(userId, update.getSettingId(), update.getSettingValue());
            }

            // 모든 쿼리를 배치로 실행
            sqlSession.flushStatements();
        } catch (Exception e) {
            throw new RuntimeException("Batch update failed", e);
        }
    }
}

매퍼 인터페이스

public interface UserSettingMapper {
    void updateSetting(@Param("userId") String userId,
                       @Param("settingId") String settingId,
                       @Param("settingValue") String settingValue);
}

최종 결과

  • 성능 개선: 배치로 실행하여 데이터베이스에 불필요한 네트워크 왕복을 줄이고, 성능이 크게 개선되었습니다.
  • 코드 유지: 기존 updateSetting 메서드를 그대로 활용하면서, SqlSession 설정만으로 배치를 구현할 수 있었습니다.

0개의 댓글

관련 채용 정보