๐Ÿ›ฃ๏ธ Async โ€“ Spring์—์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๊ตฌํ˜„ํ•˜๊ธฐ

๊น€๊ณต์˜ยท2024๋…„ 8์›” 29์ผ

how-to

๋ชฉ๋ก ๋ณด๊ธฐ
12/12
post-thumbnail

์ด ๊ธ€์—์„œ๋Š” Spring ์„œ๋ฒ„์—์„œ ์ผ๋ถ€ ๋กœ์ง์„ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋‹ค๋ฃน๋‹ˆ๋‹ค.
๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ๐Ÿงญ ํ™˜๊ฒฝ ์„ค์ • ๋ถ€ํ„ฐ ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค๊ณ ์š”?

๋น„๋™๊ธฐ.....? ์•„ ๋‚˜ ์ง„์งœ ๋“ค์–ด๋ดค๋Š”๋ฐ

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

๋™๊ธฐ ์ฒ˜๋ฆฌ(Synchronous)๋Š” ์ˆœ์ฐจ์ ์œผ๋กœ ์ผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. A ์š”์ฒญ ํ›„์— B ์š”์ฒญ์ด ๋“ค์–ด์™”์„ ๋•Œ, A ์š”์ฒญ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์‘๋‹ต์„ ๋ณด๋‚ด๊ณ  B ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์‹์ด๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์šฐ๋ฆฌ๊ฐ€ ์ต์ˆ™ํ•œ ๋ฐฉ์‹์ด๋‹ค.

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ(Asynchronous)๋Š” ์‘๋‹ต๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋‹ค์Œ ์ผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์œ„์™€ ๊ฐ™์€ ์˜ˆ์‹œ๋กœ ๋“ค์–ด๋ณด์ž๋ฉด, A ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์‹œ์— B ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๊ฐ ์š”์ฒญ์˜ ์‘๋‹ต์€ ๊ฐ ์š”์ฒญ์˜ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋œ ์‹œ์ ์— ๋ฐœ์†ก๋œ๋‹ค. ์ด๋•Œ ํ•˜๋‚˜์˜ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค์Œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค๋Š” ์ ์ด ๋™๊ธฐ ์ฒ˜๋ฆฌ์™€ ๋ช…ํ™•ํ•œ ์ฐจ์ด์ ์„ ๋ณด์ธ๋‹ค.

๊ทธ๋ž˜์„œ ์ด๊ฑธ ์™œ ์จ์•ผ ํ•œ๋‹ค๊ณ ?

์ง„ํ–‰ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์—์„œ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๊ณ , ํ•ด๋‹น ํŒŒ์ผ์— ๋Œ€ํ•œ ์ธํผ๋Ÿฐ์Šค ์ง„ํ–‰ ํ›„ ๊ฒฐ๊ณผ๊ฐ’์„ ์ €์žฅํ•˜๋Š” API๊ฐ€ ์žˆ๋‹ค. ํŽธ์˜์ƒ upload api๋ผ ๋ถ€๋ฅด๊ฒ ๋‹ค. upload api์˜ ์ „์ฒด ๋กœ์ง์„ ์‹คํ–‰ํ•˜๊ณ  ์‘๋‹ต์„ ์คฌ์„ ๋•Œ ์‘๋‹ต ์‹œ๊ฐ„์ด ๋„ˆ๋ฌด ์˜ค๋ž˜ ๊ฑธ๋ ธ๋‹ค. ํŠนํžˆ ์ธํผ๋Ÿฐ์Šค ์‹œ๊ฐ„์ด ๊ธธ์—ˆ๋‹ค. 2~3๊ฐœ์˜ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ–ˆ์„ ๋•Œ์—๋Š” ๊ดœ์ฐฎ์•˜์ง€๋งŒ, 500~1000๊ฐœ์˜ ํŒŒ์ผ์„ ์˜ฌ๋ ธ์„ ๊ฒฝ์šฐ ์‹œ์Šคํ…œ์ด ๋ถ€ํ•˜๋ฅผ ๊ฒฌ๋””์ง€ ๋ชปํ•˜๊ณ  ํ„ฐ์ง€๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ–ˆ๋‹ค. ๋˜ํ•œ ๋Œ€๋Ÿ‰์˜ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•œ ๊ฒฝ์šฐ ์‘๋‹ต ์‹œ๊ฐ„์ด ๊ณผ๋„ํ•˜๊ฒŒ ์˜ค๋ž˜ ๊ฑธ๋ ธ๋‹ค.
์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” ๋ถ€๋ถ„๊นŒ์ง€๋Š” ๋™๊ธฐ๋กœ, ์ดํ›„์˜ ์ธํผ๋Ÿฐ์Šค ๊ณผ์ •์€ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์„ค๊ณ„ํ–ˆ๋‹ค. ์•„๋ž˜์˜ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ํ•˜๋‚˜์˜ API์—์„œ ๋™๊ธฐ ๋กœ์ง๊ณผ ๋น„๋™๊ธฐ ๋กœ์ง์„ ๊ตฌ๋ถ„ํ•ด ๊ตฌํ˜„ํ–ˆ๋‹ค.

Async? ๊ทธ๊ฑฐ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”๋ฐ?

Spring์—์„œ๋Š” @Async ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๋น„๋™๊ธฐ ๋ฉ”์†Œ๋“œ๋ฅผ ์„ค์ •ํ•œ๋‹ค. ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์€ Spring AOP์— ์˜ํ•ด ํ”„๋ก์‹œ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™๋œ๋‹ค.

Spring Context์— ๋“ฑ๋ก๋œ Async Bean์ด ํ˜ธ์ถœ๋˜๋ฉด Spring์ด ๊ฐœ์ž…ํ•ด ํ•ด๋‹น Async Bean์„ ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ Wrappingํ•œ๋‹ค. ์ปจํ…Œ์ด๋„ˆ์— ์˜ํ•ด Bean์œผ๋กœ ๋“ฑ๋ก๋˜๋Š” ์‹œ์ ์— ํ”„๋ก์‹œ ๊ฐ์ฒดํ™”ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
ํ˜ธ์ถœํ•œ ๊ฐ์ฒด๋Š” ์‹ค์งˆ์ ์œผ๋กœ AOP๋ฅผ ํ†ตํ•ด ๋งŒ๋“ค์–ด์ง„ ํ”„๋ก์‹œ ๊ฐ์ฒดํ™”๋œ Async Bean์„ ์ฐธ์กฐํ•˜๊ฒŒ ๋œ๋‹ค.

์–ด๋–ค ์ ์„ ์กฐ์‹ฌํ•ด์•ผ ํ•˜๋Š”๊ฐ€

@Async๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์—๋Š” ์•„๋ž˜์˜ ์œ ์˜์‚ฌํ•ญ์„ ์ง€์ผœ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

1. ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ์˜ ์ ‘๊ทผ ์ง€์ •์ž private ์‚ฌ์šฉ ๋ถˆ๊ฐ€
2. self-invocation(์ž๊ฐ€ ํ˜ธ์ถœ) ๋ถˆ๊ฐ€, ์ฆ‰ inner method ์‚ฌ์šฉ ๋ถˆ๊ฐ€

๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ์˜ ์ ‘๊ทผ ์ง€์ •์ž๋ฅผ private์œผ๋กœ ์ง€์ •ํ•˜๋ฉด AOP๊ฐ€ ๊ฐ€๋กœ์ฑ„ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ๋•Œ ์ด์— ์ ‘๊ทผํ•  ๋•Œ ์ด์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ private method๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.
self-invocation์˜ ๊ฒฝ์šฐ, ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ์ง์ ‘ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ์— Async๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ๊ผญ ์œ ์˜์‚ฌํ•ญ์„ ์ง€์ผœ์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

๐Ÿงญ ํ™˜๊ฒฝ ์„ค์ •

environment

  • Spring Boot v3.3.1
  • Gradle build

์˜์กด์„ฑ ์„ค์ •

๊ธฐ๋ณธ์ ์œผ๋กœ spring-boot-starter-web์— ๋‚ด์žฅ๋˜์–ด ์žˆ์ง€๋งŒ, ๊ทธ๋ž˜๋„ ์˜์กด์„ฑ ์„ค์ •์ด ์ž˜ ๋˜์–ด์žˆ๋Š”์ง€ ํ•œ ๋ฒˆ ๋” ํ™•์ธํ•œ๋‹ค.

build.gradle

...
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}
...

AsyncConfig.java

๋น„๋™๊ธฐ ์„ค์ •์„ ์œ„ํ•ด AsyncConfig ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ๊ด€๋ฆฌํ•ด์ค€๋‹ค. ์ด๋•Œ @EnableAsync ์–ด๋…ธํ…Œ์ด์…˜์„ ๋‹ฌ์•„ ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•œ๋‹ค.

์ด ๊ฒฝ์šฐ์—๋Š” ์„œ๋ฒ„๊ฐ€ ํ„ฐ์ง€์ง€ ์•Š๋„๋ก ๋А๋ ค๋„ ์•ˆ์ •์ ์œผ๋กœ ์šด์˜ํ•˜๊ธฐ ์œ„ํ•ด CORE_POOL_SIZE์™€ MAX_POOL_SIZE๋ฅผ 1๋กœ ์„ค์ •ํ–ˆ๋‹ค. ๊ฐ ์ƒ์ˆ˜์˜ ์˜๋ฏธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

CORE_POOL_SIZE : ์Šค๋ ˆ๋“œ ํ’€์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์œ ์ง€๋˜๋Š” ์Šค๋ ˆ๋“œ ์ˆ˜ ์ •์˜
โ†’ ์˜ˆ์ƒ๋˜๋Š” ์ตœ๋Œ€ ๋™์‹œ ์ž‘์—… ์ˆ˜์— ๊ฐ€๊นŒ์šด ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅ

MAX_POOL_SIZE : ์Šค๋ ˆ๋“œ ํ’€์ด ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ์Šค๋ ˆ๋“œ ์ˆ˜ ์ •์˜
โ†’ ์•„๋ž˜์˜ queue๊ฐ€ ๊ฐ€๋“ ์ฐผ์„ ๋•Œ ์ƒˆ๋กœ์šด ์š”์ฒญ์ด ๋“ค์–ด์˜จ ๊ฒฝ์šฐ, ํ™•์žฅํ•  ์Šค๋ ˆ๋“œ ํ’€ ์ˆ˜

QUEUE_CAPACITY : ์Šค๋ ˆ๋“œ ํ’€์—์„œ ์‚ฌ์šฉํ•  ์ตœ๋Œ€ ํ์˜ ํฌ๊ธฐ

package example.global.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;

/**
 * ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์„ค์ •
 */
@EnableAsync
@Configuration
public class AsyncConfig {

    private static final int CORE_POOL_SIZE = 1;
    private static final int MAX_POOL_SIZE = 1;
    private static final int QUEUE_CAPACITY = 1000;

    /**
     * ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ Executor ์„ค์ •, Core Pool Size, Max Pool Size, Queue Capacity ์„ค์ •
     *
     * @return ThreadPoolTaskExecutor
     */
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setThreadNamePrefix("EXAMPLE-ASYNC-");
        executor.initialize();
        return executor;
    }
}

๐Ÿš€ How to use

์ด ๊ฒฝ์šฐ์—๋Š” ํ–ฅํ›„ ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ์ฝ”๋“œ์˜ ์œ ์—ฐ์„ฑ, ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด Async ๋ฉ”์„œ๋“œ๋ฅผ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๋Š” AsyncService ํด๋ž˜์Šค๋ฅผ ๋ณ„๋„๋กœ ๊ตฌํ˜„ํ•ด ๊ด€๋ฆฌํ–ˆ๋‹ค. ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ์— @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ๋‹ฌ์•„์ฃผ๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ๋ฉ”์„œ๋“œ์˜ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ํ•˜์œ„ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ๋‹ค๋ฅธ ํด๋ž˜์Šค์— @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ๋‹ฌ์•„ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค. ๋™์ผํ•œ ํด๋ž˜์Šค์— ๊ตฌํ˜„ ์‹œ self-invocation์˜ ์šฐ๋ ค๊ฐ€ ์žˆ๋‹ค.

AsyncService.java

์•„๋ž˜์˜ ํด๋ž˜์Šค๋Š” ์ธํผ๋Ÿฐ์Šค๋ฅผ ์ง„ํ–‰ํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•˜๋Š” ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ค.

/**
 * Async service - to process async task
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class AsyncService {

    private final StudyDao studyDao;
    private final PatientDao patientDao;
    private final InvalidInstanceDao invalidInstanceDao;
    private final InstanceDao instanceDao;
    private final SecondaryCaptureDao secondaryCaptureDao;
    private final ReportDao reportDao;

    private final InferenceManager inferenceManager;

    /**
     * Async process(1. save instance information in database,
     *               2. do inference and save result in database
     *               3. get secondary capture list
     *               4. get report
     *               5. save report in database)
     *
     * @param originPath origin dicom file path
     * @param accountEntity account entity
     */
    @Async
    protected void asyncProcess(String originPath, DicomTagValue dicomTagValue, AccountEntity accountEntity, Boolean isInvalid, String errorMessage) {

        String pngPath = FileManager.getInstanceThumbnailPath(originPath);

        // save instance information in database
        InstanceEntity instanceEntity = saveInstanceInfo(originPath, dicomTagValue, accountEntity, pngPath);
        log.info("instanceEntity id = {}", instanceEntity.getInstanceId());

        if (isInvalid) {
            invalidInstanceDao.saveInvalidInstance(instanceEntity, errorMessage);
            inferenceManager.getThumbnail(instanceEntity.getOriginDcmPath(),
                instanceEntity.getOriginPngPath());
            return ;
        }

        try {
            // do inference and save result in database
            InferenceResponseDto inferenceResponseDto = inferenceManager.getInferenceResult(instanceEntity, pngPath);

            // save inference result in database
            secondaryCaptureDao.saveWithSecondaryCaptureInfo(inferenceResponseDto, instanceEntity);

            // get secondary capture list and get report
            StudyEntity studyEntity = instanceEntity.getStudy();

            // get report
            ReportResponseDto reportResponseDto = inferenceManager.getReportFromStudyEntity(studyEntity);
            if (reportResponseDto == null) {
                log.error("Failed to get report");
                return ;
            }

            // save report in database
            reportDao.saveReportWithReportResponseDto(reportResponseDto, studyEntity);

        } catch (InferenceFailureException e) {
            invalidInstanceDao.saveInvalidInstance(instanceEntity, e.getMessage());
        } catch (Exception e) {
            log.error("Failed to get inference result", e);
        }
    }
    // private method๋“ค์€ ์ƒ๋žต
}

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

InstanceService.java

/**
 * Instance service
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class InstanceService {

    private final AsyncService asyncService;
    private final DicomValidator dicomValidator;
    private final AccountDao accountDao;

    /**
     * Upload dicom image
     *
     * @param file      image file to upload
     * @param principal user data(email)
     */
    public void uploadInstance(MultipartFile file, Principal principal) {
        // ๋™๊ธฐ ๋กœ์ง ์ฒ˜๋ฆฌ ์ƒ๋žต
        
        // ๋น„๋™๊ธฐ ๋กœ์ง ํ˜ธ์ถœ
        asyncService.asyncProcess(originPath, dicomTagValue, accountEntity, isInvalid, errorMessage);
    }
}

์˜ค~ ์ƒ๊ฐ๋ณด๋‹ค ๋” ์œ„ํ—˜ํ•œ๋ฐ~

์—ฌ๊ธฐ์„œ ๋”? ๋ญ˜? ์–ด๋–ป๊ฒŒ?

์•ž์„œ ์–ธ๊ธ‰ํ–ˆ์ง€๋งŒ, ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์‹œ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ต‰์žฅํžˆ ์ค‘์š”ํ•˜๋‹ค. ๊ทธ ์ค‘์—์„œ๋„ ๊ฐ€์žฅ ์ฃผ์˜ํ•ด์•ผ ํ•  ์Šค๋ ˆ๋“œ ๊ด€๋ จ ์ด์Šˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์Šค๋ ˆ๋“œ ํ’€ ๊ด€๋ฆฌ

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” ์ฃผ๋กœ ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. Spring์—์„œ๋Š” @Async ์‚ฌ์šฉ ์‹œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์Šค๋ ˆ๋“œ ํ’€(Thread pool)์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ด๋ฅผ ์ œ๋Œ€๋กœ ๊ด€๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด ์•„๋ž˜์˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์šฐ๋ ค๊ฐ€ ์žˆ๋‹ค.

  • ์Šค๋ ˆ๋“œ ๊ณ ๊ฐˆ(Thread exhaustion) : ์Šค๋ ˆ๋“œ ํ’€์ด ๊ฝ‰ ์ฐจ๋ฉด ๋” ์ด์ƒ ์ƒˆ๋กœ์šด ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๊ณ , ๋Œ€๊ธฐ ์ƒํƒœ์— ๋น ์ง€๊ฑฐ๋‚˜ ์š”์ฒญ์ด ๊ฑฐ๋ถ€๋  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์Šค๋ ˆ๋“œ ํ’€์˜ ํฌ๊ธฐ์™€ ์ž‘์—… ํ์˜ ํฌ๊ธฐ๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.
  • ๊ณผ๋„ํ•œ ์Šค๋ ˆ๋“œ ์ƒ์„ฑ : ์Šค๋ ˆ๋“œ ํ’€์ด ๋„ˆ๋ฌด ํด ๊ฒฝ์šฐ, ์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค(CPU, RAM ๋“ฑ)๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ์†Œ๋น„๋˜์–ด ์„œ๋ฒ„์˜ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์œ ๋ฐœํ•  ์šฐ๋ ค๊ฐ€ ์žˆ๋‹ค.

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

์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์—์„œ๋Š” ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, ๊ณต์œ  ์ž์›์— ์ ‘๊ทผํ•  ๋•Œ ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ์„ ํ™•๋ณดํ•ด์•ผ ํ•œ๋‹ค. DB์˜ concurrency control๊ณผ๋„ ๋น„์Šทํ•œ ๋ฌธ์ œ์ด๋‹ค. ๋™๊ธฐํ™”๋˜์ง€ ์•Š์€ ๊ณต์œ  ์ž์›์— ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์ ‘๊ทผ ์‹œ ๋ฐ์ดํ„ฐ ์†์ƒ์ด๋‚˜ ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ๋™์ž‘์ด ๋ฐœ์ƒํ•  ์šฐ๋ ค๊ฐ€ ์žˆ๋‹ค.

์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๊ณต์œ  ์ž์›์— ์ ‘๊ทผํ•  ๋•Œ์—๋Š” synchronized ๋ธ”๋ก, ReentrantLock ๋“ฑ์œผ๋กœ ๋™๊ธฐํ™” ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค. ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ๋ถˆ๋ณ€ ๊ฐ์ฒด(Immutable object)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ConcurrentHashMap, CopyOnWriteArrayList ๋“ฑ๊ณผ ๊ฐ™์ด ์Šค๋ ˆ๋“œ์— ์•ˆ์ „ํ•œ ์ž๋ฃŒ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋œ๋‹ค.

์–ด๋–ป๊ฒŒ ๋ณด์™„ํ•ด์•ผ ํ•˜๋Š”๊ฐ€

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

profile
๋‚˜๋Š”์•ผ ๋งํ•˜๋Š” ๊ฐœ๋ฐœ(๊ฐ)์ž

0๊ฐœ์˜ ๋Œ“๊ธ€