From 69c92b218841080a7f18aba3a48801c14d3f8dd6 Mon Sep 17 00:00:00 2001 From: Soomin Date: Sun, 10 May 2026 16:57:21 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20=EA=B0=80=EC=83=81=20=EC=8A=A4?= =?UTF-8?q?=EB=A0=88=EB=93=9C=20=EC=A0=84=ED=99=98=20=EB=B0=8F=20k6=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=8B=9C=EB=82=98=EB=A6=AC?= =?UTF-8?q?=EC=98=A4=20=EC=9E=AC=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...est-platform-thread.js => case1-100vus.js} | 8 +++-- ...test-virtual-thread.js => case2-300vus.js} | 8 +++-- k6/case3-spike.js | 20 +++++++++++ .../common/config/AsyncConfig.java | 32 ------------------ .../NotificationTestController.java | 33 +++++++++++++++++++ .../TestNotificationController.java | 30 ----------------- .../service/SseEmitterService.java | 4 +-- .../service/TestNotificationService.java | 28 ---------------- 8 files changed, 67 insertions(+), 96 deletions(-) rename k6/{test-platform-thread.js => case1-100vus.js} (52%) rename k6/{test-virtual-thread.js => case2-300vus.js} (52%) create mode 100644 k6/case3-spike.js delete mode 100644 src/main/java/com/example/auctionnotification/common/config/AsyncConfig.java create mode 100644 src/main/java/com/example/auctionnotification/notification/controller/NotificationTestController.java delete mode 100644 src/main/java/com/example/auctionnotification/notification/controller/TestNotificationController.java delete mode 100644 src/main/java/com/example/auctionnotification/notification/service/TestNotificationService.java diff --git a/k6/test-platform-thread.js b/k6/case1-100vus.js similarity index 52% rename from k6/test-platform-thread.js rename to k6/case1-100vus.js index 4babd95..63223f9 100644 --- a/k6/test-platform-thread.js +++ b/k6/case1-100vus.js @@ -2,11 +2,15 @@ import http from 'k6/http'; import { check } from 'k6'; export const options = { - vus: 200, + vus: 100, duration: '30s', }; export default function () { - const res = http.post('http://host.docker.internal:8081/test/platform-thread'); + const res = http.post( + `http://host.docker.internal:8081/test/notifications/trigger?userId=${__VU}`, + null, + { timeout: '10s' } + ); check(res, { 'status 200': (r) => r.status === 200 }); } diff --git a/k6/test-virtual-thread.js b/k6/case2-300vus.js similarity index 52% rename from k6/test-virtual-thread.js rename to k6/case2-300vus.js index 46ca2d1..a91702c 100644 --- a/k6/test-virtual-thread.js +++ b/k6/case2-300vus.js @@ -2,11 +2,15 @@ import http from 'k6/http'; import { check } from 'k6'; export const options = { - vus: 200, + vus: 300, duration: '30s', }; export default function () { - const res = http.post('http://host.docker.internal:8081/test/virtual-thread'); + const res = http.post( + `http://host.docker.internal:8081/test/notifications/trigger?userId=${__VU}`, + null, + { timeout: '10s' } + ); check(res, { 'status 200': (r) => r.status === 200 }); } diff --git a/k6/case3-spike.js b/k6/case3-spike.js new file mode 100644 index 0000000..760de56 --- /dev/null +++ b/k6/case3-spike.js @@ -0,0 +1,20 @@ +import http from 'k6/http'; +import { check } from 'k6'; + +export const options = { + stages: [ + { duration: '10s', target: 50 }, // 워밍업 + { duration: '5s', target: 500 }, // 스파이크 (Tomcat 200 2.5배) + { duration: '20s', target: 500 }, // 스파이크 유지 + { duration: '5s', target: 0 }, // 종료 + ] +}; + +export default function () { + const res = http.post( + `http://host.docker.internal:8081/test/notifications/trigger?userId=${__VU}`, + null, + { timeout: '10s' } + ); + check(res, { 'status 200': (r) => r.status === 200 }); +} \ No newline at end of file diff --git a/src/main/java/com/example/auctionnotification/common/config/AsyncConfig.java b/src/main/java/com/example/auctionnotification/common/config/AsyncConfig.java deleted file mode 100644 index 6b56f7a..0000000 --- a/src/main/java/com/example/auctionnotification/common/config/AsyncConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.auctionnotification.common.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadPoolExecutor; - -@Configuration -@EnableAsync -public class AsyncConfig { - - @Bean(name = "notificationExecutor") - public Executor asyncExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(5); - executor.setMaxPoolSize(10); - executor.setQueueCapacity(100); - executor.setThreadNamePrefix("notification-async-"); - executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); - executor.initialize(); - return executor; - } - - @Bean(name = "notificationExecutorWithVT") - public Executor asyncExecutorWithVT() { - return Executors.newVirtualThreadPerTaskExecutor(); - } -} diff --git a/src/main/java/com/example/auctionnotification/notification/controller/NotificationTestController.java b/src/main/java/com/example/auctionnotification/notification/controller/NotificationTestController.java new file mode 100644 index 0000000..65cab3b --- /dev/null +++ b/src/main/java/com/example/auctionnotification/notification/controller/NotificationTestController.java @@ -0,0 +1,33 @@ +package com.example.auctionnotification.notification.controller; + +import com.example.auctionnotification.notification.dto.NotificationMessage; +import com.example.auctionnotification.notification.enums.NotificationType; +import com.example.auctionnotification.notification.service.NotificationService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Profile; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Profile("!prod") +@RestController +@RequiredArgsConstructor +@RequestMapping("/test/notifications") +public class NotificationTestController { + + private final NotificationService notificationService; + + @PostMapping("/trigger") + public ResponseEntity trigger(@RequestParam(defaultValue = "1") Long userId) { + NotificationMessage message = new NotificationMessage( + NotificationType.NEW_BID, + userId, + 1L, + "테스트 경매 상품" + ); + notificationService.save(message); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/example/auctionnotification/notification/controller/TestNotificationController.java b/src/main/java/com/example/auctionnotification/notification/controller/TestNotificationController.java deleted file mode 100644 index 6d256f5..0000000 --- a/src/main/java/com/example/auctionnotification/notification/controller/TestNotificationController.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.auctionnotification.notification.controller; - -import com.example.auctionnotification.notification.service.TestNotificationService; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Profile; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Profile("!prod") -@RestController -@RequiredArgsConstructor -@RequestMapping("/test") -public class TestNotificationController { - - private final TestNotificationService testNotificationService; - - @PostMapping("/platform-thread") - public ResponseEntity testPlatformThread() { - testNotificationService.simulatePlatformThread(); - return ResponseEntity.ok().build(); - } - - @PostMapping("/virtual-thread") - public ResponseEntity testVirtualThread() { - testNotificationService.simulateVirtualThread(); - return ResponseEntity.ok().build(); - } -} diff --git a/src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java b/src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java index 7744016..333e13b 100644 --- a/src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java +++ b/src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java @@ -42,7 +42,7 @@ public SseEmitter subscribe(Long userId) { return emitter; } - @Async("notificationExecutorWithVT") + @Async public void send(Long userId, NotificationResponse response) { SseEmitter emitter = emitters.get(userId); if (emitter == null) return; @@ -57,7 +57,7 @@ public void send(Long userId, NotificationResponse response) { } } - @Async("notificationExecutorWithVT") + @Async @Scheduled(fixedRate = 30000) public void sendPing() { for (Map.Entry entry : emitters.entrySet()) { diff --git a/src/main/java/com/example/auctionnotification/notification/service/TestNotificationService.java b/src/main/java/com/example/auctionnotification/notification/service/TestNotificationService.java deleted file mode 100644 index ab5f189..0000000 --- a/src/main/java/com/example/auctionnotification/notification/service/TestNotificationService.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.auctionnotification.notification.service; - -import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; - -@Profile("!prod") -@Service -public class TestNotificationService { - - @Async("notificationExecutor") - public void simulatePlatformThread() { - try { - Thread.sleep(50); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - @Async("notificationExecutorWithVT") - public void simulateVirtualThread() { - try { - Thread.sleep(50); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } -} From 1c9dcfb0ba69c98d8be5c4f145eb4d9b0f34e0bc Mon Sep 17 00:00:00 2001 From: Soomin Date: Sun, 10 May 2026 17:08:30 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20@EnableAsync=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auctionnotification/AuctionNotificationApplication.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/example/auctionnotification/AuctionNotificationApplication.java b/src/main/java/com/example/auctionnotification/AuctionNotificationApplication.java index 2eb9fc7..924f969 100644 --- a/src/main/java/com/example/auctionnotification/AuctionNotificationApplication.java +++ b/src/main/java/com/example/auctionnotification/AuctionNotificationApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; +@EnableAsync @EnableScheduling @SpringBootApplication public class AuctionNotificationApplication {