대규모 데이터 처리 성능을 최적화하기 위해 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());
}
}
이 방식은 설정이 많아질수록 성능이 급격히 저하되는 문제가 있었습니다. 특히, 대량의 데이터를 처리할 때는 배치 업데이트가 필요하다는 판단을 했습니다.
FOREACH
와 separator=";"
사용 (실패)처음에는 MyBatis XML 매퍼에서 FOREACH
구문을 사용하여 separator=";"
를 추가하고, 여러 UPDATE
문을 한 번에 실행하려 했습니다.
<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
문을 한꺼번에 실행하는 것이 허용되지 않았습니다.
SqlSession
과 ExecutorType.BATCH
사용XML 매퍼 방식이 실패한 후, Java 코드에서 MyBatis ExecutorType.BATCH
옵션을 설정하여 배치 업데이트를 시도했습니다. 이 방식은 SqlSession
을 ExecutorType.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
설정만으로 배치를 구현할 수 있었습니다.