์ด ๊ธ์์๋ Graceful Shutdown์ "์ฐ์ํ ์ข ๋ฃ"๋ก ์ธ๊ธํฉ๋๋ค.
1. ์ฐ์ํ ์ข ๋ฃ ํ ์คํธ ํ๊ฒฝ ์ค์
์ฐ์ํ ์ข ๋ฃ ํ ์คํธ๋ IDE์์ ์คํ ์ ์ํํ๊ฒ ๋์ํ์ง ์๋๋ค๊ณ ์ค๋ช ๋์ด ์์ด, Linux ์๋ฒ์์ jar๋ฅผ ์๋์ผ๋ก ์ข ๋ฃํ๋ฉฐ ํ ์คํธ๋ฅผ ์งํํ์ต๋๋ค.
Using graceful shutdown with your IDE may not work properly if it does not send a proper SIGTERM signal. See the documentation of your IDE for more details.
1.1 application.yml ์ค์
application.yml์ ์ฐ์ํ ์ข ๋ฃ๋ฅผ ์ค์ ํ์์ต๋๋ค. ์คํ๋ง ๋ถํธ 3.4๋ถํฐ๋ ์ฐ์ํ ์ข ๋ฃ๊ฐ ๊ธฐ๋ณธ์ผ๋ก ์ค์ ๋๋ฉฐ, timeout-per-shutdown-phase๋ 30์ด์ ๋๋ค.
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 10s
1.2 ๋น๋๊ธฐ ์ฐ๋ ๋ ํ ์ค์
๊ทธ๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ (80์ด)์ ๋น๋๊ธฐ ์ฒ๋ฆฌ ํ๊ธฐ ์ํด ์ฐ๋ ๋ ํ์ ๊ฐ๋จํ๊ฒ ์ค์ ํ๋ค๊ณ ๊ฐ์ ํ์ต๋๋ค.
@Configuration
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(1);
taskExecutor.initialize();
return taskExecutor;
}
}
! ์ฐธ๊ณ
์์ฒ๋ผ ํ ์ฌ์ด์ฆ๋ฅผ ์ ํํ์ง ์์ผ๋ฉด, ThreadPoolTaskExecutor์ initializeExecutor, createQueue๋ฅผ ํตํด SynchronousQueue๊ฐ ์์ฑ๋ฉ๋๋ค.
SynchronousQueue ๋ ์์ ์ด ํ์ ์ ์ฌ๋์ง ์๊ณ , ์์ ์ ์ ์ถํ ์ฐ๋ ๋๋ ์์ ์ ์๋นํ ์ฐ๋ ๋๊ฐ ์์ ๋๊น์ง ๋๊ธฐํฉ๋๋ค.

2. ์ฒซ ๋ฒ์งธ ๋ฌธ์ : ์ฐ์ํ ์ข ๋ฃ๋ ๋น๋๊ธฐ ์์ ์ ๊ธฐ๋ค๋ ค์ค๊น?
2.1 ์คํ ์ฝ๋
์ด ์ํฉ์์ ์๋ ์ฝ๋์ฒ๋ผ ์ฐ๋ ๋ ํ์ 80์ด๊ฐ ๊ฑธ๋ฆฌ๋ ๋น๋๊ธฐ ์์ ์ ์ ์ถํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ๋ฃํด๋ด ๋๋ค. (jar๋ก ์คํํ์ต๋๋ค)
public class TestController {
private final TaskExecutor taskExecutor;
@GetMapping("/tasks")
public String test() throws InterruptedException {
log.info("๋น๋๊ธฐ ์์
์ ์ถ (80์ด ์์ ์์)...");
taskExecutor.execute(() -> {
try {
for (int i = 1; i <= 80; i++) {
Thread.sleep(1000);
log.info("๋น๋๊ธฐ ์์
์งํ ์ค: {}์ด ๊ฒฝ๊ณผ", i);
}
log.info("๋น๋๊ธฐ ์์
์๋ฃ!");
} catch (InterruptedException e) {
log.warn("๋น๋๊ธฐ ์์
์ด ๊ฐ์ ์ข
๋ฃ(Interrupted) ๋์์ต๋๋ค!");
}
});
return "์์
์ด ์ ์ถ๋์์ต๋๋ค. ์ด์ ์๋ฒ๋ฅผ ์ข
๋ฃ(Ctrl+C)ํ์ฌ ๋ก๊ทธ๋ฅผ ํ์ธํ์ธ์.";
}
}
2.2 ๊ฒฐ๊ณผ๋ ๋ฌด์์ผ๊น?
๊ฒฐ๊ณผ๋ ๋ฌด์์ผ๊น์?
- ํฐ์บฃ ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ๊ฐ ์ ์ฉ๋๋๋ผ๋ ๋ชจ๋ ๋น๋๊ธฐ ์์ ์ด ์ ์ถ๋์ด `log.info("๋น๋๊ธฐ ์์ ์งํ ์ค: {}์ด ๊ฒฝ๊ณผ", i);`์ ๋ก๊ทธ๊ฐ 80๋ฒ ๋ค ์ฐํ ํ์ ์ข ๋ฃ๋๋ค.
- ์๋๋ค. ์ฐ์ํ ์ข ๋ฃ๊ฐ ๋น๋๊ธฐ ์์ ์ 10์ด๋ง ๊ธฐ๋ค๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ข ๋ฃ๋๋ค. `log.info("๋น๋๊ธฐ ์์ ์งํ ์ค: {}์ด ๊ฒฝ๊ณผ", i);`์ ๋ก๊ทธ๊ฐ 1๋ฒ๋ณด๋ค ๋ฌด์กฐ๊ฑด ๋ ์ฐํ๋ค.
์ ๋ต์ 2๋ฒ์ ๋๋ค.
2.3 ๋ก๊ทธ ๋ถ์
์ ์ฒด ๋ก๊ทธ์ ๋๋ค.
2026-01-23T09:50:51.460 INFO [nio-8080-exec-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์ ์ถ (80์ด ์์ ์์)...
2026-01-23T09:50:52.461 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 1์ด ๊ฒฝ๊ณผ
2026-01-23T09:50:57.239 DEBUG [ionShutdownHook] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed from ACCEPTING_TRAFFIC to REFUSING_TRAFFIC
2026-01-23T11:46:13.097 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147482623
2026-01-23T11:46:13.099 INFO [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2026-01-23T11:46:13.108 INFO [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
2026-01-23T11:46:13.109 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147481599
2026-01-23T11:46:13.109 DEBUG [tomcat-shutdown] o.s.c.support.DefaultLifecycleProcessor : Bean 'webServerGracefulShutdown' completed its stop procedure
2026-01-23T11:46:13.111 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Bean 'webServerStartStop' completed its stop procedure
2026-01-23T11:46:13.112 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 1073741823
2026-01-23T11:46:13.646 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 2์ด ๊ฒฝ๊ณผ
2026-01-23T11:46:14.646 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 3์ด ๊ฒฝ๊ณผ
...
2026-01-23T11:46:21.650 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 10์ด ๊ฒฝ๊ณผ
2026-01-23T11:46:22.650 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 11์ด ๊ฒฝ๊ณผ
2026-01-23T11:46:23.118 INFO [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Shutdown phase 1073741823 ends with 1 bean still running after timeout of 10000ms: [taskExecutor1]
2026-01-23T11:46:23.119 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase -2147483647
2026-01-23T11:46:23.119 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Bean 'springBootLoggingLifecycle' completed its stop procedure
2026-01-23T11:46:23.120 DEBUG [ionShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'taskExecutor1'
2026-01-23T11:46:23.121 WARN [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์ด ๊ฐ์ ์ข
๋ฃ(Interrupted) ๋์์ต๋๋ค!
2026-01-23T11:46:23.121 DEBUG [ Async1-1] o.s.c.support.DefaultLifecycleProcessor : Bean 'taskExecutor1' completed its stop procedure
๋ก๊ทธ๋ฅผ ์ ๋ณด๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ACCEPTING_TRAFFIC ์ํ์์ REFUSING_TRAFFIC ์ํ๋ก ๋ณ๊ฒฝํ ํ์ 2147482623์ด๋ผ๋ phase์ ์๋ ๋น์ Stoppingํ๊ธฐ ์์ํฉ๋๋ค.
2147482623 ๊ฐ์ Integer.MAX_VALUE์ธ๋ฐ, ๋์ค์ Phased ์ธํฐํ์ด์ค๋ฅผ ์ค๋ช ํ ๋ ๋ค์ ๋์ค๋ ๊ธฐ์ตํด๋์๊ธฐ ๋ฐ๋๋๋ค.
2026-01-23T09:50:57.239 DEBUG [ionShutdownHook] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed from ACCEPTING_TRAFFIC to REFUSING_TRAFFIC
2026-01-23T11:46:13.097 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147482623
2026-01-23T11:46:13.099 INFO [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2026-01-23T11:46:13.108 INFO [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
๊ทธ ํ์ 'Stopping beans in phase {Integer ๊ฐ}' ์์ผ๋ก ๋ก๊ทธ๊ฐ ์ฐํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์, phase ๋ ๋ก๊ทธ๋ timeout-per-shutdown-phase ์ phase ๋ฅผ ์๋ฏธํฉ๋๋ค.
2026-01-23T11:46:13.109 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147481599
2026-01-23T11:46:13.109 DEBUG [tomcat-shutdown] o.s.c.support.DefaultLifecycleProcessor : Bean 'webServerGracefulShutdown' completed its stop procedure
2026-01-23T11:46:13.111 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Bean 'webServerStartStop' completed its stop procedure
2026-01-23T11:46:13.112 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 1073741823
2026-01-23T11:46:23.118 INFO [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Shutdown phase 1073741823 ends with 1 bean still running after timeout of 10000ms: [taskExecutor1]
2026-01-23T11:46:23.119 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase -2147483647
2026-01-23T11:46:23.119 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Bean 'springBootLoggingLifecycle' completed its stop procedure
2.4 timeout-per-shutdown-phase์ ์ง์ง ์๋ฏธ
๊ทธ๋์, ์๋ ์ค์ ๊ฐ์ ์ฌ์ค phase ์ซ์ ๊ฐ์ด ๋ค๋ฅธ ๋น๋ค์ ์ข ๋ฃํ ๋๋ง๋ค ์ต๋ 10์ด๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ด์ง, ์ด ์๊ฐ ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ๋ฃ๊ฐ ์๋ฃ๋๊ธฐ๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ด ์๋๋๋ค.
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 10s
๋จ์ง ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ๋ฃํ ๋ phase๊ฐ ๋์ ์์์ ๋ฎ์ ์์ผ๋ก ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌํ ํ, ๊ฐ phase ๊ทธ๋ฃน์ ์ข ๋ฃํ๊ฒ ๋๋๋ฐ, ๋ด์ฅ๋ ์น์๋ฒ(ํฐ์บฃ, ๋คํฐ) ๊ด๋ จ ๋น๋ค์ phase ๊ฐ์ด ๊ฐ์ฅ ๋๊ธฐ ๋๋ฌธ์ ๊ฐ์ฅ ๋จผ์ ์ข ๋ฃ๋์ด ์๋ก์ด ์์ฒญ์ ๋ฐ์๋ค์ด์ง ์๊ฒ ๋๋ ๊ฒ๋ฟ์ ๋๋ค.
2.5 Phase ๋ณ ์ข ๋ฃ ๋ฉ์ปค๋์ฆ
์๋ฅผ ๋ค์ด, ๋น A์ B๊ฐ phase 1000์ด๋ผ๋ ๊ฐ์ ๊ฐ์ง๊ณ , ๋น C, D, E๊ฐ phase 999๋ผ๋ ๊ฐ์ ๊ฐ์ง ๋ (๋น์ ๊ฐ์ phase ๊ฐ์ ๊ฐ์ง ์ ์์ต๋๋ค), DefaultLifecycleProcessor(์ ํ๋ฆฌ์ผ์ด์ ์ ๋น๋ค์ phase ๋จ์๋ก ์ข ๋ฃํ๋ ๊ฐ์ฒด)๋ phase 1000์ ๊ฐ์ง ๋น๋ค์ ์ ๋ฆฌํ ๋ ๊ฐ ๋น๋ค์ด ์ฌ์ฉํ๋ ๋ ๋ค๋ฅธ ๋น๋ค์ ์ฌ๊ท์ ์ผ๋ก ์ฐพ์ผ๋ฉฐ ๊ด๋ จ ๋น๋ค์ ๋ชจ๋ ์ ๋ฆฌํฉ๋๋ค. (์ด๋ ๋ด๋ถ์ ์ผ๋ก doStop()์ ์ฌ๊ท์ ์ผ๋ก ํธ์ถํฉ๋๋ค). ์ด ๊ณผ์ ์ phase ๊ฐ ๋ณ๋ก ์ต๋ 10์ด๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค๋ ๊ฒ์ ๋๋ค.
๊ทธ๋์ ์ต๋ 10์ด๋ฅผ ๊ธฐ๋ค๋ฆฐ ํ์๋ ๋ค์ phase ๊ฐ์ ๊ฐ์ง 999 phase ๋ฅผ ์ข ๋ฃํ๊ธฐ ์์ํฉ๋๋ค.

๊ทธ๋์ 1073741823 ๋น์ธ ์ฐ๋ ๋ ํ์ ์ข ๋ฃํ ๋, ์์ ์ด 10์ด ์ด์ ๊ฑธ๋ฆฌ๊ฒ ๋๋ timeout-per-shutdown-phase 10์ด๋ฅผ ๋๊ธฐ๊ฒ ๋์ด ํด๋น phase๋ ํ์์์ ๋์๋ค๋ ๋ก๊ทธ์ ํจ๊ป ๋ค์ phase๋ก ๋์ด๊ฐ๊ฒ ๋๋ ๊ฒ์ ๋๋ค.
2026-01-23T11:46:13.112 DEBUG [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 1073741823
2026-01-23T11:46:13.646 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 2์ด ๊ฒฝ๊ณผ
2026-01-23T11:46:14.646 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 3์ด ๊ฒฝ๊ณผ
...
2026-01-23T11:46:21.650 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 10์ด ๊ฒฝ๊ณผ
2026-01-23T11:46:22.650 INFO [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 11์ด ๊ฒฝ๊ณผ
2026-01-23T11:46:23.118 INFO [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Shutdown phase 1073741823 ends with 1 bean still running after timeout of 10000ms: [taskExecutor1]
3. ๋ ๋ฒ์งธ ๋ฌธ์ : ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ๋ฅผ ์ถ๊ฐํ๋ฉด?
3.1 ์ฐ๋ ๋ ํ ์ฐ์ํ ์ข ๋ฃ ์ค์
๋ค์ 2๋ฒ์งธ ๋ฌธ์ ์ ๋๋ค.
๊ทธ๋ผ ๋น๋๊ธฐ ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ๋ฅผ ์ถ๊ฐํ๊ณ , ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ๋ฃํด๋ด ์๋ค.
@Configuration
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(1);
// ์ถ๊ฐ!
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(30);
taskExecutor.initialize();
return taskExecutor;
}
}
3.2 ๊ฒฐ๊ณผ๋ ์ด๋ป๊ฒ ๋ ๊น?
๊ฒฐ๊ณผ๋ ์ด๋ป๊ฒ ๋ ๊น์?
- ํฐ์บฃ ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ ์๊ฐ์ 10์ด๋ก ์ค์ ํ๊ธฐ ๋๋ฌธ์ ๋น๋๊ธฐ ์ฐ๋ ๋ ํ์ ์ ์ฉํ 30์ด๋ ์ ์ฉ๋์ง ์๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ 10์ด๋ง ๊ธฐ๋ค๋ฆฌ๊ณ ์ข ๋ฃ๋๋ค!
- ํฐ์บฃ ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ ์๊ฐ 10์ด์ ๋น๋๊ธฐ ์ฐ๋ ๋ ํ์ ์ ์ฉํ 30์ด๊ฐ ๋ํด์ ธ ์ด 40์ด ๋์ ์ฐ์ํ ์ข ๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ณ ์ข ๋ฃ๋๋ค!
- ํฐ์บฃ ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ ์๊ฐ ๋ฐ๋ก, ์ฐ๋ ๋ ํ ์ฐ์ํ ์ข ๋ฃ๊ฐ ๋ฐ๋ก ์ ์ฉ๋์ด ๋น๋๊ธฐ ์์ ์ 30์ด ๋์ ๊ธฐ๋ค๋ฆฌ๊ณ ์ข ๋ฃ๋๋ค!
์ ๋ต์ 3๋ฒ ์ ๋๋ค.
3.3 ์ ์ด๋ ๊ฒ ๋์ํ ๊น?
์๋๋ ์ ์ฒด ๋ก๊ทธ์ ๋๋ค.
2026-01-22T21:02:43 INFO --- [nio-8080-exec-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์ ์ถ (80์ด ์์ ์์)...
2026-01-22T21:02:44 INFO --- [ taskExecutor-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 1์ด ๊ฒฝ๊ณผ
2026-01-22T21:02:44 INFO --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2026-01-22T21:02:45 INFO --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
2026-01-22T21:02:45 INFO --- [ taskExecutor-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 2์ด ๊ฒฝ๊ณผ
2026-01-22T21:02:46 INFO --- [ taskExecutor-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 3์ด ๊ฒฝ๊ณผ
...
2026-01-22T21:03:12 INFO --- [ taskExecutor-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 29์ด ๊ฒฝ๊ณผ
2026-01-22T21:03:13 INFO --- [ taskExecutor-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 30์ด ๊ฒฝ๊ณผ
2026-01-22T21:03:14 INFO --- [ taskExecutor-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 31์ด ๊ฒฝ๊ณผ
2026-01-22T21:03:15 WARN --- [ionShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Timed out while waiting for executor 'taskExecutor' to terminate
์ ์ด๋ฐ์ง timeout-per-shutdown-phase์ ์๋ฏธ๋ฅผ ์ ์๊ฐํด๋ด ์๋ค.
timeout-per-shutdown-phase๋ phase ๊ฐ์ ํ์์์์ ์ง์ ํ ๋ฟ์ด์ง, ๋ค๋ฅธ ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ๊ฐ ์ค์ ๋์ด ์์ผ๋ฉด ๊ฐ์ญํ์ง ์์ต๋๋ค.
์ฆ, ๋น๋๊ธฐ๋ฅผ ์ํ ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ๋ฅผ ์ค์ ํ๊ฒ ๋๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ๋ฃํ ๋ "๊ทธ๋, ๋ ์ฐ์ํ ์ข ๋ฃ ์ค์ ๋์ด ์์ผ๋ ๋ ์๊ฐ๋งํผ ๊ธฐ๋ค๋ฆฌ๋๋ก ํด"๋ผ๊ณ ํ๊ณ ๋ค์ phase๋ฅผ ๋ฐ๋ก ์ข ๋ฃํ๋ฌ ๊ฐ๊ณ , ๋น๋๊ธฐ๋ฅผ ์ํ ์ฐ๋ ๋ ํ์ ๊ทธ ํ ์์ฒด๋ก ์ฐ์ํ ์ข ๋ฃ๋ก ์ค์ ํ ํ์์์ ์๊ฐ๋งํผ ์์ ์ ์ํํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ฌ๋ค๊ฐ ๊ทธ ์๊ฐ๊น์ง ๋น๋๊ธฐ ์ฐ๋ ๋ ํ์ด ์์ ์ ์ข ๋ฃํ์ง ๋ชปํ๋ฉด ๋ง์ง๋ง ๋ก๊ทธ(Timed out while waiting for...)์ฒ๋ผ "ํด๋น ์ฐ๋ ๋ ํ์ ์ข ๋ฃํ๋๋ฐ ํ์์์์ด ๋ด์ด์. ์์ ์ด ์๋ฃ๋ ์๋ ์๊ณ , ์๋ ์๋ ์์ด์."๋ผ๊ณ WARN ๋ก๊ทธ ๋ ๋ฒจ์ ๋จ๊ธฐ๋ฉฐ ์ข ๋ฃํ๊ฒ ๋ฉ๋๋ค.
4. ์ธ ๋ฒ์งธ ๋ฌธ์ : ์ฌ๋ฌ ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ
4.1 ๋ณต์ ์ฐ๋ ๋ ํ ์ค์
๋ค์ ๋ง์ง๋ง ๋ฌธ์ ์ ๋๋ค.
์ธ๋ถ ์ฐ๋ ์๋น์ค A์ B ๊ฐ์ ์ฅ์ ๋ฅผ ๊ฒฉ๋ฆฌํ๊ธฐ ์ํด ์ฐ๋ ๋ ํ 2๊ฐ๋ฅผ ์ฌ์ฉํด ๊ฐ ์ฐ๋ ์๋น์ค๋ฅผ ํธ์ถํ๋ ์ํฉ์ ๊ฐ์ ํ์ต๋๋ค.(์ผ์ข ์ ๋ฒํฌํค๋ ํจํด์ ๋๋ค).
์ฐ๋ ๋ ํ 1๋ฒ์ ์ฐ์ํ ์ข ๋ฃ ์๊ฐ์ 30์ด, ์ฐ๋ ๋ ํ 2๋ฒ์ 15์ด์ ๋๋ค.
@Configuration
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor1() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(1);
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(30);
taskExecutor.setThreadNamePrefix("Async1-");
taskExecutor.initialize();
return taskExecutor;
}
@Bean
public TaskExecutor taskExecutor2() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(1);
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(15);
taskExecutor.setThreadNamePrefix("Async2-");
taskExecutor.initialize();
return taskExecutor;
}
}
API ์๋ํฌ์ธํธ๋ 2๊ฐ๊ฐ ์๊ฒผ์ต๋๋ค.
@RestController
@RequiredArgsConstructor
public class TestController {
private final TaskExecutor taskExecutor1;
private final TaskExecutor taskExecutor2;
@GetMapping("/tasks-1")
public String test() throws InterruptedException {
ThreadPoolExecutor taskExecutor = (ThreadPoolExecutor) taskExecutor1;
log.info("๋น๋๊ธฐ ์์
์ ์ถ (80์ด ์์ ์์)...");
taskExecutor.submit(() -> {
try {
for (int i = 1; i <= 80; i++) {
Thread.sleep(1000); // 1์ด์ฉ 30๋ฒ ๋๊ธฐ
log.info("๋น๋๊ธฐ ์์
์งํ ์ค: {}์ด ๊ฒฝ๊ณผ", i);
}
log.info("๋น๋๊ธฐ ์์
์๋ฃ!");
} catch (InterruptedException e) {
log.warn("๋น๋๊ธฐ ์์
์ด ๊ฐ์ ์ข
๋ฃ(Interrupted) ๋์์ต๋๋ค!");
}
});
return "์์
์ด ์ ์ถ๋์์ต๋๋ค. ์ด์ ์๋ฒ๋ฅผ ์ข
๋ฃ(Ctrl+C)ํ์ฌ ๋ก๊ทธ๋ฅผ ํ์ธํ์ธ์.";
}
@GetMapping("/tasks-2")
public String test2() throws InterruptedException {
ThreadPoolExecutor taskExecutor = (ThreadPoolExecutor) taskExecutor2;
log.info("๋น๋๊ธฐ ์์
์ ์ถ (30์ด ์์ ์์)...");
taskExecutor.submit(() -> {
try {
for (int i = 1; i <= 30; i++) {
Thread.sleep(1000); // 1์ด์ฉ 30๋ฒ ๋๊ธฐ
log.info("๋น๋๊ธฐ ์์
์งํ ์ค: {}์ด ๊ฒฝ๊ณผ", i);
}
log.info("๋น๋๊ธฐ ์์
์๋ฃ!");
} catch (InterruptedException e) {
log.warn("๋น๋๊ธฐ ์์
์ด ๊ฐ์ ์ข
๋ฃ(Interrupted) ๋์์ต๋๋ค!");
}
});
return "์์
์ด ์ ์ถ๋์์ต๋๋ค. ์ด์ ์๋ฒ๋ฅผ ์ข
๋ฃ(Ctrl+C)ํ์ฌ ๋ก๊ทธ๋ฅผ ํ์ธํ์ธ์.";
}
}
์์ฝํ์๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ์ ์ญ ์ค์ : timeout-per-shutdown-phase 10์ด
- ๋น๋๊ธฐ ์ฐ๋ ๋ ํ 1๋ฒ: ์์ ๊ธฐ๋ค๋ฆฌ๊ธฐ ๊น์ง 30์ด, ์ค์ ์์ ์์ ์๊ฐ 80์ด
- ๋น๋๊ธฐ ์ฐ๋ ๋ ํ 2๋ฒ: ์์ ๊ธฐ๋ค๋ฆฌ๊ธฐ ๊น์ง 15์ด, ์ค์ ์์ ์์ ์๊ฐ 30์ด
4.2 ๊ฒฐ๊ณผ๋?
๊ฒฐ๊ณผ๊ฐ ์ด๋ป๊ฒ ๋ ๊น์?
๋น๋๊ธฐ ์ฐ๋ ๋ ํ 2๋ฒ์ WARN ๋ ๋ฒจ์ ๊ฒฝ๊ณ ํ์์์์ด ๋ฐ์ํ๋ค๋ ๋ก๊ทธ์ ํจ๊ป ์์ ์ด ์๋ฃ๋์๋ค๋ ๋ก๊ทธ๊ฐ ๋์์ต๋๋ค.
๋น๋๊ธฐ ์ฐ๋ ๋ ํ 1๋ฒ์ 30์ด ์ดํ์ WARN ๋ ๋ฒจ์ ๊ฒฝ๊ณ ํ์์์์ด ๋ฐ์ํ๋ค๋ ๋ก๊ทธ๋ง ๋์ค๊ณ , ์์ ์ด ์๋ฃ๋์ง ์๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ข ๋ฃ๋์์ต๋๋ค.
์๋๋ ์ ์ฒด ๋ก๊ทธ์ ๋๋ค.
026-01-22T21:26:57.619+09:00 INFO 60684 --- [ Async2-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 1์ด ๊ฒฝ๊ณผ
2026-01-22T21:26:57.724+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 2์ด ๊ฒฝ๊ณผ
2026-01-22T21:26:57.750+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed from ACCEPTING_TRAFFIC to REFUSING_TRAFFIC
2026-01-22T21:26:57.751+09:00 DEBUG 60684 --- [ionShutdownHook] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@62452cc9, started on Thu Jan 22 21:26:53 KST 2026
2026-01-22T21:26:57.752+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147482623
2026-01-22T21:26:57.753+09:00 INFO 60684 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2026-01-22T21:26:58.418+09:00 INFO 60684 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
2026-01-22T21:26:58.418+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147481599
2026-01-22T21:26:58.418+09:00 DEBUG 60684 --- [tomcat-shutdown] o.s.c.support.DefaultLifecycleProcessor : Bean 'webServerGracefulShutdown' completed its stop procedure
2026-01-22T21:26:58.419+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Bean 'webServerStartStop' completed its stop procedure
2026-01-22T21:26:58.419+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 1073741823
2026-01-22T21:26:58.419+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Bean 'taskExecutor1' completed its stop procedure
2026-01-22T21:26:58.419+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Bean 'taskExecutor2' completed its stop procedure
2026-01-22T21:26:58.436+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase -2147483647
2026-01-22T21:26:58.436+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Bean 'springBootLoggingLifecycle' completed its stop procedure
2026-01-22T21:26:58.436+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'taskExecutor2'
2026-01-22T21:26:58.622+09:00 INFO 60684 --- [ Async2-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 2์ด ๊ฒฝ๊ณผ
2026-01-22T21:26:58.726+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 3์ด ๊ฒฝ๊ณผ
...
2026-01-22T21:27:11.812+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 16์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:12.729+09:00 INFO 60684 --- [ Async2-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 16์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:12.820+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 17์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:13.448+09:00 WARN 60684 --- [ionShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Timed out while waiting for executor 'taskExecutor2' to terminate
2026-01-22T21:27:13.448+09:00 DEBUG 60684 --- [ionShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'taskExecutor1'
2026-01-22T21:27:13.734+09:00 INFO 60684 --- [ Async2-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 17์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:13.825+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 18์ด ๊ฒฝ๊ณผ
...
2026-01-22T21:27:26.849+09:00 INFO 60684 --- [ Async2-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 30์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:26.849+09:00 INFO 60684 --- [ Async2-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์๋ฃ!
2026-01-22T21:27:26.926+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 31์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:27.942+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 32์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:28.944+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 33์ด ๊ฒฝ๊ณผ
...
2026-01-22T21:27:41.009+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 45์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:42.021+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 46์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:43.027+09:00 INFO 60684 --- [ Async1-1] p.shutdown.config.TestController : ๋น๋๊ธฐ ์์
์งํ ์ค: 47์ด ๊ฒฝ๊ณผ
2026-01-22T21:27:43.459+09:00 WARN 60684 --- [ionShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Timed out while waiting for executor 'taskExecutor1' to terminate
4.3 ์ ์ฐ๋ ๋ ํ 2๋ฒ์ 30์ด ์์ ์ ์๋ฃํ ์ ์์์๊น?
์ ๋น๋๊ธฐ ์ฐ๋ ๋ ํ 2๋ฒ์ ์์ ๊ธฐ๋ค๋ฆฌ๊ธฐ๊น์ง 15์ด๋๊น ์ค์ ์์ ์ ์๋ฃํ์ง ๋ชปํ๊ณ ์ข ๋ฃ๋ ๊ฒ ๊ฐ์๋ฐ, ์ด๋ป๊ฒ ์์ ์ ์๋ฃํ ์ ์์์๊น์?
์ด๋ Spring์ด ์ฐ๋ ๋ ํ์ ์ฐ์ํ ์ข ๋ฃ๋ฅผ ์ํด ๊ธฐ๋ฅ์ ํ์ฅํ๊ธฐ ๋๋ฌธ์ ๋๋ค.(Java์ ThreadPoolExecutor ๋ฅผ Spring ์ ThreadPoolTaskExecutor ๋ก ํ์ฅ)
4.4 Spring์ Java ์ฐ๋ ๋ ํ ํ์ฅ ๋ฉ์ปค๋์ฆ
Shutdown ํ ์ ์ํด ThreadPoolTaskExecutor์ shutdown์ด ํธ์ถ๋๋ฉด ์ฐ์ํ ์ข ๋ฃ๋ฅผ true๋ก ์ค์ ํ์ ์, Java ์ฐ๋ ๋ ํ ์ข ๋ฃ ๋ฐฉ์์ธ ThreadPoolExecutor ๊ตฌํ์ฒด์ ๋ง์ถฐ ์ด๋ฏธ ์ ์ถ๋ ์์ ๋ค์ ์คํ๋์ง๋ง ์๋ก์ด ์์ ์ ๋ฐ์ง ์๋ ์ข ๋ฃ๋ฅผ ์์ํฉ๋๋ค. (์ฌ๊ธฐ ๊น์ง๋ Java์ ThreadPoolExecutor ๊ธฐ๋ฅ์ ๋๋ค.) ํ์ง๋ง, Spring ์ ์ฐ๋ ๋ ํ์ shutdown ์ ๋๋ง์น๊ณ ์ถ๊ฐ๋ก awaitTerminationIfNecessary() ๋ฅผ ํธ์ถํ๊ฒ ๋ฉ๋๋ค. ๋ฉ์๋ ๋ช ๋ง ๋ด๋ ์ ์ ์๋ฏ์ด "์ฐ๋ ๋ ํ์ด ํ์ํ ๊ฒฝ์ฐ ์ข ๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฝ๋๋ค"์ ๋๋ค.

์ด๋ ์ค์ ๋ ์๊ฐ(15์ด ๋๋ 30์ด) ๋์ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋๊ณ , ๋ง์ฝ ๊ทธ ์๊ฐ ๋ด์ ์์ ์ ๋ค ๋ชป ๋๋ด๋ฉด WARN ๋ ๋ฒจ์ ๋ก๊ทธ๋ง ๋ ธ์ถํ๊ณ ๊ฐ์ ์ข ๋ฃํ์ง ์๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.

์ฌ๊ธฐ์ ํต์ฌ์ "๊ฐ์ ์ข ๋ฃํ์ง ์๋๋ค"๋ ์ ์ ๋๋ค. ์ฐ๋ ๋ ํ 2์ shutdown hook์ด ํธ์ถ๋์ด 15์ด ๋์ ๊ธฐ๋ค๋ ธ์ง๋ง ์์ ์ด ์๋ฃ๋์ง ์์์ต๋๋ค. ์ด๋ Spring์ ์ฐ๋ ๋ ํ 2๋ฅผ ๊ฐ์ ๋ก ์ข ๋ฃํ์ง ์๊ณ WARN ๋ก๊ทธ๋ง ๋จ๊ธด ์ฑ ๋ค์ phase๋ก ๋์ด๊ฐ๋๋ค. ๊ทธ๋ฆฌ๊ณ , ์ฐ๋ ๋ ํ 1์ ์ข ๋ฃ phase๊ฐ ์์๋๊ณ 30์ด ๋์ ๊ธฐ๋ค๋ฆฌ๋ ๋์, ์ฐ๋ ๋ ํ 2์ ์์ ๋ ํจ๊ป ๊ณ์ ์คํํ๋ ๊ฒ์ ๋๋ค. ์ฐ๋ ๋ ํ2๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๋์ 1๋ ์ข ๋ฃํ ํ์ ์์ด ๊ฐ์ด ์คํํ๋ ๊ฒ์ ๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ์ฐ๋ ๋ ํ 2๋ ์์ ์๊ฒ ํ ๋น๋ 15์ด๋ฅผ ๋์ด ์ฐ๋ ๋ ํ 1์ด ์ข ๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ 30์ด ๋์ ์ถ๊ฐ๋ก ์์ ์ ์ํํ ์ ์๊ฒ ๋์ด, ์ด 30์ด ์์ ์์ ์ ์๋ฃํ ์ ์์๋ ๊ฒ์ ๋๋ค. ์ ๋ฆฌํ๋ฉด, Spring ์๋ช ์ฃผ๊ธฐ์ ๋ง์ถฐ ์ฐ๋ ๋ ํ์ ์์ ์ด ์ต๋ํ ์ฒ๋ฆฌ๋ ์ ์๊ฒ ํด์ฃผ๊ณ ์์ต๋๋ค. ๊ฐ ์ฐ๋ ๋ ํ์ ์์ ์๊ฒ ์ค์ ๋ ์๊ฐ๋งํผ ๊ธฐ๋ค๋ฆฌ์ง๋ง, ๊ทธ ์๊ฐ์ด ์ง๋ฌ๋ค๊ณ ํด์ ๊ฐ์ ๋ก ์์ ์ ์ค๋จํ์ง ์๊ณ , ๋ค๋ฅธ phase๊ฐ ์ข ๋ฃ๋๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ๊ณ์ํด์ ์์ ์ ์ฒ๋ฆฌํ ์ ์๋ ์ฌ์ ๋ฅผ ์ฃผ๋ ๊ฒ์ ๋๋ค.
๊ทธ๋์, ์ฐ๋ ๋ ํ2 ์ shutdown hook ์ด ํธ์ถ๋์์ง๋ง ์ฐ๋ ๋ ํ1์ ์ฐ์ํ ์ข ๋ฃ ์๊ฐ์ ๊ธฐ๋ค๋ฆฌ๊ธฐ ๋๋ฌธ์ ์คํ๋ง์ด ์ฐ๋ ๋ ํ2 ๋ ๊ทธ๋ฅ ๊ธฐ๋ค๋ฆฌ๋ ๋์๋ง ์ผ๋จ ์คํ ์ํฌ๊ป~ ํ์ง๋ง, Warn ๋ ๋ฒจ ๋ก๊ทธ๋ฅผ ํตํด ์์ ์ด ๋ค ์๋ฃ๋์ง ์์ ์ ์์ด.๋ผ๊ณ ํ์์์ ๊ฒฝ๊ณ ๋ฅผ WARN ๋ก๊ทธ๋ก ์ฃผ๋ ๊ฒ์ ๋๋ค.
๊ทธ๋์ ์ฐ๋ฆฌ๋ ์คํ๋ง์์ ์ฐ๋ ๋ ํ์ ๋น์ผ๋ก ๋ฑ๋กํ ๋, ์คํ๋ง์ด ์ฐ๋ ๋ ํ์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ์ปจํ ์คํธ์ ๋ง์ถฐ ๊ด๋ฆฌํ ์ ์๊ฒ ThreadPoolExecutor๊ฐ ์๋ ThreadPoolTaskExecutor๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. ๋ฐ๋ฉด, ์ฐ๋ฆฌ๊ฐ ํ ์คํธํ ๋ Executors.newFixedThreadPool()์ ์ฌ์ฉํ๋ ๊ฒ์ ์ด ์ฐ๋ ๋ ํ์ด ์คํ๋ง ์๋ช ์ฃผ๊ธฐ์ ๋ง์ถฐ์ง ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
5. SmartLifecycle๊ณผ Phased: Spring์ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ ๋ฉ์ปค๋์ฆ
๊ทธ๋ผ Spring์ ์ด๋ป๊ฒ ์๋ช ์ฃผ๊ธฐ์ ๋ง์ถฐ ์ฐ๋ ๋ ํ์ ์ข ๋ฃํ๊ฒ ๊ตฌํ๋์ด ์์๊น์?
SmartLifecycle(Bean์ ๊ด๋ฆฌํ๋ ApplicationContext๊ฐ refresh๋๊ฑฐ๋ shutdown๋ ๋, ํน์ ์์์ ๋ง๊ฒ ๋น์ ์์ํ๊ฑฐ๋ ์ข ๋ฃํ๋๋ก ์ง์ํ๋ ์ธํฐํ์ด์ค์ ๋๋ค)๊ณผ Phased๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ปจํ ์คํธ๊ฐ ๊ด๋ฆฌํ๋ ๋น๋ค์ ์์(Phased)์ ๋ฐ๋ผ ํจ์จ์ ์ผ๋ก ์ ๋ฆฌํด์ค๋๋ค. ์ฐธ๊ณ ๋ก Phased์ getPhase๋ int๋ผ์ Integer.MIN_VALUE๋ถํฐ Integer.MAX_VALUE ๊ฐ์ ๊ฐ์ง๋๋ฐ, ThreadPoolTaskExecutor์ ThreadPoolTaskScheduler๋ Integer.MAX_VALUE / 2์ ๊ฐ์ ๊ฐ์ง๋๋ค.
์คํ๋ง ์ปจํ ์คํธ๊ฐ ์ข ๋ฃ๋ ๋, Phased๊ฐ ๋์ ์์(ํฐ์บฃ ์ฐ๋ ๋ ํ)๋ถํฐ ์ข ๋ฃ๋จ์ผ๋ก์จ ์น ์์ฒญ์ ๋ง๋ ๊ฒ๋ถํฐ ์์ํ๋ฉฐ ์ฒ์ฒํ ์ปจํ ์คํธ์ ๋ฑ๋ก๋ ๋น๋ค์ Phased๊ฐ ๋ฎ์ ์์ผ๋ก ์ ๋ฆฌํ๊ธฐ ์์ํฉ๋๋ค.
SmartLifecycle์ ๊ตฌํํ์ง ์์ ์ ํ๋ฆฌ์ผ์ด์ ๋น๋ค, ์ผ๋ฐ Lifecycle ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ฑฐ๋ @Component ๋ก ๋ฑ๋ก๋ ๋น๋ค์ Phase๋ฅผ 0์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
6. ๊ฒฐ๋ก
7.1 timeout-per-shutdown-phase์ ์ ํํ ์๋ฏธ
์ง์ญํ์๋ฉด "๊ฐ Phase๋ฅผ shutdown ํ ๋๋ง๋ค 10์ด์ ํ์์์"์ด๋ผ๋ ์๊ธฐ์ ๋๋ค.
๊ทธ๋ฆฌ๊ณ , ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ๋ฃํ ๋๋ Phase ๊ฐ์ด ํฐ Bean ๋ถํฐ ์ข ๋ฃํ๊ธฐ ์์ํฉ๋๋ค.
Phase 2147482623 (Tomcat):
→ stop() ํธ์ถ
→ ์ต๋ 10์ด ๋๊ธฐ
→ 10์ด ์ง๋๋ฉด ๋ค์ Phase๋ก
Phase 1073741823 (TaskExecutor๋ค):
→ stop() ํธ์ถ
→ ์ต๋ 10์ด ๋๊ธฐ
→ 10์ด ์ง๋๋ฉด ๋ค์ Phase๋ก
Phase 0:
→ stop() ํธ์ถ
→ ์ต๋ 10์ด ๋๊ธฐ
7.2 import(ํจํค์ง)์ ์ค์์ฑ
์, ThreadPoolExecutor, Executor, ExecutorService์ import๋ java์ ๋๋ค. ThreadPoolTaskExecutor, ThreadPoolTaskScheduler์ ํจํค์ง๋ spring์ ๋๋ค.
์ด๋ ๊ฒ ์ฌ์ํ๊ฒ ํจํค์ง๋ง ๋๋๊ฒ ๋์ง๋ง ๊ทธ์ ๋ฐ๋ผ ๋์ํ๋ ๊ฒ์ ๋ค๋ฅด๋ค๋ ๊ฒ์ ๊ธฐ์ตํ๋ฉฐ, "์ด? ๋น์ทํ ๊ธฐ๋ฅ์ ๊ฐ์ง ํด๋์ค๊ฐ ์๋๋ฐ Spring์ ๋ฌด์จ ์ด์ ๋ก ํ์ฅํ ๊ฑฐ์ง? ์ด๋ค ๊ธฐ๋ฅ์ด ๋ ์๋ ๊ฑฐ์ง?"๋ฅผ ์๊ฐํ๋ ๊ณ๊ธฐ๊ฐ ๋์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค.
7.3 ThreadPoolTaskExecutor์ ์ถ๊ฐ ๊ธฐ๋ฅ
์ด์ธ์๋ ThreadPoolTaskExecutor๋ ๋น์ด ๋ฑ๋ก๋ ๋ Core ์๋งํผ ์ฐ๋ ๋๋ฅผ ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋๋ ์ค์ , Max ์ฐ๋ ๋๋ฟ ์๋๋ผ Core ์ฐ๋ ๋๋ keep-alive ์๊ฐ์ ์ค ์ ์๋ ์ค์ ๋ฑ ๋ค์ํ๋ ์ฐพ์๋ณด๋ ๊ฒ๋ ๋์์ด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
7.4 ์์ ์ ์์์ฑ์ ๋ณด์ฅํ๋ TransactionalOutbox ํจํด
๋ง์ง๋ง์ผ๋ก, ๊ทธ๋ผ ์ฐ๋ฆฌ๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ๋ฃํ ๋ ๋ชจ๋ ์์ ์ ๊น๋ํ๊ฒ ๋๋ด์ผ๋ง ํ๋ค๋ฉด, ์์ ์ด ์ ์ค๋์ด์ผ ํ์ง ์์์ผ ํ๋ค๋ฉด ๊ทธ ์์ ์ด ์ผ๋ง๋ ๊ฑธ๋ฆด์ง ์์ธกํ๊ณ ๊ณ์ฐํด์ ์ฐ์ํ ์ข ๋ฃ์ ์๊ฐ์ ์ค์ ํ๋ฉด ๋ ๊น์?
์ฐจ๋ผ๋ฆฌ DB ๋ ๋ฉ์์ง ํ ๊ฐ์ ๋ฏธ๋ค์จ์ด์ ๋จผ์ ์ ์ฅํธ์ด ๋์ ๋ณด์ ๋๋ค. ๋ฉ์์ง ํ ๊ฐ์ ๋ฏธ๋ค์จ์ด์ ์ ์ฅํ๋ ค๋ฉด TransactionalOutbox ํจํด์ ํตํด at least once ๋ฐฉ์์ผ๋ก ์ ํฉ์ฑ์ ๋ง์ถ๋ ๊ฒ์ ๋ค์ด๊ตฌ์.
์ฐธ๊ณ ์๋ฃ
์ถ๊ฐ๋ก ์ฝ์ด๋ณผ๋งํ ์๋ฃ