Skip to content

Conversation

@ParkJunGyu26
Copy link
Contributor

@ParkJunGyu26 ParkJunGyu26 commented Sep 27, 2025

PR 본문 (Body)

수정 사유 (Reason for modification)

  • 버그수정 (Bug fixes)
  • 기능개선 (Enhancements)
  • 기능추가 (Adding features)
  • 기타 (Others)

기존 FileManageDAO의 다중 파일 처리 메서드(insertFileInfs, updateFileInfs, deleteFileInfs)들은 모두 내부적으로 반복문(Loop)을 사용해 파일 건수만큼 개별적인 CUD 쿼리를 호출했습니다. 이로 인해 처리할 파일이 많아질수록 N+1 쿼리 문제가 발생하여, 불필요한 DB 커넥션 및 오버헤드로 성능이 심각하게 저하되었습니다.

특히, 기존 updateFileInfs는 실제 '수정' 기능이 아닌 '추가' 기능으로 동작하여 기존 데이터를 수정할 수 없는 기능적 한계도 있었습니다.

이번 개선은 MyBatis의 <foreach>를 활용한 Bulk 연산을 도입하여, 단 한 번의 쿼리로 여러 데이터를 동시에 처리함으로써 위 문제들을 모두 해결했습니다.


수정된 소스 내용 (Modified source)

1. FileManageDAO.java

  • insertFileInfs, updateFileInfs, deleteFileInfs 메서드의 내부 로직을 반복문 방식에서 Bulk 쿼리를 호출하는 방식으로 변경했습니다.
  • 외부로 노출되는 메서드 시그니처(List<?> fileList)는 그대로 유지하여 기존 서비스와의 호환성을 보장했습니다.

2. Mapper XMLs (EgovFile_SQL_*.xml)

  • Bulk Insert, Bulk Upsert(ON DUPLICATE KEY UPDATE), **Bulk Delete(WHERE ... IN (...))**를 위한 쿼리 3개를 새로 추가했습니다.
  • 전자정부 표준프레임워크가 지원하는 6개 DBMS(mysql, altibase, cubrid, hsql, oracle, tibero)의 Mapper XML에 모두 동일하게 적용하여 DB 호환성을 유지했습니다.

3. FileManageDAOBulkPerformanceTest.java

  • 개선 효과를 명확히 증명하기 위해, 기존 Loop 방식과 개선된 Bulk 방식의 성능을 정량적으로 비교하는 JUnit 테스트 코드를 추가했습니다.
  • Insert, Update, Delete 각 시나리오별로 데이터 건수를 늘려가며 실행 시간을 측정하여 개선 효과를 직관적으로 보여줍니다.

4. 테스트 환경 구성

  • 리뷰어가 성능 테스트를 쉽게 재현하고 검증할 수 있도록 pom.xmlp6spy 의존성을 추가하고, src/test/resources 경로에 application.properties, schema.sql 등 테스트 실행에 필요한 환경 설정 파일들을 추가했습니다.

JUnit 테스트 (JUnit tests)

  • JUnit 테스트 (JUnit tests)
  • 수동 테스트 (Manual testing)

성능 테스트 결과 📈

아래는 FileManageDAOBulkPerformanceTest 실행을 통해 측정한 성능 비교 결과입니다. 모든 CUD(등록, 수정, 삭제) 작업에서 데이터가 증가할수록 개선 효과가 극명하게 나타났으며, 10만 건 처리 시 수정 작업은 약 19.5배, 삭제는 약 18.8배의 압도적인 성능 향상을 확인했습니다.

[성능 비교] 다중 파일 등록 (Insert)

데이터 수 Loop 방식 (ms) Bulk 방식 (ms) 개선율
100건 112 25 4.5배
1,000건 474 63 7.5배
10,000건 3,176 283 11.2배
100,000건 32,967 2,297 14.4배

[성능 비교] 다중 파일 수정 (Update)

데이터 수 Loop 방식 (ms) Bulk 방식 (ms) 개선율
100건 121 3 40.3배
1,000건 636 31 20.5배
10,000건 6,644 343 19.4배
100,000건 67,090 3,441 19.5배

[성능 비교] 다중 파일 삭제 (Delete)

데이터 수 Loop 방식 (ms) Bulk 방식 (ms) 개선율
100건 52 2 26.0배
1,000건 333 13 25.6배
10,000건 3,093 206 15.0배
100,000건 33,021 1,759 18.8배

테스트 환경 정보 (Test Environment Details)

리뷰어가 결과를 재현할 수 있도록 테스트 환경 정보를 공유합니다.

로컬 테스트 DB 환경 (Docker)

성능 테스트는 Docker를 사용하여 임시 MySQL 8.0 데이터베이스 환경을 구축하여 진행했습니다.

docker run --name egov-mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=egovtest -d -p 3306:3306 mysql:8.0

테스트 스키마 (schema.sql)

-- 테스트 실행 전, 기존 테이블이 있다면 삭제하여 환경을 초기화합니다.
DROP TABLE IF EXISTS LETTNFILEDETAIL;
DROP TABLE IF EXISTS LETTNFILE;

-- 파일 마스터 테이블을 생성합니다.
CREATE TABLE LETTNFILE (
   ATCH_FILE_ID VARCHAR(20) NOT NULL PRIMARY KEY,
   CREAT_DT DATETIME,
   USE_AT CHAR(1)
);

-- 파일 상세 테이블을 생성합니다.
CREATE TABLE LETTNFILEDETAIL (
   ATCH_FILE_ID VARCHAR(20) NOT NULL,
   FILE_SN DECIMAL(10,0) NOT NULL,
   FILE_STRE_COURS VARCHAR(2000) NOT NULL,
   STRE_FILE_NM VARCHAR(255) NOT NULL,
   ORIGNL_FILE_NM VARCHAR(255),
   FILE_EXTSN VARCHAR(20) NOT NULL,
   FILE_CN LONGTEXT,
   FILE_SIZE DECIMAL(8,0),
   PRIMARY KEY (ATCH_FILE_ID, FILE_SN)
);

테스트 설정 (application.properties)

# Mybatis가 mysql용 매퍼 파일을 읽도록 Globals.DbType을 mysql로 설정
Globals.DbType=mysql

# p6spy를 사용하도록 URL과 DriverClassName을 수정
Globals.mysql.DriverClassName=com.p6spy.engine.spy.P6SpyDriver
Globals.mysql.Url=jdbc:p6spy:mysql://127.0.0.1:3306/egovtest?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
Globals.mysql.UserName=root
Globals.mysql.Password=password

# Mybatis 매퍼 XML 파일의 위치 지정
mybatis.mapper-locations=classpath*:egovframework/mapper/**/*_${Globals.DbType}.xml

# 테스트 시작 시 항상 schema.sql 파일을 실행하여 테이블을 생성
spring.sql.init.mode=always
spring.sql.init.platform=mysql
# ... (기타 Globals 프로퍼티)

테스트 브라우저 (Test Browser)

이번 수정은 백엔드 로직 개선에 해당하므로 브라우저 테스트는 진행하지 않았습니다.

  • Chrome
  • Firefox
  • Edge
  • Safari
  • Opera
  • Internet Explorer
  • 기타 Others

테스트 스크린샷 또는 캡처 영상 (Test screenshots or captured video)

image

기존의 다중 파일 삭제 기능은 반복문(while)을 통해 파일 건수만큼 DELETE 쿼리를 실행하여 N+1 문제를 유발했습니다.

이로 인해 삭제할 파일이 많아질수록 DB 부하가 급증하고 성능이 저하되는 문제가 있었습니다.

MyBatis의 foreach를 활용한 Bulk DELETE 쿼리를 새로 추가하여, 단 한 번의 쿼리로 여러 개의 파일을 동시에 삭제하도록 개선했습니다.

성능 비교 테스트 결과, 데이터 삭제 시 약 10배 이상의 성능 향상을 확인했습니다.
@ParkJunGyu26 ParkJunGyu26 changed the title refactor(FileManageDAO): N+1 문제를 해결하여 다중 파일 삭제 성능 개선 refactor(FileManageDAO): 다중 파일 삭제 시 N+1 문제 해결로 성능 개선 Sep 28, 2025
@ParkJunGyu26 ParkJunGyu26 changed the title refactor(FileManageDAO): 다중 파일 삭제 시 N+1 문제 해결로 성능 개선 refactor(cmm/fms): 파일 처리(Write - CUD) 시 N+1 문제 해결을 위한 Bulk 연산 적용 Sep 28, 2025
기존 FileManageDAO의 다중 파일 처리(Insert, Update, Delete) 메서드는 내부적으로 Loop를 순회하며 N+1 쿼리 문제를 유발했습니다.

이를 MyBatis의 <foreach>를 사용한 Bulk 연산으로 변경하여 DB 호출을 최소화하고 성능을 대폭 향상시켰습니다.
FileManageDAOBulkPerformanceTest를 재현 가능하도록 테스트 환경을 구성합니다.

- pom.xml: p6spy 테스트 의존성 추가
- FileManageDAOBulkPerformanceTest.java: Loop 방식과 Bulk 방식의 성능을 비교하는 테스트 코드 추가
- application.properties, schema.sql: 테스트용 DB 및 테이블 자동 생성 설정
- logback-spring.xml, spy.properties: 테스트 실행 시 로그를 깔끔하게 제어하기 위한 설정
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant