BACKEND/JAVA & SPRING

๋ฒŒํฌ์—ฐ์‚ฐ์„ ํ†ตํ•œ update ์„ฑ๋Šฅ ๊ฐœ์„  ์‹œ๋„ ๊ทธ๋ฆฌ๊ณ  ์ผ๋ฐ˜ ์—ฐ์‚ฐ๊ณผ์˜ ๋น„๊ต

์ด-ํ”„ 2024. 8. 7. 15:01

๐Ÿ’ก Issue

  • ๊ธฐ์กด์—๋Š” ๊ฐ๊ฐ์˜ ๋ฐ์ดํ„ฐ ์ค‘ 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 ๋ฐœ๊ธ‰๋ณธ์ด ์ €์žฅ๋  ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ ๊ฐœ์„ ์ด ํ•„์š”ํ•˜๋‹ค.

 

๐Ÿ’ก MyBatis์—์„œ Bulk ์—ฐ์‚ฐ

  • ๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋Š” JPA๊ฐ€ ์•„๋‹Œ, MyBatis์—์„œ Bulk ์—ฐ์‚ฐ์„ ์‹œ๋„ํ•œ๋‹ค.foreach ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ์ŠคํŠธ๋‚˜ ๋ฐฐ์—ด์˜ ๊ฐ ์•„์ดํ…œ์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ ์ฟผ๋ฆฌ๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑ
    • foreach ํƒœ๊ทธ
      • ์ „๋‹ฌ๋ฐ›์€ collection ์ธ์ž ๊ฐ’์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฐ˜๋ณต์ ์ธ SQL ๊ตฌ๋ฌธ์„ ์ž‘์„ฑ
        • collection : ์ „๋‹ฌ๋ฐ›์€ ์ธ์ž๋ฅผ ์†์„ฑ ๊ฐ’์œผ๋กœ ์‚ฝ์ž… (ex. Map, Array, List, Set ๋“ฑ)
        • item : collection ์†์„ฑ์—์„œ ์ „๋‹ฌ๋ฐ›์€ collection์˜ ์ธ์ž๊ฐ’์„ ๋Œ€์ฒดํ•  ‘์ด๋ฆ„’์„ ์‚ฝ์ž…
        • open & close : ๊ตฌ๋ฌธ์ด ์‹œ์ž‘/์ข…๋ฃŒ ๋ ๋•Œ ์‚ฝ์ž…ํ•  ๋ฌธ์ž์—ด
        • separator : ๋ฐ˜๋ณต๋œ๋А ๊ตฌ๋ฌธ ์‚ฌ์ด์— ์‚ฝ์ž…ํ•  ๋ฌธ์ž์—ด
        • index : index๊ฐ’์„ ๋ถ€๋ฅผ ๋ณ€์ˆ˜๋ช…์„ ์†์„ฑ๊ฐ’์œผ๋กœ ์‚ฝ์ž…
  • @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>
  • SELECT๋กœ, ์˜ค๋Š˜ ๊ธฐ์ค€ 30์ผ ์ด์ „์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ์ถ”์ถœ
  • ํ•ด๋‹น ์•„์ดํ…œ์ด ์กด์žฌํ•  ๊ฒฝ์šฐ, UPDATE๋ฅผ ์ง„ํ–‰ํ•˜๋Š”๋ฐ, ์ด๋•Œ foreach ๊ตฌ๋ฌธ์œผ๋กœ ํ•œ๋ฒˆ์— UPDATE๋ฅผ ์ง„ํ–‰

 

๐Ÿ’ก Quality Test

  • FK ์œ„๋ฐ˜์„ ๊ธˆํ•˜๊ธฐ ์œ„ํ•ด, ๋จผ์ € Scan_Target_Info์— ๋ฐ์ดํ„ฐ 10๋งŒ๊ฐœ ์ €์žฅ
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 $$;
  • ์ดํ›„, Sbom_History์— ์•ฝ 10๋งŒ๊ฐœ์˜ ๋ฐ์ดํ„ฐ ์ฃผ์ž… (30์ผ ์ด์ „์œผ๋กœ ์ฃผ์ž…ํ•œ๋‹ค.)
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 $$;

 

 

 

๐Ÿšจ ํ…Œ์ŠคํŠธ ๋„์ค‘ ๋ฐœ๊ฒฌํ•œ ๋ฌธ์ œ ๐Ÿšจ

PreparedStatement can have at most 65,535 parameters. Please consider using arrays, or splitting the query in several ones, or using COPY. Given query has 100,000 parameters
  • ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” postgreSQL์€ ํ•œ ์ฟผ๋ฆฌ์—์„œ ์ตœ๋Œ€ 65,535๊ฐœ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ง€์›ํ•œ๋‹ค...
    • ์ด ์ œํ•œ์€ PostgreSQL์˜ ๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์™€ ๊ด€๋ จ์žˆ์œผ๋ฏ€๋กœ ๋‹น์žฅ ๋ณ€๊ฒฝ์€ ์–ด๋ ค์šธ ๊ฒƒ ๊ฐ™์•˜๋‹ค.
    • 10000๊ฐœ์”ฉ 10๋ฒˆ ํ…Œ์ŠคํŠธ ํ•˜๋Š” ๊ฑธ๋กœ ์„œ๋น„์Šค ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ–ˆ๋‹ค
    	if(!sbomHistoryUpdateList.isEmpty()) {
    		int batchSize = 10000; // ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ID์˜ ๊ฐœ์ˆ˜
    	    for (int i = 0; i < sbomHistoryUpdateList.size(); i += batchSize) {
    	        int end = Math.min(sbomHistoryUpdateList.size(), i + batchSize);
    	        List<Long> batchList = sbomHistoryUpdateList.subList(i, end);
    	        System.out.println("update ์ฟผ๋ฆฌ ์‹œ์ž‘ : " +  System.currentTimeMillis());
    	        sbomMapper.updateOutOfDateSchedule(batchList);
    	        System.out.println("update ์ฟผ๋ฆฌ ๋ : " +  System.currentTimeMillis());
    	    }
    	}

 

๐Ÿ“— Result

  • select ์‹œ๊ฐ„ : 575ms (0.5์ดˆ)
  • update ์‹œ๊ฐ„ : 10000๊ฐœ์”ฉ :
    • 0๋ฒˆ์งธ: 289 ms
    • 10000๋ฒˆ์งธ: 619 ms
    • 20000๋ฒˆ์งธ: 235 ms
    • 30000๋ฒˆ์งธ: 202 ms
    • 40000๋ฒˆ์งธ: 207 ms
    • 50000๋ฒˆ์งธ: 172 ms
    • 60000๋ฒˆ์งธ: 189 ms
    • 70000๋ฒˆ์งธ: 172 ms
    • 80000๋ฒˆ์งธ: 216 ms
    • 90000๋ฒˆ์งธ: 168 ms
    ⇒ ์ด : 2,449 ms (2.5์ดˆ)
  • ⇒ ํ‰๊ท  : 244.9 ms
  • ๊ฒฐ๊ณผ์ ์œผ๋กœ 10๋งŒ๊ฐœ์˜ ๋ฐ์ดํ„ฐ 3์ดˆ update