<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>인생을 코딩하다.</title>
    <link>https://junghyungil.tistory.com/</link>
    <description>기술 블로그</description>
    <language>ko</language>
    <pubDate>Thu, 16 Apr 2026 06:56:40 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Hyung1</managingEditor>
    <image>
      <title>인생을 코딩하다.</title>
      <url>https://tistory1.daumcdn.net/tistory/4198002/attach/4a76cdbb78d846ddba9d3ca07bc5efbb</url>
      <link>https://junghyungil.tistory.com</link>
    </image>
    <item>
      <title>ThreadPool과 Virtual Thread의 성능 및 메모리 사용 비교</title>
      <link>https://junghyungil.tistory.com/232</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8NsRz/dJMcahXmtEN/8iGukZgr4EDeXveQ3LSIbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8NsRz/dJMcahXmtEN/8iGukZgr4EDeXveQ3LSIbK/img.png&quot; data-alt=&quot;이미지 출처 : https://blog.stackademic.com/virtual-threads-in-java-21-how-enterprise-scale-to-millions-of-concurrent-connections-without-26601f820614&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8NsRz/dJMcahXmtEN/8iGukZgr4EDeXveQ3LSIbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8NsRz%2FdJMcahXmtEN%2F8iGukZgr4EDeXveQ3LSIbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;293&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 : https://blog.stackademic.com/virtual-threads-in-java-21-how-enterprise-scale-to-millions-of-concurrent-connections-without-26601f820614&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;목차&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 배경&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 테스트 환경&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 테스트 결과&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 마치며..&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 배경&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;최근 &lt;b&gt;차세대 마이그레이션을 진행하는 업무&lt;/b&gt;를 맡아 수행하게 되었습니다. 이번 마이그레이션에서는 지금까지의 &lt;b&gt;JDK 버전 흐름과 지원 현황을 종합적으로 검토한 결과&lt;/b&gt;, 여러 기술적&amp;middot;운영상의 이유로 &lt;b&gt;JDK 25를 도입하는 것이 가장 적합하다고 판단하여 이를 채택&lt;/b&gt;하였습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JDK 21부터 정식으로 도입된 Virtual Thread는 동기식 코드 스타일을 유지하면서도 JVM 경량 스레드를 사용하여 적은 리소스로도 높은 동시성을 효율적으로 처리할 수 있는 실행 모델을 제공합니다. 저는 이러한 특징들로 인해 Virtual Thread를 적극적으로 도입하여 아래와 같은 &lt;b&gt;장점&lt;/b&gt;들을 경험할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring MVC 기반 동기&amp;middot;블로킹 서버의 스레드 풀 구조를 Virtual Thread 기반으로 전환하여, 고부하 상황에서도 안정적인 처리와 자원 효율 개선을 달성하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;WebFlux(+Coroutine) 기반 서버를 Spring MVC + Virtual Thread 모델로 전환하여, 기존 성능을 유지하면서 복잡도와 유지보수 비용, 개발자 러닝 커브를 낮추고 생산성을 향상시켰습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 Virtual Thread를 도입하면서 장점만 있었던 것은 아니었고, 몇 가지 고려해야 &lt;b&gt;단점&lt;/b&gt;도 있었습니다. &lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 경험을 바탕으로, 이번 글에서는 ThreadPool 기반 모델과 Virtual Thread 모델의 성능을 비교해보고자 합니다. (본문에 등장하는 코드는 이해를 돕기 위해 일부 재구성되었습니다.)&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 테스트 환경&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2-1. 서버 구성&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Callee Server (외부 API 서버)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Endpoint: http://localhost:8001/api/test/slow&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;응답 시간: 3초 (데이터 분석용 조회 API)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Tomcat Worker Thread: 200개&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Caller Server (테스트 실행 서버)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;총 요청 수: 1,000개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM Heap Memory: 1GB&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CPU Core: 10개&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2-2. Configuration 설정&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;/**
 * 이 설정은 &quot;부하 실험 / 동작 비교&quot;를 위한 값이며,
 * 운영 환경에서는 서비스 특성, 트래픽 패턴, 외부 API SLA에 맞게 반드시 재조정되어야 합니다.
 *
 * ---------------------------------------------------------------------
 * Timeout 설정 기준
 *
 * [readTimeout]
 * - 외부 API 응답 지연을 기준으로 설정
 * - 평균 응답 3초 + 네트워크 지연/스케줄링 여유를 고려해 5초로 설정
 * 
 * ※ connectionRequestTimeout 은 의도적으로 사용하지 않음
 *&amp;nbsp;&amp;nbsp; - 커넥션 풀 대기 시간 제한은 &quot;풀 병목&quot; 실험이 되어버림
 *&amp;nbsp;&amp;nbsp; - 본 실험에서는 병목 지점을 callee(외부 서버)로 두기 위해 제거
 *
 * ---------------------------------------------------------------------
 * Connection Pool 설정
 *
 * [maxTotal / maxPerRoute = 1000]
 * - caller 쪽 커넥션 풀이 병목이 되지 않도록 충분히 크게 설정
 * 
 * [connectTimeout = 500ms]
 * - TCP 연결 수립 실패를 빠르게 감지하기 위한 값, DNS 문제, 서버 다운 등 네트워크 장애를 빠르게 드러내기 위함
 *
 * [validateAfterInactivity = 5s]
 * - 일정 시간 이상 유휴 상태였던 커넥션 재사용 전 유효성 검사, 오래된 커넥션 재사용으로 인한 예외 방지 목적
 *
 * [evictIdleConnections = 60s]
 * - 60초 이상 사용되지 않은 커넥션을 정리, 리소스 회수 목적 (실험 결과에는 큰 영향 없음)
 */
@ConfigurationProperties(prefix = &quot;clients.test&quot;)
data class TestClientProperties(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val baseUrl: String = &quot;http://localhost:8001&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val timeProperties: TimeoutProperties = TimeoutProperties(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val connectionPool: ConnectionPoolProperties = ConnectionPoolProperties()
) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data class TimeoutProperties(
//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val connectionRequest: Duration = Duration.ofMillis(1000),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val readTimeOut: Duration = Duration.ofSeconds(5),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data class ConnectionPoolProperties(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val maxTotal: Int = 1000,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val maxPerRoute: Int = 1000,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val connectTimeout: Duration = Duration.ofMillis(500),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val validateAfterInactivity: Duration = Duration.ofSeconds(5),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val evictIdleConnections: Duration = Duration.ofSeconds(60)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
}

@Configuration
@PropertySource(
    value = [&quot;classpath:clients-\${spring.profiles.active}.yaml&quot;],
    factory = YamlPropertySourceFactory::class
)
@EnableConfigurationProperties(TestClientProperties::class)
@ImportHttpServices(group = CLIENT_GROUP, basePackageClasses = [TestClient::class])
class TestClientConfiguration(
    private val properties: TestClientProperties
) {

    @Bean
    fun testHttpServiceGroupConfigurer(): RestClientHttpServiceGroupConfigurer {
        val poolProps = properties.connectionPool
        val timeProps = properties.timeProperties

        val connectionConfig = ConnectionConfig.custom()
            .setConnectTimeout(Timeout.ofMilliseconds(poolProps.connectTimeout.toMillis()))
            .setValidateAfterInactivity(Timeout.ofMilliseconds(poolProps.validateAfterInactivity.toMillis()))
            .build()

        val connectionManager = PoolingHttpClientConnectionManager().apply {
            setDefaultConnectionConfig(connectionConfig)
            maxTotal = poolProps.maxTotal
            defaultMaxPerRoute = poolProps.maxPerRoute
        }

        val httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .evictIdleConnections(TimeValue.ofMilliseconds(poolProps.evictIdleConnections.toMillis()))
            .build()

        val requestFactory = HttpComponentsClientHttpRequestFactory(httpClient).apply {
            setReadTimeout(timeProps.readTimeOut.toMillis().toInt())
            timeProps.connectionRequestTimeout?.let {
                setConnectionRequestTimeout(it.toMillis().toInt())
            }
        }

        return RestClientHttpServiceGroupConfigurer { groups -&amp;gt;
            groups.filterByName(CLIENT_GROUP)
                .forEachClient { _, clientBuilder -&amp;gt;
                    clientBuilder
                        .baseUrl(properties.baseUrl)
                        .requestFactory(requestFactory)
                }
        }
    }

    companion object {
        private const val CLIENT_GROUP = &quot;test&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2-2. 테스트 시나리오&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;603&quot; data-start=&quot;478&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;496&quot; data-start=&quot;478&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;총 요청 수: &lt;b&gt;1000&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;521&quot; data-start=&quot;497&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 API 호출 시간: 약 &lt;b&gt;3초&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;603&quot; data-start=&quot;544&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;측정 지표:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;603&quot; data-start=&quot;555&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;565&quot; data-start=&quot;555&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 처리 시간&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;582&quot; data-start=&quot;568&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성공 / 실패 요청 수&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;603&quot; data-start=&quot;585&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM Thread 상태 변화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
class LoadTestController(
    private val testClient: TestClient
) {
    /**
     * 1. 스레드풀 Executor 호출 (플랫폼 스레드 100개)
     * - 고정 스레드풀 100개로 1000개 요청 처리
     */
    @GetMapping(
        value = [&quot;/api/test/threadpool&quot;],
        produces = [MediaType.APPLICATION_JSON_VALUE]
    )
    fun testThreadPool() {
        val executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE)
        val successCount = AtomicInteger(0)
        val failCount = AtomicInteger(0)

        val startTime = System.currentTimeMillis()
        log.info(&quot;=== ThreadPool Test Started ($THREAD_POOL_SIZE threads) ===&quot;)

        val futures = (1..TOTAL_REQUESTS).map { index -&amp;gt;
            CompletableFuture.runAsync({
                try {
                    testClient.slow()
                    successCount.incrementAndGet()
                    if (index % 100 == 0) {
                        log.info(&quot;ThreadPool progress: $index/$TOTAL_REQUESTS&quot;)
                    }
                } catch (e: Exception) {
                    failCount.incrementAndGet()
                }
            }, executor)
        }

        CompletableFuture.allOf(*futures.toTypedArray()).join()

        executor.shutdown()

        val duration = System.currentTimeMillis() - startTime
        log.info(&quot;=== ThreadPool Test Completed in ${duration}ms (${duration / 1000.0}s) ===&quot;)
        log.info(&quot;=== Success: ${successCount.get()}, Fail: ${failCount.get()} ===&quot;)
    }

    /**
     * 2. 가상 스레드 호출 (제한 없음)
     * - Virtual Thread Executor 사용 (무제한)
     * - 1000개 요청 동시 처리
     * - 주의: 외부 API가 감당 못하면 타임아웃 발생 가능
     */
    @GetMapping(
        value = [&quot;/api/test/virtual&quot;],
        produces = [MediaType.APPLICATION_JSON_VALUE]
    )
    fun testVirtualThread() {
        val startTime = System.currentTimeMillis()
        val successCount = AtomicInteger(0)
        val failCount = AtomicInteger(0)
        log.info(&quot;=== Virtual Thread Test Started (Unlimited) ===&quot;)

        val virtualThreadExecutor = Executors.newVirtualThreadPerTaskExecutor()

        val futures = (1..TOTAL_REQUESTS).map { index -&amp;gt;
            CompletableFuture.runAsync({
                try {
                    testClient.slow()
                    successCount.incrementAndGet()
                    if (index % 100 == 0) {
                        log.info(&quot;Virtual Thread progress: $index/$TOTAL_REQUESTS&quot;)
                    }
                } catch (e: Exception) {
                    failCount.incrementAndGet()
                }
            }, virtualThreadExecutor)
        }

        CompletableFuture.allOf(*futures.toTypedArray()).join()

        virtualThreadExecutor.close()

        val duration = System.currentTimeMillis() - startTime
        log.info(&quot;=== Virtual Thread Test Completed in ${duration}ms (${duration / 1000.0}s) ===&quot;)
        log.info(&quot;=== Success: ${successCount.get()}, Fail: ${failCount.get()} ===&quot;)
    }

    /**
     * 3. 가상 스레드 호출 (Semaphore로 동시 실행 제한)
     * - Virtual Thread Executor 사용
     * - 1000개 요청 생성하되, 동시 실행은 100개로 제한
     */
    @GetMapping(
        value = [&quot;/api/test/virtual-limited&quot;],
        produces = [MediaType.APPLICATION_JSON_VALUE]
    )
    fun testVirtualThreadLimited() {
        val startTime = System.currentTimeMillis()
        val successCount = AtomicInteger(0)
        val failCount = AtomicInteger(0)
        log.info(&quot;=== Virtual Thread Test Started (Limited to $SEMAPHORE_PERMITS) ===&quot;)

        val virtualThreadExecutor = Executors.newVirtualThreadPerTaskExecutor()
        val semaphore = Semaphore(SEMAPHORE_PERMITS)

        val futures = (1..TOTAL_REQUESTS).map { index -&amp;gt;
            CompletableFuture.runAsync({
                try {
                    semaphore.acquire()  // 허가 받을 때까지 대기
                    try {
                        testClient.slow()
                        successCount.incrementAndGet()
                        if (index % 100 == 0) {
                            log.info(&quot;Virtual Thread (Limited) progress: $index/$TOTAL_REQUESTS&quot;)
                        }
                    } finally {
                        semaphore.release()
                    }
                } catch (e: Exception) {
                    failCount.incrementAndGet()
                }
            }, virtualThreadExecutor)
        }

        CompletableFuture.allOf(*futures.toTypedArray()).join()

        virtualThreadExecutor.close()

        val duration = System.currentTimeMillis() - startTime
        log.info(&quot;=== Virtual Thread (Limited) Test Completed in ${duration}ms (${duration / 1000.0}s) ===&quot;)
        log.info(&quot;=== Success: ${successCount.get()}, Fail: ${failCount.get()} ===&quot;)
    }

    companion object {
        private val log = LoggerFactory.getLogger(LoadTestController::class.java)
        private const val TOTAL_REQUESTS = 1000
        private const val THREAD_POOL_SIZE = 100
        private const val SEMAPHORE_PERMITS = 100
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 테스트 결과&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 144px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 29.651%; height: 20px; text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;방식&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.349%; height: 20px; text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;소요 시간&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 20px; text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;성공&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.7907%; height: 20px; text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;실패&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.0%; height: 20px; text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;성공률&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 29.651%; height: 36px; text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ThreadPool(100)&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.349%; height: 36px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;30s&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 36px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1,000&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.7907%; height: 36px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.0%; height: 36px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;100%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;width: 29.651%; height: 35px; text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread (무제한)&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.349%; height: 35px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5s&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 35px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;200&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.7907%; height: 35px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;800&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.0%; height: 35px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;20%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 53px;&quot;&gt;
&lt;td style=&quot;width: 29.651%; height: 53px; text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread + Semaphore(100)&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.349%; height: 53px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;30s&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 53px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1,000&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.7907%; height: 53px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.0%; height: 53px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;100%&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread(무제한) 방식에서는 성공률이 20%에 그쳤습니다. 이는 외부 서버가 동시에 처리할 수 있는 톰캣 워커 수를 200으로 가정했기 때문입니다. 1,000개의 요청이 동시에 전달되면서, 실제로 처리 가능한 200개만 정상 처리되고 나머지 요청은 대기 상태에 들어가 응답 지연이 발생했습니다. 이 중 상당수가 readTimeout(5s)을 초과하면서 실패로 처리되었습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 서버의 톰캣 워커 수를 400으로 늘려 다시 테스트하면 성공률은 약 40%까지 증가합니다. 이 결과를 통해, 가상 스레드는 병렬 실행을 매우 쉽게 만들어 주지만 동시 실행에 대한 제한을 제공하지는 않으며, 외부 API의 처리 한계를 초과할 경우 대량의 요청이 동시에 지연되고 타임아웃으로 실패할 수 있음을 확인할 수 있었습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 가상 스레드는 &amp;ldquo;더 많은 작업을 동시에 시작할 수 있게 해주는 도구&amp;rdquo;일 뿐, 외부 시스템의 처리 용량을 자동으로 고려하거나 보호해 주지는 않기 때문에 유량 제어가 필요하다는 것을 느꼈습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-1. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 어떻게 해결할 수 있을까요?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유량 제어를 적용하는 방법에는 여러 가지가 있을 수 있습니다. 다만 개인적으로는, 자바 진영에서도 이미 이러한 문제를 인지하고 있었을 것이고, 공식적으로 권장하는 방식이 존재할 것이라 생각해 관련 문서를 찾아보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6f9ac/dJMcafFegqT/osdJaIRvjdOkw82lWdPUK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6f9ac/dJMcafFegqT/osdJaIRvjdOkw82lWdPUK1/img.png&quot; data-alt=&quot;https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-E695A4C5-D335-4FA4-B886-FEB88C73F23E&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6f9ac/dJMcafFegqT/osdJaIRvjdOkw82lWdPUK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6f9ac%2FdJMcafFegqT%2FosdJaIRvjdOkw82lWdPUK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;133&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-E695A4C5-D335-4FA4-B886-FEB88C73F23E&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조사해보니, Java 공식 문서에서도 &lt;b&gt;가상 스레드 환경에서의 동시성 제어 방식으로 Semaphore 사용을 예시로 안내하고 있었습니다.&lt;/b&gt; 즉, 가상 스레드는 실행 비용이 매우 낮기 때문에 무제한으로 생성할 수 있지만, 외부 리소스 접근 시에는 별도의 동시성 제한 장치를 두는 것이 필요하다는 점을 명확히 하고 있습니다. &lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이에 따라 본 실험에서도 세마포어를 이용해 동시 실행 수를 제한하는 방식으로 유량 제어를 적용하였습니다. 실제 구현 관점에서도 세마포어는 다음과 같은 장점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구현이 단순하고 직관적임&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동시 실행 개수를 명확하게 제한할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상 스레드와 함께 사용해도 부담이 적음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;별도의 프레임워크 의존 없이 JDK 기본 기능으로 사용 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 이유로, 가상 스레드를 사용하는 환경에서 외부 API 호출에 대한 유량 제어가 필요하다면 세마포어를 사용하는 방식이 가장 현실적이고 적절한 선택이라고 판단했습니다. 동일한 질문을 AI에게 문의했을 때 역시 세마포어 기반의 제어 방식을 권장하는 답변을 확인할 수 있었으며, 이는 공식 문서의 방향성과도 일치하였었습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하면, 가상 스레드는 동시 실행을 쉽게 만들어 주지만, 외부 시스템의 처리 한계를 자동으로 보호해 주지는 않습니다. 따라서 실제 서비스 환경에서는 세마포어와 같은 명시적인 유량 제어 수단을 함께 사용해야함을 알게되었습니다. 이후 세마포어로 유량 제어하면서 다시 요청했을 경우 성공률이 100%인 것을 확인하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-2. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매트릭 지표 확인&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빨간선 점선 기준으로 &lt;b&gt;ThreadPool(100) &amp;rarr; Virtual Thread(무제한) &amp;rarr; Virtual Thread + Semaphore(100) &lt;/b&gt;세 가지 방식의 지표 상태 변화를 비교한 결과입니다. (가운데 중간에 비어있는 부분은 서버를 잠시 종료시켜서 비어보이는 것이니 무시하셔도 됩니다.)&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-2-1. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Thread&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Aizlq/dJMcad1EAx0/97iwGmqUn6I5Qr9lNezoX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Aizlq/dJMcad1EAx0/97iwGmqUn6I5Qr9lNezoX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Aizlq/dJMcad1EAx0/97iwGmqUn6I5Qr9lNezoX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAizlq%2FdJMcad1EAx0%2F97iwGmqUn6I5Qr9lNezoX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1326&quot; height=&quot;772&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 톰캣 워커, HTTP 클라이언트, JVM 내부 스레드 등으로 인해 우선 테스트 시작 전부터 항상 약 100개 내외의 스레드가 기본적으로 존재했었습니다. 이 기준을 감안하여, 기본 100개를 제외한 증가분 중심으로 해석하였습니다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;# ThreadPool(100)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ThreadPool 구간에서는 전체 live 스레드가 약 &lt;b&gt;190~200 수준&lt;/b&gt;까지 증가하였습니다. 기본 스레드가 약 100개 존재하므로, 실제로 테스트로 인해 추가된 스레드는 약 &lt;b&gt;+100개 &lt;/b&gt;수준입니다. 그래프는 아래와 같이 해석할 수 있을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청은 최대 100개까지만 동시에 실행됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 이상은 큐에서 대기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스레드 수 증감이 매우 안정적&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, &lt;b&gt;ThreadPool 자체가 자연스러운 유량 제어 장치 역할&lt;/b&gt;을 수행하고 있는 것을 확인하였습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;# Virtual Thread (무제한)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread 구간에서는 live thread 수가 약 110개 수준으로 유지되었으며, 이는 ThreadPool(100) 방식 대비 약 1/10 수준인 것을 확인하였습니다. 이는 JVM이 논리 CPU 개수만큼의 Carrier Thread만 유지하고, 그 위에서 다수의 Virtual Thread를 스케줄링하기 때문입니다. 그 결과 적은 수의 스레드로도 동일한 요청 처리가 가능했습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 이 구간에서는 아래와 같은 문제가 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청 1,000개가 거의 동시에 실행됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상 스레드가 대량 생성됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 서버(톰캣 200) 처리 한계를 초과&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다수 요청이 대기 상태로 밀림&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;응답 지연 누적으로 readTimeout 발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과적으로 &lt;b&gt;성공률 20%&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, &lt;b&gt;스레드 수가 늘지 않았다고 해서 부하가 적은 것은 아니였습니다. &lt;/b&gt;가상 스레드는 커널 스레드를 늘리지 않을 뿐, 논리적인 동시 실행 자체는 무제한으로 발생하는 것을 확인할 수 있었습니다. 따라서 유량 제어가 필요함을 느꼈습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;# Virtual Thread + Semaphore(100)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread (무제한) 방식과 동일하게 live thread 수가 유지되었습니다. 하지만 중요한 차이는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동시에 실행되는 작업 수가 100으로 제한됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;초과 요청은 가상 스레드 상태로 park 되어 대기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 API 처리 한계를 넘지 않음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;read timeout 발생 없음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 요청 성공&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, &lt;b&gt;가상 스레드의 가벼움 + 명시적 유량 제어가 결합된 구조로 모든 요청&lt;/b&gt;을 성공적으로 처리할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-2-2.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CPU Usage&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CPU Bound 작업이 아닌 I/O Bound 작업이라 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CPU Usage는 세 방식 모두 비슷하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-2-3. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM Heap&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjMARk/dJMcaaqnCpo/KYGEKpjGDEDYyspe4FRKE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjMARk/dJMcaaqnCpo/KYGEKpjGDEDYyspe4FRKE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjMARk/dJMcaaqnCpo/KYGEKpjGDEDYyspe4FRKE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjMARk%2FdJMcaaqnCpo%2FKYGEKpjGDEDYyspe4FRKE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1282&quot; height=&quot;786&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread는 OS 스레드를 직접 늘리는 구조가 아니라, 많은 수의 &lt;b&gt;가상 스레드 객체&lt;/b&gt;를 생성하여 실행합니다. 가상 스레드는 실행이 중단되거나 I/O 대기 상태에 들어가면, 스택을 네이티브 스택에 유지하지 않고 &lt;b&gt;힙에 저장되는 continuation 형태로 보관&lt;/b&gt;합니다. 이때 스택은 하나의 큰 연속 메모리가 아니라, 여러 개의 &lt;b&gt;stack chunk(청크 스택)&lt;/b&gt; 단위로 분할되어 힙에 저장됩니다. 이후 다시 실행될 때는 이 stack chunk들을 복원하여 이어서 실행합니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 구조 덕분에 가상 스레드는 매우 가볍게 생성&amp;middot;중단될 수 있지만, 동시에 많은 가상 스레드가 존재하면 각 스레드가 보유한 continuation과 stack chunk 객체들이 힙에 함께 존재하게 됩니다. 그 결과, ThreadPool 방식처럼 동시 실행 수가 제한된 구조에 비해 힙 사용량이 상대적으로 더 증가하게 됩니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 가상 스레드는 &lt;b&gt;OS 스레드 사용량을 줄이는 대신, 힙 기반 구조(continuation + stack chunk)를 활용하는 모델&lt;/b&gt;이며, 이로 인해 동시 실행 수가 많아질수록 힙 메모리 사용량이 증가하는 특성을 가집니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서, 그래프에서도 &lt;b&gt;ThreadPool 방식에 비해 Virtual Thread 방식에서 힙 사용량이 더 증가하는 것을 확인&lt;/b&gt;할 수 있었습니다.&lt;b&gt; GC 또또한&lt;/b&gt; &lt;b&gt;ThreadPool 방식에 비해 Virtual Thread 방식이 더 증가하였습니다.&lt;/b&gt;&amp;nbsp;&amp;nbsp;따라서 가상 스레드를 사용할 때도 무제한 실행보다는 세마포어 등의 기법으로 동시성을 제어하는 것이 메모리 안정성 측면에서 중요할 것 같다고 생각됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-2-4. NativeMemory&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ThreadPool 방식은 Virtual Thread와 달리 &lt;b&gt;OS 스레드(= 플랫폼 스레드)&lt;/b&gt; 가 직접 생성됩니다. 즉, 지정한 개수만큼(혹은 무제한으로) OS 스레드가 늘어나며, 각 스레드는 고정 크기의 &lt;b&gt;네이티브 스택 메모리&lt;/b&gt;를 차지하게 됩니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반면 &lt;b&gt;Virtual Thread는 OS 스레드가 아니라 JVM 내부 객체&lt;/b&gt;로 생성되며, 실제 실행은 소수의 &lt;b&gt;carrier thread&lt;/b&gt;&amp;nbsp;위에서 이루어집니다. 따라서 Virtual Thread의 개수가 늘어나더라도 OS 스레드 수는 거의 증가하지 않고, 대신 힙 메모리 사용량만 소폭 증가하게 됩니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, ThreadPool 방식은 네이티브 메모리 사용량이 증가하고 Virtual Thread 방식은 힙 메모리 사용이 증가하는 구조라고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;네이티브 메모리란?

네이티브 메모리는 JVM 힙 외부에서 사용되는 메모리 영역으로, 대표적으로 다음을 포함합니다.
- OS 스레드 스택 메모리 (-Xss)
- JNI 메모리
- 코드 캐시(Code Cache)
- 클래스 메타데이터
- 기타 JVM 내부 구조체

이 중 스레드 수 증가와 직접적으로 연결되는 영역이 바로 스레드 스택 메모리입니다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 이것 또한 실제 눈으로 확인해보기 위해 실험해보았습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네이티브 메모리 사용량을 확인하기 위해 JVM의 &lt;b&gt;Native Memory Tracking&lt;/b&gt;&amp;nbsp;기능을 활성화했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; tasks.withType&amp;lt;BootRun&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jvmArgs = listOf(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;-XX:NativeMemoryTracking=summary&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이후 실행 중인 JVM에 대해 아래 명령어로 스레드 관련 네이티브 메모리 변화를 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;jcmd &amp;lt;PID&amp;gt; VM.native_memory baseline
# 테스트 실행
jcmd &amp;lt;PID&amp;gt; VM.native_memory summary.diff scale=MB | grep -A 3 &quot;Thread&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[ThreadPool 방식 (플랫폼 스레드-100개)]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Thread (reserved=216MB +100MB, committed=13MB +5MB)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (threads #198 +100)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (stack: reserved=215MB +100MB, committed=12MB +5MB)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OS 스레드 증가: +100&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네이티브 스택 증가: 약 +100MB&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스레드 1개당 약 1MB 사용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ThreadPool 방식에서는 스레드 수 증가만큼 네이티브 메모리가 선형적으로 증가함을 확인할 수 있었습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[Virtual Thread + Semaphore(100) 방식]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Thread (reserved=125MB +10MB, committed=8MB +1MB)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (threads #108 +10)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (stack: reserved=125MB +10MB, committed=8MB +1MB)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OS 스레드 증가: 약 +10 (캐리어 스레드)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네이티브 스택 증가: 약 +10MB&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상 스레드 수 증가와 무관하게 네이티브 메모리는 거의 증가하지 않음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread 방식에서는 네이티브 메모리가 거의 증가하지 않았습니다. 이는 가상 스레드가 OS 스레드가 아닌 JVM 객체로 관리되며, 실제 실행은 CPU 개수 수준의 캐리어 스레드에서 이루어지기 때문입니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 위 차이를 통해 다음을 확인할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ThreadPool 방식은 플랫폼 스레드 수만큼 네이티브 메모리가 증가한다.(+100MB)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread는 캐리어 스레드 수만큼만 네이티브 메모리가 증가한다.(+10MB)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 대량의 동시 요청 환경에서는 Virtual Thread가 훨씬 메모리 효율적이다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하자면, ThreadPool은 스레드 수만큼 네이티브 메모리를 소모하지만, Virtual Thread는 캐리어 스레드 수만큼만 네이티브 메모리를 사용하는 것을 알 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-2-5. Duration&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/unGd2/dJMcadtPaie/C3m6pG0UlKY84qopDXG6G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/unGd2/dJMcadtPaie/C3m6pG0UlKY84qopDXG6G0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/unGd2/dJMcadtPaie/C3m6pG0UlKY84qopDXG6G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FunGd2%2FdJMcadtPaie%2FC3m6pG0UlKY84qopDXG6G0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1264&quot; height=&quot;776&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Duration 같은 경우 ThreadPool 방식보다는 Virtual Thread 방식이 좀 더 길었던 것을 확인할 수 있었습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 Virtual Thread + Semaphore 방식은 ThreadPool과 동일한 수준의 동시 실행 개수를 유지하지만, 작업을 처리하는 방식에는 구조적인 차이가 있습니다. ThreadPool은 내부적으로 &lt;b&gt;BlockingQueue 기반의 작업 큐&lt;/b&gt;를 사용하여 요청을 정렬한 뒤, 가용한 스레드가 순차적으로 작업을 가져가 처리합니다. 이 때문에 실행 순서가 비교적 안정적이며, 작업이 배치 형태로 처리되는 특성을 가집니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반면, Virtual Thread + Semaphore 방식은 별도의 작업 큐를 두지 않고, 각 요청이 세마포어의 permit을 직접 획득하기 위해 경쟁하는 구조입니다. 이 과정에서는 요청 간 실행 순서가 보장되지 않으며, permit이 반환되는 시점에 어떤 가상 스레드가 먼저 실행 권한을 얻느냐는 스케줄링 타이밍에 따라 달라집니다. 그 결과 일부 요청은 비교적 빠르게 처리되는 반면, 일부 요청은 permit 획득이 뒤로 밀리면서 실행 시점이 늦어질 수 있습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 특성 때문에 요청 완료 순서가 뒤섞이고, 마지막 요청들이 늦게 종료되면서 전체 처리 시간이 ThreadPool 방식보다 약간 길어지는 현상이 발생하지 않았을까? 추측됩니다. 이는 성능 저하라기보다는, 블로킹 큐 기반 처리와 경쟁 기반 처리라는 스케줄링 방식의 차이에서 비롯된 자연스러운 현상일 것으로 보입니다.&lt;b&gt; 따라서 대량의 트래픽을 Virtual Thread + Semaphore 방식으로 처리할 경우 ThreadPool 방식에 비해 Duration이 좀 더 길어질 수 있다는 점도 고려해봐야하지 않을까 싶습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 마치며..&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제로 Virtual Thread를 도입해보며 느꼈던 장단점과, 실험을 통해 확인한 성능 &amp;middot; 메모리 특성을 정리해보았습니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특히 네이티브 메모리 사용량을 기준으로 비교해보니, 왜 Virtual Thread가 &amp;ldquo;가볍다&amp;rdquo;고 이야기되는지 체감할 수 있었습니다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단순히 이론이 아니라, 실제 수치로 확인해보니 인상적이었습니다. 모든 상황에서 무조건 Virtual Thread를 써야 하는 것은 아니지만, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;I/O 비중이 높은 서버라면 충분히 검토해볼 만한 선택지라고 느꼈습니다. 이 글이 도입을 고민하시는 분들께 작은 참고가 되었으면 합니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>JDK 21</category>
      <category>JDK 25</category>
      <category>virtual thread</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/232</guid>
      <comments>https://junghyungil.tistory.com/232#entry232comment</comments>
      <pubDate>Sat, 27 Dec 2025 20:44:02 +0900</pubDate>
    </item>
    <item>
      <title>Jackson 2 vs Jackson 3 성능 비교 실험기</title>
      <link>https://junghyungil.tistory.com/231</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;목차&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 실험을 하게 된 배경&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 왜 JMH를 사용했는가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 테스트 환경&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 결과 및 요약 해석&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. 정리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6. 왜 Jackson3이 더 빠를까?&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 실험을 하게 된 배경&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;최근 &lt;b&gt;차세대 마이그레이션을 진행하는 업무&lt;/b&gt;를 맡아 수행하게 되었습니다. 이번 마이그레이션에서는 지금까지의 &lt;b&gt;JDK 버전 흐름과 지원 현황을 종합적으로 검토한 결과&lt;/b&gt;, 여러 기술적&amp;middot;운영상의 이유로 &lt;b&gt;JDK 25를 도입하는 것이 가장 적합하다고 판단하여 이를 채택&lt;/b&gt;하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;224&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JDK 25의 주요 변화 중 하나는 &lt;b&gt;Jackson 메이저 버전이 Jackson 2에서 Jackson 3으로 전환&lt;/b&gt;되었다는 점입니다. Jackson 3로의 전환 배경과 상세한 변경 사항에 대해서는 아래의 공식 문서와 커뮤니티 자료에 잘 정리되어 있어, 본 글에서는 해당 내용을 &lt;b&gt;깊게 다루지는 않았습니다&lt;/b&gt;. 관련 내용은 아래 문서들을 참고하시면 됩니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;224&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 3 마이그레이션 가이드&lt;/span&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md&quot;&gt;https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;742&quot; data-start=&quot;653&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 3.0 공식 릴리즈 노트&lt;/span&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/FasterXML/jackson/wiki/Jackson-Release-3.0&quot;&gt;https://github.com/FasterXML/jackson/wiki/Jackson-Release-3.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;837&quot; data-start=&quot;744&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;본 글에서는 &lt;b&gt;Jackson 2와 -&amp;gt; Jackson 3로 전환하면서 비교했던 성능 비교 실험 결과를 중심으로&lt;/b&gt; 내용을 구성하였습니다. 잘못된 내용이 있으면 댓글 남겨주세요.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-end=&quot;648&quot; data-start=&quot;630&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 왜 JMH를 사용했는가&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;724&quot; data-start=&quot;650&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 System.currentTimeMillis()나 단순 반복 루프 기반 측정은 JVM 환경에서는 신뢰하기 어렵습니다. 이유는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;848&quot; data-start=&quot;741&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;773&quot; data-start=&quot;741&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM은 실행 중에 &lt;b&gt;JIT 컴파일&lt;/b&gt;을 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;804&quot; data-start=&quot;774&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드가 실행될수록 &lt;b&gt;최적화 수준이 달라집니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;848&quot; data-start=&quot;805&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GC, OS 스케줄링, 캐시 상태에 따라 결과가 크게 흔들릴 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;933&quot; data-start=&quot;850&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 문제를 피하기 위해 OpenJDK에서 공식적으로 제공하는 &lt;b&gt;JMH(Java Microbenchmark Harness)&lt;/b&gt; 를 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;956&quot; data-start=&quot;935&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JMH는 다음을 자동으로 처리해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1044&quot; data-start=&quot;958&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;976&quot; data-start=&quot;958&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;워밍업과 실제 측정 구간 분리&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;997&quot; data-start=&quot;977&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM Fork(새 JVM 실행)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1022&quot; data-start=&quot;998&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;통계적 평균, 표준편차, 신뢰 구간 계산&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1044&quot; data-start=&quot;1023&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;벤치마크 코드의 잘못된 최적화 방지&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-end=&quot;1119&quot; data-start=&quot;1104&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3.&amp;nbsp; 테스트 환경&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1121&quot; data-end=&quot;1130&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-1. 실행 환경&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1131&quot; data-end=&quot;1201&quot;&gt;
&lt;li data-start=&quot;1131&quot; data-end=&quot;1158&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OS: macOS (Apple Silicon)&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;1159&quot; data-end=&quot;1177&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행 위치:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;로컬 머신&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;1178&quot; data-end=&quot;1201&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원격 서버, 컨테이너, CI 사용 없음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1203&quot; data-end=&quot;1227&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로컬 환경을 선택한 이유는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1229&quot; data-end=&quot;1303&quot;&gt;
&lt;li data-start=&quot;1229&quot; data-end=&quot;1272&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 Jackson 버전을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;완전히 동일한 머신 조건&lt;/b&gt;에서 비교하기 위함&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;1273&quot; data-end=&quot;1303&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 네트워크, 서버 부하 등 변수를 제거하기 위함&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1272&quot; data-start=&quot;1229&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 편의성(단순히 직렬화, 역직렬화시의 성능을 벤치마킹하기 위함)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1310&quot; data-end=&quot;1320&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-2. &lt;/span&gt;JVM 선택&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1322&quot; data-end=&quot;1390&quot;&gt;
&lt;li data-start=&quot;1322&quot; data-end=&quot;1357&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 3 테스트:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;JDK 25&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;1358&quot; data-end=&quot;1390&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 2 테스트:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;JDK 17&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1392&quot; data-end=&quot;1516&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 3는 최신 JDK 환경에서의 사용을 염두에 두고 설계된 반면, Jackson 2는 여전히 JDK 17 환경에서 가장 많이 사용되고 있기 때문에 각각&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;현실적인 사용 환경을 기준&lt;/b&gt;으로 테스트했습니다. &lt;/span&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;중요한 점은,&lt;/span&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;각 비교는 같은 JVM 내에서 Jackson 버전만 바꿔서 수행&lt;/b&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;되었다는 점입니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(Jackson 2 vs 3 비교 시 JVM 자체는 동일하게 유지)&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1130&quot; data-start=&quot;1121&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-3. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소스코드&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1766247441097&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput, Mode.AverageTime) // ops/s + 평균 시간 둘 다 측정
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Fork(value = 2, jvmArgs = [&quot;-Xms2g&quot;, &quot;-Xmx2g&quot;]) // 힙 크기 고정으로 GC 영향 최소화
open class JacksonBench {

    @Param(&quot;1000&quot;, &quot;10000&quot;)
    var bigSize: Int = 0
	
    // A 프로젝트에서는 JsonMapper, B 프로젝트에서는 ObjectMapper로 로직 변경하며 테스트
    // tools.jackson.databind.json.JsonMapper
    private lateinit var mapper: JsonMapper

    // import com.fasterxml.jackson.databind.ObjectMapper
    // private lateinit var mapper: ObjectMapper

    private lateinit var small: SmallDto
    private lateinit var big: BigDto

    private lateinit var smallBytes: ByteArray
    private lateinit var bigBytes: ByteArray

    @Setup(Level.Trial)
    fun setup() {
        mapper = GLOBAL_OBJECT_MAPPER

        small = SmallDto.sample()
        big = BigDto.sample(size = bigSize)

        // deserialize 벤치에서 매번 serialize하지 않도록 미리 만들어둠
        smallBytes = mapper.writeValueAsBytes(small)
        bigBytes = mapper.writeValueAsBytes(big)
    }

    // ---------- Small: Serialize / Deserialize ----------

    @Benchmark
    fun serializeSmall(bh: Blackhole) {
        val bytes = mapper.writeValueAsBytes(small)
        bh.consume(bytes)
    }

    @Benchmark
    fun deserializeSmall(bh: Blackhole) {
        val obj = mapper.readValue(smallBytes, SmallDto::class.java)
        bh.consume(obj)
    }

    @Benchmark
    fun serializeBig(bh: Blackhole) {
        val bytes = mapper.writeValueAsBytes(big)
        bh.consume(bytes)
    }

    @Benchmark
    fun deserializeBig(bh: Blackhole) {
        val obj = mapper.readValue(bigBytes, BigDto::class.java)
        bh.consume(obj)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1766247421677&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class SmallDto(
    val id: Long,
    val name: String,
    val email: String?,
    val age: Int,
    val score: Double,
    val active: Boolean,
    val status: Status,
    val createdAt: Instant,
    val updatedAt: Instant?,
    val requestId: String,
) {
    companion object {
        fun sample(): SmallDto = SmallDto(
            id = 123456789L,
            name = &quot;jeonghyeongil&quot;,
            email = &quot;jeonghyeongil@gmail.com&quot;,
            age = 29,
            score = 98.76,
            active = true,
            status = Status.ACTIVE,
            createdAt = Instant.parse(&quot;2025-12-20T00:00:00Z&quot;),
            updatedAt = Instant.parse(&quot;2025-12-20T01:02:03Z&quot;),
            requestId = UUID.randomUUID().toString()
        )
    }
}

data class Item(
    val id: Long,
    val sku: String,
    val title: String,
    val price: Int,
    val tags: List&amp;lt;String&amp;gt;,
    val attrs: Map&amp;lt;String, String&amp;gt;?,
    val createdAt: Instant
)

data class BigDto(
    val id: Long,
    val items: List&amp;lt;Item&amp;gt;
) {
    companion object {
        fun sample(size: Int): BigDto {
            val rnd = Random(1)
            val tagsPool = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;, &quot;f&quot;)

            val items = (0 until size).map { i -&amp;gt;
                Item(
                    id = i.toLong(),
                    sku = &quot;SKU-$i&quot;,
                    title = &quot;Item Title $i&quot;,
                    price = rnd.nextInt(1000, 100_000),
                    tags = List(3) { t -&amp;gt; tagsPool[(i + t) % tagsPool.size] },
                    attrs = if (i % 10 == 0) null else mapOf(
                        &quot;color&quot; to &quot;black&quot;,
                        &quot;size&quot; to &quot;M&quot;,
                        &quot;idx&quot; to i.toString()
                    ),
                    createdAt = Instant.ofEpochMilli(1_700_000_000_000L + i)
                )
            }

            return BigDto(
                id = 1L,
                items = items
            )
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1130&quot; data-start=&quot;1121&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-4. 힙 메모리를 고정한 이유&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1766247675382&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Fork(
    value = 2,
    jvmArgs = [&quot;-Xms2g&quot;, &quot;-Xmx2g&quot;]
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;힙 메모리를 2GB로 &lt;b&gt;초기 크기와 최대 크기를 동일하게&lt;/b&gt; 설정했습니다. 이렇게 설정한 이유는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;1824&quot; data-start=&quot;1794&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM 힙 확장/축소로 인한 노이즈 제거&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1899&quot; data-start=&quot;1825&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM은 기본적으로 필요에 따라 힙 크기를 늘리거나 줄입니다. 이 과정에서 GC 패턴이 바뀌고, 측정 결과가 흔들릴 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;1949&quot; data-start=&quot;1901&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;serialize / deserialize 자체 비용만 보고 싶었기 때문&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;1960&quot; data-start=&quot;1950&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 실험의 목적은&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2022&quot; data-start=&quot;1961&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1988&quot; data-start=&quot;1961&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;ldquo;메모리가 부족할 때 어떻게 되는가&amp;rdquo;가 아니라&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2022&quot; data-start=&quot;1989&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;ldquo;같은 메모리 조건에서 순수 처리 성능이 어떤가&amp;rdquo;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2065&quot; data-start=&quot;2024&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이기 때문에, 힙 크기를 고정하여 &lt;b&gt;환경 변수를 최대한 제거&lt;/b&gt;했습니다&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1130&quot; data-start=&quot;1121&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-5. 왜 Fork를 사용했는가 (Fork = 2)&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1766247803885&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Fork(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Fork는 &lt;b&gt;JVM을 완전히 새로 띄워서&lt;/b&gt; 벤치마크를 실행하는 옵션입니다. Fork를 사용한 이유는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2280&quot; data-start=&quot;2198&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2227&quot; data-start=&quot;2198&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM은 실행 초기에 JIT 컴파일을 진행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2280&quot; data-start=&quot;2228&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 JVM에서만 실행하면, 이전 실행의 최적화 상태가 다음 측정에 영향을 줄 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2293&quot; data-start=&quot;2282&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Fork를 사용하면:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2347&quot; data-start=&quot;2295&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2312&quot; data-start=&quot;2295&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM을 새로 띄운 상태에서&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2332&quot; data-start=&quot;2313&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동일한 벤치마크를 다시 실행하고&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2347&quot; data-start=&quot;2333&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 결과를 평균냅니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2351&quot; data-start=&quot;2349&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, &lt;b&gt;JVM을 껐다 켠 상태에서 같은 실험을 여러 번 반복&lt;/b&gt;하는 효과를 얻을 수 있습니다. 이번 실험에서는 신뢰도를 확보하면서 실행 시간이 과도하게 늘어나지 않도록 Fork = 2로 설정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1121&quot; data-end=&quot;1130&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-6. Warmup을 왜 이렇게 설정했는가&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1766247899392&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;2604&quot; data-start=&quot;2590&quot; data-ke-size=&quot;size20&quot;&gt;Warmup의 의미&lt;/h4&gt;
&lt;p data-end=&quot;2631&quot; data-start=&quot;2605&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Warmup은 &lt;b&gt;측정하지 않는 구간&lt;/b&gt;입니다. 이 구간에서는 JIT 컴파일, 클래스 로딩, 내부 캐시 준비 등이 이루어집니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;2700&quot; data-start=&quot;2685&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 3초 &amp;times; 3회인가&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2773&quot; data-start=&quot;2701&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2739&quot; data-start=&quot;2701&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1회만으로는 JVM 최적화가 충분히 끝나지 않는 경우가 많습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2773&quot; data-start=&quot;2740&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;너무 길게 잡으면 전체 실행 시간이 과도하게 증가합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2779&quot; data-start=&quot;2775&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 &lt;b&gt;3초 동안 최대한 많이 실행하고 &lt;/b&gt;이를 &lt;b&gt;3번 반복 &lt;/b&gt;하는 설정을 선택했습니다. 이는 JVM이 충분히 &amp;ldquo;몸을 푼 상태&amp;rdquo; 이지만 실행 시간이 과하지 않은 타협점이라고 판단했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1130&quot; data-start=&quot;1121&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-7. &lt;span&gt;&lt;/span&gt;&lt;/span&gt;Measurement 설정 의도&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1766247998581&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Measurement는 &lt;b&gt;실제 결과로 사용되는 구간&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;3064&quot; data-start=&quot;3046&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;ldquo;3초 동안 실행&amp;rdquo;의 의미&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;3089&quot; data-start=&quot;3065&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이것은 &amp;ldquo;3초 동안 한 번 실행&amp;rdquo;이 아니라, &lt;b&gt;3초 동안 해당 메서드를 최대한 많이 반복 실행&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;한다는 의미입니다. &lt;/span&gt;예를 들어 메서드 한 번이 2ms 걸린다면 3초 동안 약 150만 번 실행됩니다. 이렇게 많은 샘플을 모아야 OS 스케줄링, GC 타이밍, 순간적인 노이즈 를 평균으로 눌러서 &lt;b&gt;안정적인 수치&lt;/b&gt;를 얻을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;3288&quot; data-start=&quot;3270&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;iterations = 5&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;3308&quot; data-start=&quot;3289&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 3초짜리 측정을 5번 반복하여 평균, 표준편차, 신뢰 구간 을 계산합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1121&quot; data-end=&quot;1130&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-8. Throughput + AverageTime를 동시에 측정한 이유&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1766248163167&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@BenchmarkMode(Mode.Throughput, Mode.AverageTime)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3481&quot; data-start=&quot;3454&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3481&quot; data-start=&quot;3454&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 모드는 같은 현상을 다른 관점에서 보여줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3621&quot; data-start=&quot;3483&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3549&quot; data-start=&quot;3483&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Throughput (ops/ms)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 서버 처리량 관점&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; &amp;ldquo;1ms에 몇 번 처리 가능한가&amp;rdquo;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3621&quot; data-start=&quot;3551&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;AverageTime (ms/op)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 지연 시간 관점&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; &amp;ldquo;1번 처리하는 데 평균 얼마나 걸리는가&amp;rdquo;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1130&quot; data-start=&quot;1121&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3-9. 코드 설계 의도&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-end=&quot;3768&quot; data-start=&quot;3752&quot; data-ke-size=&quot;size20&quot;&gt;State와 Setup&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1766248346145&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@State(Scope.Benchmark)
@Setup(Level.Trial)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3875&quot; data-start=&quot;3829&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3852&quot; data-start=&quot;3829&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;벤치마크 실행 동안 동일한 객체를 공유&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3875&quot; data-start=&quot;3853&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 시작 시 한 번만 데이터 준비&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3887&quot; data-start=&quot;3877&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 한 이유는 serialize / deserialize &lt;b&gt;자체 비용만 측정&lt;/b&gt;하기 위함입니다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;특히 deserialize 테스트에서&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766248371820&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;smallBytes = mapper.writeValueAsBytes(small)
bigBytes = mapper.writeValueAsBytes(big)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3887&quot; data-start=&quot;3877&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3887&quot; data-start=&quot;3877&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 미리 수행한 이유는 &lt;b&gt;deserialize 성능을 측정하는데 &lt;/b&gt;&lt;b&gt;serialize 비용이 섞이지 않도록 하기 위함&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;4181&quot; data-start=&quot;4168&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Small DTO&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4219&quot; data-start=&quot;4182&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4202&quot; data-start=&quot;4182&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 API 응답 DTO 크기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;4232&quot; data-start=&quot;4221&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Big DTO&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4313&quot; data-start=&quot;4233&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4254&quot; data-start=&quot;4233&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내부에 List&amp;lt;Item&amp;gt; 포함&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4313&quot; data-start=&quot;4255&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대량 리스트 응답 DTO 크기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4324&quot; data-start=&quot;4315&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Big DTO는:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4343&quot; data-start=&quot;4325&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4333&quot; data-start=&quot;4325&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1,000개&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4343&quot; data-start=&quot;4334&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;10,000개&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4387&quot; data-start=&quot;4345&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 케이스로 나누어 &lt;b&gt;크기에 따른 선형 증가 여부&lt;/b&gt;도 함께 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 결과 요약 및 해석&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson3&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766249698849&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Benchmark                      (bigSize)   Mode  Cnt     Score     Error   Units
JacksonBench.deserializeBig         1000  thrpt   10     0.837 &amp;plusmn;   0.043  ops/ms
JacksonBench.deserializeBig        10000  thrpt   10     0.086 &amp;plusmn;   0.001  ops/ms
JacksonBench.deserializeSmall       1000  thrpt   10   541.207 &amp;plusmn;  22.379  ops/ms
JacksonBench.deserializeSmall      10000  thrpt   10   550.785 &amp;plusmn;   6.788  ops/ms
JacksonBench.serializeBig           1000  thrpt   10     2.837 &amp;plusmn;   0.046  ops/ms
JacksonBench.serializeBig          10000  thrpt   10     0.274 &amp;plusmn;   0.004  ops/ms
JacksonBench.serializeSmall         1000  thrpt   10  1708.548 &amp;plusmn;  36.480  ops/ms
JacksonBench.serializeSmall        10000  thrpt   10  1640.783 &amp;plusmn; 114.743  ops/ms
JacksonBench.deserializeBig         1000   avgt   10     1.194 &amp;plusmn;   0.054   ms/op
JacksonBench.deserializeBig        10000   avgt   10    11.696 &amp;plusmn;   0.363   ms/op
JacksonBench.deserializeSmall       1000   avgt   10     0.002 &amp;plusmn;   0.001   ms/op
JacksonBench.deserializeSmall      10000   avgt   10     0.002 &amp;plusmn;   0.001   ms/op
JacksonBench.serializeBig           1000   avgt   10     0.349 &amp;plusmn;   0.006   ms/op
JacksonBench.serializeBig          10000   avgt   10     3.588 &amp;plusmn;   0.070   ms/op
JacksonBench.serializeSmall         1000   avgt   10     0.001 &amp;plusmn;   0.001   ms/op
JacksonBench.serializeSmall        10000   avgt   10     0.001 &amp;plusmn;   0.001   ms/o&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson2&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766249723525&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Benchmark                      (bigSize)   Mode  Cnt     Score    Error   Units
JacksonBench.deserializeBig         1000  thrpt   10     0.677 &amp;plusmn;  0.046  ops/ms
JacksonBench.deserializeBig        10000  thrpt   10     0.075 &amp;plusmn;  0.002  ops/ms
JacksonBench.deserializeSmall       1000  thrpt   10   502.430 &amp;plusmn;  4.089  ops/ms
JacksonBench.deserializeSmall      10000  thrpt   10   487.280 &amp;plusmn; 14.380  ops/ms
JacksonBench.serializeBig           1000  thrpt   10     2.016 &amp;plusmn;  0.025  ops/ms
JacksonBench.serializeBig          10000  thrpt   10     0.190 &amp;plusmn;  0.004  ops/ms
JacksonBench.serializeSmall         1000  thrpt   10  1425.201 &amp;plusmn; 36.081  ops/ms
JacksonBench.serializeSmall        10000  thrpt   10  1458.080 &amp;plusmn; 51.928  ops/ms
JacksonBench.deserializeBig         1000   avgt   10     1.420 &amp;plusmn;  0.029   ms/op
JacksonBench.deserializeBig        10000   avgt   10    13.221 &amp;plusmn;  0.096   ms/op
JacksonBench.deserializeSmall       1000   avgt   10     0.002 &amp;plusmn;  0.001   ms/op
JacksonBench.deserializeSmall      10000   avgt   10     0.002 &amp;plusmn;  0.001   ms/op
JacksonBench.serializeBig           1000   avgt   10     0.500 &amp;plusmn;  0.017   ms/op
JacksonBench.serializeBig          10000   avgt   10     5.068 &amp;plusmn;  0.092   ms/op
JacksonBench.serializeSmall         1000   avgt   10     0.001 &amp;plusmn;  0.001   ms/op
JacksonBench.serializeSmall        10000   avgt   10     0.001 &amp;plusmn;  0.001   ms/op&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;136&quot; data-start=&quot;132&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;136&quot; data-start=&quot;132&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기준&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;259&quot; data-start=&quot;139&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;175&quot; data-start=&quot;139&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;thrpt 개선율&lt;/b&gt; = 처리량 기준 (높을수록 좋음)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;218&quot; data-start=&quot;178&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;avgt 개선율&lt;/b&gt; = 1건 처리 시간 기준 (낮을수록 좋음)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;259&quot; data-start=&quot;221&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;절감 시간&lt;/b&gt; = 객체 &lt;b&gt;1건&lt;/b&gt; 처리 시 줄어든 실제 시간&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Big DTO&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 254px;&quot; border=&quot;1&quot; data-end=&quot;925&quot; data-start=&quot;291&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Size&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;thrpt 개선율&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;avgt 개선율&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1건당 절감 시간&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 51px;&quot; data-end=&quot;530&quot; data-start=&quot;402&quot;&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;421&quot; data-start=&quot;402&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;serializeBig&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;428&quot; data-start=&quot;421&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1000&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;458&quot; data-start=&quot;428&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2.016 ops/ms&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0.500 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;488&quot; data-start=&quot;458&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2.837 ops/ms&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0.349 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;501&quot; data-start=&quot;488&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;+40.7%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;514&quot; data-start=&quot;501&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;-30.2%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;530&quot; data-start=&quot;514&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;0.151 ms&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 51px;&quot; data-end=&quot;660&quot; data-start=&quot;531&quot;&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;550&quot; data-start=&quot;531&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;serializeBig&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;558&quot; data-start=&quot;550&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;10000&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;588&quot; data-start=&quot;558&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0.190 ops/ms&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5.068 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;618&quot; data-start=&quot;588&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0.274 ops/ms&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3.588 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;631&quot; data-start=&quot;618&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;+44.2%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;644&quot; data-start=&quot;631&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;-29.2%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;660&quot; data-start=&quot;644&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1.480 ms&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 51px;&quot; data-end=&quot;791&quot; data-start=&quot;661&quot;&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;682&quot; data-start=&quot;661&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;deserializeBig&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;689&quot; data-start=&quot;682&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1000&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;719&quot; data-start=&quot;689&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0.677 ops/ms&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1.420 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;749&quot; data-start=&quot;719&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0.837 ops/ms&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1.194 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;762&quot; data-start=&quot;749&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;+23.6%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;775&quot; data-start=&quot;762&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;-15.9%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;791&quot; data-start=&quot;775&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;0.226 ms&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 51px;&quot; data-end=&quot;925&quot; data-start=&quot;792&quot;&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;813&quot; data-start=&quot;792&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;deserializeBig&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;821&quot; data-start=&quot;813&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;10000&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;852&quot; data-start=&quot;821&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0.075 ops/ms&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;13.221 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;883&quot; data-start=&quot;852&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0.086 ops/ms&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;11.696 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;896&quot; data-start=&quot;883&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;+14.7%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;909&quot; data-start=&quot;896&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;-11.5%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 51px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;925&quot; data-start=&quot;909&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1.525 ms&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Small DTO (&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;절대 시간이 &lt;/span&gt;&lt;b&gt;1~2ms 수준&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;이라&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;개선율은 존재하지만 단일 요청 체감은 거의 없습니다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 72px;&quot; border=&quot;1&quot; data-end=&quot;1311&quot; data-start=&quot;1035&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;항목&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson 3&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;thrpt 개선율&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;avgt 개선율&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-end=&quot;1212&quot; data-start=&quot;1111&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1132&quot; data-start=&quot;1111&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;serializeSmall&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1153&quot; data-start=&quot;1132&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1425 ~ 1458 ops/ms&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1174&quot; data-start=&quot;1153&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1641 ~ 1709 ops/ms&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1196&quot; data-start=&quot;1174&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;+12.5% ~ +19.9%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1212&quot; data-start=&quot;1196&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;~0.001 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-end=&quot;1311&quot; data-start=&quot;1213&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1236&quot; data-start=&quot;1213&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;deserializeSmall&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1255&quot; data-start=&quot;1236&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;487 ~ 502 ops/ms&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1274&quot; data-start=&quot;1255&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;541 ~ 551 ops/ms&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1295&quot; data-start=&quot;1274&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;+7.7% ~ +13.0%&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1311&quot; data-start=&quot;1295&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;~0.002 ms/op&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하면 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1503&quot; data-start=&quot;1279&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1350&quot; data-start=&quot;1279&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Small DTO&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; Jackson 3가 더 빠르지만, 절대 시간이 매우 작아 단일 요청 체감은 제한적입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1503&quot; data-start=&quot;1352&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Big DTO&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; Jackson 3는&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1503&quot; data-start=&quot;1387&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1421&quot; data-start=&quot;1387&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;직렬화 기준 최대 &lt;b&gt;약 40~45% 처리량 개선&lt;/b&gt;,&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1462&quot; data-start=&quot;1424&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역직렬화 기준 &lt;b&gt;약 15~25% 처리량 개선&lt;/b&gt;,&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1503&quot; data-start=&quot;1465&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체 1건당 &lt;b&gt;약 0.15~1.48ms&lt;/b&gt;, 역직렬화는 객체 1건당 &lt;b&gt;약 0.23~1.53ms&lt;/b&gt;의 밀리초 단위의 실질적인 시간 절감&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1593&quot; data-start=&quot;1505&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대용량 JSON 처리 구간에서 실제로 의미 있는 개선을 확인할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. 정리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 실험은 동일 코드, 동일 데이터 구조, 동일 JMH 설정 동일 머신 환경 에서 &lt;b&gt;Jackson 버전만 변경&lt;/b&gt;하여 수행되었습니다.&amp;nbsp; 그 결과 &lt;b&gt;Jackson 3는 Jackson 2 대비 &lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특히 대용량 JSON 처리에서 명확하고 일관&lt;/span&gt;된 성능 개선을 보여주었습니다. &lt;/b&gt;Small DTO 위주의 서비스에서는 체감이 크지 않을 수 있지만, Big DTO일때는 체감이 꽤나 컸습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6. 왜 Jackson3이 더 빠를까?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson3이 왜 더 빠른지 실제 라이브러리 내부 코드를 확인해보았습니다. Jackson 3은 직렬화 과정에서 &lt;b&gt;반복과 런타임 판단을 최대한 제거하는 방향&lt;/b&gt;으로 성능을 개선한 것을 확인하였습니다. 대표적으로 아래와 같은 최적화들이 적용되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;268&quot; data-start=&quot;238&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6-1. UnrolledBeanSerializer (반복문 + 배열 접근 제거)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;310&quot; data-start=&quot;289&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 구조 (Jackson 2)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766328954037&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 배열을 순회하면서 프로퍼티 직렬화
protected void _serializePropertiesNoView(
        Object bean,
        JsonGenerator gen,
        SerializationContext provider,
        BeanPropertyWriter[] props
) {
    int i = 0;
    int left = props.length;

    do {
        BeanPropertyWriter prop = props[i];
        prop.serializeAsProperty(bean, gen, provider);

        prop = props[i + 1];
        prop.serializeAsProperty(bean, gen, provider);

        i += 2;
        left -= 2;
    } while (left &amp;gt; 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-end=&quot;823&quot; data-start=&quot;816&quot; data-ke-size=&quot;size20&quot;&gt;문제점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;902&quot; data-start=&quot;824&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;857&quot; data-start=&quot;824&quot;&gt;props[i] 접근마다 bounds check 발생&lt;/li&gt;
&lt;li data-end=&quot;887&quot; data-start=&quot;858&quot;&gt;i, left 같은 카운터 변수 관리 필요&lt;/li&gt;
&lt;li data-end=&quot;902&quot; data-start=&quot;888&quot;&gt;반복 조건을 매번 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;992&quot; data-start=&quot;946&quot; data-ke-size=&quot;size20&quot;&gt;개선 구조 (Jackson 3 &amp;ndash; UnrolledBeanSerializer)&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1766328979681&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 프로퍼티를 배열이 아닌 필드로 보관
protected BeanPropertyWriter _prop1;
protected BeanPropertyWriter _prop2;
protected BeanPropertyWriter _prop3;
protected BeanPropertyWriter _prop4;
protected int _propCount;

protected void serializeNonFiltered(
        Object bean,
        JsonGenerator gen,
        SerializationContext provider
) throws IOException {
    gen.writeStartObject(bean);

    switch (_propCount) {
        case 4:
            _prop1.serializeAsProperty(bean, gen, provider);
        case 3:
            _prop2.serializeAsProperty(bean, gen, provider);
        case 2:
            _prop3.serializeAsProperty(bean, gen, provider);
        case 1:
            _prop4.serializeAsProperty(bean, gen, provider);
    }

    gen.writeEndObject();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1762&quot; data-start=&quot;1753&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개선 효과&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1833&quot; data-start=&quot;1763&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1773&quot; data-start=&quot;1763&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배열 접근 제거&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1782&quot; data-start=&quot;1774&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반복문 제거&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1793&quot; data-start=&quot;1783&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조건 체크 제거&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1833&quot; data-start=&quot;1794&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;switch fall-through 구조로 CPU 분기 예측에 유리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;238&quot; data-end=&quot;268&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6-2.&lt;/span&gt;&amp;nbsp;직렬화 사전 결정&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;기존 구조 - 매번 타입 확인(Jackson 2)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766329102934&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void serializeAsProperty(
        Object bean,
        JsonGenerator g,
        SerializationContext ctxt
) throws Exception {
    Object value = get(bean);

    ValueSerializer&amp;lt;Object&amp;gt; ser = _serializer;
    if (ser == null) {
        Class&amp;lt;?&amp;gt; cls = value.getClass();
        PropertySerializerMap m = _dynamicSerializers;

        ser = m.serializerFor(cls);
        if (ser == null) {
            ser = _findAndAddDynamic(m, cls, ctxt);
        }
    }

    ser.serialize(value, g, ctxt);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-end=&quot;2467&quot; data-start=&quot;2449&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매 호출마다 발생하는 작업&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2516&quot; data-start=&quot;2468&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2483&quot; data-start=&quot;2468&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;직렬화기 존재 여부 체크&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2495&quot; data-start=&quot;2484&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;런타임 타입 확인&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2503&quot; data-start=&quot;2496&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;캐시 조회&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2516&quot; data-start=&quot;2504&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필요 시 신규 생성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2540&quot; data-start=&quot;2518&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체 수 &amp;times; 프로퍼티 수만큼 반복됩니다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;2540&quot; data-start=&quot;2518&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;개선 구조 - 생성 시점에 한 번만 결정(Jackson 3)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1766329172502&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public void resolve(SerializationContext provider) throws JsonMappingException {
    for (BeanPropertyWriter prop : _props) {
        if (prop.hasSerializer()) {
            continue;
        }

        JavaType type = prop.getType();

        // final 타입은 런타임에 바뀌지 않음
        if (type.isFinal()) {
            ValueSerializer&amp;lt;Object&amp;gt; ser =
                provider.findPrimaryPropertySerializer(type, prop);
            prop.assignSerializer(ser);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 직렬화시&lt;/p&gt;
&lt;pre id=&quot;code_1766329187791&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void serializeAsProperty(
        Object bean,
        JsonGenerator g,
        SerializationContext ctxt
) throws Exception {
    Object value = get(bean);

    // final 타입이면 이미 serializer가 할당되어 있음
    _serializer.serialize(value, g, ctxt);
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;238&quot; data-end=&quot;268&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6-3. 요약&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;3614&quot; data-start=&quot;3482&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;항목&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackeson2&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Jackson3&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3544&quot; data-start=&quot;3527&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3535&quot; data-start=&quot;3527&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배열 접근&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3539&quot; data-start=&quot;3535&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;O&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3544&quot; data-start=&quot;3539&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3560&quot; data-start=&quot;3545&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3551&quot; data-start=&quot;3545&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반복문&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3555&quot; data-start=&quot;3551&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;O&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3560&quot; data-start=&quot;3555&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3589&quot; data-start=&quot;3561&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3573&quot; data-start=&quot;3561&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;런타임 타입 체크&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3578&quot; data-start=&quot;3573&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매번&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3589&quot; data-start=&quot;3578&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성 시 1회&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3614&quot; data-start=&quot;3590&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3602&quot; data-start=&quot;3590&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CPU 분기 예측&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3608&quot; data-start=&quot;3602&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어려움&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3614&quot; data-start=&quot;3608&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쉬움&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 외에도 Jackson 3에는 다음과 같은 개선이 함께 적용되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4037&quot; data-start=&quot;3847&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3905&quot; data-start=&quot;3847&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PropertySerializerMap에서 단일 타입 사용 시 해시맵을 우회하는 빠른 조회 경로 추가&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3950&quot; data-start=&quot;3906&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SerializerCache에 읽기 전용 캐시를 분리하여 락 없는 조회 가능&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3987&quot; data-start=&quot;3951&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;리스트 직렬화 시 Iterator 대신 인덱스 기반 접근 사용&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4037&quot; data-start=&quot;3988&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java 17 환경에서 Record 타입의 구조적 특성을 활용하여 리플렉션 비용 감소&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <category>jackson2</category>
      <category>jackson3</category>
      <category>jdk25</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/231</guid>
      <comments>https://junghyungil.tistory.com/231#entry231comment</comments>
      <pubDate>Sun, 21 Dec 2025 01:46:21 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 파티션이 증가할수록 commit_offset log file 리소스도 증가할까?</title>
      <link>https://junghyungil.tistory.com/230</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안녕하세요. 이전에 파티션 증가 없이 동시 처리량을 늘리기 위해 Parallel Consumer에 관해 살펴보고 PoC를 진행해보았습니다. 파티션이 증가하게 되면 아래와 같은 문제들이 발생합니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브로커 파일 시스템 리소스 증가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장애에 더 취약한 구조&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;복제 비용 증가&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 이러한 문제들을 해결하기 위해 파티션 증가 없이 동시 처리량을 늘려보고자 하였고&lt;/span&gt; &lt;a href=&quot;https://junghyungil.tistory.com/229&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이에 대한 글도 작성&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하였습니다. &lt;br /&gt;&lt;br /&gt;저는 기존 카프카에서 파티션이 증가할 수록 브로커 파일 시스템 리소스가 증가할 것이라고 생각했습니다. 왜냐하면 동시 처리량을 늘리기 위해서는 파티션과 컨슈머를 늘려야합니다. 컨슈머 수가 늘어난 만큼 오프셋 커밋도 늘어나서 comit_offset log file에 기록되는 리소스 또한 증가할 것이라고 생각했기 떄문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743939089161&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;오프셋 커밋은 consumer가 요청하고, 그 요청을 broker의 group coordinator가 받아서
__consumer_offsets라는 내부 topic에 기록하는 구조입니다. consumer마다 발생합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만, 테스트 결과 제 생각은 틀렸다는 것을 확인할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;파티션이&amp;nbsp;증가할수록&amp;nbsp;commit_offset&amp;nbsp;log&amp;nbsp;file&amp;nbsp;리소스도&amp;nbsp;증가할까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 Kafka에서 토픽 파티션 수(1 vs 3)에 따라__consumer_offsets log 파일 시스템 리소스 용량 차이가 발생하는지 직접 확인해보았습니다. 파티션 수를 1과 3으로 각각 설정하고 메시지를 십만 건 발행, 컨슘, 오프셋 커밋하여&amp;nbsp; 파일 시스템 리소스 용창 차이를 확인해보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래의 명령어로 파일 시스템 리소스 용량 차이를 확인해보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743939919994&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 커밋 로그가 기록된 __consumer_offsets 파티션 확인
find .data -name '*.log' | grep __consumer_offsets

# 각 offsets 파티션별 log 파일 크기 확인
du -ch .data/kafka*/__consumer_offsets-*/*.log

# 전체 __consumer_offsets log 파일 총합 확인
du -ch $(find .data -name '*.log' | grep __consumer_offsets) | tail -n1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 동일한 consumer group ID를 유지한 채, 메시지 key를 각각 다르게 설정해보았고, key에 % 2 연산을 적용하여 일부러 분산되도록 구성해보기도 했습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;실험 결과&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1007&quot; data-start=&quot;863&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;863&quot; data-end=&quot;936&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;파티션 1개 / 파티션 3개&lt;/b&gt;&amp;nbsp;모두 특정 __consumer_offsets-N 디렉토리 하위의 .log file 하나만 리소스 증가&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;863&quot; data-end=&quot;936&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션 수 변경시 로그 파일은 초기화하고 다시 테스트하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;967&quot; data-start=&quot;937&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파일 사이즈도 &lt;b&gt;두 경우 모두 64KB로 동일&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1007&quot; data-start=&quot;968&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, &lt;b&gt;파티션 수가 늘어나도 파일 시스템 사용량에는 차이 없음&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743940203904&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// find .data -name '*.log' | grep __consumer_offsets
// du -ch .data/kafka*/__consumer_offsets-*/*.log

  
0B	.data/kafka1/__consumer_offsets-11/00000000000000000000.log
0B	.data/kafka1/__consumer_offsets-14/00000000000000000000.log
896K	.data/kafka1/__consumer_offsets-29/00000000000000000000.log
.. 생략
0B	.data/kafka3/__consumer_offsets-7/00000000000000000000.log&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;두 경우 모두 파일 사이즈가 64KB로 동일한 이유가 무엇일까요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션이나 컨슈머 수가 많아도 메시지 단위로 오프셋 커밋하면 커밋 횟수가 같기 때문입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메시지 단위를 배치로 커밋해도 결국 &amp;lt;groupId, topic, partition&amp;gt; 단위의 최신 오프셋만 유지되므로, 메모리 사용량은 크게 달라지지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;1134&quot; data-end=&quot;1194&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kafka는 compacted topic 구조를 사용해 offset을 &amp;lt;groupId, topic, partition&amp;gt; 기준으로 덮어쓰기 때문에, 메시지 key가 다르든 같든, 배치 커밋이든 메시지당 커밋이든 관계없이 &lt;b&gt;각 key별로 최신 오프셋만 유지되며&lt;/b&gt;, 그 결과 디스크 사용량이 최적화됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;1134&quot; data-end=&quot;1194&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kafka가 offset 커밋을 compacted topic에 덮어쓰는 이유는, &amp;ldquo;어디까지 읽었는가&amp;rdquo;만 중요하고, 과거에 뭐까지 읽었는지는 중요하지 않기 때문입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;b&gt;두 경우 &lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;모두 특정 __consumer_offsets-N 디렉토리 하위의 .log file 하나만 리소스가 증가한 이유가 무엇일까요?&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1323&quot; data-start=&quot;1036&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1133&quot; data-start=&quot;1036&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kafka는 offset commit을 groupId 기준으로 해싱하여 &lt;b&gt;__consumer_offsets 파티션 중 하나에만 기록하기 때문입니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1194&quot; data-start=&quot;1134&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 &lt;b&gt;같은 groupId&lt;/b&gt;를 사용하면, 모든 커밋이 &lt;b&gt;같은 offsets 로그 파일&lt;/b&gt;에만 몰리게 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.confluent.io/kafka/design/log_compaction.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.confluent.io/kafka/design/log_compaction.html?utm_source=chatgpt.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kafka</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/230</guid>
      <comments>https://junghyungil.tistory.com/230#entry230comment</comments>
      <pubDate>Sun, 6 Apr 2025 21:11:13 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] Kafka에서 파티션 증가 없이 동시 처리량을 늘리는 방법 - Parallel Consumer</title>
      <link>https://junghyungil.tistory.com/229</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 Kafka 사용 시 처리량을 늘리기 위해 컨슈머와 파티션 수를 늘린 후 병렬처리를 통해 처리량을 증가시킵니다. 하지만 저는 처리량을 늘릴 때마다 파티션을 추가하는 것이 최선인지에 대한 의문을 가지고 있었습니다. 파티션을 늘리는 대신, 한 파티션에서 단일 메시지가 아니라 여러 개의 메시지를 가져와 병렬 처리하면, 파티션 개수를 증가시키지 않고도 성능을 높일 수 있지 않을까 하는 고민을 해왔습니다. 이러한 고민을 하게 된 이유는 아래와 같습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;true&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;인프라 비용 증가&lt;/b&gt;&lt;/span&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-spread=&quot;false&quot;&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션이 많아질수록 추가적인 브로커가 필요할 가능성이 높아지고&amp;nbsp;브로커 노드 수 증가에 따른 서버 비용, 네트워크 비용 등이 상승할 수 있습니다.&lt;/span&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;브로커 파일 시스템 리소스 사용량 증가&lt;/b&gt;&lt;/span&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kafka 브로커는 각 파티션별로 데이터를 저장합니다. 데이터 정보(.log 파일)뿐만 아니라 메타 정보(.index, .timeindex, .snapshot 파일)도 함께 저장되며, 파티션 수가 증가할수록 파일 오픈 비용, 디스크 사용량이 증가합니다.&lt;/span&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;장애에 더 취약한 구조&lt;/b&gt;&lt;/span&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단일 브로커에 많은 파티션 리더가 배치되면, 브로커 장애 또는 재시작 시 영향받는 범위가 넓어질 수 있습니다.&lt;/span&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;복제 비용 증가&lt;/b&gt;&lt;/span&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션 단위로 설정된 replicas 수만큼 복제가 이루어지므로, 복제 과정에서 디스크 사용량 증가 및 레이턴시 증가가 발생합니다&lt;/span&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ol&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;div&gt;
   &amp;nbsp;
  &lt;/div&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;div&gt; 
   &lt;div data-message-model-slug=&quot;gpt-4o&quot; data-message-id=&quot;350ac221-6dc7-4886-8707-14b6d38838d3&quot; data-message-author-role=&quot;assistant&quot;&gt; 
    &lt;div&gt; 
     &lt;div&gt; 
      &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처리량을 높이기 위해 무조건 파티션을 늘려 인프라 비용을 증가시키기보다는, 기존 파티션 수를 유지한 채 하나의 파티션에서 여러 개의 메시지를 조회하는 방식으로 불필요한 비용 증가를 방지할 수 있다고 판단하였습니다. 또한 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;파티션 수가 적은 환경에서는 이러한 문제가 크지 않을 수 있지만, 과도하게 증가한 환경에서는 심각한 성능 저하로 이어질 수 있다고 생각하였습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; 
     &lt;/div&gt; 
    &lt;/div&gt; 
   &lt;/div&gt; 
  &lt;/div&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 문제를 해결할 방법을 찾던 중, &lt;b&gt;Confluent Inc.에서 개발한 오픈소스 라이브러리인 &lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #006DD7;&quot;&gt;Parallel Consumer&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 발견하였고, 이를 활용한 PoC(Proof of Concept)를 진행해보았습니다.&lt;/span&gt;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0593D3;&quot;&gt;개요&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Parallel Consumer&lt;/b&gt;는 Confluent Inc.에서 개발한 오픈소스 라이브러리로, &lt;b&gt;단일 파티션 내에서 여러 컨슈머 스레드를 활용하여 동시 처리량을 증가&lt;/b&gt;시키는 기능을 제공합니다. 일반적으로 Kafka에서는 하나의 파티션이 하나의 컨슈머 스레드에 의해 처리되지만, Parallel Consumer를 사용하면 &lt;b&gt;파티션 개수를 증가시키지 않고도 병렬 처리 성능을 향상&lt;/b&gt;시킬 수 있습니다. 해당 글에서는 Parallel Consumer의 내부 구조를 분석하고, 연동 및 개발 과정에서 수행한 PoC(Proof of Concept) 결과를 정리하였습니다.&lt;br&gt;&lt;br&gt;아래는 기존 kafka Consumer(위)과 Parallel Kafka Consumer(아래)를 비교한 그림입니다.&lt;br&gt;&lt;/span&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccSJNf/btsL1Y3gNy3/yFFXQW0FJE4QOQTk3WLKmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccSJNf/btsL1Y3gNy3/yFFXQW0FJE4QOQTk3WLKmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccSJNf/btsL1Y3gNy3/yFFXQW0FJE4QOQTk3WLKmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccSJNf%2FbtsL1Y3gNy3%2FyFFXQW0FJE4QOQTk3WLKmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;522&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0593D3;&quot;&gt;Parallel Consumer의 주요 특징&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;true&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;단일 파티션 내 동시성 증가&lt;/b&gt;&lt;/span&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 Kafka의 소비 모델은 하나의 파티션이 단일 컨슈머 스레드에 의해 처리됩니다.&lt;/span&gt;&lt;/li&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Parallel Consumer는 하나의 파티션 내에서 여러 개의 워커 스레드가 메시지를 병렬로 처리할 수 있습니다.&lt;/span&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;오프셋 관리 개선&lt;/b&gt;&lt;/span&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kafka의 기본 컨슈머 모델에서는 특정 레코드가 처리되지 않으면 다음 레코드로 진행할 수 없습니다.&lt;/span&gt;&lt;/li&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Parallel Consumer는 개별 레코드 단위로 오프셋을 관리하여 특정 메시지가 지연되더라도 다른 메시지를 먼저 처리할 수 있습니다.&lt;/span&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;순서 보장 옵션 제공&lt;/b&gt;&lt;/span&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메시지 처리 순서를 엄격하게 유지해야 하는 경우, 특정 키 또는 파티션 단위로 순서를 보장하는 옵션을 제공합니다.&lt;/span&gt; 
    &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
     &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;KEY&lt;/b&gt;: 동일 키의 메시지 순서를 보장.&lt;/span&gt;&lt;/li&gt; 
     &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PARTITION&lt;/b&gt;: 파티션 내 순서만 보장.&lt;/span&gt;&lt;/li&gt; 
     &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;UNORDERED&lt;/b&gt;: 순서 보장 없이 병렬 처리.&lt;/span&gt;&lt;/li&gt; 
    &lt;/ul&gt; &lt;/li&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비순차 처리가 가능한 경우에는 높은 처리량을 달성할 수 있도록 최적화 되어 있습니다.&lt;/span&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;좀 더 자세한 내용은&lt;/span&gt; &lt;a href=&quot;https://d2.naver.com/helloworld/7181840&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;해당 글&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 참고해주세요.&lt;/span&gt;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0593D3;&quot;&gt;조건&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;1 ~ 100 사이의 key를 가진 message 100개를 생성하고 topic에 send 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
class UserController(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val userMessageProducer: UserMessageProducer,
) {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PostMapping(&quot;/send&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun send() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generateUserMessages().forEach { userMessageProducer.sendMessage(it) }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PostMapping(&quot;/parallel-send&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun parallelSend() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generateUserMessages().forEach { userMessageProducer.sendParallelMessage(it) }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PostMapping(&quot;/batch-parallel-send&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun batchParallelSend() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generateUserMessages().forEach { userMessageProducer.sendBatchParallelMessage(it) }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private fun generateUserMessages() = (1..100).map { index -&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserMessage(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id = index.toLong(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;age = index.toLong(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name = &quot;name$index&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;kafkaListener, ParallelKafkaListener(단 건, 배치)를 통해 topic의 message를 consume 합니다.&lt;/span&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 글에서는 배치 consume은 테스트 하지 않습니다.&lt;/span&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;명확한 처리 시간 비교를 위해 message consume후 Thread.sleep(1000)을 걸어줍니다.&lt;/span&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ParallelKafkaListener의 ordering는 &lt;b&gt;UNORDERED&lt;/b&gt;로 지정합니다.&lt;/span&gt;&lt;/li&gt; 
&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
class UserMessageConsumer(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val objectMapper: ObjectMapper,
) {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@KafkaListener(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;topics = [USER_TOPIC],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;groupId = &quot;user-consumer-group&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;concurrency = &quot;2&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun listen(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;record: ConsumerRecord&amp;lt;String, String&amp;gt;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;acknowledgment: Acknowledgment,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val message = objectMapper.readValue(record.value(), UserMessage::class.java)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&quot;[Main Consumer(${Thread.currentThread().id})] Message arrived! - $message&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Thread.sleep(1000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;acknowledgment.acknowledge()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (e: InterruptedException) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;e.printStackTrace()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(e.message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@KafkaParallelListener(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;topics = [PARALLEL_USER_TOPIC],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;groupId = &quot;parallel-user-consumer-group&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;concurrency = 2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ordering = ParallelConsumerOptions.ProcessingOrder.UNORDERED
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun parallelListen(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;record: ConsumerRecord&amp;lt;String, String&amp;gt;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;acknowledgment: Acknowledgment
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val message = objectMapper.readValue(record.value(), UserMessage::class.java)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&quot;[Thread ${Thread.currentThread().id}] Partition ${record.partition()} - Message arrived! - $message&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;acknowledgment.acknowledge()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Thread.sleep(1000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (e: InterruptedException) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;e.printStackTrace()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(e.message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@KafkaParallelListener(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;topics = [BATCH_PARALLEL_USER_TOPIC],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;groupId = &quot;batch-parallel-user-consumer-group&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;concurrency = 1,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batchSize = 10,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ordering = ParallelConsumerOptions.ProcessingOrder.UNORDERED
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun batchListen(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;records: List&amp;lt;ConsumerRecord&amp;lt;String, String&amp;gt;&amp;gt;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val messages = records.map { it.value() }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&quot;[Main Consumer(${Thread.currentThread().id})] Messages arrived! - $messages&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Thread.sleep(1000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (e: InterruptedException) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;e.printStackTrace()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(e.message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;companion object {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val log = LoggerFactory.getLogger(this::class.java)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kafka Parallel Consumer 라이브러리는 @Listener 같은 어노테이션을 지원해주지 않아서 직접 개발하였습니다.&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.ANNOTATION_CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class KafkaParallelListener(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val topics: Array&amp;lt;String&amp;gt;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val groupId: String = &quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val concurrency: Int = ParallelConsumerOptions.DEFAULT_MAX_CONCURRENCY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val batchSize: Int = 1,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val ordering: ParallelConsumerOptions.ProcessingOrder = ParallelConsumerOptions.ProcessingOrder.KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val clientIdPrefix: String = &quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val clientIdSuffix: String = &quot;&quot;,
)&lt;/code&gt;&lt;/pre&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class KafkaParallelStreamProcessorFactory&amp;lt;K, V&amp;gt; {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun createParallelStreamProcessor(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kafkaConsumerFactory: ConsumerFactory&amp;lt;K, V&amp;gt;, // Kafka Consumer를 생성하는 팩토리
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;topics: Array&amp;lt;String&amp;gt;, // 구독할 Kafka 토픽 목록
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ordering: ParallelConsumerOptions.ProcessingOrder, // 메시지 처리 순서 설정 (UNORDERED, PARTITION, KEY)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;concurrency: Int, // 병렬로 처리할 최대 동시 실행 개수
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batchSize: Int, // 한 번에 가져올 메시지 배치 크기 (1 이상일 경우 배치 처리)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;groupId: String, // Kafka Consumer Group ID (같은 그룹이면 메시지를 분배하여 처리)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clientIdPrefix: String, // Kafka Consumer의 Client ID 접두사 (개별 Consumer 식별용)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clientIdSuffix: String, // Kafka Consumer의 Client ID 접미사 (여러 개의 Consumer 구별용)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;): ParallelStreamProcessor&amp;lt;K, V&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val options: ParallelConsumerOptions&amp;lt;K, V&amp;gt; = ParallelConsumerOptions.builder&amp;lt;K, V&amp;gt;()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.ordering(ordering)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.maxConcurrency(concurrency)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.batchSize(batchSize)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.consumer(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kafkaConsumerFactory.createConsumer(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;groupId.takeIf { it.isNotEmpty() },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clientIdPrefix,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clientIdSuffix,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val eosStreamProcessor = ParallelStreamProcessor.createEosStreamProcessor(options)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;eosStreamProcessor.subscribe(topics.toList())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return eosStreamProcessor
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
class KafkaParallelStreamProcessor(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val kafkaConsumerFactory: ConsumerFactory&amp;lt;String, Any&amp;gt;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val kafkaParallelListenerValidator: KafkaParallelListenerValidator,
) : BeanPostProcessor, DisposableBean {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val kafkaParallelConsumerFactory = KafkaParallelStreamProcessorFactory&amp;lt;String, Any&amp;gt;()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val consumers = mutableListOf&amp;lt;ParallelStreamProcessor&amp;lt;String, Any&amp;gt;&amp;gt;()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override fun postProcessAfterInitialization(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bean: Any,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;beanName: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;): Any? {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bean.javaClass.methods
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.mapNotNull { method -&amp;gt; method.getAnnotation(KafkaParallelListener::class.java)?.let { method to it } }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.forEach { (method, annotation) -&amp;gt; processKafkaParallelListenerMethod(bean, method, annotation) }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return bean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private fun processKafkaParallelListenerMethod(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bean: Any,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;method: Method,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kafkaParallelListener: KafkaParallelListener,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kafkaParallelListenerValidator.validate(kafkaParallelListener)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val processor = with(kafkaParallelListener) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kafkaParallelConsumerFactory.createParallelStreamProcessor(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kafkaConsumerFactory = kafkaConsumerFactory,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;topics = topics,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ordering = ordering,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;concurrency = concurrency,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batchSize = batchSize,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;groupId = groupId,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clientIdPrefix = clientIdPrefix,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clientIdSuffix = &quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;processor.poll { context: PollContext&amp;lt;String, Any&amp;gt; -&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val records = context.consumerRecordsFlattened
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val parameterType = method.parameterTypes.firstOrNull()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (parameterType != null &amp;amp;&amp;amp; parameterType.isAssignableFrom(List::class.java)) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;method.invoke(bean, records)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return@poll
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;records.forEach { method.invoke(bean, it) }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (e: Exception) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&quot;Error while processing message&quot;, e)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;consumers.add(processor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override fun destroy() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;consumers.forEach(ParallelStreamProcessor&amp;lt;String, Any&amp;gt;::close)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;companion object {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val log = LoggerFactory.getLogger(this::class.java)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체적인 코드는&lt;/span&gt; &lt;a href=&quot;https://github.com/Hyung1Jung/kafka-parallel-consumer&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/Hyung1Jung/kafka-parallel-consumer&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 참고해주세요.&lt;/span&gt;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #0593D3;&quot;&gt;&lt;b&gt;Kafka Consumer vs Kafka Parallel Consumer &lt;/b&gt;&lt;b&gt;처리 시간 비교&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 51px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 17px;&quot;&gt;&lt;td style=&quot;width: 27.8682%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #FFFFFF;&quot;&gt;partition, concurrency 수&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.7984%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #FFFFFF;&quot;&gt;kafka consumer&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #FFFFFF;&quot;&gt;kafka parallel consumer&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 17px;&quot;&gt;&lt;td style=&quot;width: 27.8682%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;partition=1, concurrency=2&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.7984%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;100s&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 17px;&quot;&gt;&lt;td style=&quot;width: 27.8682%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;partition=2, concurrency=2&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.7984%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위의 조건 기반으로 테스트 해본 결과 &lt;b&gt;kafka parallel consumer 경우 파티션을 늘리지 않고도 동시 처리량을 늘릴 수 있는 것을 확인&lt;/b&gt;할 수 있습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가로, parallel consumerd의 경우&amp;nbsp; &lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;partition=1, concurrency=2, &lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;partition=2, concurrency=2&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt; &amp;nbsp;&lt;/span&gt;의 처리 시간이 동일한 것을 볼 수 있는데요. Parallel Consumer에서 &lt;b&gt;concurrency는 전체적인 병렬 실행 수를 제한&lt;/b&gt;하며, &lt;b&gt;각 파티션별 concurrency를 설정하는 것이 아님을 &lt;/b&gt;테스트를 통해 확인할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Case 1) partition=1, concurrency=2&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션 1개에서 최대 2개 동시 실행&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;[Thread 1] - Message 1
[Thread 2] - Message 2
(1초 후)
[Thread 1] - Message 3
[Thread 2] - Message 4
...&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Case 2) partition=2, concurrency=2&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 파티션이 아닌 전체적 파티션 기준으로 랜덤하게 최대 2개 실행&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;[Thread 1] - Message 1 (Partition 0)
[Thread 2] - Message 51 (Partition 1)
(1초 후)
[Thread 1] - Message 2 (Partition 1)
[Thread 2] - Message 52 (Partition 1)
(1초 후)
[Thread 1] - Message 3 (Partition 0)
[Thread 2] - Message 53 (Partition 0)
...

Kafka Parallel Consumer는 전체적으로 처리량을 조절하지만, 매번 각 파티션에서 정확히 한 개씩 가져오지는 
않고 파티션마다 번갈아 가며 메시지를 가져옴.

- 파티션 0,1에서 각각 한 개씩 → 기본적인 Fetch 동작 (각 파티션에서 균등하게 메시지를 가져옴)
- 파티션 1에서 두 개 → 특정 Poll 타이밍에 따라 파티션 1에서 먼저 Fetch되는 경우 발생
- 파티션 0에서 두 개 → Kafka가 Offset Commit 상태를 기반으로 특정 파티션을 우선적으로 Poll할 수 있음

즉, Parallel Consumer는 전체 동시 실행 개수를 조절하지만, 특정 시점에서 어떤 파티션이 먼저 가져가는지는 
Kafka의 Poll 타이밍과 Offset Commit 상태에 따라 달라짐&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;순서 보장이 필요 없는 경우 &lt;b&gt;Kafka Parallel Consumer는 기존 Kafka와 다르게 Partiton을 늘리지 않고 concurrency만 늘림으로써 처리량을 높일 수&lt;/b&gt; 있는 것을 확인할 수 있었습니다. 처리량을 높일때 순서 보장이 필요 없다면 굳이 기존 Kafka를 사용하여 파티션을 늘리는 것보단 &lt;b&gt;Parallel Consumer을 사용하여 파티션을 늘리지 않고 인프라 비용 절감, 성능 최적화 등의 이점&lt;/b&gt;을 얻는게 좋을 것 같습니다.&lt;/span&gt;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kafka Parallel Consumer Key, Unordered, Partition 동작 방식 및 처리 시간 비교&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번엔 Kafka Parallel Consumer의 순서 보장 방식인 Unordered, Key, Partition의 동작 방식 및 처리 시간을 비교해보았습니다. .&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PostMapping(&quot;/parallel-send&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun parallelSend() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generateUserMessages().forEach { userMessageProducer.sendParallelMessage(it) }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private fun generateUserMessages() = (1..100).map { index -&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserMessage(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id = index.toLong() // or index.toLong() % 2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;age = index.toLong(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name = &quot;name$index&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@KafkaParallelListener(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;topics = [PARALLEL_USER_TOPIC],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;groupId = &quot;parallel-user-consumer-group&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;concurrency = 2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ordering = ParallelConsumerOptions.ProcessingOrder.UNORDERED // or KEY or PARTITION
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun listen(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;record: ConsumerRecord&amp;lt;String, String&amp;gt;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val message = objectMapper.readValue(record.value(), UserMessage::class.java)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&quot;[Thread ${Thread.currentThread().id}] Partition ${record.partition()} - Message arrived! - $message&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Thread.sleep(1000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (e: InterruptedException) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;e.printStackTrace()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(e.message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. Unordered&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;partition=1, concurrency=2&lt;/span&gt;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 57px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;처리 개수&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;처리 순서&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;처리 시간&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유한(&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;1~100)&lt;/span&gt; message 100개&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유하지 않은&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;((1~100)%2)&lt;/span&gt; message 100개&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체&amp;nbsp;순서 X, 같은 키 끼리 순서 X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;partition=2, concurrency=2&lt;/span&gt;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 57px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;처리 개수&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;처리 순서&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;처리 시간&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유한(&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;1~100)&lt;/span&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X, 파티션내 순서 X,&amp;nbsp;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유하지 않은&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;((1~100)%2)&lt;/span&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X, 파티션내 순서 X, 같은 키 끼리 순서 X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이유&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Unordered는 순서를 보장하지 않는 방식입니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key에 따라 특정 파티션으로 가더라도, 같은 키라도&amp;nbsp;동시성(concurrency)&amp;nbsp;때문에 순서가 보장되지 않습니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Concurrency가 2이므로&amp;nbsp;같은 파티션 내에서도 여러 개의 메시지가 동시에 처리될 가능성이 있습니다.&amp;nbsp;→&amp;nbsp;파티션 내 순서 X.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. Key&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;partition=1, concurrency=2&lt;/span&gt;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 57px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 40.2325%; height: 19px; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;td style=&quot;width: 11.1629%; height: 19px; text-align: justify;&quot;&gt;처리 개수&lt;/td&gt;&lt;td style=&quot;width: 38.0233%; height: 19px; text-align: justify;&quot;&gt;처리 순서&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;처리 시간&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 40.2325%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;Key가 고유한(&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;1~100)&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 11.1629%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.0233%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 40.2325%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;Key가 고유하지 않은&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;((1~100)%2)&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 11.1629%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.0233%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X, 같은 키 끼리 순서 O&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;partition=2, concurrency=2&lt;/span&gt;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 57px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;처리 개수&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;처리 순서&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;처리 시간&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유한(&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;1~100)&lt;/span&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X, 파티션내 순서 X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유하지 않은&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;((1~100)%2)&lt;/span&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 11.0465%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 38.4884%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X, 파티션내 순서 O, 같은 키 끼리 순서 O, 같은 파티션 내 다른 키끼리 순서 X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 10.5813%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이유&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 Key는 같은 파티션으로 가기 때문에 Key별 순서는 보장됩니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 파티션 내에서는 concurrency(병렬 처리)로 인해 순서가 보장되지 않을 수도 있습니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유하면 여러 파티션으로 분산될 가능성이 높아지고, 병렬처리되면서 순서가 꼬일 가능성이 커집니다.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. Partition&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;partition=1, concurrency=2&lt;/span&gt;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 57px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 40.8139%; height: 19px; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;td style=&quot;width: 9.76764%; height: 19px; text-align: justify;&quot;&gt;처리 개수&lt;/td&gt;&lt;td style=&quot;width: 40.2325%; height: 19px; text-align: justify;&quot;&gt;처리 순서&lt;/td&gt;&lt;td style=&quot;width: 9.18595%; height: 19px; text-align: justify;&quot;&gt;처리 시간&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 40.8139%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;Key가 고유한(&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;1~100)&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 9.76764%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 40.2325%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 O&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 9.18595%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;100s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 40.8139%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;Key가 고유하지 않은&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;((1~100)%2)&lt;/span&gt;&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 9.76764%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 40.2325%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X, 같은 키 끼리 순서 O&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 9.18595%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;100s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;partition=2, concurrency=2&lt;/span&gt;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 57px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;td style=&quot;width: 9.30231%; height: 19px; text-align: justify;&quot;&gt;처리 개수&lt;/td&gt;&lt;td style=&quot;width: 42.2093%; height: 19px; text-align: justify;&quot;&gt;처리 순서&lt;/td&gt;&lt;td style=&quot;width: 8.60456%; height: 19px; text-align: justify;&quot;&gt;처리 시간&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유한(&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;1~100)&lt;/span&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 9.30231%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 42.2093%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X, 파티션내 순서 O&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 8.60456%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 19px;&quot;&gt;&lt;td style=&quot;width: 39.8838%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key가 고유하지 않은&lt;span style=&quot;background-color: #EFEFEF;&quot;&gt;((1~100)%2)&lt;/span&gt;&amp;nbsp;message 100개&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 9.30231%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2개씩&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 42.2093%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서 X, 파티션내 순서 O, 같은 키 끼리 순서 O, 같은 파티션 내 다른 키 끼리 순서 X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 8.60456%; height: 19px; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50s&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이유&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Partition 모드는 각 파티션 내에서만 순서를 보장합니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션이 하나면(Partition=1) 전체 순서가 유지되지만, 여러 개이면 전체 순서는 틀어집니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 키는 같은 파티션으로 가므로 같은 키 순서는 유지되지만, 파티션 내에서 다른 키가 섞이면 같은 파티션 내에서 순서가 섞일 수 있습니다.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과 정리 및 주요 차이점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;width: 12.907%; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/td&gt;&lt;td style=&quot;width: 21.3953%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 순서&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 23.721%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션 내 순서&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 20.2907%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 키 순서&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 21.686%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 파티션 내 다른 키 순서&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;width: 12.907%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Unordered&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 21.3953%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 23.721%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 20.2907%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 21.686%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;width: 12.907%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Key&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 21.3953%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 23.721%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;O (같은 키)&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 20.2907%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;O&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 21.686%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;width: 12.907%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Partition&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 21.3953%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 23.721%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;O&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 20.2907%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;O&lt;/span&gt;&lt;/td&gt;&lt;td style=&quot;width: 21.686%; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;X&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Unordered:&lt;/b&gt; 완전한 무질서입니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Key:&lt;/b&gt; 같은 키는 순서 보장되지만, 파티션 내에서 다른 키끼리는 섞일 수 있습니다.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Partition:&lt;/b&gt; 파티션 내 순서가 유지되고, 같은 키 순서 보장됩니다.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0593D3;&quot;&gt;마치며&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션을 늘리는 것이 항상 나쁜 것은 아니라고 생각합니다. 트래픽이 적고 기존 파티션이 많지 않다면 1~2개 추가하는 것으로 충분히 해결 가능할 것 같습니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만, 인프라 비용 최적화가 필요하거나 단일 메시지 처리 속도 개선이 어려운 상황이거나 기존 파티션이 이미 너무 많이 생성된 상황이라면 Parallel Consumer을 고려해볼만 할 것 같습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특히 순서 보장이 필요 없는 경우에는 Parallel Consumer을 사용하여 사전에 인프라 비용을 최적화 하는 것이 좋을 것 같습니다.&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0593D3;&quot;&gt;Reference&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://github.com/confluentinc/parallel-consumer&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/confluentinc/parallel-consumer&lt;/span&gt;&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt; &lt;a href=&quot;https://d2.naver.com/helloworld/7181840&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://d2.naver.com/helloworld/7181840&lt;/span&gt;&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kafka</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/229</guid>
      <comments>https://junghyungil.tistory.com/229#entry229comment</comments>
      <pubDate>Fri, 31 Jan 2025 00:14:44 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 카프카 메시지 중복 컨슘, 누락이 발생할 수 있는 경우</title>
      <link>https://junghyungil.tistory.com/227</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카프카를 사용시 consume이 단 한 번만 일어나면 좋겠지만 실제로 그렇지 않은 경우가 종종 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 10.19.51.png&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5NLdb/btsFmfLiRnf/ASzxmWVlbmYdeF22KI5Hj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5NLdb/btsFmfLiRnf/ASzxmWVlbmYdeF22KI5Hj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5NLdb/btsFmfLiRnf/ASzxmWVlbmYdeF22KI5Hj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5NLdb%2FbtsFmfLiRnf%2FASzxmWVlbmYdeF22KI5Hj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;537&quot; height=&quot;257&quot; data-filename=&quot;스크린샷 2024-03-03 오후 10.19.51.png&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;consume&lt;/span&gt;이 딱 한 번만 일어나지 않으면 어떤 문제가 발생할까요?&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;consumer가 카프카 브로커로부터 메시지를 가져와서 데이터 처리를 할 텐데, 만일 중복 &lt;span style=&quot;text-align: start;&quot;&gt;consume&lt;/span&gt;이 발생하면 그 데이터 처리도 중복으로 처리될 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;때로는 &lt;span style=&quot;text-align: start;&quot;&gt;consume&lt;/span&gt; 누락이 발생할 수도 있습니다. 그럼 데이터 처리도 누락됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 문제를 막기 어려운 근본적인 이유는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;commit 시점과 데이터 처리 완료 시점이 완벽하게 일치할 수 없는 경우(그 시간 간극 동안 리밸런싱이 발생할 수도 있음)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;offset 관리가 consumer측이 아니라 broker 측에서 이루어지는 경우(_consumer_offsets 토픽)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개발자 자의에 의해 offset reset을 해서 다시 consume할 일이 있는 경우&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 누락이 발생하는 경우부터 알아보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;자동커밋(Auto comit) - 누락 발생&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;* 주기 : auto.commit.interval.ms&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 10.48.03.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct5eu0/btsFt6r6bs8/JH5KaIe8nFFcbIqveBL1KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct5eu0/btsFt6r6bs8/JH5KaIe8nFFcbIqveBL1KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct5eu0/btsFt6r6bs8/JH5KaIe8nFFcbIqveBL1KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct5eu0%2FbtsFt6r6bs8%2FJH5KaIe8nFFcbIqveBL1KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;219&quot; data-filename=&quot;스크린샷 2024-03-03 오후 10.48.03.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;fetch 후에&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 10.50.47.png&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/begFQl/btsFuOkB44l/5mJA1K3o0Oyn7npZVWRotK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/begFQl/btsFuOkB44l/5mJA1K3o0Oyn7npZVWRotK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/begFQl/btsFuOkB44l/5mJA1K3o0Oyn7npZVWRotK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbegFQl%2FbtsFuOkB44l%2F5mJA1K3o0Oyn7npZVWRotK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;236&quot; data-filename=&quot;스크린샷 2024-03-03 오후 10.50.47.png&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터를 처리하는 쪽에서는 101번, 102번 까지 데이터를 처리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;commit 103번까지 완료&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인 상황에서 Rebalnce가 시작되었다면 컨슈머와 파티션 간의 관계는 끊어지고 다시 새로운 관계를 만들게 됩니다. 그럼 broker의 기록을 기준으로 message consume이 다시 시작되게 됩니다. 이 경우 broker 입장에서는 103번 부터는 데이터 처리가 되었다고 보기 때문에 message consume는 104번부터 다시 fetch 되게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.00.01.png&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kEWMM/btsFscffkNu/GQfABV2oPDJqouwOC9YaF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kEWMM/btsFscffkNu/GQfABV2oPDJqouwOC9YaF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kEWMM/btsFscffkNu/GQfABV2oPDJqouwOC9YaF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkEWMM%2FbtsFscffkNu%2FGQfABV2oPDJqouwOC9YaF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;230&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.00.01.png&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 데이터를 처리하는 쪽에서도 103번 message는 pass하고 104번부터 처리하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 이제 중복 컨슘이 발생하는 경우를 알아보도록 하곘습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;자동커밋(Auto comit) - 중복 발생&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.06.02.png&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmqsca/btsFqL90vyU/TDkjQBJGFwKj8f14ubsHJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmqsca/btsFqL90vyU/TDkjQBJGFwKj8f14ubsHJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmqsca/btsFqL90vyU/TDkjQBJGFwKj8f14ubsHJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcmqsca%2FbtsFqL90vyU%2FTDkjQBJGFwKj8f14ubsHJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;738&quot; height=&quot;260&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.06.02.png&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;fetch 후에&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.07.12.png&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsKcqn/btsFnYPPi2H/IoWhiLSmXbKsfkamy0Apu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsKcqn/btsFnYPPi2H/IoWhiLSmXbKsfkamy0Apu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsKcqn/btsFnYPPi2H/IoWhiLSmXbKsfkamy0Apu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsKcqn%2FbtsFnYPPi2H%2FIoWhiLSmXbKsfkamy0Apu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;243&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.07.12.png&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터를 처리하는 쪽에서는 102번까지 처리한 상황입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.07.33.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYGAQu/btsFuLIa6p4/rbkMPtXlakKex3dxm85mUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYGAQu/btsFuLIa6p4/rbkMPtXlakKex3dxm85mUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYGAQu/btsFuLIa6p4/rbkMPtXlakKex3dxm85mUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYGAQu%2FbtsFuLIa6p4%2FrbkMPtXlakKex3dxm85mUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;242&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.07.33.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 아직 commit하지 않았는데 갑자기 Rebalnce가 발생하여 broker측에서는 100번까지만 commit된 것으로 기록합니다. 그리고 파티션과 컨슈머의 관계가 재형성됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.17.56.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lypkq/btsFmnbg9ga/C1fnFGC3EKm92Epmm120y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lypkq/btsFmnbg9ga/C1fnFGC3EKm92Epmm120y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lypkq/btsFmnbg9ga/C1fnFGC3EKm92Epmm120y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flypkq%2FbtsFmnbg9ga%2FC1fnFGC3EKm92Epmm120y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;262&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.17.56.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 경우에 브로커는 100번까지만 커밋했으니 101번부터 메시지를 다시 가져가라고 하기 때문에 데이터를 처리하는 쪽에서 101, 102번 데이터를 한 번더 처리하게 됩니다. 이 경우에 중복 컨슘이 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수동 커밋도 마찬가지 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;수동커밋(Auto comit) - 중복 발생&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.22.50.png&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EwEBr/btsFs3oOWfv/B6a9t8akNAvySmyVfVswa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EwEBr/btsFs3oOWfv/B6a9t8akNAvySmyVfVswa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EwEBr/btsFs3oOWfv/B6a9t8akNAvySmyVfVswa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEwEBr%2FbtsFs3oOWfv%2FB6a9t8akNAvySmyVfVswa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;696&quot; height=&quot;239&quot; data-filename=&quot;스크린샷 2024-03-03 오후 11.22.50.png&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수동 커밋은 보통 데이터를 다 처리한 뒤에 커밋하게 됩니다. 하지만 커밋을 하기전에 리밸런싱이 발생하면 자동 커밋처럼 똑같이 중복이 발생할 수도 있습니다. 따라서&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;수동 커밋은 데이터 처리 타이밍과 커밋 타이밍을 개발자가 원하는 데로 조절할 수 있지만 그 시점을 완전히 일치 시킬 수는 없습니다. 이러한 근본적인 문제는 해결할 수가 없습니다.&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;결국에는 Rebalncing이 문제&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;지금까지의 경우를 살펴보았을때 Rebalncing이 문제인 것을 알수가 있습니다. Rebalncing이 자주 발생하면 여러가지 문제가 될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Rebalncing이 발생하면 중복 Consume 혹은 Consume 누락 가능성이 발생하는 타이밍이 생깁니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;심지어, Rebalncing이 발생하는 동안 Consume이 멈추기 때문에 성능상 문제도 발생합니다.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Rebalncing은 언제 생기는건데?&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파티션이 추가 되거나 리어사인이 되서 서로 관계를 바꿔야 하는 경우 (broker의 변화)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨슈머그룹 내 컨슈머가 추가되거나 제거된 경우 (컨슈머 측의 변화) &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 경우 좀 더 유심히 봐야할 건 컨슈머 측의 변화입니다. 컨슈머 측에서 능동적인 변화가 발생한 경우가 아니더라도 컨슈머 측의 변화중에서는 비정상 적인 변화의 경우도 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;session.timeout.ms와 heatbeat.interval.ms&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;heartbeat(consumer이 정상인지 주기적으로 보내는 신호)가 한동안 broker쪽으로 오지 않아서 결국 timeout이 발생하면 broker쪽에서는 consumer 상태 비정상인 것으로 판단합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;u&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://junghyungil.tistory.com/217&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;따라서 보통은 &lt;span style=&quot;text-align: start;&quot;&gt;session.timeout.ms 대비 &lt;span style=&quot;text-align: start;&quot;&gt;heatbeat.interval.ms을 짧게(3분의 1 정도) 가져갑니다. 이렇게 성정하면 &lt;span style=&quot;text-align: left;&quot;&gt;heartbeat를&lt;/span&gt; 두 번 정도 보내지 않아도 한 번정도 기회가 남기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;max.poll.interval.ms&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;consumer쪽에서 poll을 한동한 해가지 않으면 consumer 상태를 비정상으로 판단합니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;consumer에서는 매번 건건이 시간이 오래 걸리는 로직을 수행하서는 안됩니다. 안그러면 consumer가 poll을 하지 못해서&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;max.poll.interval.ms를 넘겨버리고 계속 중단되는 상황이 발생하기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;또한 중단되는 경우에 conumser에 lag이 계속 쌓이는 상황도 발생합니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사실 consume 중복 및 누락은 consumer만의 입장입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 그림 및 설명들을 다시 생각해보면 commit 실제로 한 번만 발생합니다. 즉, 단 한번만 레코드를 다루고 있습니다. 결국 comiit을 하는 건 broker 쪽이고 그리고 이것을 consume하는 건 결국 consumer 쪽입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;consume 중복 및 누락을 대비하기 위해서는&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Rebalncing을 줄일 수 있게 옵션 값을 잘 설정해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자동 커밋으로 설정할지 수동 커밋으로 설정할지는 고려해봐야 합니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로 중복이 누락보다는 낫다고 생각합니다. 중복 같은 경우는 해결할 수 있는 여러 대안(redis, outbox pattern 등)들이 존재하지만 유실 같은 경우는 해결할 수 있는 방법이 없습니다. 따라서 유실을 해결할 수 없는 자동 커밋보다는 유실을 사전에 방지하고 중복을 해결할 수 있는 방향인 수동 커밋을 사용하는게 나을 것 같다고 생각합니다. (유실의 경우도 outbox pattern으로 해결 가능할 것 같긴 하네요)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 어느 정도의 누락은 별로 상관없을 수도 있습니다. 예를들어 로그 데이터 같은 경우 전체적인 통계에 큰 영향을 미치지 않을 수도 있기 때문입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 상황에 따라 적절한 설정이 필요하다고 생각됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kafka</category>
      <category>Consume</category>
      <category>kafka</category>
      <category>중복 컨슘</category>
      <category>카프카 메시지 유실</category>
      <category>카프카 메시지 중복 컨슘</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/227</guid>
      <comments>https://junghyungil.tistory.com/227#entry227comment</comments>
      <pubDate>Sun, 3 Mar 2024 23:03:43 +0900</pubDate>
    </item>
    <item>
      <title>도메인 주도 설계 첫걸음 4장</title>
      <link>https://junghyungil.tistory.com/226</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;컨트랙트&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바운디드 컨텍스트 모델은 서로 독립적으로 발전하고 구현될 수 있다. 그러나 바운디드 컨텍스트 자체는 독립적이지 않고 &lt;b&gt;서로 상호작용&lt;/b&gt; 해야한다. 결국, 바운디드 컨텍스트 사이에는 항상 접점이 있는데 이것을 컨트랙트라 고 부른다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 &lt;b&gt;상호작용&lt;/b&gt; 해야하는데?&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;비즈니스 프로세스 흐름&lt;/b&gt;: 예를 들어, 주문이 생성되고 처리되는 과정에서 고객 관리, 재고 관리, 결제 시스템 등 여러 컨텍스트 간의 상호작용이 필요할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;데이터 공유&lt;/b&gt;: 서로 다른 컨텍스트 간에는 종종 데이터를 공유해야함. 예를 들어, 고객 정보를 처리하는 서비스는 주문 처리 시스템과 고객 정보를 공유해야 할 수 있음.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;업무 통합&lt;/b&gt;: 여러 바운디드 컨텍스트 간의 상호작용은 업무 통합을 가능하게. 서로 다른 컨텍스트 간에 정보를 교환하고 업무를 조율함으로써 전체 비즈니스 프로세스를 효율적으로 관리할 수 있음.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;경계 조정&lt;/b&gt;: 서로 다른 바운디드 컨텍스트 간의 상호작용은 컨텍스트 경계를 조정하는 데 도움이됨. 예를 들어, 한 컨텍스트에서 발생한 이벤트가 다른 컨텍스트에 영향을 주는 경우, 이를 통해 경계를 조정하고 업데이트할 수 있음.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;시스템 확장성&lt;/b&gt;: 바운디드 컨텍스트 간의 상호작용은 시스템의 확장성을 향상시킬 수 있다. 각 컨텍스트가 독립적으로 작동하면서도 필요에 따라 상호작용할 수 있기 때문에 시스템을 쉽게 확장하고 변경할 수 있음.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;협력형 패턴 그룹&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 팀의 성공이 다른 팀의 성공에 달려있고, 그 반대도 마찬가지인 의존적 목표가 있는 팀에 해당. 다시 말해, 이 패턴이 적합한 요건은 팀의 커뮤니케이션과 협업 수준에 있음.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파트너십 패턴&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바운디드 컨텍스트 간의 연동은 애드훅 방식으로 조정한다. 한 팀은 다른팀에게 API의 변경을 알리고 다른 팀은 충돌없이 이를 받아들인다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;연동의 조정은 양방향에서 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;양 팀은 차이점을 함께 해결하고 가장 적절한 솔루션을 찾기위해 협력하고 선정한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.05.10.png&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AuUiX/btsEAbPDBI7/BTgt9bgkA00oOHY16Hkab1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AuUiX/btsEAbPDBI7/BTgt9bgkA00oOHY16Hkab1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AuUiX/btsEAbPDBI7/BTgt9bgkA00oOHY16Hkab1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAuUiX%2FbtsEAbPDBI7%2FBTgt9bgkA00oOHY16Hkab1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;137&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.05.10.png&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장점&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단점&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;양방향 조장이 필요하지만 어느 정도는 독립적&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;팀 간의 잦은 동기화가 필수&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주의 사항&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;커뮤니케이션의 어려움으로 인해 지리적으로 떨어져 있는 팀에게는 적합하지 않을 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공유 커널 패턴&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바운디드 컨텍스트가 모델의 경계임에도 불구하고, 하위 도메인의 동일 모델이 여러 바운디드 컨텍스트에서 구현되는 경우&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공유 커널(Shared Kernel)과&amp;nbsp;&lt;b&gt;같은 공유 모델&lt;/b&gt;은 모든 바운디드 컨텍스트의 필요에 따라 설계된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;권한 모델은 각 바운디드 컨텍스트에서 수정할 수 있고 이 변경은 다른 바운디드 컨텍스트에 영향을 준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.05.03.png&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n496w/btsECmCmVCr/rS7yOBq7Lm0027AM86xhr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n496w/btsECmCmVCr/rS7yOBq7Lm0027AM86xhr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n496w/btsECmCmVCr/rS7yOBq7Lm0027AM86xhr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn496w%2FbtsECmCmVCr%2FrS7yOBq7Lm0027AM86xhr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;749&quot; height=&quot;161&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.05.03.png&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장점&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단점&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지리적인 제약이나 조직의 정치적 문제로 커뮤니케이션 또는 협업이 어려워서 파트너십 패턴을 구현하기 어려운 경우에 대안 고려할 수 있음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;겹치는 형태의 모델은 해당되는 바운디드 컨텍스트의 수명주기도 서로 엮이게 한다. 공유 모델의 변경은 바운디드 컨텍스트에 즉시 영향을 준다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;레거시 시스템을 점진적으로 현대화할 경우에 시스템을 서서히 바운디드 컨텍스트로 분해해서 공유 코드베이스로 만드는 것이 실용적인 중간 솔루션&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 방식이든 공유 커널에 대 한 변경이 생길 때마다 영향을 받는 모든 바운디드 컨텍스트와 연동 테스트(통합)를 수행해야 한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바운디드 컨텍스트의 연동 컨트랙트를 명시적으로 정의할 수 있음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공유 커널은 소스코드의 모든 변경이 즉시 연동되도록 구현해야함. 이를 지키지 않으면 모델의 일관성이 깨질 수 있음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주의 사항&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변경의 연쇄 영향을 최소화하기 위해 양쪽의 겹치는 모델을 제한해서 바운디드 컨텍스트에서 공통으로 구현돼야 하는 모델의 일부분만 노출하도록 해야함.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공유 커널은 여러 바운디드 컨텍스트에 속하기 때문에 변경은 지속적으로 통합&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;중복 비용이 조율 비용보다 클 경우에 고려&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사용자-제공자 패턴 그룹&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제공자는 사용자에게 서비스를 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서비스 제공자는 &amp;lsquo;업스트림(upsteam)&amp;rsquo;이고, 고객 또는 사용자는 &amp;lsquo;다운스트림(downstream)&amp;rsquo;이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;업스트림과 다운스트림은 서로 독립적으로 성장할 수 있지만 연동 컨트랙트를 주도하는 권력의 불균형이 존재한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.26.10.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8cyKD/btsEyr6wqh1/tvw0Cj9vOxdpl8Iu4eUkx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8cyKD/btsEyr6wqh1/tvw0Cj9vOxdpl8Iu4eUkx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8cyKD/btsEyr6wqh1/tvw0Cj9vOxdpl8Iu4eUkx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8cyKD%2FbtsEyr6wqh1%2Ftvw0Cj9vOxdpl8Iu4eUkx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;172&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.26.10.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;순응주의자 패턴&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다운스트림 팀이 업스트림 팀의 모델을 받아들이는 바운디드 컨텍스트의 관계&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제공자의 모델에 따라 정의된 연동 컨트랙트를 제공할 뿐이므로 사용자의 선택지는 이를 받아들이거나 떠나거나 둘 중 하나다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.28.41.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEuhxZ/btsEx8e1Usd/rJU6vO1RpTbQmtLyqfUXkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEuhxZ/btsEx8e1Usd/rJU6vO1RpTbQmtLyqfUXkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEuhxZ/btsEx8e1Usd/rJU6vO1RpTbQmtLyqfUXkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEuhxZ%2FbtsEx8e1Usd%2FrJU6vO1RpTbQmtLyqfUXkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;463&quot; height=&quot;119&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.28.41.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;업스트림이 노출한 컨트랙트가 산업 표준이거나 잘 구축된 모델 또는 다운스트림의 요건에 충분하다면 다운스트림이 팀의 자율성의 일부를 포기하는 결정은 정당화 될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;충돌 방지 계층 패턴&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;힘의 균형이 업스트림에 치우쳐 있을 때 다운스트림 바운디드 컨텍스트가 업스트림에 순응하지 않을 경우 충돌 방지 계층을 통해 업스트림의 모델을&amp;nbsp;&lt;b&gt;스스로에 맞게 가공&lt;/b&gt;할 수 있다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.31.38.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs0tBd/btsEz9ddn22/1dsIsQKHtiRfco3sltKPk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs0tBd/btsEz9ddn22/1dsIsQKHtiRfco3sltKPk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs0tBd/btsEz9ddn22/1dsIsQKHtiRfco3sltKPk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs0tBd%2FbtsEz9ddn22%2F1dsIsQKHtiRfco3sltKPk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;596&quot; height=&quot;148&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.31.38.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;충돌 방지 계층(ACL: anticorruption layer) 패턴은 다음 사례와 같이 제공자의 모델을 따르는 것을 원치 않거나 순응에 필요한 노력이 가치가 없을 경우를 다룬다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조건&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다운스트림 바운디드 컨텍스트가 핵심 하위 도메인을 포함할 경우&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심 하위 도메인은 각별한 주의가 필요하다. 제공자의 모델이 자칫 문제 도메인에 대한 모델링을 방해할 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;업스트림 모델이 사용자의 요건에 비효율적이거나 불편한 경우&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바운디드 컨텍스트가 혼란에 순응하면 그 자체로 위험에 빠지게 된다. 이런 경우는 레거시 시스템과 연동할 때 종종 발생한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제공자가 컨트랙트를 자주 변경하는 경우&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자는 잦은 변경으로부터 모델을 보호하기를 원한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오픈 호스트 서비스 패턴&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오픈 호스트 서비스(OHS, Open-Host Service) 패턴은 충돌 방지 계층 패턴의 반대로 사용자 대신&amp;nbsp;제공자가 내부 모델 번역을 구현한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 패턴은 제공자는 사용자를 보호하는 데 관심이 있고 힘의 균형이 사용자 측에 치우쳐 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구현 모델의 변경으로부터 사용자를 보호하기 위해 퍼블릭 모델과 내부 구현 모델을 분리한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.31.38.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dg6un4/btsEClDtfbB/8VOREc3qZzJybKncqawA3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dg6un4/btsEClDtfbB/8VOREc3qZzJybKncqawA3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dg6un4/btsEClDtfbB/8VOREc3qZzJybKncqawA3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdg6un4%2FbtsEClDtfbB%2F8VOREc3qZzJybKncqawA3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;596&quot; height=&quot;148&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.31.38.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제공자의 퍼블린 인터페이스는 연동 지향 언어를 통해 사용자에게 더 편리한 프로토콜을 노출 시킨다. 이런 퍼블릭 프로토콜을 공표된 언어(published language)라고 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오픈 호스트 서비스 패턴은 충돌 방지 계층 패턴의 반대다. 사용자 대신 제공자가 내부 모델 번역을 구현한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구현 모델과 연동 모델을 분리하면 업스트림 바운디드 컨텍스트는 다운스트림 컨텍스트에 영향을 주지 않으면서 자신의 구현을 자유롭게 발전시킬 수 있다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.42.15.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUmDlI/btsECnOOyPN/d8QIkOFATUvu9093z5CXLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUmDlI/btsECnOOyPN/d8QIkOFATUvu9093z5CXLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUmDlI/btsECnOOyPN/d8QIkOFATUvu9093z5CXLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUmDlI%2FbtsECnOOyPN%2Fd8QIkOFATUvu9093z5CXLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;212&quot; data-filename=&quot;스크린샷 2024-02-07 오전 1.42.15.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 연동 모델을 분리하면 업스트림 바운디드 컨텍스트는 이미 공표된 언어의 여러 버전을 동 시에 노출할 수 있어서 사용자가 점진적으로 새로운 버전으로 이관할 수 있게 한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;분리형 노선&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;분리형 노선(Separated Ways) 패턴은&amp;nbsp;전혀 협력하지 않는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;커뮤니케이션 이슈&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반 하위 도메인&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반 하위 도메인이 일반 솔루션과 연동하는 것이 쉽다면 각 바운디드 컨텍스트 내에서 각자 연동하는 것이 더 효과적일 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어 로깅 프레임워크처럼 기능 중복이 없을 경우보다 솔루션을 연동했을 때 복잡성이 커진다면 이를 서비스로 노출하는 것은 바람직하지 않다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모델의 차이&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모델이 너무 달라서 순응주의자 관계가 불가능하고, 충돌 방지 계층을 구현하는 것도 기능 중복보다 효과적이지 않을 수 있다. 이런 경우에는 팀이 각자의 길을 가는 것이 더 비용 효과적이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바운디드 컨텍스트는 독립적이지 않고 서로 상호작용 해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;파트너십&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바운디드 컨텍스트는 애드혹 방식으로 연동된다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;공유 커널&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 개 이상의 바운디드 컨텍스트가 참여하는 모든 바운디드 컨텍스트가 공유하는 제한적으로 겹치는 모델을 공유해서 연동한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;순응주의자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자는 서비스 제공자의 모델에 순응한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;충돌 방지 계층&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자는 서비스 제공자의 모델을 사용자의 요건에 맞게 번역한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;오픈 호스트 서비스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서비스 제공자는 사용자의 요건에 최적화된 모델인 공표된 언어를 구현한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;&lt;b&gt;분리형 노선&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;협력과 연동보다 특정 기능을 중복으로 두는 것이 더 저렴한 경우다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/226</guid>
      <comments>https://junghyungil.tistory.com/226#entry226comment</comments>
      <pubDate>Wed, 7 Feb 2024 23:54:37 +0900</pubDate>
    </item>
    <item>
      <title>[DB] reWriteBatchedInserts, rewriteBatchedStatements</title>
      <link>https://junghyungil.tistory.com/225</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;reWriteBatchedInserts은 MySql 전용 옵션입니다. 사실 PostgreSql에서도 동일하게 사용되는 것으로 착각하고 있었으나 PostgreSql에서는 reWriteBatchedInserts가 아닌 rewriteBatchedStatements 사용하는게 맞습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 34px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;mySql&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;postgreSql&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;reWriteBatchedInserts&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;rewriteBatchedStatements&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 reWriteBatchedInserts, rewriteBatchedStatements은 무엇일까요?&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 주말에 작성하기&lt;/span&gt;&lt;/p&gt;</description>
      <category>DataBase</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/225</guid>
      <comments>https://junghyungil.tistory.com/225#entry225comment</comments>
      <pubDate>Wed, 7 Feb 2024 23:42:44 +0900</pubDate>
    </item>
    <item>
      <title>ColpletableFuture</title>
      <link>https://junghyungil.tistory.com/224</link>
      <description>&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;ColpletableFuture&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2014년에 발표된 java8에소 처음 도입&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비동기 프로그래밍 지원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Lambda, Metho reference 등 java 8의 새로운 기능 지원&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Method reference&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;:: 연산자를 이용해서 함수에 대한 참조를 간결하게 표현&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;method reference&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;statuc method reference&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;instance method reference&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;constructor method reference&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.48.55.png&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CAfkR/btsDKKEiqxX/wgWOtdN1HCdsZaQ56cRaq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CAfkR/btsDKKEiqxX/wgWOtdN1HCdsZaQ56cRaq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CAfkR/btsDKKEiqxX/wgWOtdN1HCdsZaQ56cRaq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCAfkR%2FbtsDKKEiqxX%2FwgWOtdN1HCdsZaQ56cRaq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;478&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.48.55.png&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ColpletableFuture 클래스&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.49.36.png&quot; data-origin-width=&quot;1934&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/stnpT/btsDKyKEazS/BLOVFFLwNmZcE0I3qEY8pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/stnpT/btsDKyKEazS/BLOVFFLwNmZcE0I3qEY8pK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/stnpT/btsDKyKEazS/BLOVFFLwNmZcE0I3qEY8pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FstnpT%2FbtsDKyKEazS%2FBLOVFFLwNmZcE0I3qEY8pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1934&quot; height=&quot;704&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.49.36.png&quot; data-origin-width=&quot;1934&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot; colspan=&quot;2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CompletableFuture&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Future&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CompletionStage&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비동기적인 작업을 수행&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비동기적인 작업을 수행&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 작업이 오한료되면 결과를 반환하는 인터페이스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 작업이 완료되면 결과를 처리하거나 다른 CmpletionStage를 연결하는 인터페이스&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Future 인터페이스&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.51.50.png&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCR625/btsDKawqytn/2d0vxBelLP2lxoWsl3Qjfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCR625/btsDKawqytn/2d0vxBelLP2lxoWsl3Qjfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCR625/btsDKawqytn/2d0vxBelLP2lxoWsl3Qjfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCR625%2FbtsDKawqytn%2F2d0vxBelLP2lxoWsl3Qjfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1546&quot; height=&quot;324&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.51.50.png&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Executor Service&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쓰레드 풀을 이용하여 비동기적으로 작업을 실행하고 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;별도의 쓰레드를 생성하고 관리하지 않아도 되므로, 코드를 간결하게 유지 가능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쓰레드 풀을 이용하여 자원을 효율적으로 관리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ExecutorService Method&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;excute: Runnalbe 인터페이스를 구현한 작업을 쓰레드 풀에서 비동기적으로 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;submit: Callable 인터페이스를 구현한 적업을 쓰레드 풀에서 비동기적으로 실행하고, 해당 작업의 결과를 Future&amp;lt;T&amp;gt; 객체로 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;shutdown: ExecutorService를 종료. 더 이상 task를 받지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.54.33.png&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A4LRN/btsDIACHKEb/TKzS7nN8FmT9JegbXiwEQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A4LRN/btsDIACHKEb/TKzS7nN8FmT9JegbXiwEQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A4LRN/btsDIACHKEb/TKzS7nN8FmT9JegbXiwEQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA4LRN%2FbtsDIACHKEb%2FTKzS7nN8FmT9JegbXiwEQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;169&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.54.33.png&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Excutors - ExecutorService 생성&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;newSungleThreadExectutor: 단일 스레드로 구성된 스레드 풀을 생성. 한 번에 하나의 작업만 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;newFixedThreadPool: 고정된 크기의 쓰레드 풀을 생성. 크기는 인자로 주어진 n과 동일&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;newCachedThreadPool: 사용 가능한 쓰레드가 없다면 새로 생성해서 작업을 처리하고, 있다면 재사용. 쓰레드가 일정시간 사용되지 않으면 회수&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;newScheduledThreadPool: 스케줄링 기능을 갖춘 고정 크기의 쓰레드 풀을 생성. 주기적이거나 지연이 발생하는 작업을 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;newWorkStealngPool: work steal 알고리즘을 사용하는 ForkJoinPool을 생성.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;FutureHelper&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getFuture: 새로운 쓰레드를 생성하여 1을 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getFutureCompleteAfter1S : 새로운 쓰레드를 생성하고 1초 대기 후 1을 반환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.59.51.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/babf2s/btsDHqm3ZiM/l6wmG58Sbokseai3GGlyW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/babf2s/btsDHqm3ZiM/l6wmG58Sbokseai3GGlyW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/babf2s/btsDHqm3ZiM/l6wmG58Sbokseai3GGlyW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbabf2s%2FbtsDHqm3ZiM%2Fl6wmG58Sbokseai3GGlyW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;534&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.59.51.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Future: isDone(), isCancelled()&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;future의 상태를 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;isDone: task가 완료되었다면, 원인과 상관없이 true 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;isCancelled: task가 명시적으로 취소된 경우 true 반환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.01.33.png&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaYcTP/btsDJfE4Np3/3UXPGkVKIxkewnFZsxxRoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaYcTP/btsDJfE4Np3/3UXPGkVKIxkewnFZsxxRoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaYcTP/btsDJfE4Np3/3UXPGkVKIxkewnFZsxxRoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaYcTP%2FbtsDJfE4Np3%2F3UXPGkVKIxkewnFZsxxRoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;613&quot; height=&quot;328&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.01.33.png&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Future: get()&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과를 구할 때까지 thread가 계속 block&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;future에서 무한 루프나 오랜 시간이 걸린다면 thread가 blocking 상태 유지&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.02.47.png&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wJ5BY/btsDLJrAbQq/Fe2mIW0sSuI9uhUyKkH4HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wJ5BY/btsDLJrAbQq/Fe2mIW0sSuI9uhUyKkH4HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wJ5BY/btsDLJrAbQq/Fe2mIW0sSuI9uhUyKkH4HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwJ5BY%2FbtsDLJrAbQq%2FFe2mIW0sSuI9uhUyKkH4HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;613&quot; height=&quot;296&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.02.47.png&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Future: get(long timeout, Timeunit unit)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과를 구할 때까지 timeout동안 thread가 block&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;timeout이 넘어가도 응답이 반환되지 않으면 TimeoutException 발생&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.03.55.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9B5Eh/btsDJhixyeB/V8bFhHFiDAIb7F7HQ8jM1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9B5Eh/btsDJhixyeB/V8bFhHFiDAIb7F7HQ8jM1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9B5Eh/btsDJhixyeB/V8bFhHFiDAIb7F7HQ8jM1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9B5Eh%2FbtsDJhixyeB%2FV8bFhHFiDAIb7F7HQ8jM1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;269&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.03.55.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Future: cancel(boolean mayInterruptIfRunning)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;future의 작업 실행을 취소&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;취소할 수 없는 상황이라면 false를 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;mayInterruptIfRunning가 false라면 시작하지 않은 작업에 대해서만 취소&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.05.15.png&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3MdXD/btsDHqN6mBf/7mw035obZR3V2CQcAxAqk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3MdXD/btsDHqN6mBf/7mw035obZR3V2CQcAxAqk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3MdXD/btsDHqN6mBf/7mw035obZR3V2CQcAxAqk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3MdXD%2FbtsDHqN6mBf%2F7mw035obZR3V2CQcAxAqk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;257&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.05.15.png&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Future 인터페이스의 한계&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;cancel을 제외하고 외부에서 future를 컨트롤 할 수 없다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반환된 결과를 get()해서 접근하기 때문에 비동기 처리가 어렵다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.06.26.png&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sMLcf/btsDJC03tcN/gVFBl2Wn7FqDzWLkkWvY2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sMLcf/btsDJC03tcN/gVFBl2Wn7FqDzWLkkWvY2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sMLcf/btsDJC03tcN/gVFBl2Wn7FqDzWLkkWvY2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsMLcf%2FbtsDJC03tcN%2FgVFBl2Wn7FqDzWLkkWvY2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;275&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.06.26.png&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;완료되거나 에러가 발생했는지 구분하기 어렵다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.06.56.png&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mNB6Y/btsDGIusoJ9/JJyHbkv4vIAKeFlf0k6INk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mNB6Y/btsDGIusoJ9/JJyHbkv4vIAKeFlf0k6INk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mNB6Y/btsDGIusoJ9/JJyHbkv4vIAKeFlf0k6INk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmNB6Y%2FbtsDGIusoJ9%2FJJyHbkv4vIAKeFlf0k6INk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;515&quot; height=&quot;334&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.06.56.png&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;CompletionStage 인터페이스&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.13.17.png&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gm302/btsDKxZfyaL/11vDsTu8YnvfJPK5Li5521/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gm302/btsDKxZfyaL/11vDsTu8YnvfJPK5Li5521/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gm302/btsDKxZfyaL/11vDsTu8YnvfJPK5Li5521/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGm302%2FbtsDKxZfyaL%2F11vDsTu8YnvfJPK5Li5521%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;242&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.13.17.png&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CompletionStage 연산자 조합&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;50개에 가까운 연산자들을 활용하여 비동기 task들을 실행하고 값을 변경하는 등 chaining을 이용한 조합 가능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에러를 처리하기 위한 콜백 제공&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.08.14.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lf1fp/btsDJledlfV/27KWKTtYxepnhhleKGuedK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lf1fp/btsDJledlfV/27KWKTtYxepnhhleKGuedK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lf1fp/btsDJledlfV/27KWKTtYxepnhhleKGuedK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flf1fp%2FbtsDJledlfV%2F27KWKTtYxepnhhleKGuedK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;313&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.08.14.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ForkJoinPool - thread pool&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CompleteableFuture는 내부적으로 비동기 함수들을 실행하기 위해 ForkJoinPoll을 사용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ForkJoinPool의 기본 size = 할당된 cpu 코어 -1&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데몬 쓰레드. main 쓰레드가 종료되면 즉각적으로 종료&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.09.50.png&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/63Sln/btsDJB17gM6/0r0cVTSVwCpdQRkPh5Jk6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/63Sln/btsDJB17gM6/0r0cVTSVwCpdQRkPh5Jk6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/63Sln/btsDJB17gM6/0r0cVTSVwCpdQRkPh5Jk6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F63Sln%2FbtsDJB17gM6%2F0r0cVTSVwCpdQRkPh5Jk6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;343&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.09.50.png&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ForkJoinPool - fork &amp;amp; join&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;task를 for를 통해서 subtask로 나누고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;thread pool에서 steal work 알고리즘을 이용해서 균등하게 처리해서&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;join을 통해서 결과를 생성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.11.31.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwERu/btsDKa4hZNC/ySCrJAnI65vV5NX5KSREqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwERu/btsDKa4hZNC/ySCrJAnI65vV5NX5KSREqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwERu/btsDKa4hZNC/ySCrJAnI65vV5NX5KSREqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwERu%2FbtsDKa4hZNC%2FySCrJAnI65vV5NX5KSREqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;497&quot; data-filename=&quot;스크린샷 2024-01-20 오후 8.11.31.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;966&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;CompletionStage 연산자&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수형 프로그래밍을 지원하기 위해 java8부터 도입&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1개의 추상 메서드를 갖고 있는 인터페이스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수를 1급 객체로 사용할 수 있다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수를 변수에 할당하거나 인자로 전달하고 반환값으로 사용 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Function, Consumer, Supplier, Runnable등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수형 인터페이스를 구현한 익명 클래스 람다식으로 변환 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 9.35.53.png&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkZJRl/btsDJCfJywL/ykzHDwP8sUoUzmXXQLeZI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkZJRl/btsDJCfJywL/ykzHDwP8sUoUzmXXQLeZI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkZJRl/btsDJCfJywL/ykzHDwP8sUoUzmXXQLeZI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkZJRl%2FbtsDJCfJywL%2FykzHDwP8sUoUzmXXQLeZI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;355&quot; data-filename=&quot;스크린샷 2024-01-20 오후 9.35.53.png&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CompletionStage 연산자와 연결&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;consumer - accept method -&amp;gt; thenAccept(Coinsumer action)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Function - apply method -&amp;gt; thenApplu(Function fn)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Function - compose method ( abstract method X) -&amp;gt; thenCompose(Function fn)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Runnable - run method -&amp;gt; thenRun(Runnable action)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;thenAccept[Async]&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Consumer를 파라미터로 받는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 task로부터 값을 받지만 값을 넘기지는 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음 task에게 null이 전달된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;값을 받아서 action만 수행하는 경우 유용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 9.40.04.png&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPFY1Y/btsDGmrC6nU/UkWJsstSvLedRRRrcvsXk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPFY1Y/btsDGmrC6nU/UkWJsstSvLedRRRrcvsXk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPFY1Y/btsDGmrC6nU/UkWJsstSvLedRRRrcvsXk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPFY1Y%2FbtsDGmrC6nU%2FUkWJsstSvLedRRRrcvsXk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;198&quot; data-filename=&quot;스크린샷 2024-01-20 오후 9.40.04.png&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Helper&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;finishedStage: 1을 반환하는 완료된 CompletableFuture 반환&lt;/li&gt;
&lt;li&gt;runningStage: 1초를 sleep한 후 1을 반환하는 completableFuture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 9.43.08.png&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS5JA6/btsDKJMaumc/Mbxd9Nk0Gib0AVyhfUwO11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS5JA6/btsDKJMaumc/Mbxd9Nk0Gib0AVyhfUwO11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS5JA6/btsDKJMaumc/Mbxd9Nk0Gib0AVyhfUwO11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS5JA6%2FbtsDKJMaumc%2FMbxd9Nk0Gib0AVyhfUwO11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;387&quot; data-filename=&quot;스크린샷 2024-01-20 오후 9.43.08.png&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;thenAccept[Async]의 실행 스레드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;done 상태가 아닌 thenAccept는 callee(forkJoinPoll)의 쓰레드에서 실행&lt;/li&gt;
&lt;li&gt;done 상태가 아닌 completionStage에 thenAccept를 사용하는 경우, callee를 block 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;thenAccept vs thenAcceptAsync&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 9.44.59.png&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VQ0DR/btsDKawrPjc/VkORV2G1i0O9RoKw90pYck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VQ0DR/btsDKawrPjc/VkORV2G1i0O9RoKw90pYck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VQ0DR/btsDKawrPjc/VkORV2G1i0O9RoKw90pYck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVQ0DR%2FbtsDKawrPjc%2FVkORV2G1i0O9RoKw90pYck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1930&quot; height=&quot;936&quot; data-filename=&quot;스크린샷 2024-01-20 오후 9.44.59.png&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.. 작성중&lt;/p&gt;</description>
      <category>Java</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/224</guid>
      <comments>https://junghyungil.tistory.com/224#entry224comment</comments>
      <pubDate>Sat, 20 Jan 2024 20:13:59 +0900</pubDate>
    </item>
    <item>
      <title>I/O 관점에서 Blocking/Non-blocking</title>
      <link>https://junghyungil.tistory.com/223</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공부한 것 기록하기..&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Blocking의 종류&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;blocking은 thread가 오랜 시간 일을 하거나&amp;nbsp; 대기하는 경우 발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CPU-bound blocking: 오랜 시간 일을 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IO-bound blocking: 오랜 시간 대기한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CPU-bound blocking&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;thread가 대부분의 시간 CPU 점유&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;연산이 많은 경우&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적인 코어를 투입&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.21.21.png&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3OmOR/btsDKbB7CJ5/3seMK0SSG2MpcPG8hNIr00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3OmOR/btsDKbB7CJ5/3seMK0SSG2MpcPG8hNIr00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3OmOR/btsDKbB7CJ5/3seMK0SSG2MpcPG8hNIr00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3OmOR%2FbtsDKbB7CJ5%2F3seMK0SSG2MpcPG8hNIr00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;358&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.21.21.png&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IO-bound blocking&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;thread가 대부분의 시간을 대기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파일 일기/쓰기, netowrk 요청 처리, 요청 전달 등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IO-bound non-blocing 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.22.28.png&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSAYxM/btsDKzbGRQy/3MY7Emf5yLBZpnfWxE7v0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSAYxM/btsDKzbGRQy/3MY7Emf5yLBZpnfWxE7v0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSAYxM/btsDKzbGRQy/3MY7Emf5yLBZpnfWxE7v0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSAYxM%2FbtsDKzbGRQy%2F3MY7Emf5yLBZpnfWxE7v0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;394&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.22.28.png&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Blocking의 전파&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나의 함수에서 여러 함수를 호출하기도 하고, 함수 호출은 중첩적으로 발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;callee는 caller가 되고 다시 다른 callee를 호출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;blocking한 함수를 하나라도 호출한다면 caller는 blocking이 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.24.17.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqhGQD/btsDJhXdB2r/T8JxTE5RC76kKHJ5wE7Ky0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqhGQD/btsDJhXdB2r/T8JxTE5RC76kKHJ5wE7Ky0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqhGQD/btsDJhXdB2r/T8JxTE5RC76kKHJ5wE7Ky0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqhGQD%2FbtsDJhXdB2r%2FT8JxTE5RC76kKHJ5wE7Ky0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;415&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.24.17.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수가 non-blocking하려면 모든 함수가 non-blockingdldjdi gksek.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 I/O bound blocking 또한 발생하면 안된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.25.17.png&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJLqvW/btsDIvPcLWM/76v7M8TkkiaFkZgjpoD1j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJLqvW/btsDIvPcLWM/76v7M8TkkiaFkZgjpoD1j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJLqvW/btsDIvPcLWM/76v7M8TkkiaFkZgjpoD1j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJLqvW%2FbtsDIvPcLWM%2F76v7M8TkkiaFkZgjpoD1j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;441&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.25.17.png&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 I/O 모델의 공통점과 차이점&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.26.08.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vtJTp/btsDI7gcjjx/xkTRA6sqpYLkkM79AwNRs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vtJTp/btsDI7gcjjx/xkTRA6sqpYLkkM79AwNRs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vtJTp/btsDI7gcjjx/xkTRA6sqpYLkkM79AwNRs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvtJTp%2FbtsDI7gcjjx%2FxkTRA6sqpYLkkM79AwNRs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1912&quot; height=&quot;830&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.26.08.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Application이 Kernel 결과에 관심이 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Application의 Kernel로 부터 받은 결과를 이용해서 다음 작업을 수행한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee의 결과에 관심이 있다 -&amp;gt; 동기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;A 모델&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;B 모델&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kernel이 응답을 돌려주기 전까지 Application은 아무것도 하지 않는다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kernel이 응답을 돌려주기 전에 Application은 다른 일을 수행한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 Callee가 완료될때까지 대기 -&amp;gt; Blocking&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee를 기다리지 않고 본인의 일을 한다 -&amp;gt; Non-blocking&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.29.01.png&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;1018&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uYN1l/btsDGrTZHZh/FeaHzwYKhTultkHXSI55vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uYN1l/btsDGrTZHZh/FeaHzwYKhTultkHXSI55vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uYN1l/btsDGrTZHZh/FeaHzwYKhTultkHXSI55vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuYN1l%2FbtsDGrTZHZh%2FFeaHzwYKhTultkHXSI55vK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1950&quot; height=&quot;1018&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.29.01.png&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;1018&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 80px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동기 Blocing I/O&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동기 Non-blocking I/O&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;recvfrom을 호출&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;recvfrom을 주기적으로 호출&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;blocking socket을 이용해서 read/writer을 수행&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;non-blocking socket을 이용해서 read/write 수행&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쓰레드가 block 된다.(wait queue에서 기다린다)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작업이 완료되지 않았다면 EAGAIN/EWOULDBLOCK 에러 반환&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.31.17.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LBTvr/btsDGJfP4LK/8ILjOA6hVC1DliChI86Eb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LBTvr/btsDGJfP4LK/8ILjOA6hVC1DliChI86Eb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LBTvr/btsDGJfP4LK/8ILjOA6hVC1DliChI86Eb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLBTvr%2FbtsDGJfP4LK%2F8ILjOA6hVC1DliChI86Eb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;1028&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.31.17.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 I/O 모델의 공통점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;kernel이 Application의&amp;nbsp; 실행을 막지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee를 기다리지 않고 본인의 일을 한다 -&amp;gt; Non-blocking&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;B 모델&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;C 모델&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Application은 Kernel의 결과에 관심이 있다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Thread1은 Kernel의 결과에 관심이 없다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과를 이용해서 액션을 수행한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kernel은 작업을 완료 후 Thread2에게 결과를 전달한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee의 결과의 감시이 있다 -&amp;gt; 동기&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee의 결과에 관심이 없다 -&amp;gt; 비동기&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.40.28.png&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvZbVg/btsDGmkSnHm/rUX7HLE6M9J6kmHdnmt0y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvZbVg/btsDGmkSnHm/rUX7HLE6M9J6kmHdnmt0y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvZbVg/btsDGmkSnHm/rUX7HLE6M9J6kmHdnmt0y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvZbVg%2FbtsDGmkSnHm%2FrUX7HLE6M9J6kmHdnmt0y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1924&quot; height=&quot;1022&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.40.28.png&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동기 Non-blocking I/O&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비동기 Non-blocking I/O&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;recvfrom을 주기적으로 호출&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;aio_read를 호출&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;non-blocking socker를 이용해서 read/write 수행&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작업이 완료되면 커널이 완료 시그널을 보내거나 callback을 호출&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작업이 완료되지 않았다면 EAGAIN/EWOULDBLOCK 에러 반환&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;I/O 모델&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;동기&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;비동기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Blocking&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Application은 Kernel가 I/O 작업을 완료할 때까지 기다린다. 그 후 결과를 직접 이용해서 이후 본인의 일을 수행한다.&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Non-blocking&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Application은 Kerenl에 주기적으로 I/O 작업이 완료되었는지 확인한다. 중간중간 본인의 일을 할 수 있고 작업이 완료되면 그때 본인의 일을 수행한다.&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Application은 Kernel에 I/O 작업 요청을 보내고 본인의 일을 한다.작업이 완료되면 Kernel은 signal을 보내거나 callback를 호출한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>blocking</category>
      <category>I/O</category>
      <category>Non-Blocking</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/223</guid>
      <comments>https://junghyungil.tistory.com/223#entry223comment</comments>
      <pubDate>Sat, 20 Jan 2024 19:44:11 +0900</pubDate>
    </item>
    <item>
      <title>함수 호출 관점에서 동기/비동기, Blocking/Non-blocking</title>
      <link>https://junghyungil.tistory.com/222</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공부한 것 기록하기&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;함수 호출 관점에서 동기와 비동기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수형 인터페이스&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수형 프로그래밍을 지원하기 위해 java8 부터 도입&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1개의 추상메서드를 갖고 있는 인터페이스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수를 1급 객체로 사용할 수 있다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수를 변수에 할당하거나 인자로 전달하고 반환값으로 사용 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Function, Consumer, Suppier, Runnable등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수형 인터페이스를 구현한 익명 클래스를 람다식으로 변경 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.48.22.png&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba5Ylz/btsDKbIS9kt/5peewhtTptJtpgH8pCOR8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba5Ylz/btsDKbIS9kt/5peewhtTptJtpgH8pCOR8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba5Ylz/btsDKbIS9kt/5peewhtTptJtpgH8pCOR8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba5Ylz%2FbtsDKbIS9kt%2F5peewhtTptJtpgH8pCOR8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;463&quot; height=&quot;458&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.48.22.png&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수형 인터페이스는 호출한 쓰레드에서 실행된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.52.28.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;1152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NFyvU/btsDJTVZtjI/GCxib2KSD8Zi2yynOKj660/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NFyvU/btsDJTVZtjI/GCxib2KSD8Zi2yynOKj660/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NFyvU/btsDJTVZtjI/GCxib2KSD8Zi2yynOKj660/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNFyvU%2FbtsDJTVZtjI%2FGCxib2KSD8Zi2yynOKj660%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;508&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.52.28.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;1152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.52.32.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k5nwS/btsDGqAO5h2/Fcq3LwpQ25iiP8fRlBfHnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k5nwS/btsDGqAO5h2/Fcq3LwpQ25iiP8fRlBfHnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k5nwS/btsDGqAO5h2/Fcq3LwpQ25iiP8fRlBfHnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk5nwS%2FbtsDGqAO5h2%2FFcq3LwpQ25iiP8fRlBfHnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;127&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.52.32.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.53.22.png&quot; data-origin-width=&quot;1938&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7iQzj/btsDKOmo4mp/nqoKIKm99OLXK3CP4Q1pfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7iQzj/btsDKOmo4mp/nqoKIKm99OLXK3CP4Q1pfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7iQzj/btsDKOmo4mp/nqoKIKm99OLXK3CP4Q1pfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7iQzj%2FbtsDKOmo4mp%2FnqoKIKm99OLXK3CP4Q1pfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;400&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.53.22.png&quot; data-origin-width=&quot;1938&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.53.53.png&quot; data-origin-width=&quot;1944&quot; data-origin-height=&quot;1042&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buEDp3/btsDItDNZv3/uFTJUhoRm6IDEvaqdj4ezK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buEDp3/btsDItDNZv3/uFTJUhoRm6IDEvaqdj4ezK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buEDp3/btsDItDNZv3/uFTJUhoRm6IDEvaqdj4ezK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuEDp3%2FbtsDItDNZv3%2FuFTJUhoRm6IDEvaqdj4ezK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;397&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.53.53.png&quot; data-origin-width=&quot;1944&quot; data-origin-height=&quot;1042&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 모델의 차이점&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.54.25.png&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxmUWI/btsDLtWA0nC/83wNWs3ksX4rOCeLLZWFF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxmUWI/btsDLtWA0nC/83wNWs3ksX4rOCeLLZWFF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxmUWI/btsDLtWA0nC/83wNWs3ksX4rOCeLLZWFF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxmUWI%2FbtsDLtWA0nC%2F83wNWs3ksX4rOCeLLZWFF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;744&quot; height=&quot;398&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.54.25.png&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 56px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;A 모델&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;B 모델&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main는 getResult의 결과에 관심이 있다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main은 getResult의 결과에 관심이 없다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main은 결과를 이용해서 다음 코드를 실행한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getResult는 결과를 이용해서 함수형 인터페이스를 실행한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동기&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비동기&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee의 결과에 관심이 있다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee의 결과에 관심이 없다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 결과를 이용해서 action을 수행한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;callee는 결과를 이용해서 callback를 수행한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;함수 호출 관점에서 Blocking과 Non-blocking&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 모델의 공통점&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.57.18.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dK1MqR/btsDKbITbyi/4K2jBjbdbxyQOiSBBVmCcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dK1MqR/btsDKbITbyi/4K2jBjbdbxyQOiSBBVmCcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dK1MqR/btsDKbITbyi/4K2jBjbdbxyQOiSBBVmCcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdK1MqR%2FbtsDKbITbyi%2F4K2jBjbdbxyQOiSBBVmCcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1024&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.57.18.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.57.41.png&quot; data-origin-width=&quot;1946&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kRCIA/btsDLqS6olS/62WBEtmCPkwAwiKA0KpItK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kRCIA/btsDLqS6olS/62WBEtmCPkwAwiKA0KpItK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kRCIA/btsDLqS6olS/62WBEtmCPkwAwiKA0KpItK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkRCIA%2FbtsDLqS6olS%2F62WBEtmCPkwAwiKA0KpItK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1946&quot; height=&quot;926&quot; data-filename=&quot;스크린샷 2024-01-20 오후 6.57.41.png&quot; data-origin-width=&quot;1946&quot; data-origin-height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;A 모델&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;B 모델&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main는 getResult가 결과를 돌려주기 전까지 아무것도 할 수 없다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main은 getResult가 결과를 구하고 callback을 실행하기 전까지 아무것도 할 수 없다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main은 getResult가 완료될때까지 대기한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main은 getResult가 완료될 때까지 대기한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Blocking&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;callee를 호출한 후, callee가 완료되기 전까지 caller가 아무것도 할 수 없다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제어권을 callee가 가지고 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.01.02.png&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AGMrx/btsDJTPfL5l/glBZAQiHoS4XFFRGV0U6c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AGMrx/btsDJTPfL5l/glBZAQiHoS4XFFRGV0U6c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AGMrx/btsDJTPfL5l/glBZAQiHoS4XFFRGV0U6c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAGMrx%2FbtsDJTPfL5l%2FglBZAQiHoS4XFFRGV0U6c0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;423&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.01.02.png&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.01.35.png&quot; data-origin-width=&quot;1946&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/spXi7/btsDHpg8sle/fQcG3QwYH8n4SfAopYKox0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/spXi7/btsDHpg8sle/fQcG3QwYH8n4SfAopYKox0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/spXi7/btsDHpg8sle/fQcG3QwYH8n4SfAopYKox0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FspXi7%2FbtsDHpg8sle%2FfQcG3QwYH8n4SfAopYKox0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;731&quot; height=&quot;375&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.01.35.png&quot; data-origin-width=&quot;1946&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.01.54.png&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VCc0d/btsDI7mXUtr/DqhMp59P9JJtvE7lpwbaRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VCc0d/btsDI7mXUtr/DqhMp59P9JJtvE7lpwbaRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VCc0d/btsDI7mXUtr/DqhMp59P9JJtvE7lpwbaRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVCc0d%2FbtsDI7mXUtr%2FDqhMp59P9JJtvE7lpwbaRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;416&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.01.54.png&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 모델의 차이점&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.02.21.png&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx68Sf/btsDItDN5QP/nJI8cu42DTDurVKkh8y4Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx68Sf/btsDItDN5QP/nJI8cu42DTDurVKkh8y4Ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx68Sf/btsDItDN5QP/nJI8cu42DTDurVKkh8y4Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx68Sf%2FbtsDItDN5QP%2FnJI8cu42DTDurVKkh8y4Ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;735&quot; height=&quot;377&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.02.21.png&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;A 모델&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;C 모델&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getResult를 호출한 후, getResult가 완료되지 않으면 main은 본인의 일을 할 수가 없다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getResult를 호출한 후, getResult가 완료되지 않더라도 main은 본인의 일을 할 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Non-Blocking&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller를 호출한 후, callee가 완료되지 않더라도 caller은 본인의 일을 할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제어권을 caller가 가지고 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Blocking vs Non-blocking&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 98px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Bloking&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Non-blocking&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee가 완료될때까지 대기한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 callee를 기다리지 않고 본인의 일을 한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제어권을 caller가 가지고 있다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제어권을 caller가 가지고 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller와 다른 별도의 thread가 필요하지 않다.(혹은 thread를 추가로 쓸 수도 있다)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller와 다른 별도의 thread가 필요하다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;D 모델&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.07.40.png&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhdu4i/btsDJ0tPKUt/Wks8sDy8LQGurdMlZjEmqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhdu4i/btsDJ0tPKUt/Wks8sDy8LQGurdMlZjEmqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhdu4i/btsDJ0tPKUt/Wks8sDy8LQGurdMlZjEmqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhdu4i%2FbtsDJ0tPKUt%2FWks8sDy8LQGurdMlZjEmqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1900&quot; height=&quot;1050&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.07.40.png&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.07.56.png&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddN4RF/btsDHp9tXqc/SPh9pYpAGWQo7YDKegXYo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddN4RF/btsDHp9tXqc/SPh9pYpAGWQo7YDKegXYo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddN4RF/btsDHp9tXqc/SPh9pYpAGWQo7YDKegXYo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddN4RF%2FbtsDHp9tXqc%2FSPh9pYpAGWQo7YDKegXYo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1936&quot; height=&quot;902&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.07.56.png&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;D 모델&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main은 getResult의 결과에 관심이 없다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비동기이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getResult를 호출한 후, getResult가 완료되지 않더라도 main은 본인의 일을 할 수 있다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;non-blocking이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, D 모델은 비동기 non-blocking이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동기 Blocking vs 비동기 Blocking&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.09.23.png&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;896&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdSo2Y/btsDKPZTZ8A/yAr57gXqc5arLOcpOZWvmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdSo2Y/btsDKPZTZ8A/yAr57gXqc5arLOcpOZWvmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdSo2Y/btsDKPZTZ8A/yAr57gXqc5arLOcpOZWvmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdSo2Y%2FbtsDKPZTZ8A%2FyAr57gXqc5arLOcpOZWvmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1928&quot; height=&quot;896&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.09.23.png&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;896&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.09.50.png&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8msEX/btsDJYv0CBr/N5fFTL2EfdlcqNvKKx8w3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8msEX/btsDJYv0CBr/N5fFTL2EfdlcqNvKKx8w3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8msEX/btsDJYv0CBr/N5fFTL2EfdlcqNvKKx8w3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8msEX%2FbtsDJYv0CBr%2FN5fFTL2EfdlcqNvKKx8w3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1940&quot; height=&quot;1022&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.09.50.png&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수 호출 모델&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 55px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.217%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.4496%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동기&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비동기&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.217%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Blocking&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.4496%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 아무 것도 할 수 없는 상태가 된다. 결과를 얻은 후 직접 처리한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 아무것도 할 수 없는 상태가 된다. 결과는 callee가 처리한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.217%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Non-blocking&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.4496%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 자기 할 일을 할 수 있다, 결과를 얻은 후 직접 처리한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;caller는 자기 할 일을 할 수 있다. 결과는 callee가 처리한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Java</category>
      <category>blocking</category>
      <category>Non-Blocking</category>
      <author>Hyung1</author>
      <guid isPermaLink="true">https://junghyungil.tistory.com/222</guid>
      <comments>https://junghyungil.tistory.com/222#entry222comment</comments>
      <pubDate>Sat, 20 Jan 2024 19:16:57 +0900</pubDate>
    </item>
  </channel>
</rss>