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/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 { 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(); - } - } -}