๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

ํ”„๋กœ์ ํŠธ/๊ณ ๋ฏผ ์ƒ๋‹ด ํ”Œ๋žซํผ

CompletableFuture๋ฅผ ํ™œ์šฉํ•ด ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ, ์‘๋‹ต ์‹œ๊ฐ„ 1๋ถ„ → 14์ดˆ๋กœ ๋‹จ์ถ•

๋ฌธ์ œ ์ƒํ™ฉ

ํ”„๋กœ์ ํŠธ QA์‹œ, ์ฃผ๊ฐ„ ๋ฆฌํฌํŠธ ๊ธฐ๋Šฅ์˜ ์‘๋‹ต ์‹œ๊ฐ„์ด ์‚ฌ์šฉ์ž 1๋ช…๋‹น ์ตœ์•…์˜ ๊ฒฝ์šฐ 2๋ถ„ ์ด์ƒ, ๋Œ€์ฒด๋กœ ํ‰๊ท  1๋ถ„ ์ด์ƒ ์†Œ์š”๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ฃผ๊ฐ„ ๋ฆฌํฌํŠธ ๊ธฐ๋Šฅ์˜ QA๊ฒฐ๊ณผ

 

์ด ๋ฌธ์ œ๋ฅผ ๋ถ„์„ํ•œ ๊ฒฐ๊ณผ, DB ์ฟผ๋ฆฌ๋Š” ์‹คํ–‰ ๊ณ„ํš์ƒ eq_ref ์™€ ref๋กœ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌ๋˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์‘๋‹ต ์‹œ๊ฐ„์ด ๊ธธ์–ด์ง„ ์ฃผ์š” ์›์ธ์€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ์ตœ๋Œ€ 7๋ฒˆ์˜ ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

 

์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ, stream() ๋‚ด๋ถ€์—์„œ ์—ฐ์†์ ์ธ ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ ์ผ์ผ ์„œ๋น„์Šค์™€ Clova AI๊ฐ€ ๋™๊ธฐ์ ์œผ๋กœ ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์ฃผ๊ณ ๋ฐ›๋Š” ๊ตฌ์กฐ๋กœ ๋™์ž‘ํ•˜๊ณ  ์žˆ์—ˆ๊ณ , ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค.

Map<์™ธ๋ถ€ API ๊ฒฐ๊ณผ, List<Value>> valuesByKey = map.values().stream()  <<-- ์™ธ๋ถ€ API ์‘๋‹ต collect
                .collect(Collectors.toMap(
                        values -> ์™ธ๋ถ€ API ํ˜ธ์ถœ,                     <<-- ์™ธ๋ถ€ API ํ˜ธ์ถœ
                        values -> values));

3 ๊ฐ€์ง€ ๋Œ€์•ˆ ๋ฐฉ์•ˆ

๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด 3 ๊ฐ€์ง€ ๋Œ€์•ˆ์„ ๊ณ ๋ คํ–ˆ์Šต๋‹ˆ๋‹ค.

  1. parallel()์„ ์‚ฌ์šฉํ•˜์—ฌ ์ŠคํŠธ๋ฆผ์„ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  2. ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ @Async / CompletableFuture ๋ฅผ ํ™œ์šฉํ•ด ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  3. ์™ธ๋ถ€ API ํ˜ธ์ถœ ๋ฐฉ์‹์„ Non-Blocking I/O๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
    • ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ OpenFeign ๋Œ€์‹  Spring WebFlux์˜ WebClient๋กœ ๊ต์ฒดํ•˜๋Š” ๋“ฑ ์ƒˆ๋กœ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋„์ž…ํ•œ๋‹ค.

์ด ์ค‘ 2๋ฒˆ, @Async / CompletableFuture ๋ฅผ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ํ˜ธ์ถœ ๋ฐฉ์‹์„ ์„ ํƒํ•œ ์ด์œ ์™€ ๋‹ค๋ฅธ ๋Œ€์•ˆ์„ ๋ฐฐ์ œํ•œ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

1๋ฒˆ์ธ parallel() ์„ ์‚ฌ์šฉํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ๋ฐฐ์ œํ•œ ์ด์œ 

parallel() ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ŠคํŠธ๋ฆผ์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์€ ์ด์œ ๋Š” paralle(๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ)์˜ ๋™์ž‘ ๋ฐฉ์‹์€ CPU ์ž‘์—…์— ํšจ์œจ์ ์ด๋ฉฐ, I/O ์ž‘์—…(I/O-bound)์— ์ ํ•ฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

 

  • parallel() ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ž๋ฐ”๋Š” ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ™œ์šฉํ•ด ์ŠคํŠธ๋ฆผ์„ ์žฌ๊ท€์ ์œผ๋กœ ๋ถ„ํ• ํ•˜๊ณ (fork), ๊ฐ๊ฐ์˜ ์“ฐ๋ ˆ๋“œ์—์„œ ๋ถ„ํ• ํ•œ ์ŠคํŠธ๋ฆผ์„ ์ž‘์—…ํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด ๋ƒ…๋‹ˆ๋‹ค. ์ด ํ›„, ์ด ๊ฒฐ๊ณผ๋“ค์„ ํ•ฉ์น˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.(join)
  • ์ด ๋•Œ, ์ŠคํŠธ๋ฆผ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์“ฐ๋ ˆ๋“œ๋“ค์€ ForkJoinPool ํ†ตํ•ด ์ƒ์„ฑํ•˜๋ฉฐ ForkJoinPool ์€ JVM์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” CPU ์ฝ”์–ด ์ˆ˜์— ๋งž์ถฐ ์“ฐ๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
int threads = Runtime.getRuntime().availableProcessors();
  • ๊ทธ๋ฆฌ๊ณ , ๊ฐ๊ฐ์˜ ์“ฐ๋ ˆ๋“œ๋“ค์€ ๋ถ„ํ• ๋œ stream() ์—์„œ ์ž์‹ ์˜ ์—ฐ์‚ฐ์„ ๋งˆ์ณ์„œ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด ๋ƒˆ๋‹ค๋ฉด ์ž‘์—… ํ›”์น˜๊ธฐ(Work Stealing) ๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์˜ ์ž‘์—…์„ "ํ›”์ณ์„œ" ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด CPU ์‚ฌ์šฉ๋ฅ ์„ ๊ทน๋Œ€ํ™”ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด I/O ์ž‘์—…์€ ์“ฐ๋ ˆ๋“œ๊ฐ€ Blocking ์ƒํƒœ๋กœ ์ธํ•ด ์Šค๋ ˆ๋“œ๊ฐ€ ๋Œ€๊ธฐํ•˜๋Š” ์‹œ๊ฐ„์ด ๋งŽ์•„, ์ž‘์—…์„ ๋‚˜๋ˆŒ ์—ฌ์ง€๊ฐ€ ์ ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์ž‘์—…์„ "ํ›”์ณ์˜ค๋Š”" ๊ฒƒ์ด ํฐ ์˜๋ฏธ๋ฅผ ๊ฐ–์ง€ ๋ชปํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ, "๋ชจ๋˜ ์ž๋ฐ” ์ธ ์•ก์…˜"์—์„œ๋„ ์ œ ๋ฌธ์ œ ์ƒํ™ฉ์ฒ˜๋Ÿผ ๋ชจ๋“  ์ž‘์—…์ด I/O ์ž‘์—…์ผ ๊ฒฝ์šฐ, ๋น„๋™๊ธฐ API๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

(์ด๋Ÿฌํ•œ ์ด์œ ๋กœ ForkJoinPool์˜ Work Stealing ์— ๋Œ€ํ•œ ์†Œ๊ฐœ๋ฅผ ๋ณด์ž๋งˆ์ž ํ•ด๋‹น ๋ฐฉ์‹์€ ๋งž์ง€ ์•Š๊ตฌ๋‚˜๋ฅผ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Work Stealing ๊ณผ ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ๋‹ค์Œ๋ฒˆ์— ๊ณต๋ถ€ํ•˜๋Š” ๊ฑธ๋กœ..) 

 

3๋ฒˆ์ธ Non-Blocking I/O(WebClient)๋กœ์˜ ๊ต์ฒด๋ฅผ ๋ฐฐ์ œํ•œ ์ด์œ 

์ฆ‰๊ฐ์ ์ธ ์ ์šฉ ์–ด๋ ค์›€

  • WebClient์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์™„์ „ํžˆ ํ•™์Šตํ•ด์•ผ ํ•˜๋ฉฐ, ์ด๋Š” ํ˜„์žฌ ์„ฑ๋Šฅ ๊ฐœ์„ ์ด ๊ธด๊ธ‰ํžˆ ์š”๊ตฌ๋˜๋Š” ์ƒํ™ฉ์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

ํŒ€์›์˜ ํ•™์Šต ๋น„์šฉ ์ฆ๊ฐ€

  • ํ˜‘์—… ํ”„๋กœ์ ํŠธ์—์„œ OpenFeign์„ ์ฃผ๋กœ ์‚ฌ์šฉ ์ค‘์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, WebClient๋กœ ๊ต์ฒดํ•  ๊ฒฝ์šฐ ํŒ€์›๋“ค์ด WebClient์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์ตํžˆ๋Š” ๋ฐ ์ถ”๊ฐ€์ ์ธ ํ•™์Šต ์‹œ๊ฐ„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

 

2๋ฒˆ์ธ '@Async / CompletableFuture ๋ฅผ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ' ์„ ํƒํ•œ ์ด์œ 

๋น ๋ฅธ ์ ์šฉ ๊ฐ€๋Šฅ

  • ํŒ€์› ๋ชจ๋‘๊ฐ€ ๊ฐ™์€ ์ฝ”๋“œ์Šค์ฟผ๋“œ ์ˆ˜๋ฃŒ์ƒ์ด์—ˆ์œผ๋ฉฐ, ํ•ด๋‹น ๊ต์œก ๊ธฐ๊ด€์—์„œ CompletableFuture ๋ฅผ ํ™œ์šฉํ•œ ๊ฒฝํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋น„๋™๊ธฐ์— ๋Œ€ํ•ด ๊ธฐ๋ณธ์ ์ธ ์ง€์‹์„ ๋ชจ๋‘ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฌผ๋ก , 2๋ฒˆ๋„ ๊ณ ๋ คํ•  ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ๋ฅผ ์œ„ํ•ด ์“ฐ๋ ˆ๋“œ ํ’€์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ๋™์‹œ ์š”์ฒญ์ด ๋งŽ์„ ๊ฒฝ์šฐ ์Šค๋ ˆ๋“œ ํ’€์˜ ํ ์šฉ๋Ÿ‰์ด ๊ฐ€๋“ ์ฐจ๋ฉด์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋Œ€๊ธฐํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ๋Š” ๋ณ‘๋ชฉ ํ˜„์ƒ์ด @Async์˜ ์Šค๋ ˆ๋“œ ํ’€ ๋•Œ๋ฌธ์ธ์ง€, ๋‹ค๋ฅธ ์ง€์ ์—์„œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€๋ฅผ ํŒŒ์•…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๊ธฐ์กด์— ๊ตฌ์ถ•ํ•œ Prometheus์™€ Grafana๋ฅผ ํ™œ์šฉํ•ด ๋ฌธ์ œ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ๋ถ„์„ํ•  ๊ณ„ํš์— ์žˆ์Šต๋‹ˆ๋‹ค.

 

2๋ฒˆ ๋ฐฉ๋ฒ• ์“ฐ๋ ˆ๋“œ ํ’€ ์„ค์ • ๊ณผ์ •

๋จผ์ € ์ฝ”๋“œ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฌ๊ณ , ๊ทธ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ด์–ด๋‚˜๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๋จผ์ €, ์“ฐ๋ ˆ๋“œ ํ’€ ์„ค์ • ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

@EnableAsync
@Configuration
public class AsyncConfig {

    @Bean
    public Executor clovaExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(16);
        executor.setMaxPoolSize(32);
        executor.setQueueCapacity(100);
        executor.setKeepAliveSeconds(60); // ๊ธฐ๋ณธ ๊ฐ’ 60 ์ด์ง€๋งŒ, ๋ช…์‹œ์ ์œผ๋กœ ์•Œ๊ธฐ ์œ„ํ•จ
        executor.setThreadNamePrefix("clova-async-");
        executor.setAwaitTerminationSeconds(60);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setRejectedExecutionHandler(new CallerRunsPolicy());
        executor.initialize();
    }
}

 

@EnableAsync ์„ค๋ช…

@EnableAsync๋Š” Spring์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์„ ํ™œ์„ฑํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ž…๋‹ˆ๋‹ค. Spring์€ ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ ์‹คํ–‰ ์‹œ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•  ์Šค๋ ˆ๋“œ ํ’€์„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ˆœ์„œ๋กœ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.

  • ์Šคํ”„๋ง ์ปจํ…์ŠคํŠธ์— ์œ ์ผํ•œ org.springframework.core.task.TaskExecutor ๋นˆ์ด ์žˆ๊ฑฐ๋‚˜, java.util.concurrent.Executor ๋นˆ์˜ ์ด๋ฆ„์ด "taskExecutor"๋ผ๋ฉด ์ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ ์œ„์˜ ๋‘ ๊ฐ€์ง€ ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ๋นˆ์ด ์—†์œผ๋ฉด, Spring์€ org.springframework.core.task.SimpleAsyncTaskExecutor๋ฅผ ์‚ฌ์šฉํ•ด ๋น„๋™๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    • SimpleAsyncTaskExecutor ๋Š” ๋น„๋™๊ธฐ ์ž‘์—… ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ๋งˆ๋‹ค ์“ฐ๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋ฏ€๋กœ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
-- @EnableAsync ์˜ Javadoc ์ผ๋ถ€
By default, Spring will be searching for an associated thread pool definition: either a unique org.springframework.core.task.TaskExecutor bean in the context, or an java.util.concurrent.Executor bean named "taskExecutor" otherwise. If neither of the two is resolvable, a org.springframework.core.task.SimpleAsyncTaskExecutor will be used to process async method invocations. Besides, annotated methods having a void return type cannot transmit any exception back to the caller. By default, such uncaught exceptions are only logged.

 

Spring Boot ์™€ @EnableAsync

ํ•˜์ง€๋งŒ Spring Boot ์™€ ํ•จ๊ป˜ @EnableAsync ์‚ฌ์šฉ ์‹œ, ์“ฐ๋ ˆ๋“œ ํ’€ ์„ค์ •์„ ๋”ฐ๋กœํ•˜์ง€ ์•Š์•„๋„ SimpleAsyncTaskExecutor ๋Œ€์‹  Auto configuration ์— ์˜ํ•ด ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋œ ์“ฐ๋ ˆ๋“œ ํ’€์ด ์“ฐ์ž…๋‹ˆ๋‹ค.

Spring Boot 2.1 ๋ถ€ํ„ฐ ์ž๋™ ๊ตฌ์„ฑ์— ์˜ํ•ด org.springframework.boot.autoconfigure.task์˜ TaskExecutionProperties ๊ฐ€ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ์„ค์ •์˜ ๊ธฐ๋ณธ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ๊ธฐ๋ณธ ์“ฐ๋ ˆ๋“œ ์ˆ˜: 8
  • ์ตœ๋Œ€ ์“ฐ๋ ˆ๋“œ: INTEGER.MAX_VALUE
  • ํ ์šฉ๋Ÿ‰: INTEGER.MAX_VALUE
  • ์ตœ๋Œ€ ์“ฐ๋ ˆ๋“œ์˜ ์‚ด์•„์žˆ๋Š” ์‹œ๊ฐ„: 60์ดˆ

๊ทธ๋Ÿฌ๋‚˜, ์ด ์„ค์ •์€ ์„œ๋ฒ„ ์ŠคํŽ™๊ณผ ๋งž์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์„œ๋ฒ„ ์ŠคํŽ™์— ๋งž๊ฒŒ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

 

๋˜ํ•œ, ์Šคํ”„๋ง ๋ถ€ํŠธ๋„ ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์“ฐ๋ ˆ๋“œ ํ’€์„ 2๊ฐœ ์ด์ƒ ์„ค์ • ์‹œ, @Async ์–ด๋…ธํ…Œ์ด์…˜์— ์–ด๋–ค ์“ฐ๋ ˆ๋“œ ํ’€์„ ์‚ฌ์šฉํ•  ๊ฑด์ง€ ์ •์˜ํ•˜์ง€ ์•Š์œผ๋ฉด @Async ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ SimpleAsyncTaskExecutor ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋‹ˆ ์ฃผ์˜ํ•ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

@Bean
public Executor asyncExecutor1() {
    ThreadPoolTaskExecutor executor1 = new ThreadPoolTaskExecutor();
    return executor1;
}

@Bean
public Executor asyncExecutor2() {
    ThreadPoolTaskExecutor executor2 = new ThreadPoolTaskExecutor();
    return executor2;
}

...
@Async // asyncExecutor1 ๊ณผ aysncExecutor2 ์ค‘ ์–ด๋–ค ์“ฐ๋ ˆ๋“œ ํ’€์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ์ง€ ๋ช…์‹œํ•ด์•ผ ํ•œ๋‹ค.
public void doAsync()...

 

์šฐ์•„ํ•œ ์ข…๋ฃŒ๋ฅผ ์œ„ํ•œ ThreadPoolTaskExecutor ์‚ฌ์šฉ

๋น„๋™๊ธฐ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด Spring์˜ ThreadPoolTaskExecutor๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” Java์˜ ThreadPoolExecutor๋ฅผ Spring ์ƒ๋ช…์ฃผ๊ธฐ์— ๋งž๊ฒŒ ํ™•์žฅํ•œ ๊ตฌํ˜„์ฒด๋กœ, ์šฐ์•„ํ•œ ์ข…๋ฃŒ(graceful shutdown)๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. 

 

ThreadPoolTaskExecutor๋Š” ์“ฐ๋ ˆ๋“œ ํ’€ ์ข…๋ฃŒ ๋ฉ”์„œ๋“œ์ธ shutdown()์„ Spring์˜ ๋ผ์ดํ”„์‚ฌ์ดํด๊ณผ ๊ด€๋ จ์ง€์–ด ์ถ”์ƒํ™”ํ•˜์—ฌ ์ œ๊ณตํ•จ์œผ๋กœ์จ, ์“ฐ๋ ˆ๋“œ ํ’€์˜ ์šฐ์•„ํ•œ ์ข…๋ฃŒ๋ฅผ ์Šคํ”„๋ง ๋ผ์ดํ”„์‚ฌ์ดํด์— ๋งž์ถฐ ์‰ฝ๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์šฐ์•„ํ•œ ์ข…๋ฃŒ๋ž€?

  • ์„œ๋ฒ„ ์ข…๋ฃŒ ์‹œ, ์Šค๋ ˆ๋“œ ํ’€์˜ ์ž‘์—… ์ค‘์ธ ์Šค๋ ˆ๋“œ์™€ ๋Œ€๊ธฐ ์ค‘์ธ ํ์˜ ์ž‘์—…์ด ๋ชจ๋‘ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์Šค๋ ˆ๋“œ ํ’€์˜ ์ข…๋ฃŒ๋ฅผ ์ง€์—ฐ์‹œํ‚ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ ์„œ๋ฒ„๊ฐ€ ์ข…๋ฃŒ ์ค‘์— ์Šค๋ ˆ๋“œ ํ’€์ด ๊ฐ•์ œ๋กœ ์ข…๋ฃŒ๋œ๋‹ค๋ฉด I/O ์ž‘์—… ์ค‘์ด๋˜ ์Šค๋ ˆ๋“œ๊ฐ€ ์ž‘์—…์„ ์™„๋ฃŒํ•˜์ง€ ๋ชปํ•˜๊ฑฐ๋‚˜, ํ์— ๋Œ€๊ธฐ ์ค‘์ด๋˜ ์ž‘์—…์ด ์ฒ˜๋ฆฌ๋˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์Šค๋ ˆ๋“œ์™€ ํ์˜ ๋ชจ๋“  ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ์ข…๋ฃŒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค๋งŒ, ์šฐ์•„ํ•œ ์ข…๋ฃŒ ๊ณผ์ •์—์„œ ์ž‘์—…์˜ ์ข…๋ฃŒ ์‹œ๊ฐ„์ด ์ง€๋‚˜์น˜๊ฒŒ ๊ธธ์–ด์งˆ ๊ฒฝ์šฐ ์„œ๋น„์Šค๊ฐ€ ์ข…๋ฃŒ๋˜์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ์ž‘์—… ์™„๋ฃŒ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ตœ๋Œ€ ์‹œ๊ฐ„์„ ์„ค์ •ํ•ด์„œ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜๋ฉด ์Šค๋ ˆ๋“œ ํ’€์ด ๊ฐ•์ œ๋กœ ์ข…๋ฃŒ๋˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setAwaitTerminationSeconds(60);

(++์ฒ˜์Œ์—๋Š” ๋ถ„๋ช…ํžˆ ์šฐ์•„ํ•œ ์ข…๋ฃŒ๋ฅผ ์ง€์›ํ•ด์ฃผ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์„ํ…๋ฐ ํ•˜๋ฉฐ ThreadPoolTaskExecutor์˜ Javadoc ์„ ํ•ด์„ํ•˜๋ฉฐ ์‹œ๊ฐ„์„ ๋งŽ์ด ์†Œ๋น„ํ•˜๊ณ , ์ •๋ฆฌํ•  ์—„๋‘๊ฐ€ ์•ˆ๋‚ฌ์—ˆ๋Š”๋ฐ ์šฐ์•„ํ•œ ์ข…๋ฃŒ๋ฅผ ์–ด๋–ป๊ฒŒ ์ง€์›ํ•ด์ฃผ๋Š”์ง€, ์ •๋ฆฌ๊ฐ€ ์ž˜ ๋œ ๊ธ€์„ ์ฐพ์•„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ThreadPoolTaskExecutor์˜ waitForTasksToCompleteOnShutdown ์†์„ฑ ์•Œ์•„๋ณด๊ธฐ)

 

I/O bound ์— ๋งž๊ฒŒ ์“ฐ๋ ˆ๋“œ ํ’€์˜ ์“ฐ๋ ˆ๋“œ ์„ค์ •

์ผ๋ฐ˜์ ์œผ๋กœ I/O ์ž‘์—…์ด ๋งŽ์€ ๊ฒฝ์šฐ, ์“ฐ๋ ˆ๋“œ ํ’€์˜ coreSize๋Š” ์ฝ”์–ด์˜ 2~3๋ฐฐ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.([Youtube] ์‰ฌ์šด ์ฝ”๋“œ - ์Šค๋ ˆ๋“œ ํ’€ ์„ค์ •)

ํ•˜์ง€๋งŒ, ์ •ํ™•ํ•œ ๊ฐ’์€ ์•„๋ž˜ ๊ณต์‹์„ ๊ณ„์‚ฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์ €๋Š” ์•„๋ž˜ ๊ณต์‹์„ ์–ด๋–ค ์ƒํ™ฉ์—์„œ์˜ CPUํ™œ์šฉ ๋น„์œจ์„ ์ธก์ •ํ•ด์•ผ ํ•˜๋Š”์ง€, ๋Œ€๊ธฐ์‹œ๊ฐ„๊ณผ ๊ณ„์‚ฐ์‹œ๊ฐ„์„ ์–ธ์ œ ์ธก์ •ํ•ด์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•ด ์•„์ง ํ•™์Šต์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ, ๊ณต์‹์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ coreSize ๋ฅผ 2๋ฐฐ๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

-- ์ถœ์ฒ˜: ๋ชจ๋˜ ์ž๋ฐ” ์ธ ์•ก์…˜
์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฝ”์–ด ์ˆ˜ * 0๊ณผ 1 ์‚ฌ์ด์˜ ๊ฐ’์„ ๊ฐ–๋Š” CPUํ™œ์šฉ ๋น„์œจ * (1+ ๋Œ€๊ธฐ์‹œ๊ฐ„/๊ณ„์‚ฐ์‹œ๊ฐ„)

 

ThreadPoolTaskExecutor์˜ BlockingQueue

ThreadPoolTaskExecutor์˜ ํ๋Š” ์–ด๋–ค BlockingQueue ๋กœ ๊ตฌํ˜„๋˜์–ด์žˆ๋Š”์ง€ ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค.

์–‘์ˆ˜๋ฉด LinkedBlockingQueue, ๋‹ค๋ฅธ ๊ฐ’์€ SynchronousQueue ๋กœ ๊ตฌํ˜„๋จ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ThreadPoolTaskExecutor์˜ setQueueCapacity ๋ฉ”์„œ๋“œ

 

ํ•ด๋‹น ๊ตฌํ˜„์ฒด ๋ฐ ์ฃผ์š” BlockingQueue ๊ตฌํ˜„์ฒด์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

  • LinkedBlockingQueue
    • ํฌ๊ธฐ๊ฐ€ ๋ฌดํ•œํ•˜๊ฑฐ๋‚˜ ๊ณ ์ •๋œ FIFO ํ˜•ํƒœ์˜ ํ ๋ˆ๋‹ค.
  • SynchronousQueue
    • ๋ฐ์ดํ„ฐ๋ฅผ ํ์— ์ €์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ƒ์‚ฐ์ž๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์†Œ๋น„์ž๊ฐ€ ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•œ๋‹ค. ์ฆ‰, ์ค‘๊ฐ„์— ํ ์—†์ด ์ƒ์‚ฐ์ž์™€ ์†Œ๋น„์ž๊ฐ„์˜ ์ง์ ‘ ๊ฑฐ๋ž˜ํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜.
  • ArrayBlockingQueue
    • ํฌ๊ธฐ๊ฐ€ ๊ณ ์ •๋œ ๋ธ”๋กœํ‚น ํ๋กœ ๊ณต์ •(fair)๋ชจ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ๋‹ค.
    • ๊ณต์ • ๋ชจ๋“œ: ํŠน์ • ํ์— ์žˆ๋Š” ์ž‘์—…์„ ๋จผ์ € ๋Œ€๊ธฐํ•˜๊ณ  ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๋“ค ์ค‘ ํŠน์ • ์Šค๋ ˆ๋“œ๋กœ ํ•˜์—ฌ๊ธˆ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒŒ ํŠน์ง•์ด๋‹ค.
  • PrioirtyBlockingQueue
    • ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์š”์†Œ๋ฅผ ๋จผ์ € ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. (๊ทธ ์ด์ƒ์˜ ์„ค๋ช…์€ Javadoc์„ ๋ด์•ผํ•ฉ๋‹ˆ๋‹ค..)
  • DelayQueue
    • ๊ฐ ์š”์†Œ๋Š” ์ง€์ •๋œ ์ง€์—ฐ ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„์— ์†Œ๋น„๋  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์Šค์ผ€์ค„๋ง ์ž‘์—…์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ € ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ๊ณต์ •๋ชจ๋“œ, ์šฐ์„ ์ˆœ์œ„, ์ง€์—ฐ ์š”์†Œ๋ฅผ ๊ณ ๋ คํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๊ณ  ํŒ๋‹จํ•ด์„œ ThreadPoolTaskExecutor์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ธ LinkedBlockingQueue ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์œผ๋ฉฐ ํ์˜ ์‚ฌ์ด์ฆˆ๋ฅผ ์ž„์˜์˜ ๊ฐ’์ธ 100์œผ๋กœ ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

 

๋‹ค๋งŒ, ํ์˜ ์šฉ๋Ÿ‰์— ์ œํ•œ์„ ๋‘”๋‹ค๋ฉด ํ์˜ ์šฉ๋Ÿ‰์ด ๊ฐ€๋“ ์ฐผ์„ ๋•Œ์˜ ์ž‘์—…์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ๊ฒƒ์ธ์ง€ ๊ฑฐ์ ˆ ์ •์ฑ…์„ ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

CallerRunsPolicy ์„ ํ†ตํ•ด ์ž‘์—… ์ œ์ถœ ์Šค๋ ˆ๋“œ๋ฅผ ํ™œ์šฉํ•˜๊ธฐ

๊ฑฐ์ ˆ ์ •์ฑ…์€ ์ž‘์—…์ด ์Šค๋ ˆ๋“œ ํ’€์˜ ์ตœ๋Œ€ ํฌ๊ธฐ์— ๋„๋‹ฌํ•˜๊ณ  ํ๊ฐ€ ๊ฐ€๋“ ์ฐผ์„ ๋•Œ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • AbortPolicy: ๊ธฐ๋ณธ ๊ฑฐ์ ˆ ์ •์ฑ…์œผ๋กœ, ์ƒˆ๋กœ์šด ์ž‘์—…์„ ์ œ์ถœํ•  ๋•Œ, RejectedExecutionException ์„ ๋ฐœ์ƒํ•˜๋ฉฐ ๊ฑฐ์ ˆํ•ฉ๋‹ˆ๋‹ค.
  • DiscardPolicy: ์ƒˆ๋กœ์šด ์ž‘์—…์„ ์กฐ์šฉํžˆ ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.
  • CallerRunPolicy: ์ƒˆ๋กœ์šด ์ž‘์—…์„ ์ œ์ถœํ•œ ์Šค๋ ˆ๋“œ๊ฐ€ ๋Œ€์‹ ํ•ด์„œ ์ง์ ‘ ์ž‘์—…์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž ์ •์˜(RejectedExecutionHandler): ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ •์˜ํ•œ ๋กœ์ง์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ €๋Š” CallerRunsPolicy๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

1. ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์€ ์ƒํ™ฉ์—์„œ์˜ ์ตœ์†Œํ•œ์˜ ์ž‘์—… ์ฒ˜๋ฆฌ

  • ์ž‘์—… ์ œ์ถœ์ด ๊ฑฐ๋ถ€๋˜๋Š” ์ƒํ™ฉ์€ ๋Œ€์ฒด๋กœ ํŠธ๋ž˜ํ”ฝ์ด ๊ณผ๋„ํ•˜๊ฒŒ ๋งŽ์€ ์ƒํ™ฉ์ผ ๊ฒƒ์ด๋ผ ์˜ˆ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ, CallerRunsPolicy ๋ฅผ ํ†ตํ•ด ์ž‘์—…์„ ์ œ์ถœํ•œ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ตœ์†Œํ•œ์˜ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

2. ์–ด์ฐจํ”ผ join() ๊ณผ allOf() ๋กœ ์ธํ•ด ์ž‘์—… ์ œ์ถœ ์Šค๋ ˆ๋“œ๊ฐ€ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค.

  • ์•„์ง ์„ค๋ช…์— ์•ˆ๋‚˜์™”์ง€๋งŒ, ์ €๋Š” @Async ๋Œ€์‹  CompletableFuture ๋ฅผ ํ™œ์šฉํ•ด 7๋ฒˆ์˜ ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ๋น„๋™๊ธฐ ์ž‘์—…์œผ๋กœ ์ฒ˜๋ฆฌํ•œ ํ›„, join() ์„ ํ†ตํ•ด ์ž‘์—… ์ œ์ถœ ์Šค๋ ˆ๋“œ๊ฐ€ ๋Œ€๊ธฐํ•˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
  • ์–ด์ฐจํ”ผ ํ˜ธ์ถœ ์Šค๋ ˆ๋“œ๊ฐ€ ๋Œ€๊ธฐ ์ค‘์ด๋ผ๋ฉด, CallerRunsPolicy ๋ฅผ ์‚ฌ์šฉํ•ด ํ˜ธ์ถœ ์Šค๋ ˆ๋“œ๊ฐ€ ์™ธ๋ถ€ API ํ˜ธ์ถœ ์ž‘์—…์„ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•˜๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค

๋‹ค๋งŒ, ์ด๋Ÿฌํ•œ ๊ณผ์ •์€ ๋งˆ์น˜ ํ๊ฐ€ ๊ฝ‰ ์ฐจ๋”๋ผ๋„ ํ˜ธ์ถœ ์Šค๋ ˆ๋“œ๊ฐ€ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๋ฉด์„œ, ์‹œ์Šคํ…œ์ด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ณผ๋„ํ•œ ํŠธ๋ž˜ํ”ฝ ๋ฌธ์ œ๋ฅผ ์ฆ‰๊ฐ ์ธ์ง€ํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์œ„ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด, ๋ชจ๋‹ˆํ„ฐ๋ง ์„œ๋ฒ„์— ํŠธ๋ž˜ํ”ฝ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์•Œ๋ฆผ ์‹œ์Šคํ…œ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

 

์ฝ”๋“œ ์„ค๋ช…: CompletableFuture๋ฅผ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ์œผ๋กœ ๋‚˜ํƒ€๋‚ธ ์ „์ฒด ํ๋ฆ„๋„์ž…๋‹ˆ๋‹ค.

3๋ฒˆ์ด CompletableFuture ๋ฅผ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์ด๋ฉฐ, 3๋ฒˆ๊ณผ 4๋ฒˆ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ์˜ 3๋ฒˆ์€ ์•„๋ž˜ ์ฝ”๋“œ 3๋ฒˆ์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

// FACADE ๊ณ„์ธต์˜ ์ฝ”๋“œ ์ค‘ ์ผ๋ถ€

public ์‘๋‹ต๊ฐ’ createWeeklyReport(UUID userId, LocalDate startDate) {
    1. ์ฃผ๊ฐ„ ๋ฆฌํฌํŠธ ์ƒ์„ฑ ์—ฌ๋ถ€ ํ™•์ธ
    2. ํด๋กœ๋ฐ”์—๊ฒŒ ์š”์ฒญ์„ ๋ณด๋‚ผ 1์ฃผ์ผ์น˜ ํŽธ์ง€ ์กฐํšŒ
    3. ์ผ์ผ ๋ฆฌํฌํŠธ๋ฅผ ๋งŒ๋“ค ํŽธ์ง€๋“ค์„ Clova์—๊ฒŒ ์ „์†ก       <-- ์™ธ๋ถ€ API ์‚ฌ์šฉ ๋ฐ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
    4. ํด๋กœ๋ฐ”๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ผ์ผ ๋ฆฌํฌํŠธ์˜ ๋ถ„์„๊ฒฐ๊ณผ๋“ค์„ ์ €์žฅ <-- ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘ ๋ฐ ์ข…๋ฃŒ
    5. ์ฃผ๊ฐ„ ๋ถ„์„์— ํ•„์š”ํ•œ ์œ„๋กœ ํ•œ๋งˆ๋”” ์กฐํšŒ
    6. ์ฃผ๊ฐ„ ๋ฆฌํฌํŠธ ์š”์ฒญ                                <-- ์™ธ๋ถ€ API ์‚ฌ์šฉ
    7. ์ฃผ๊ฐ„ ๋ฆฌํฌํŠธ ์ €์žฅ                                <-- ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘ ๋ฐ ์ข…๋ฃŒ
    return ์ฃผ๊ฐ„ ๋ฆฌํฌํŠธ ์ €์žฅ ํ›„ ๋ฐ˜ํ™˜๋ฐ›์€ ์‘๋‹ต๊ฐ’; 
}

 

์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ๋‹ด๋‹นํ•˜๋Š” Clova ์„œ๋น„์Šค์—์„œ๋Š” CompletableFuture.supplyAsync() ๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ๋กœ ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ–ˆ์œผ๋ฉฐ, ๋น„๋™๊ธฐ์— ํ•„์š”ํ•œ ์“ฐ๋ ˆ๋“œ๋Š” ์ˆ˜๋™ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ ์“ฐ๋ ˆ๋“œ ํ’€์„ ์‚ฌ์šฉํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

public class ClovaService {

    private final Executor clovaExecutor;

    public CompletableFuture<ClovaResponseDto> sendAsyncDailyReportRequest(...) {
        return CompletableFuture.supplyAsync(() -> {
            ...๋กœ์ง ์ƒ๋žต
            return ์™ธ๋ถ€ API ํ˜ธ์ถœ;
        }, clovaExecutor);
    }

 

์ด๋ ‡๋“ฏ ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด CompletableFuture๋ฅผ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , thenApply ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต ๊ฐ’์— ๋Œ€ํ•œ ์ฝœ๋ฐฑ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

// 3๋ฒˆ ๋กœ์ง ์ค‘, ์ผ๋ถ€
Map<CompletableFuture<ํ‚ค>, List<๊ฐ’>> futures = Map.values().stream()
                .collect(Collectors.toMap(
                        ๊ฐ’ -> clovaService.sendAsyncDailyReportRequest(letters)
                                .thenApply(~~),
                        ๊ฐ’ -> ๊ฐ’
                ));

 

โ–ถ ์™ธ๋ถ€ API ๊ฒฐ๊ณผ ์ €์žฅํ•˜๋Š” ๋กœ์ง์„ ๋น„๋™๊ธฐ ์ž‘์—… ํ›„ ์ฝœ๋ฐฑ์— ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์€ ์ด์œ 

์™ธ๋ถ€ API ํ˜ธ์ถœ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•˜๋Š” ์ž‘์—…์„ ์ฝœ๋ฐฑ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, ๊ทธ๋ ‡๊ฒŒ ๋˜๋ฉด save ์ฟผ๋ฆฌ๊ฐ€ ์ตœ๋Œ€ 7๋ฒˆ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜์–ด saveAll๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์†๋„๊ฐ€ ๋Š๋ ค์กŒ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, saveAll์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋น„๋™๊ธฐ ๊ฒฐ๊ณผ๊ฐ€ ๋ชจ๋‘ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ join() ์ด๋‚˜ allOf()๋กœ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ํ•œ ๋ฒˆ์— ์ €์žฅํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

โ–ถ @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜์ง€ ์•Š์€ ์ด์œ ?

@Async๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Async-nonBlocking ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜์—ฌ ์ฝœ๋Ÿฌ ์Šค๋ ˆ๋“œ์—๊ฒŒ ์ œ์–ด๊ถŒ์„ ์ฆ‰์‹œ ๋„˜๊ฒจ์„œ ์ฝœ๋Ÿฌ๊ฐ€ ๋‹ค์Œ ๋กœ์ง์„ ์‹คํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ €๋Š” saveAll() ์„ ์œ„ํ•ด join() ์ฒ˜๋ฆฌ(Blocking)๊ฐ€ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ CompletableFuture๋กœ ํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ, @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ–ˆ๋‹ค๋ฉด CompletableFuture.supplyAsync() ๊ฐ€ ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ CompletableFuture.completedFuture() ๋กœ ๋ณ€ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

์ด๋ ‡๊ฒŒ ๋  ๊ฒฝ์šฐ, ๋น„๋™๊ธฐ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” CompletableFuture๊ณผ @Async ์˜ ์—ญํ• ์ด ์ค‘๋ณต๋œ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— @Async๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

public class ClovaService {

    @Async("clovaExecutor")
    public CompletableFuture<ClovaResponseDto> sendAsyncDailyReportRequest(...) {
        
        ...๋กœ์ง ์ƒ๋žต
        return CompletableFuture.completedFuture(์™ธ๋ถ€ API ํ˜ธ์ถœ);
    }

 

โ–ถ CompletableFuture ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ForkJoinPool ๋Œ€์‹  ThreadPoolTaskExecutor ์„ ์‚ฌ์šฉํ•œ ์ด์œ 

CompletableFuture๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ForkJoinPool ์˜ ์“ฐ๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ, ์“ฐ๋ ˆ๋“œ ํ’€์„ ์„ค์ •ํ•˜์ง€ ์•Š๋”๋ผ๋„ ๋น„๋™๊ธฐ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ForkJoinPool์˜ ์“ฐ๋ ˆ๋“œ๋Š” ๋ฐ๋ชฌ ์“ฐ๋ ˆ๋“œ๋กœ, I/O ์ž‘์—…์ด ์™„๋ฃŒ๋˜์ง€ ์•Š๋”๋ผ๋„ JVM์ด ์ข…๋ฃŒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, I/O ์ž‘์—… ์ค‘ ์„œ๋ฒ„๊ฐ€ ์ข…๋ฃŒ๋˜๊ณ  ์šฐ์•„ํ•œ ์ข…๋ฃŒ๋Š” ๋ถˆ๊ฐ€ํ•˜๊ฒŒ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ThreadPoolTaskExecutor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, CompletableFuture ์ž‘์—…์ด ๋ฐ๋ชฌ ์“ฐ๋ ˆ๋“œ๊ฐ€ ์•„๋‹Œ ์‚ฌ์šฉ์ž ์“ฐ๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค

* ์ด์— ๋Œ€ํ•ด ์ •๋ฆฌํ•œ ์ œ ๊ธ€: ์™œ ๋‚ด CompletableFuture ๋Š” Console ์— ๋กœ๊ทธ๊ฐ€ ์—†๋Š”๊ฐ€? ForkJoinPool ์˜ ๋ฐ๋ชฌ ์Šค๋ ˆ๋“œ๋ฅผ ์•Œ์•„๋ณด์ž

 

โ–ถ I/O ์ž‘์—…์„ ๋น„๋™๊ธฐ๋กœ ์ž‘์—… ์‹œ, ํƒ€์ž„์•„์›ƒ ์„ค์ •์€ ์–ด๋–ป๊ฒŒ ํ–ˆ๋Š”์ง€?

๋จผ์ € allOf()๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์ด์œ ๋Š” Map์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด์„œ ์ฝ”๋“œ๊ฐ€ join() ๋ณด๋‹ค ๋ณต์žกํ•ด์กŒ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ, join()์„ stream() ๋‚ด๋ถ€์—์„œ join()์„ ํ™œ์šฉํ•ด ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฌ๋‚˜, stream()์€ ๋ณธ์งˆ์ ์œผ๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ ์—ฐ์‚ฐ์ด ์ง„ํ–‰๋˜๋ฏ€๋กœ, ์•ž์„  ์ž‘์—…์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์œผ๋ฉด ๋’ค์˜ ์ž‘์—… ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด CompletableFuture ๋Š” ๋น„๋™๊ธฐ ์—ฐ์‚ฐ์ด ์ง€์ •๋œ ์‹œ๊ฐ„์•ˆ์— ๋๋‚˜์•ผํ•˜๋Š” orTimeout() ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ, ์ œ ๊ฒฝ์šฐ ๋น„๋™๊ธฐ ์ž‘์—…์˜ ์ง€์—ฐ ๊ฐ€๋Šฅ์„ฑ์ด ์™ธ๋ถ€ API ํ˜ธ์ถœ์— ํ•œ์ •๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— FeignClient ์˜ timeout ์„ 10์ดˆ๋กœ ์„ค์ •ํ•˜์—ฌ stream() ์˜ ์ˆœ์ฐจ์ ์ธ ๋ฌธ์ œ๋ฅผ ๋ณด์™„ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

โ–ถ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” ์–ด๋–ป๊ฒŒ?

CompletableFuture์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, exceptionally()๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ์ˆ˜ํ–‰ํ•  ํ–‰๋™์„ ์ฝœ๋ฐฑ์œผ๋กœ ๋“ฑ๋กํ•˜์—ฌ ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์˜ˆ์™ธ๋ฅผ ๋˜์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ €๋Š” join()์„ ์‚ฌ์šฉํ–ˆ๊ณ , join() ํ˜ธ์ถœ ์‹œ ๋น„๋™๊ธฐ ์ž‘์—… ์ค‘ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด CompletionException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์™ธ๋ฅผ @RestControllerAdvice๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜์˜€๊ณ , ๋‹ค๋ฅธ ์˜ˆ์™ธ๋“ค๊ณผ ํ•จ๊ป˜ ๋ชจ๋“ˆํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

join() ์˜ Javadoc

 

๊ฒฐ๊ณผ: 1๋ถ„ → 15์ดˆ

๋กœ์ปฌ ์„œ๋ฒ„ (๋…ผ๋ฆฌ ์ฝ”์–ด 16, ๋ฉ”๋ชจ๋ฆฌ 16)

  • API ์ „์ฒด ์‘๋‹ต ์‹œ๊ฐ„
    • ๊ธฐ์กด ๋™๊ธฐ ๋ฐฉ์‹: ์•ฝ 1๋ถ„
    • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ํ›„: ์•ฝ 14์ดˆ

๋น„๋™๊ธฐ๋กœ ์‹คํ–‰๋˜๋Š” 7๋ฒˆ์˜ ์™ธ๋ถ€ API ํ˜ธ์ถœ
๋กœ์ปฌ ์„œ๋ฒ„์—์„œ ๋น„๋™๊ธฐ ์ ์šฉ ํ›„์˜ ์ „์ฒด API ์‘๋‹ต ์‹œ๊ฐ„

 

QA ์‹œ ์‚ฌ์šฉํ–ˆ๋˜ ์„œ๋ฒ„ ์ŠคํŽ™์€ ์ฝ”์–ด 2, ๋ฉ”๋ชจ๋ฆฌ 8 ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , ๋™๊ธฐ ๋ฐฉ์‹์˜ API ์ „์ฒด ์‘๋‹ต ์‹œ๊ฐ„์€ ์‚ฌ์šฉ์ž๋‹น 1๋ถ„ 30์ดˆ ~ 2๋ถ„ ์ด์ƒ์œผ๋กœ ๊ฑธ๋ ธ๋Š”๋ฐ, QA๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์ถ”ํ›„์— ์—…๋ฐ์ดํŠธ ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๋งˆ์น˜๋ฉฐ

์ €๋Š” ์ด์ „์—๋„ @Async ๋ฅผ ํ™œ์šฉํ•ด ์ด๋ฉ”์ผ ์ „์†ก์„ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•œ ๊ฒฝํ—˜์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๊ทธ ๋•Œ์™€ ๋‹ค๋ฅธ์ ์€ ๊ณ ๊ธ‰ ์ž๋ฐ” ๊ฐ•์˜๋ฅผ ์™„๊ฐ•์„ ํ•˜๊ธฐ ์ „๊ณผ ํ›„๋กœ, ํ˜„์žฌ๋Š” ์“ฐ๋ ˆ๋“œ ํ’€์„ ๋ฐ”๋ผ๋ณด๋‹ˆ ๋ฐ”๋ผ๋ณด๋Š” ์‹œ๊ฐ์ด ์กฐ๊ธˆ์€ ๋„“์–ด์ ธ์„œ ๊ธ€์ด ๊ธธ์–ด์ง€๊ณ , ์ข€ ๋” ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์–ด ์ฆ๊ฒ๊ฒŒ ๊ฐœ์„ ํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ฝ”๋“œ์Šค์ฟผ๋“œ ๊ต์œก ๋‹น์‹œ ๋น„๋™๊ธฐ๋กœ ์ฝ˜์†”์— CompletableFuture ๋ฅผ ํ™œ์šฉํ•ด ์ฝ˜์†”์— ๋น„๋™๊ธฐ๋กœ ์นดํŽ˜ํ…Œ๋ฆฌ์•„๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒฝํ—˜์ด ๋„์›€์ด ๋˜์–ด์„œ ๋‹คํ–‰์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ์•„์ง CompletableFuture ๊ฐ€ ์“ฐ๋ ˆ๋“œ ํ’€์—์„œ ์–ด๋–ค ์›๋ฆฌ๋กœ join() ํ•˜๋Š”์ง€, ์–ด๋–ป๊ฒŒ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ์ฝ”๋“œ ๋ ˆ๋ฒจ์˜ ๊ฒฝํ—˜์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ์•ž์œผ๋กœ๋„ ๋น„๋™๊ธฐ๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉฐ ๋ณด์™„ํ•ด ๋‚˜๊ฐ€๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค. 

 

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋น„๋™๊ธฐ๋กœ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ–ˆ์ง€๋งŒ, Non-Blocking I/O (WebClient) ๋ฐฉ์‹์„ ํ™œ์šฉํ•ด ์‘๋‹ต ์‹œ๊ฐ„์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ, ์ถ”ํ›„ WebClient ๋ฅผ ํ•™์Šตํ•˜๊ณ  ์ ์šฉํ•ด ๋ณผ ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

 

๋์œผ๋กœ, ๊ธ€์„ ์ฝ์œผ์‹œ๋ฉด์„œ ๋ถ€์กฑํ•œ ๋ถ€๋ถ„์ด๋‚˜ ๊ฐœ์„ ์ด ํ•„์š”ํ•œ ์ ์ด ์žˆ๋‹ค๋ฉด ํ”ผ๋“œ๋ฐฑ ์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

 

@Async ๋ฐ CompletableFuture ํ™œ์šฉ์— ๋„์›€ ์ฃผ์—ˆ๋˜ ์ฐธ๊ณ  ์ž๋ฃŒ๋“ค

- ๋ชจ๋˜ ์ž๋ฐ” ์ธ ์•ก์…˜

- ๊น€์˜ํ•œ๋‹˜ ๊ณ ๊ธ‰ ์ž๋ฐ” ๊ฐ•์˜

- [Spring] @async ๋กœ์ง ์‹คํŒจ ์ผ๋Œ€๊ธฐ, ThreadPoolTaskExecutor์˜ awaitTerminate์™€ Async

- [Youtube] ๊ฐœ๋ฐœ์ž ์žฅ๊ณ ๋‹˜ - ์ž๋ฐ” ๋น„๋™๊ธฐ ๋”ฐ๋ผํ•˜๊ธฐ 4, Spring Async

- Graceful Shutdown in Spring Boot with Sync and Async Tasks

- ThreadPoolTaskExecutor์˜ waitForTasksToCompleteOnShutdown ์†์„ฑ ์•Œ์•„๋ณด๊ธฐ

- ์“ฐ๋ ˆ๋“œ ํ’€๊ณผ ForkJoinPool

- [Java] CompletableFuture ์‚ฌ์šฉ๋ฒ•

- ์žฌ๋Šฅ๋„ท: Spring @Async์™€ CompletableFuture๋ฅผ ์ด์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์˜ ์„ธ๊ณ„๋กœ! 

- f-lab: ์Šคํ”„๋ง๊ณผ CompletableFuture๋ฅผ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•