기존에는 각각의 데이터 중 30일이 지난 데이터를 dirty checking 하여 status를 update했음
<update id="updateOutOfDateSchedule">
UPDATE "Sbom_History"
SET status = 'exp'
WHERE DATE(created_at) < CURRENT_DATE - INTERVAL '30 days';
</update>
실제로 단건의 dirty checking이었으므로 DB에 반영하는 매 건마다 커넥션을 가져오고, Commit을 하는 과정에서 비용이 발생하여 성능적으로 문제가 발생할 것이 우려됐다.
앞으로 많은 양의 sbom 발급본이 저장될 경우를 대비하여 개선이 필요하다.
기존에 사용하는 JPA가 아닌, MyBatis에서 Bulk 연산을 시도한다.
foreach 태그
를 사용하여 리스트나 배열의 각 아이템에 대한 업데이트 쿼리를 동적으로 생성
foreach 태그
@Transactional
을 활용해서 bulk update 작업을 하나의 트랜잭션으로 관리함으로써, 모든 업데이트가 성공적으로 수행되거나, 하나라도 실패할 경우 전체가 롤백되어 데이터의 일관성을 유지하도록 변경한다.
최종 변경 코드
@Transactional
@Scheduled(cron = SCHEDULE)
public void sbomOutOfDateScheduling() {
List<sbomHistoryUpdateResponse> sbomHistoryUpdateList = sbomMapper.getSbomHistoryUpdateList();
if(!sbomHistoryUpdateList.isEmpty()) {
sbomMapper.updateOutOfDateSchedule(sbomHistoryUpdateList);
}
}
<select id="getSbomHistoryUpdateList" resultType = "com.sga.sbomProject.domain.sbom.dto.sbomHistoryUpdateResponse">
<![CDATA[
SELECT id
FROM "Sbom_History"
WHERE created_at::date < CURRENT_DATE - INTERVAL '30 days'
]]>
</select>
<update id="updateOutOfDateSchedule" parameterType="list">
<![CDATA[
UPDATE "Sbom_History"
SET status = 'exp'
WHERE id IN
<foreach collection="sbomHistoryUpdateList" item="item" open="(" separator="," close=")">
#{item}
</foreach>
]]>
</update>
DO $$
BEGIN
FOR i IN 1..1000000 LOOP
INSERT INTO "Scan_Target_Info" (target_type, target_uploaded_at)
VALUES('Solution', '2024-08-05 11:21:02.206909');
END LOOP;
END $$;
DO $$
BEGIN
FOR i IN 1..100000 LOOP
INSERT INTO "Sbom_History" (scan_target_id, request_user_name, description, type, status, name, path, created_at, finished_at, created_by )
VALUES (i+2, 'test', 'test', 'SPDX','ava','test_name', 'test_path', NOW() - (INTERVAL '1 day' * (30 + i)),NOW() - (INTERVAL '1 day' * (30 + i)), 1 );
END LOOP;
END $$;