<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>백번 열번</title>
    <link>https://100100e.tistory.com/</link>
    <description>beck33333@naver.com</description>
    <language>ko</language>
    <pubDate>Tue, 28 Jul 2026 22:32:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>100win10</managingEditor>
    <item>
      <title>Retry Jitter</title>
      <link>https://100100e.tistory.com/601</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Retry 메커니즘에서 'jitter'는 반복적인 재시도 간의 시간을 조정하기 위해 사용되는 기법이다. 이는 주로 네트워크 요청이나 분산 시스템에서 실패한 요청을 다시 시도할 때 사용된다. 기본적으로, 재시도 메커니즘은 일정한 지연 시간 후에 요청을 다시 시도한다. 그러나 모든 클라이언트가 동일한 간격으로 재시도하면, 서버에 일시에 많은 요청이 집중되어 과부하가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 'jitter'를 사용하여 지연 시간을 무작위로 변동시킨다. 이는 각 클라이언트의 재시도 시간을 약간 다르게 만들어 서버에 대한 요청이 분산되도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;import reactor.util.retry.Retry;
import java.time.Duration;

// 예시 WebClient 설정
WebClient client = WebClient.create();

client.get()
    .uri(&quot;http://test.com&quot;)
    .retrieve()
    .bodyToMono(String.class)
    .retryWhen(Retry.backoff(2, Duration.ofSeconds(1)).jitter(0.1))
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux에서 Retry.backoff(2, Duration.ofSeconds(1)).jitter(0.1)와 같이 설정된 부분에서 0.1은 Jitter를 추가하는 정도를 나타내는데 이 값은 백오프(backoff) 지연 시간에 대해 무작위 변동을 도입하는 비율이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Retry.backoff(2, Duration.ofSeconds(1)): 최대 2번 재시도하며, 각 재시도 간에 1초의 지연 시간을 두는 백오프 전략을 설정한다.&lt;/li&gt;
&lt;li&gt;.jitter(0.1): 지연 시간에 대해 &amp;plusmn;10%의 무작위 변동을 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초 지연 시간을 기준으로 &amp;plusmn;10%의 jitter를 적용하면, 지연 시간은 다음과 같이 변동될 수 있습다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최소 지연 시간: 0.9초 (1초 - 10%)&lt;/li&gt;
&lt;li&gt;최대 지연 시간: 1.1초 (1초 + 10%)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 각 재시도 시 지연 시간은 0.9초에서 1.1초 사이의 랜덤한 값이 된다. 이렇게 하면 모든 클라이언트가 동일한 시간에 재시도하지 않도록 하여 서버에 과부하가 걸리는 것을 방지할 수 있다. 위 코드에서 retryWhen 연산자는 최대 2번 재시도하며, 각 재시도 간에는 0.9초에서 1.1초 사이의 랜덤한 지연 시간을 갖는다. 이렇게 하면 재시도 시점이 분산되어 서버에 과부하가 걸리는 것을 예방할 수 있다.&lt;/p&gt;</description>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/601</guid>
      <comments>https://100100e.tistory.com/601#entry601comment</comments>
      <pubDate>Sun, 4 Aug 2024 15:46:17 +0900</pubDate>
    </item>
    <item>
      <title>반복적인 lua script를 사용한다면 EVAL 대신 EVALSHA 로</title>
      <link>https://100100e.tistory.com/599</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;EVAL과 EVALSHA는 Redis에서 Lua 스크립트를 실행하는 명령어로, 둘 다 Lua 스크립트를 사용하여 복잡한 로직을 Redis 서버 측에서 실행할 수 있게 해준다.&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;EVAL vs. EVALSHA 비교&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;EVAL (Evaluate)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EVAL은 Lua 스크립트를 처음부터 Redis 서버로 보내고 실행한다.&lt;/li&gt;
&lt;li&gt;Redis는 EVAL로 받은 스크립트를 실행하기 전에 해당 스크립트를 해시화하고, 캐시에 저장한다.&lt;/li&gt;
&lt;li&gt;매번 스크립트를 Redis 서버에 전달하고 컴파일해야 하므로 네트워크 및 CPU 리소스가 더 많이 소모된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;EVALSHA (Evaluate SHA-1)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EVALSHA는 이미 Redis 서버에 로드된 Lua 스크립트의 해시(SHA-1) 값을 사용하여 스크립트를 실행한다.&lt;/li&gt;
&lt;li&gt;EVALSHA는 스크립트의 해시를 사용하여 스크립트를 다시 전달하지 않으므로 네트워크 오버헤드가 줄어든다.&lt;/li&gt;
&lt;li&gt;스크립트의 해시값으로 요청을 보내기 때문에 Redis 서버는 이미 컴파일된 스크립트를 찾아 즉시 실행한다.&lt;/li&gt;
&lt;li&gt;스크립트를 매번 컴파일할 필요가 없기 때문에 CPU 사용량이 적어지고 성능이 개선된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능과 CPU 측면에서의 차이점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;네트워크 효율성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EVAL은 스크립트를 매번 서버에 전달하므로 네트워크 트래픽이 증가한다.&lt;/li&gt;
&lt;li&gt;EVALSHA는 SHA-1 해시값만 전달하므로 네트워크 오버헤드가 크게 줄어든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CPU 효율성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EVAL은 매번 스크립트를 파싱하고 컴파일해야 하므로 CPU 리소스를 더 많이 사용한다.&lt;/li&gt;
&lt;li&gt;EVALSHA는 이미 컴파일된 스크립트를 실행하므로 CPU 사용량이 낮.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;속도 개선&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EVALSHA는 기존의 컴파일된 스크립트를 재사용하므로 실행 속도가 EVAL에 비해 더 빠르다. 이 차이는 특히 반복적인 Lua 스크립트 실행에서 두드러진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시 효과&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EVALSHA는 Redis 서버의 스크립트 캐시를 적극 활용하므로 동일한 스크립트를 여러 번 실행할 때 성능 향상이 크다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복적으로 동일한 Lua 스크립트를 실행해야 하는 상황에서는 EVALSHA가 훨씬 더 효율적이라고 볼 수 있을 것 같다. EVALSHA를 사용하면 스크립트를 처음 한 번만 로드하고, 이후에는 해당 스크립트의 SHA-1 해시값만 사용하여 실행하기 때문에 네트워크 트래픽이 줄고 CPU 리소스도 절약할 수 있다.&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;</description>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/599</guid>
      <comments>https://100100e.tistory.com/599#entry599comment</comments>
      <pubDate>Sun, 4 Aug 2024 15:43:18 +0900</pubDate>
    </item>
    <item>
      <title>logback 사용시 대용량 배치 작업에서 성능 저하를 유발할 수도!</title>
      <link>https://100100e.tistory.com/597</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;성능테스트를 어느정도 마친 배치 서비스에서 코드를 리펙터링하고 로그 포맷을 정리하여 배치 task 를 실행시켰을때 기존 테스트때보다 2배 정도 걸리는 것을 확인하였다. 어떤 부분이 영향을 미쳤을까 하나씩 확인해보던 중 logback.xml 을 설정을 다음과 같이 추가한 부분에 영향이 미침을 확인하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728830542979&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;configuration&amp;gt;

	...
    &amp;lt;springProfile name=&quot;prod&quot;&amp;gt;
        &amp;lt;include resource=&quot;.../tracer-console-appender.xml&quot; /&amp;gt;

        &amp;lt;logger name=&quot;CUSTOM_LOG&quot; level=&quot;INFO&quot;&amp;gt;
            &amp;lt;appender-ref ref=&quot;CUSTOM_CONSOLE&quot; /&amp;gt;
        &amp;lt;/logger&amp;gt;
        &amp;lt;logger name=&quot;CUSTOM_ERROR_LOG&quot; level=&quot;ERROR&quot;&amp;gt;
            &amp;lt;appender-ref ref=&quot;CUSTOM_CONSOLE&quot; /&amp;gt;
        &amp;lt;/logger&amp;gt;
    &amp;lt;/springProfile&amp;gt;

&amp;lt;/configuration&amp;gt;&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;다음과 같이 로그백 설정을 주어 INFO 나 ERROR 상태에 있는 로그를 콘솔로 출력하고 kibana 를 통해서 JSON 형태의 로그를 확인할 수 있도록 하였는데 INFO 수준에 레벨을 처리하는 CUSTOM_LOG 가 문제였다. 배치는 청크단위로 reader-processor-writer 로 처리되는데 INFO 수준에서 실제로 남기는 로그는 없었기에 이부분에 문제일 거라 생각하지 못했다.&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;b&gt;콘솔에 출력되는 로그가 없다 하더라도&lt;/b&gt;, INFO 레벨로 로그 설정이 되어 있으면 성능에 영향을 미칠 가능성이  있다는 것을 확인했다. 그 이유는 로그백(Logback)이 내부적으로 로그 레벨에 맞는 로그 메시지를 &lt;b&gt;평가&lt;/b&gt;하고 &lt;b&gt;필터링&lt;/b&gt;하는 과정에서 리소스를 사용하기 때문이다. 이 과정이 누적되면 특히 대용량 배치 작업에서 성능 저하를 유발할 수 있다. 몇 가지 성능 저하를 유발할 수 있는 이유는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;로그 메시지 생성 과정&lt;/b&gt;: 로그 메시지를 기록할 때, 로그 레벨이 일치하지 않아 실제로 출력되지 않더라도, 메시지 자체를 만들기 위해 문자열 연산 및 로그 처리 작업이 발생한다. 특히 로그 메시지에 많은 정보가 포함될수록 이 연산의 비용이 커질 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필터링 및 레벨 평가&lt;/b&gt;: 로그백은 로그가 출력되기 전에 현재 로그 레벨이 설정된 레벨에 맞는지 평가한다. 이 평가 작업도 성능 부하가 될 수 있다. 특히 많은 양의 로그가 기록될 경우, 필터링 비용이 쌓일 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동시성 및 스레드 관리&lt;/b&gt;: 많은 병렬 작업이 로그를 남기려 할 때, 콘솔 출력을 하지 않더라도 내부적으로 스레드와 자원 관리에 비용이 발생할 수 있다. 이 때문에 병렬 작업에서 성능 저하가 발생할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;INFO&lt;/b&gt; 레벨의 로거가 설정된 상태에서 성능이 영향을 받을 가능성이 있는 이유는 이 과정들이 반복되기 때문. 따라서, 실제로 로그를 콘솔에 출력하지 않더라도 불필요한 INFO 레벨의 로거가 설정되어 있으면 성능에 영향을 미칠 수 있을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/597</guid>
      <comments>https://100100e.tistory.com/597#entry597comment</comments>
      <pubDate>Sun, 14 Jul 2024 15:13:12 +0900</pubDate>
    </item>
    <item>
      <title>scdf 배치 task 실행시 볼륨과 함께 initContainer 실행시키기</title>
      <link>https://100100e.tistory.com/596</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud Data Flow(SCDF)는 배치 작업과 스트리밍 작업을 관리하고 실행할 수 있는 플랫폼이다. 이 중 배치 작업(Task)을 실행할 때, 경우에 따라 특정 작업이 실행되기 전에 미리 설정해야 할 작업이 있을 수 있다. 이를 위해 Kubernetes 환경에서는 initContainer와 함께 필요한 볼륨을 설정하여 작업을 준비할 수 있다.&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;size18&quot;&gt;배치 작업에서 볼륨과 initContainer 사용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치 작업에서 볼륨을 사용하여 데이터를 저장하거나 공유하고, initContainer를 이용해 그 데이터를 미리 세팅할 수 있다. 예를 들어, 배치 작업이 실행되기 전에 로그 파일을 생성하거나, 모니터링을 위한 환경을 준비하는 데 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Spring Cloud Data Flow에서 배치 Task를 실행할 때, 볼륨과 함께 initContainer를 설정하는 예시이다.&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;1. scdf helm values 파일 수정하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;Task가 실행되는 동안 사용할 임시 볼륨을 설정한다. emptyDir 볼륨은 Pod가 실행되는 동안에만 존재하며, Pod가 종료되면 해당 볼륨도 함께 삭제된다. Task가 일시적으로 데이터를 저장하거나 공유할 때 적합하다.&lt;/p&gt;
&lt;pre id=&quot;code_1725503362104&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;deployer:
  volumeMounts:
    - name: share-vol
      mountPath: /share-vol
  volumes:
    - name: share-vol
      emptyDir:
        medium: &quot;&quot;
        sizeLimit: &quot;500Mi&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. initContainer 설정&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;- dataflow launch 시에 properties 값 추가&lt;/p&gt;
&lt;pre id=&quot;code_1725503155260&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  val properties = mapOf(
            &quot;deployer.${taskName}.kubernetes.initContainer&quot; to INIT_CONTAINER_PROPERTY
        )

        return dataflowTaskClient.launchV2(taskName, properties, arguments)
        ..
        

        const val INIT_CONTAINER_PROPERTY = &quot;&quot;&quot;
        {
            &quot;containerName&quot;: &quot;test-container&quot;,
            &quot;environmentVariables&quot;: ['TEST=test'],
            &quot;imageName&quot;: &quot;imageName: 'busybox:latest&quot;,
            &quot;volumeMounts&quot;: [
                {
                    &quot;name&quot;: &quot;share-vol&quot;,
                    &quot;mountPath&quot;: &quot;/share-vol&quot;
                }
            ]
        }
        
    &quot;&quot;&quot;&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;&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;a href=&quot;https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_init_containers&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_init_containers&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1725503059022&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Cloud Data Flow Reference Guide&quot; data-og-description=&quot;You can also tap into various task and batch events when the task is launched. If the task is enabled to generate task or batch events (with the additional dependencies of spring-cloud-task-stream and, in the case of Kafka as the binder, spring-cloud-strea&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_init_containers&quot; data-og-url=&quot;https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_init_containers&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pxN93/hyWZkGwjjl/O9u2l8W3ysD6glIpacOtak/img.png?width=1141&amp;amp;height=903&amp;amp;face=0_0_1141_903,https://scrap.kakaocdn.net/dn/slhPh/hyWZmqNHLx/FW9CoMDINkrARSB5Kxw2LK/img.png?width=676&amp;amp;height=508&amp;amp;face=0_0_676_508,https://scrap.kakaocdn.net/dn/uwNcg/hyWZmj2LcQ/gozQIAuCncLBrgYCCg3AR1/img.png?width=914&amp;amp;height=224&amp;amp;face=0_0_914_224&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_init_containers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_init_containers&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pxN93/hyWZkGwjjl/O9u2l8W3ysD6glIpacOtak/img.png?width=1141&amp;amp;height=903&amp;amp;face=0_0_1141_903,https://scrap.kakaocdn.net/dn/slhPh/hyWZmqNHLx/FW9CoMDINkrARSB5Kxw2LK/img.png?width=676&amp;amp;height=508&amp;amp;face=0_0_676_508,https://scrap.kakaocdn.net/dn/uwNcg/hyWZmj2LcQ/gozQIAuCncLBrgYCCg3AR1/img.png?width=914&amp;amp;height=224&amp;amp;face=0_0_914_224');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Data Flow Reference Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;You can also tap into various task and batch events when the task is launched. If the task is enabled to generate task or batch events (with the additional dependencies of spring-cloud-task-stream and, in the case of Kafka as the binder, spring-cloud-strea&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;</description>
      <category>c</category>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/596</guid>
      <comments>https://100100e.tistory.com/596#entry596comment</comments>
      <pubDate>Sun, 14 Jul 2024 14:36:42 +0900</pubDate>
    </item>
    <item>
      <title>Transactional Outbox Pattern</title>
      <link>https://100100e.tistory.com/594</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Transactional Outbox 패턴은 데이터베이스와 메시지 브로커 간의 데이터 일관성을 유지하기 위해 사용된다. 핵심 아이디어는 트랜잭션 내에서 데이터베이스에 변경 사항을 기록할 때, 동시에 &quot;아웃박스&quot; 테이블에 메시지(또는 이벤트)를 기록하는 것이다. 그런 다음, 별도의 프로세스가 이 아웃박스 테이블에서 메시지를 가져와 메시지 브로커에 전송하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴을 사용하면 데이터베이스와 메시지 브로커 간의 일관성을 유지하면서도, 트랜잭션을 통해 데이터의 원자성을 보장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 아웃박스 테이블에 메시지가 기록되고 데이터베이스 커밋이 성공적으로 이루어졌다면, 그 이후에만 메시지가 브로커로 전송되므로, 롤백 상황에서도 데이터의 일관성이 보장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴을 사용하면, 데이터베이스 커밋과 메시지 전송은 분리되지만, &lt;b&gt;데이터 커밋과 메세지 전송에 대한 커밋 모두 데이터베이스를 이용하기 때문에 하나의 트랜잭션에 대한 일관성을 유지할 수 있으며 &lt;b&gt;메세지 전송에 대한 테이블에 row 가 삭제될때까지 재시도할 수 있기 때문에 신뢰성을 보장할 수 있다.&lt;/b&gt;&lt;/b&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;b&gt;데이터 베이스를 먼저 커밋한다면?&lt;/b&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;/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;/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;즉 순서만 바꾸는 것은 일관성 문제를 완전히 해결하지 못한다. 메시지 전송이 실패하거나 네트워크 문제가 생길 수 있기 때문. 반면, Transactional Outbox Pattern은 데이터베이스 커밋과 메시지 전송을 안전하게 분리해주고, 메시지 전송 실패 시에도 데이터를 잃지 않도록 도와준다.&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;b&gt;메세지가 at least once 를 보장한다면?&lt;/b&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;기본적으로 at least once 메시징을 보장한다면 트랜잭셔널 아웃박스 패턴을 구현하지 않아도 될 수 있을 것 같은데 이 방식에도 몇 가지 고려해야 할 점이 있다. at least once 메시징은 메시지가 적어도 한 번 이상 전송됨을 보장하는 방식이다. 만약 메시지 전송 중에 실패하면, 메시지를 재전송하여 브로커가 메시지를 결국 수신하게 된다. 이 방식의 장점은 메시지 손실을 최소화할 수 있다는 점이다. 하지만 at least once 메시징에는 다음과 같은 단점도 있다&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;중복 메시지 처리 필요&lt;/b&gt;: 메시지가 재전송되는 과정에서 브로커가 동일한 메시지를 여러 번 수신할 수 있다. 이를 처리하려면, 수신 측(혹은 메시지 소비자)이 중복 메시지를 감지하고 적절히 처리해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복잡성 증가&lt;/b&gt;: 메시지를 재전송하는 로직이나 중복 메시지를 감지하는 로직을 추가로 구현해야 하므로, 시스템의 복잡성이 증가할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭셔널 아웃박스 패턴은 데이터베이스와 메시징 시스템 간의 일관성을 유지하는 데 중점을 두기 때문에, 시스템이 특정한 비즈니스 요구사항을 가지고 있을 때 적합하다. 반면, at least once 메시징은 일관성보다는 메시지 손실을 피하는 데 더 초점을 맞춘 접근법. 따라서, 시스템 요구사항에 따라 트랜잭셔널 아웃박스 패턴을 사용할지, 아니면 at least once 메시징으로 충분할지 결정해야 한다. 둘 다 상황에 따라 유용할 수 있지만, 목표로 하는 일관성 수준과 메시지 전달 보장을 고려해 선택하는 것이 중요할 듯 하다.&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;b&gt;Trasnactional outbox pattern&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c56Tyx/btsI0K7ANGL/OCzPuG2sWdUW2KRa830elK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c56Tyx/btsI0K7ANGL/OCzPuG2sWdUW2KRa830elK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c56Tyx/btsI0K7ANGL/OCzPuG2sWdUW2KRa830elK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc56Tyx%2FbtsI0K7ANGL%2FOCzPuG2sWdUW2KRa830elK%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;1018&quot; height=&quot;447&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;/figure&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;b&gt;Outbox 테이블&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션이 발생할 때, 변경된 데이터를 직접 다른 서비스에 전송하지 않고, 변경 내용을 별도의 Outbox 테이블에 기록한다. 이 테이블은 같은 데이터베이스 트랜잭션 내에서 업데이트된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;폴링(Polling) 프로세스&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백그라운드 작업이나 별도의 서비스가 주기적으로 Outbox 테이블을 확인하고, 새로운 변경 내역을 메시지 브로커나 다른 서비스로 전송한다. 이 전송이 성공하면 Outbox 테이블에서 해당 기록을 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 일관성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Outbox 패턴은 데이터베이스 트랜잭션의 일부분으로 처리되기 때문에, 원자성을 보장한다. 즉, 트랜잭션이 성공하면 Outbox 테이블에 데이터가 기록되고, 실패하면 기록되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작동 방식&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;쓰기 트랜잭션&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션이 어떤 데이터를 변경할 때, 이 변경 내역을 Outbox 테이블에 기록한다. 이 과정은 데이터베이스 트랜잭션의 일부분으로 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Outbox 테이블 확인&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백그라운드 프로세스가 주기적으로 Outbox 테이블을 확인한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메시지 전송&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 기록이 있으면, 이를 메시지 브로커(예: Kafka, RabbitMQ)나 다른 서비스로 전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기록 삭제&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지 전송이 성공하면 Outbox 테이블에서 해당 기록을 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;주문 서비스&lt;/b&gt;가 주문을 생성하면, 주문 내역을 데이터베이스에 저장하고, 동시에 Outbox 테이블에 주문 생성 이벤트를 기록한다.&lt;/li&gt;
&lt;li&gt;백그라운드 프로세스가 Outbox 테이블을 확인하고, 새로운 주문 생성 이벤트를 메시지 브로커를 통해 &lt;b&gt;배송 서비스&lt;/b&gt;로 전송한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배송 서비스&lt;/b&gt;는 메시지를 수신하고, 주문을 처리하여 배송을 준비한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;b&gt;데이터 일관성 보장&lt;/b&gt;: 데이터베이스 트랜잭션의 일부분으로 처리되기 때문에 데이터 일관성이 보장된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내결함성&lt;/b&gt;: 메시지 전송 실패 시, 재시도를 통해 신뢰성 있는 메시지 전달을 보장한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산 시스템에서의 효율성&lt;/b&gt;: 서비스 간의 직접적인 통신을 줄이고, 메시지 브로커를 통해 간접적으로 통신함으로써 시스템의 복잡도를 줄인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Transactional Outbox Pattern은 마이크로서비스 환경에서 데이터 일관성과 메시지 전달의 신뢰성을 보장하는 데 유용한 패턴이며 이를 통해 복잡한 분산 시스템에서의 데이터 동기화를 효과적으로 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예)&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String description;

    // Getters and Setters
}

@Entity
public class OutboxEvent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String eventType;
    private String payload;
    private LocalDateTime createdAt;

    // Getters and Setters
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;...
 @Transactional
    public Order createOrder(String description) {
        // 1. 주문 생성
        Order order = new Order();
        order.setDescription(description);
        order = orderRepository.save(order);

        // 2. Outbox 이벤트 기록
        OutboxEvent event = new OutboxEvent();
        event.setEventType(&quot;ORDER_CREATED&quot;);
        event.setPayload(&quot;Order ID: &quot; + order.getId());
        event.setCreatedAt(LocalDateTime.now());
        outboxEventRepository.save(event);

        return order;
    }

 ...


@Service
public class OutboxEventProcessor {

    @Autowired
    private OutboxEventRepository outboxEventRepository;

    @Scheduled(fixedRate = 5000)
    public void processOutboxEvents() {
        List&amp;lt;OutboxEvent&amp;gt; events = outboxEventRepository.findAll();
        for (OutboxEvent event : events) {
            // 메시지 브로커로 이벤트 전송 (여기서는 단순히 로그로 대체)
            System.out.println(&quot;Processing event: &quot; + event.getPayload());

            // 이벤트 처리 후 Outbox 테이블에서 삭제
            outboxEventRepository.delete(event);
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설명&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Order 생성&lt;/b&gt;: OrderService에서 주문을 생성하고, 이 주문 생성 이벤트를 OutboxEvent 테이블에 기록한다. 이 과정은 하나의 트랜잭션으로 처리된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OutboxEvent 처리&lt;/b&gt;: OutboxEventProcessor가 주기적으로 Outbox 테이블을 확인하고, 새로운 이벤트가 있으면 이를 메시지 브로커(여기서는 단순히 로그)로 전송한다. 전송이 완료되면 해당 이벤트를 Outbox 테이블에서 삭제한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제를 통해 Transactional Outbox Pattern의 기본적인 개념과 작동 방식을 이해할 수 있다. 이 패턴을 사용하면 마이크로서비스 간 데이터 일관성을 유지하면서 신뢰성 있는 메시지 전달을 구현할 수 있다.&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;b&gt;Transactional Outbox Pattern 적용 방법 CDC?&lt;/b&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;CDC(Change Data Capture)는 트랜잭셔널 아웃박스 패턴에서 자주 사용되는 또 다른 방법이다. CDC는 데이터베이스의 변경 사항을 실시간으로 감지하고, 이를 기반으로 다른 시스템(예: 메시지 브로커)으로 데이터를 전송하는 기술이다. 즉 데이터베이스에 기록된 변경 사항을 CDC 도구가 감지하는 것인데 CDC 도구는 데이터베이스 로그(예: MySQL의 binlog, PostgreSQL의 WAL)를 모니터링하면서, 데이터베이스 테이블에 발생한 변경 사항을 실시간으로 추적할 수 있다.&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;CDC를 활용한 트랜잭셔널 아웃박스 패턴은 실시간 데이터 전송이 필요하거나, 아웃박스 테이블을 주기적으로 조회하는 방식이 비효율적일 때&amp;nbsp; 유용할 수 있다. 이를 통해 시스템의 복잡성을 줄이고, 실시간성을 확보할 수 있다. 대표적인 CDC 도구로는 Debezium, Apache Kafka의 Kafka Connect 등이 있다. CDC를 활용하면 메시지를 보다 빠르게, 그리고 더 정확하게 브로커로 전송할 수 있어 많은 대규모 시스템에서 선호된다고 한다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pradeepl.com/blog/transactional-outbox-pattern/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pradeepl.com/blog/transactional-outbox-pattern/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/594</guid>
      <comments>https://100100e.tistory.com/594#entry594comment</comments>
      <pubDate>Sun, 7 Jul 2024 20:45:18 +0900</pubDate>
    </item>
    <item>
      <title>RabbitMQ에서 At Least Once 보장</title>
      <link>https://100100e.tistory.com/593</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;메시징 큐 시스템에서 메시지의 신뢰성과 전달 보장은 매우 중요하다. 특히, &quot;at least once&quot; 보장은 메시지가 최소한 한 번은 수신자에게 전달된다는 것을 의미한다. 이번 포스트에서는 Spring Boot를 사용하여 RabbitMQ에서 &quot;at least once&quot; 보장을 구현하는 방법과 RabbitMQ의 2PC(Two-Phase Commit) 지원 방안에 대해 알아보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RabbitMQ에서 At Least Once 보장하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 RabbitMQ는 &quot;at least once&quot; 보장을 하지 않는다. 네트워크 장애나 메시지를 보내는 측의 문제로 인해 메시지가 유실될 수 있다. 이를 방지하기 위해 메시지 지속성과 확인 메시지를 활용한 설정이 필요하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;메시지 지속성(Persistence)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 퍼블리싱할 때 지속성 모드를 설정하여 RabbitMQ 서버가 재시작되더라도 메시지가 유실되지 않도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확인 메시지(Acknowledgements)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 소비한 후 RabbitMQ에 확인 응답(ACK)을 보내야 한다. 이를 통해 메시지를 처리한 후에만 큐에서 제거할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메시지 재전송&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지 처리 중 오류가 발생하면 RabbitMQ가 해당 메시지를 다시 큐에 넣고 재전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 이러한 설정을 반영한 퍼블리셔와 컨슈머 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;퍼블리셔 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼블리셔 코드에는 지속성 큐를 설정하여 메시지를 전송한다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;   @Bean
    public Queue myQueue() {
        return new Queue(&quot;myQueue&quot;, true); // 지속성 큐 설정
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨슈머 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨슈머 코드에는 메시지를 수신하고 처리한 후 수동으로 ACK 또는 NACK를 보내는 로직을 추가한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;    @Bean
    public Queue myQueue() {
        return new Queue(&quot;myQueue&quot;, true); // 지속성 큐 설정
    }

    @RabbitListener(queues = &quot;myQueue&quot;)
    public void receiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
        try {
            // 메시지 처리 로직
            channel.basicAck(tag, false); // 메시지 처리 성공 시 ACK 전송
        } catch (Exception e) {
            channel.basicNack(tag, false, true); // 메시지 처리 실패 시 NACK 전송
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 퍼블리셔와 컨슈머 서비스가 메시지를 지속성 큐에 전송하고, 수신한 메시지를 수동으로 확인(ACK) 또는 거부(NACK)하여 &quot;at least once&quot; 보장을 구현가능하게 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수동 확인(ACK/NACK) 및 메시지 지속성 설정은 성능에 영향을 줄 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;메시지 지속성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 지속성 큐에 저장하면 RabbitMQ는 메시지를 디스크에 기록하므로 디스크 I/O가 추가된다. 이는 메시지 전송 속도를 낮출 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수동 확인(ACK/NACK)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수동 확인을 사용하면 메시지를 처리한 후 ACK 또는 NACK를 보내야 한다. 이 과정은 자동 확인보다 시간이 더 걸릴 수 있다.&lt;/li&gt;
&lt;li&gt;특히, 메시지 처리 로직이 복잡하거나 시간이 많이 걸리는 경우 전체 처리 시간이 길어질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;Batch ACK&lt;/b&gt;: 여러 메시지를 한 번에 ACK하여 성능을 향상시킬 수 있다. 이는 네트워크 통신을 줄여준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;QoS 설정&lt;/b&gt;: prefetch 설정을 사용하여 소비자가 한 번에 처리할 수 있는 메시지 수를 제한할 수 있다. 이를 통해 메시지 처리량을 조절하고 과부하를 방지할 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기 처리&lt;/b&gt;: 메시지 처리를 비동기로 처리하여 병렬로 여러 메시지를 동시에 처리할 수 있음.&lt;/li&gt;
&lt;/ol&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지가 컨슈머에게 도달하지 않았을 경우, 컨슈머는 NACK를 보낼 수 없다. 이러한 경우에 &quot;at least once&quot; 보장을 제공하기 위해서는 추가적인 방안이 필요하다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&quot;At Least Once&quot; 보장을 위한 추가적인 메커니즘&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;메시지 지속성(Persistence)&lt;/b&gt;: 메시지가 RabbitMQ 서버에 안전하게 저장되도록 보장한다. 이렇게 하면 서버 재시작이나 네트워크 문제에도 메시지가 손실되지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 퍼블리싱할 때, deliveryMode를 persistent로 설정 및 큐를 생성할 때 durable 속성을 true로 설정하여 디스크에 저장되도록 한다.&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;b&gt;&amp;nbsp; &amp;nbsp; 2. 퍼블리셔 확인(Publisher Confirms)&lt;/b&gt;: 메시지를 브로커에 보낸 후, 브로커가 메시지를 안전하게 수신했음을 퍼블리셔에게 확인해주는 방식. 퍼블리셔는 확인 응답을 기다리며, 응답이 오지 않으면 메시지를 다시 전송할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;channel.confirmSelect();

if (channel.waitForConfirms()) {
    // 메시지가 브로커에 성공적으로 도달함
} else {
    // 메시지가 브로커에 도달하지 않음, 재전송 로직 필요
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 3. &lt;b&gt;컨슈머 연결 상태 모니터링&lt;/b&gt;: RabbitMQ는 소비자와의 연결 상태를 모니터링하여 연결이 끊어진 경우 이를 감지할 수 있다. 메시지가 특정 소비자에게 전달되지 않으면 RabbitMQ는 메시지를 다시 큐에 넣어 다른 소비자가 처리할 수 있도록 한다.&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;&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;/p&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;http://www.rabbitmq.com/tutorials/amqp-concepts.html&quot;&gt;http://www.rabbitmq.com/tutorials/amqp-concepts.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[AMQP 0-9-1 Model Explained | RabbitMQ&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1721917360571&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;AMQP 0-9-1 Model Explained | RabbitMQ&quot; data-og-description=&quot;&amp;lt;!--&quot; data-og-host=&quot;www.rabbitmq.com&quot; data-og-source-url=&quot;http://www.rabbitmq.com/tutorials/amqp-concepts.html&quot; data-og-url=&quot;https://www.rabbitmq.com/tutorials/amqp-concepts&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/brXCQj/hyWCLZZoEN/MCiPpbW5DckOKxTeR38B8K/img.png?width=698&amp;amp;height=526&amp;amp;face=83_101_622_374&quot;&gt;&lt;a href=&quot;http://www.rabbitmq.com/tutorials/amqp-concepts.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.rabbitmq.com/tutorials/amqp-concepts.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/brXCQj/hyWCLZZoEN/MCiPpbW5DckOKxTeR38B8K/img.png?width=698&amp;amp;height=526&amp;amp;face=83_101_622_374');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AMQP 0-9-1 Model Explained | RabbitMQ&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;!--&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.rabbitmq.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/593</guid>
      <comments>https://100100e.tistory.com/593#entry593comment</comments>
      <pubDate>Sun, 7 Jul 2024 20:45:11 +0900</pubDate>
    </item>
    <item>
      <title>맵리듀스와 분산 파일 시스템</title>
      <link>https://100100e.tistory.com/592</link>
      <description>&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;맵 리듀스(MapReduce)는 구글에서 개발한 대용량 데이터 처리를 위한 분산 컴퓨팅 모델이다. 이 모델은 주로 빅데이터를 분석하고 처리하는 데 사용된다. 맵 리듀스는 두 가지 주요 단계로 구성돠는데 크게 맵(Map) 단계와 리듀스(Reduce) 단계이다.&lt;/span&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;/p&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;0da0899f-e52a-497c-95ce-8190838842a0&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 맵(Map) 단계에서는 입력 데이터를 여러 조각으로 나누어 각 조각을 병렬로 처리한다.. 각 조각은 키-값 쌍으로 변환되며, 이 쌍들이 중간 결과로 생성된다. 예를 들어, 단어 빈도를 계산하는 경우, 입력 텍스트의 각 단어를 키로, 단어의 출현 횟수를 값으로 하는 쌍이 만들어 질 수 있다.&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;그 다음, 리듀스(Reduce) 단계에서는 맵 단계에서 생성된 중간 결과를 취합하여 최종 결과를 생성한다. 같은 키를 가진 데이터들을 모아서 지정된 연산을 수행한다. 단어 빈도 계산의 예시에서는 같은 단어에 대한 빈도들을 합산하여 최종 빈도를 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;맵 리듀스 작업 실행하기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 입력 파일을 읽는다. 레코드로 쪼갠다. 예 ) 레코드 분리자로 '\n' 을 사용&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 각 입력 레코드마다 매퍼 함수를 호출해 키와 값을 추출한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 키를 기준으로 키와 값 쌍을 모두 정렬한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4. 정렬된 키 값 쌍 전체를 대상으로 reduce 함수를 호출한다. 같은 키가 여러 번 등장했다면 정렬 과정에서 해당 키-값 쌍은 서로 인접한다. 따라서 같은 키를 가지는 값들을 따로 메모리 상에 상태를 유지하지 않고도 쉽게 결합할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2, 4 단계는 사용자가 직접 작성한 데이터 처리 코드다. 1단계는 파일을 나누어 레코드를 만드는 데 입력 형식 파서를 쓴다. 3단계는 정렬 단계로 맵리듀스에 내재하는 단계에서 직접 작성할 필요가 없는데 매퍼의 출력은 리듀스로 들어가기 전에 이미 정렬됐기 때문이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;맵리듀스 작업을 생성하려면 매퍼와 리듀서라는 두 가지 콜백 함수를 구현해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매퍼 ( 정렬에 적합한 형태로 데이터를 준비 )&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 매퍼는 모든 입력 레코드마다 한번씩만 호출된다. 매퍼는 입력 레코드로부터 키와 값을 추출하는 작업이다. 각 입력으로부터 생성하는 키-값 쌍은 빈 쌍을 포함해 원하는 만큼 생성 가능하다. 매퍼는 입력 레코드로부터 다음 레코드까지 상태를 유지하지 않기 때문에 각 레코드를 독립적으로 처리한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리듀서 ( 정렬된 데이터를 가공 )&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 맵리듀스 프레임워크는 매퍼가 생산한 키-값 쌍을 받아 같은 키를 가진 레코드를 모으고 해당 값의 집합을 반복해 리듀서 함수를 호출한다. 리듀서는 출력 레코드를 생상한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 대용량 데이터를 효율적으로 분산 처리하고, 병렬 처리를 통해 성능을 크게 향상시킬 수 있다. 대표적인 구현체로는 Apache Hadoop이 있다.&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;하둡 맵리듀스 작업의 병렬 실행은 파티셔닝을 기반으로 한다. 작업 입력으로 HDFS 상의 디렉터리를 사용하는 것이 일반적이고, 입력 디렉터리 내 각 파일 또는 파일 블록을 독립된 맵 태스크에서 처리할 독립 파티션으로 간주한다.&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;b&gt;HDFS&lt;/b&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;HDFS는 대용량 데이터를 저장하기 위해 설계된 분산 파일 시스템이며, Hadoop 맵 리듀스 구현에서의 파일 시스템을 말한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;분산 저장&lt;/b&gt;: 데이터를 여러 노드에 분산하여 저장한다.. 이는 데이터의 신뢰성을 높이고, 대규모 데이터 세트를 처리할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중복 저장&lt;/b&gt;: 각 데이터 조각(블록)을 여러 노드에 복제하여 저장한다. 이는 하드웨어 고장 시 데이터 손실을 방지한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대규모 파일 지원&lt;/b&gt;: 매우 큰 파일을 여러 블록으로 나누어 저장하므로, 대용량 파일을 쉽게 저장하고 접근할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HDFS는 확장성이 뛰어나다. HDFS를 이용한 데이터 저장과 접근 비용은 범용 하드웨어와 오픈소스 소프트웨어를 사용하기 때문에 동급 용량의 전용 저장소 장치를 사용하는 비용보다 훨씬 저렴하다. 그렇기에 대규모 확장이 가능하다.&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;b&gt;블록(Block)이란 무엇인가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p 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;b&gt;블록&lt;/b&gt;은 파일 시스템에서 데이터를 나누는 기본 단위이다.&lt;/li&gt;
&lt;li&gt;HDFS에서는 파일을 일정 크기의 블록으로 나눈다. 기본적으로 블록 크기는 128MB 또는 256MB로 설정될 수 있지만, 사용자가 조정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;블록으로 나누어 저장하는 이유&lt;/b&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;b&gt;확장성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;큰 파일을 여러 블록으로 나누면 각 블록을 여러 노드에 분산하여 저장할 수 있다. 이는 파일 시스템이 매우 큰 파일을 효율적으로 저장하고 관리할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;여러 블록으로 나누어진 파일을 여러 노드에서 동시에 읽고 쓸 수 있으므로, 처리 속도가 빨라진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;신뢰성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 블록은 여러 복제본(replica)을 가지며, 이 복제본들은 다른 노드에 저장된다. 이는 하나의 노드가 고장나더라도 데이터 손실을 방지할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;HDFS는 기본적으로 각 블록의 복제본을 세 개로 유지한다. 하나의 노드가 고장나면 다른 복제본을 사용하여 데이터를 복구할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;병렬 처리&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 노드에 분산 저장된 블록들을 병렬로 처리할 수 있어, 대용량 데이터 처리 작업(예: MapReduce 작업)이 더 효율적이다.&lt;/li&gt;
&lt;li&gt;여러 블록을 동시에 읽고 쓰기 때문에 대용량 데이터를 빠르게 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 1GB 크기의 파일을 HDFS에 저장한다고 가정해보자. HDFS가 사용하는 블록 크기가 128MB라면, 이 파일은 8개의 블록으로 나누어진다. 이 블록들은 HDFS 클러스터의 여러 노드에 분산 저장된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 분할&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1GB 파일을 128MB 크기의 8개의 블록으로 나눈다.&lt;/li&gt;
&lt;li&gt;각 블록은 Block 1, Block 2, ..., Block 8로 나뉜다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산 저장&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Block 1은 노드 A, Block 2는 노드 B, Block 3은 노드 C 등으로 분산 저장된다.&lt;/li&gt;
&lt;li&gt;각 블록의 복제본도 다른 노드에 저장된다. (예: Block 1의 복제본은 노드 B와 노드 C에 저장).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 접근 및 처리&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MapReduce 작업을 수행할 때, 각 맵(Map) 작업은 서로 다른 노드에서 병렬로 블록을 읽어 처리할 수 있다.&lt;/li&gt;
&lt;li&gt;이는 작업의 병렬성과 속도를 크게 향상시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWTcdV/btsIHcpFpkF/nIXxxkYbd6qf1TOcw6hGHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWTcdV/btsIHcpFpkF/nIXxxkYbd6qf1TOcw6hGHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWTcdV/btsIHcpFpkF/nIXxxkYbd6qf1TOcw6hGHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWTcdV%2FbtsIHcpFpkF%2FnIXxxkYbd6qf1TOcw6hGHK%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;1470&quot; height=&quot;900&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;900&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;1. 맵(Map) 단계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HDFS 입력 디렉터리에서 데이터를 읽어들인다. 여기서 데이터는 3개의 맵 태스크(Mapper Task)로 분산된다.&lt;/li&gt;
&lt;li&gt;각 맵 태스크는 입력 데이터를 처리하여 키-값 쌍을 생성한다. 예를 들어, 맵 태스크 1은 입력 데이터 m1을 처리하고, 맵 태스크 2는 m2를, 맵 태스크 3는 m3를 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 셔플(Shuffle) 및 정렬(Sort) 단계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맵 단계에서 생성된 중간 키-값 쌍들은 셔플과 정렬 과정을 거쳐 리듀스 태스크로 전달된다.&lt;/li&gt;
&lt;li&gt;이미지에서 보면 각 맵 태스크의 출력이 여러 리듀스 태스크로 분배됩니다. 예를 들어, 맵 태스크 1의 출력(m1, r1, m1, r2, m1, r3)이 리듀스 태스크 1, 2, 3으로 분배되며 이는 키 값에 따라 데이터를 적절한 리듀서로 보내는 과정이다.&lt;/li&gt;
&lt;li&gt;이 과정은 모든 맵퍼에서 생성된 동일한 키를 가진 중간 데이터를 동일한 리듀서로 보내기 위해 수행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 리듀스(Reduce) 단계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리듀스 태스크는 셔플 및 정렬 단계에서 전달된 키-값 쌍을 받아서 처리한다. 여기서 같은 키를 가진 값들을 모아서 최종 결과를 계산한다.&lt;/li&gt;
&lt;li&gt;예를 들어, 리듀스 태스크 1은 m1, r1, m2, r1, m3, r1 데이터를 받아 처리하고, 최종 결과 r1을 생성한다.&lt;/li&gt;
&lt;li&gt;마찬가지로, 리듀스 태스크 2는 m1, r2, m2, r2, m3, r2를 처리하여 r2를 생성하고, 리듀스 태스크 3는 m1, r3, m2, r3, m3, r3를 처리하여 r3를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 결과 저장&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 리듀스 태스크의 출력 결과는 HDFS 출력 디렉터리에 저장된다. 이는 최종 처리 결과가 HDFS에 저장되는 것을 의미한다&lt;/li&gt;
&lt;/ul&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;맵 테스크를 m1,m2,m3으로 표시했다. 각 입력 파일은 보통 크기가 수백 메가바이트에 달한다. 각 매퍼 입력 파일의 복제본이 있는 장비에 RAM과 CPU에 여유가 충분하다면 맵리듀스 스케쥴러가 입력 파일이 있는 장비에서 작업을 수행하려 한다. 이 원리를 &lt;b&gt;데이터 가까이에서 연산하기&amp;nbsp;&lt;/b&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;/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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝은 맵 단계에서 생성된 키-값 쌍을 여러 파티션으로 나누는 과정이다. 이 과정을 통해 동일한 키를 가진 모든 데이터가 동일한 리듀스 태스크로 전달되도록 한다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;맵 단계 (그림의 왼쪽 부분)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 맵 태스크는 입력 데이터를 처리하여 키-값 쌍을 생성한다.&lt;/li&gt;
&lt;li&gt;예: 맵 태스크 1(m1)이 키1-값1, 키2-값2를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파티셔닝&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성된 키-값 쌍은 키의 해시값을 기반으로 여러 파티션으로 나뉜다.&lt;/li&gt;
&lt;li&gt;동일한 키를 가진 데이터는 동일한 파티션에 속하게 된다.&lt;/li&gt;
&lt;li&gt;예를 들어, 키1과 키2가 서로 다른 파티션에 속할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로컬 정렬 및 저장&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 파티션은 맵 태스크의 로컬 디스크에 정렬된 상태로 저장된다.&lt;/li&gt;
&lt;li&gt;예: 파티션1에는 키1-값1, 파티션2에는 키2-값2가 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;셔플 및 정렬 단계 (그림의 가운데 부분)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맵 태스크가 작업을 완료하면, 맵리듀스 스케줄러는 리듀스 태스크에게 이 맵 태스크의 출력 파일이 준비되었음을 알린다.&lt;/li&gt;
&lt;li&gt;리듀스 태스크는 각 맵 태스크로부터 자신이 담당할 파티션의 데이터를 가져온다.&lt;/li&gt;
&lt;li&gt;예: 리듀스 태스크 1(r1)은 모든 맵 태스크로부터 파티션1의 데이터를 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리듀스 단계 (그림의 오른쪽 부분)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리듀스 태스크는 병합된 데이터를 받아 같은 키를 가진 값들을 처리한다.&lt;/li&gt;
&lt;li&gt;예: 리듀스 태스크 1(r1)은 키1과 관련된 모든 데이터를 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인접한 데이터를 모으는 과정은 주로 두 단계에서 이루어진다: 파티셔닝(Partitioning) 단계와 셔플(Shuffle) 및 정렬(Sort) 단계이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구체적인 과정&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 맵 단계&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맵 태스크는 입력 데이터를 처리하여 키-값 쌍을 생성한다.&lt;/li&gt;
&lt;li&gt;생성된 키-값 쌍은 키의 해시값을 이용해 여러 파티션으로 나뉜다.&lt;/li&gt;
&lt;li&gt;각 파티션은 맵 태스크의 로컬 디스크에 정렬된 상태로 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 셔플 및 정렬 단계&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맵 태스크가 작업을 완료하면, 리듀서는 각 맵 태스크로부터 자신이 담당할 파티션의 데이터를 다운로드한다.&lt;/li&gt;
&lt;li&gt;리듀서는 다운로드한 데이터를 키를 기준으로 정렬한다.&lt;/li&gt;
&lt;li&gt;이 정렬 과정에서 동일한 키를 가진 값들이 인접하게 배열된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 리듀스 단계&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리듀스 태스크는 정렬된 데이터를 받아 같은 키를 가진 값들을 처리한다.&lt;/li&gt;
&lt;li&gt;같은 키를 가진 값들이 인접하게 배열되어 있으므로, 리듀스 태스크는 효율적으로 데이터를 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;맵 단계&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맵 태스크 1: ('apple', 1), ('banana', 1), ('apple', 1)&lt;/li&gt;
&lt;li&gt;맵 태스크 2: ('apple', 1), ('banana', 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파티셔닝&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파티션 1: ('apple', 1), ('apple', 1)&lt;/li&gt;
&lt;li&gt;파티션 2: ('banana', 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;셔플 및 정렬&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리듀서 1은 모든 맵 태스크로부터 파티션 1의 데이터를 받아 정렬.&lt;/li&gt;
&lt;li&gt;리듀서 2는 모든 맵 태스크로부터 파티션 2의 데이터를 받아 정렬.&lt;/li&gt;
&lt;li&gt;정렬된 데이터에서 동일한 키를 가진 값들이 인접하게 배열됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리듀스 단계&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리듀서 1은 ('apple', [1, 1, 1])을 처리하여 최종 결과를 생성.&lt;/li&gt;
&lt;li&gt;리듀서 2는 ('banana', [1, 1])을 처리하여 최종 결과를 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 과정 요약&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;맵 단계&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력 데이터를 처리하여 키-값 쌍을 생성하고, 이를 파티셔닝하여 로컬 디스크에 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파티셔닝&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키의 해시값을 이용해 데이터를 여러 파티션으로 나누고, 각 파티션은 로컬 디스크에 정렬된 상태로 저장됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;셔플 및 정렬&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맵 태스크가 완료되면, 리듀스 태스크가 각 맵 태스크로부터 파티션 데이터를 다운로드합니다.&lt;/li&gt;
&lt;li&gt;데이터는 리듀스 태스크로 전송되기 전에 정렬됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리듀스 단계&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리듀스 태스크는 병합된 데이터를 받아 정렬된 상태로 처리하여 최종 결과를 생성합니다.&lt;/li&gt;
&lt;li&gt;최종 결과는 HDFS와 같은 분산 파일 시스템에 저장됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MapReduce 작업을 여러 단계로 연결하고 전체 데이터 처리 파이프라인을 구성할 때, Workflow 관리 도구나 스케줄러 도구를 사용한다.&amp;nbsp; 이러한 도구들을 사용하면 여러 MapReduce 작업을 순차적으로 또는 병렬로 실행할 수 있고, 복잡한 데이터 처리 워크플로우를 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;참고:&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;책: 데이터 중심 어플리케이션 설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>책/데이터 중심 어플리케이션 설계</category>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/592</guid>
      <comments>https://100100e.tistory.com/592#entry592comment</comments>
      <pubDate>Thu, 27 Jun 2024 13:57:23 +0900</pubDate>
    </item>
    <item>
      <title>분산 시스템에서의 일관성 모델: 다이나모 스타일과 완화된 일관성</title>
      <link>https://100100e.tistory.com/591</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;분산 시스템에서 데이터 일관성을 유지하는 것은 매우 중요한 과제이다. 특히, 데이터가 여러 노드에 복제되는 환경에서 일관성을 유지하면서도 성능과 가용성을 높이는 것은 어려운 일이다. 다이나모 스타일 복제를 사용하는 시스템에서의 일관성 모델과 완화된 일관성 모델에 대해 알아보.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다이나모 스타일 복제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다이나모 스타일 복제(Dynamo-style replication)는 리더 없는 분산 시스템에서 사용된다. 이러한 시스템은 모든 노드가 동일한 역할을 수행하며, 쓰기 및 읽기 요청이 임의의 노드에 분산된다. 이로 인해 특정 노드의 장애가 시스템 전체에 미치는 영향을 최소화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다이나모 스타일 복제를 사용하는 시스템은 궁극적 일관성(eventual consistency)을 제공한다. 궁극적 일관성이란 시간이 지나면 모든 노드가 동일한 데이터를 가지게 된다는 것을 의미한다. 즉, 데이터의 일관성을 즉각적으로 보장하지 않지만, 일정 시간이 지나면 일관된 상태에 도달한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선형성과 다이나모 스타일&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형성(linearity)은 분산 시스템에서 매우 강력한 일관성 모델이다. 이는 모든 연산이 단일한 순서로 직렬화된 것처럼 보이게 하여, 모든 클라이언트가 동일한 순서로 동일한 결과를 보장받게 한다. 그러나 다이나모 스타일 복제를 사용하는 시스템은 일반적으로 선형성을 보장하지 않는다. 그 이유는 다음과 같다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;읽기-쓰기 지연&lt;/b&gt;: 데이터를 읽거나 쓸 때 여러 노드에 분산되어 요청이 이루어지므로 지연이나 네트워크 분할이 발생할 수 있다. 이는 즉각적인 일관성을 보장하기 어렵게 만든다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;충돌 해결&lt;/b&gt;: 동일한 키에 대한 여러 쓰기 요청이 동시에 발생하면 데이터 충돌이 발생할 수 있다. 다이나모 스타일 시스템은 버전 벡터(version vectors)나 마지막 쓰기 승리(Last Write Wins)와 같은 충돌 해결 메커니즘을 사용하지만, 이는 궁극적 일관성을 제공할 뿐 즉각적인 선형성을 보장하지는 않는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동기식 연산 부족&lt;/b&gt;: 성능을 위해 동기식 연산을 최소화하므로, 쓰기 및 읽기 작업이 즉각적으로 모든 노드에 반영되지 않을 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다이나모 스타일 복제를 사용하는 리더 없는 시스템은 선형성을 제공하지 않는다고 보는 것이 일반적으로 안전하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;완화된 일관성 모델&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완화된 일관성 모델(relaxed consistency model)은 분산 시스템에서 일관성 보장 수준을 완화하여 성능을 향상시키는 모델이다. 이러한 모델들은 시스템의 성능과 가용성을 높이기 위해 일관성 요구 사항을 약화시킨다. 완화된 일관성 모델의 주요 예시와 그 특성은 다음과 같다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;궁극적 일관성 (Eventual Consistency)&lt;/b&gt;: 모든 업데이트가 결국 모든 복제본에 전파될 것을 보장한다. 시간이 지나면 모든 노드가 동일한 상태가 되지만, 즉각적으로 일관성을 보장하지 않는다. Amazon DynamoDB와 Cassandra가 이를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;약한 일관성 (Weak Consistency)&lt;/b&gt;: 시스템은 읽기 연산이 최신 쓰기 연산을 반영할 것을 보장하지 않는다. 일부 최신 업데이트가 반영되지 않은 데이터를 읽을 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;연결 일관성 (Session Consistency)&lt;/b&gt;: 같은 세션 내에서 일관성을 보장한다. 즉, 클라이언트가 동일한 세션 동안 일관된 데이터를 읽을 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순차 일관성 (Sequential Consistency)&lt;/b&gt;: 모든 프로세스가 동일한 순서로 연산을 관찰할 것을 보장한다. 이는 직렬화 가능성보다 약하지만 여전히 강력한 일관성 모델이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 완화된 일관성 모델을 사용하는가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;성능 향상&lt;/b&gt;: 엄격한 일관성 모델을 유지하려면 많은 통신 오버헤드와 동기화 시간이 필요하다. 완화된 일관성 모델은 이러한 오버헤드를 줄여 성능을 향상시킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가용성 증가&lt;/b&gt;: 완화된 일관성 모델은 시스템의 일부 노드가 일시적으로 불가용 상태가 되더라도 전체 시스템의 가용성을 유지하는 데 도움이 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt;: 대규모 분산 시스템에서는 일관성을 유지하는 데 드는 비용이 기하급수적으로 증가한다. 완화된 일관성 모델은 더 많은 노드를 추가해도 일관성 유지 비용을 줄일 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon DynamoDB와 같은 시스템은 궁극적 일관성을 제공한다. 이는 다음과 같은 상황에서 유용하다:&lt;/p&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;/b&gt;: 쇼핑 카트에 아이템을 추가하는 연산은 즉각적인 일관성이 필요하지 않다. 시간이 지나면 모든 노드가 동일한 쇼핑 카트 상태를 가지게 되는 것으로 충분하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소셜 미디어 피드&lt;/b&gt;: 사용자가 게시물을 작성하면, 해당 게시물이 모든 사용자에게 즉시 보이지 않아도 괜찮다. 시간이 지나면서 모든 피드에 전파되면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책: 데이터 중심 어플리케이션 설계&lt;/p&gt;</description>
      <category>책/데이터 중심 어플리케이션 설계</category>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/591</guid>
      <comments>https://100100e.tistory.com/591#entry591comment</comments>
      <pubDate>Wed, 26 Jun 2024 23:38:36 +0900</pubDate>
    </item>
    <item>
      <title>io.kubernetes.client.openapi.ApiException configmaps is forbidden</title>
      <link>https://100100e.tistory.com/589</link>
      <description>&lt;pre id=&quot;code_1712566058445&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;io.kubernetes.client.openapi.ApiException: Message: 
HTTP response code: 403
HTTP response body: {
    &quot;kind&quot;:&quot;Status&quot;,
    &quot;apiVersion&quot;:&quot;v1&quot;,
    &quot;metadata&quot;:{},
    &quot;status&quot;:&quot;Failure&quot;,
    &quot;message&quot;:&quot;configmaps is forbidden: User \&quot;system:serviceaccount:default:default\&quot; cannot list resource \&quot;configmaps\&quot; in API group \&quot;\&quot; in the namespace \&quot;default\&quot;&quot;,
    &quot;reason&quot;:&quot;Forbidden&quot;,
    &quot;details&quot;:{&quot;kind&quot;:&quot;configmaps&quot;},
    &quot;code&quot;:403
}&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RBAC가 활성화 되어있는 &lt;/span&gt;쿠버네티스 클러스터에서 pod 내에 자바 client api를 이용한 어플리케이션을 띄워 클러스터 내에 동적으로 configMap 리소스를 만드려는 도중 다음의 에러를 발견했다. 인증에 실패한 서비스 어카운트는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712566458081&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User: system:serviceaccount:default:default

&amp;gt; system:serviceaccount:&amp;lt;namespace&amp;gt;:&amp;lt;service account name&amp;gt;&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; 파드내에 어플리케이션을 띄우고 해당 어플리케이션에서 특정 쿠버네티스 클러스터 API 서버에 접속하려 한다면 인증 플러그인이 서비스 어카운트를 인증하는 과정을 거치게 되고 별다른 서비스 어카운트를 지정하지 않는다면 deployment 리소스에 정의한 네임스페이스와 서비스 어카운트를 따라가게 된다.&amp;nbsp; ( 나의 어플리케이션에 경우 ns : default , sa : 지정하지 않았기에 default)&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;서비스 어카운트의 기본 권한으로는 리소스를 나열하거나 수정할 수 없다. 수정에 대한 롤을 만들고 서비스 어카운트에 롤을 바인딩해보자. 우선 default 서비스 어카운트를 그대로 사용하지는 않았는데 default 서비스 어카운트에 롤을 변경한다면 다른 서비스들까지 보안상의 위협이 될 수도 있을거라 생각했기 때문이다. 새로운 서비스 어카운트를 생성하고 파드에 어플리케이션을 띄울때 해당 서비스 어카운트를 이용하도록 변경하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712566290795&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create serviceaccount {이름}&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 롤 생성&lt;/p&gt;
&lt;pre id=&quot;code_1712565675530&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: {이름}
  namespace: default
rules:
  - verbs:
      - '*'
    apiGroups:
      - ''
    resources:
      - configmaps
      - deployments&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;2. 롤 바인딩&lt;/p&gt;
&lt;pre id=&quot;code_1712566375391&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create rolebinding {롤 바인딩 이름} --role={ 롤 이름 } --serviceaccount={네임스페이스:서비스 어카운트 이름}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 해결후 정상적으로 configMap 이 생성되었다. 그런데 deployment 생성시 롤을 추가해도 동일한 에러가 발생하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1712745696797&quot; class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;io.kubernetes.client.openapi.ApiException: Message: HTTP response code: 403
HTTP response body: 
{
    &quot;kind&quot;:&quot;Status&quot;,
    &quot;apiVersion&quot;:&quot;v1&quot;,
    &quot;metadata&quot;:{},
    &quot;status&quot;:&quot;Failure&quot;,
    &quot;message&quot;:&quot;deployments.apps is forbidden: User \&quot;system:serviceaccount:default:keyword\&quot; cannot list resource \&quot;deployments\&quot; in API group \&quot;apps\&quot; in the namespace \&quot;default\&quot;&quot;,
    &quot;reason&quot;:&quot;Forbidden&quot;,
    &quot;details&quot;:{&quot;group&quot;:&quot;apps&quot;,&quot;kind&quot;:&quot;deployments&quot;},
    &quot;code&quot;:403    
}​&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;cluster-admin 클러스터 롤을 해당 서비스 어카운트에 클러스터 롤 바인딩 시켜 해결&lt;/p&gt;
&lt;pre id=&quot;code_1712745802620&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create clusterrolebinding {클러스터 롤 바인딩 이름} --clusterrole=cluster-admin --serviceaccount=default:{서비스 어카운트 이름}&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;&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- &lt;/b&gt;쿠버네티스 인 액션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&lt;/b&gt; &lt;a href=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://stackoverflow.com/questions/49173838/deployments-apps-is-forbidden-user-systemserviceaccountdefaultdefault-cann&quot;&gt;https://stackoverflow.com/questions/49173838/deployments-apps-is-forbidden-user-systemserviceaccountdefaultdefault-cann&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712567616630&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Configure a Pod to Use a ConfigMap&quot; data-og-description=&quot;Many applications rely on configuration which is used during either application initialization or runtime. Most times, there is a requirement to adjust values assigned to configuration parameters. ConfigMaps are a Kubernetes mechanism that let you inject c&quot; data-og-host=&quot;kubernetes.io&quot; data-og-source-url=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/&quot; data-og-url=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tyF28/hyVJT5Tu5n/kYsq2nNMjqLn0Luol5st11/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373,https://scrap.kakaocdn.net/dn/b90NjK/hyVJ6jRoV2/FELpW4iBmkJRfe7YPQsMG1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tyF28/hyVJT5Tu5n/kYsq2nNMjqLn0Luol5st11/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373,https://scrap.kakaocdn.net/dn/b90NjK/hyVJ6jRoV2/FELpW4iBmkJRfe7YPQsMG1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Configure a Pod to Use a ConfigMap&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Many applications rely on configuration which is used during either application initialization or runtime. Most times, there is a requirement to adjust values assigned to configuration parameters. ConfigMaps are a Kubernetes mechanism that let you inject c&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kubernetes.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/Kubernetes</category>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/589</guid>
      <comments>https://100100e.tistory.com/589#entry589comment</comments>
      <pubDate>Mon, 8 Apr 2024 17:53:17 +0900</pubDate>
    </item>
    <item>
      <title>Spring Cloud Kubernetes: ConfigMap 변경 시 전체 서비스 갱신</title>
      <link>https://100100e.tistory.com/588</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Kubernetes 환경에서 Spring Cloud를 사용하면 ConfigMap 변경 시 전체 서비스가 갱신되는 기능을 제공한다. 이는 spring-cloud-starter-kubernetes-client-config 라이브러리를 통해 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ConfigMap 감지&lt;/span&gt;&lt;/h4&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Spring Cloud Kubernetes는 Kubernetes API 서버와 통신하여 ConfigMap의 변경을 감지한다. 이를 위해 Kubernetes Watch API를 사용하여 ConfigMap 리소스를 모니터링한다. Watch API는 Kubernetes 리소스의 생성, 업데이트, 삭제 등의 이벤트를 실시간으로 스트리밍한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이벤트 수신 및 트리거&lt;/span&gt;&lt;/h4&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ConfigMap이 변경되면, Watch API는 해당 변경 이벤트를 Spring Cloud Kubernetes로 전달한다. Spring Cloud Kubernetes는 이 이벤트를 수신하고, &lt;b&gt;설정된 리스너&lt;/b&gt;(listener)를 통해 애플리케이션에 알린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Spring Cloud Kubernetes는 내부적으로 다양한 리스너를 사용하여 ConfigMap 변경 이벤트를 처리한다. 이를 통해 설정 값이 동적으로 반영될 수 있다. 여기서 리스너는 일종의 콜백 함수나 메서드로, 특정 이벤트가 발생했을 때 자동으로 호출된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Spring Cloud Kubernetes Configuration Watch&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Spring Cloud Kubernetes에서는 기본적으로 &lt;b&gt;ConfigurationChangeDetector&lt;/b&gt;라는 컴포넌트가 ConfigMap의 변경 사항을 감지하는 역할을 한다. 이 컴포넌트는 Kubernetes 클러스터와 연결되어 ConfigMap 변경 이벤트를 수신하고, 이를 Spring Cloud Context로 전달한다.&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://github.com/fabric8io/spring-cloud-kubernetes/blob/master/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java&quot;&gt;https://github.com/fabric8io/spring-cloud-kubernetes/blob/master/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719541088359&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;spring-cloud-kubernetes/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChange&quot; data-og-description=&quot;Kubernetes integration with Spring Cloud. Contribute to fabric8io/spring-cloud-kubernetes development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/fabric8io/spring-cloud-kubernetes/blob/master/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java&quot; data-og-url=&quot;https://github.com/fabric8io/spring-cloud-kubernetes/blob/master/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yqiyz/hyWrM40FZm/0QEQSpmkyfJkmYQ0mT4Vr1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/fabric8io/spring-cloud-kubernetes/blob/master/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/fabric8io/spring-cloud-kubernetes/blob/master/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChangeDetector.java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yqiyz/hyWrM40FZm/0QEQSpmkyfJkmYQ0mT4Vr1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;spring-cloud-kubernetes/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/reload/ConfigurationChange&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes integration with Spring Cloud. Contribute to fabric8io/spring-cloud-kubernetes development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;동작 과정 요약&lt;/span&gt;&lt;/h3&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Kubernetes Watch 설정&lt;/b&gt;: Spring Cloud Kubernetes는 Kubernetes Watch API를 사용하여 ConfigMap의 변경을 실시간으로 감지한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;이벤트 수신&lt;/b&gt;: ConfigMap이 변경되면, Watch API는 변경 이벤트를 Spring Cloud Kubernetes로 전달한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;리스너 호출&lt;/b&gt;: Spring Cloud Kubernetes는 수신된 이벤트를 기반으로 설정된 리스너를 호출한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;설정 업데이트&lt;/b&gt;: 리스너는 변경된 설정 값을 처리하고, 필요 시 @ConfigurationProperties 또는 @RefreshScope 빈을 다시 로드하거나 갱신한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;설정 업데이트?&lt;/span&gt;&lt;/h4&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Spring Cloud Kubernetes는 수신된 이벤트를 바탕으로 애플리케이션의 환경 설정을 동적으로 업데이트한다. Spring의 Environment 객체가 업데이트되며, 이 과정에서 변경된 설정 값들이 반영된다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;빈(Bean) 리프레시&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Spring Cloud Kubernetes는 @ConfigurationProperties 또는 @RefreshScope로 선언된 빈(Bean)들을 &lt;b&gt;다시 로드하거나 갱신한다&lt;/b&gt;. @RefreshScope는 Spring Cloud Context 모듈의 기능으로, 빈의 상태를 유지하면서 설정값을 갱신할 수 있다. 이로 인해 변경된 설정 값이 즉시 애플리케이션에 반영되고, 관련 빈이 재생성되거나 갱신된다.&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;@ConfigurationProperties 빈이 갱신되는 메커니즘은 Spring Cloud Kubernetes와 Spring Cloud Context의 기능을 결합하여 이루어진다. 이 과정은 Kubernetes에서 ConfigMap 변경을 감지하고, 변경된 설정을 Spring 애플리케이션에 동적으로 반영하며, 필요한 경우 관련 빈을 갱신하는 것을 포함한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. ConfigMap 변경 감지&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Spring Cloud Kubernetes는 Kubernetes Watch API를 사용하여 ConfigMap의 변경을 실시간으로 감지한다. 변경이 감지되면, Spring Cloud Kubernetes는 변경 이벤트를 수신하고 이를 처리하기 시작한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. 환경(Environment) 재설정&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;변경 이벤트가 수신되면, Spring Cloud Kubernetes는 변경된 ConfigMap 값을 Spring의 Environment 객체에 반영한다. 이는 Spring의 환경 설정이 갱신된다는 것을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3. @RefreshScope 빈 갱신&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;@RefreshScope로 주석이 달린 빈은 Spring Cloud Context의 RefreshScope에 의해 관리된다. 이 어노테이션은 해당 빈의 상태를 유지하면서 설정 값이 변경될 때 빈을 다시 로드하거나 갱신할 수 있게 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;4. /actuator/refresh 엔드포인트 호출&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Spring Boot Actuator를 사용하여 /actuator/refresh 엔드포인트를 활성화할 수 있다. 이 엔드포인트를 호출하면 Spring Cloud Context는 Environment 객체가 갱신된 것을 감지하고, @RefreshScope로 주석이 달린 모든 빈을 다시 로드하거나 갱신한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;@ConfigurationProperties와 @RefreshScope&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. @ConfigurationProperties&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;@ConfigurationProperties는 설정 파일의 값을 클래스에 바인딩하는 데 사용된다. 이를 통해 애플리케이션 설정을 쉽게 관리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = &quot;my&quot;)
public class MyConfigProperties {
    private String message;
    private int value;

    // Getters and Setters
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RestController;

@RefreshScope
@RestController
public class MyConfigController {

    @Autowired
    private MyConfigProperties myConfigProperties;

    @GetMapping(&quot;/config&quot;)
    public String getConfig() {
        return &quot;Message: &quot; + myConfigProperties.getMessage() + &quot;, Value: &quot; + myConfigProperties.getValue();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ConfigMap 생성&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
data:
  application.yml: |
    my:
      message: Hello from ConfigMap
      value: 42&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;어플리케이션 설정&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;spring:
  cloud:
    kubernetes:
      config:
        enabled: true
        name: my-config
        namespace: default

management:
  endpoints:
    web:
      exposure:
        include: refresh, health, info&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;참고 자료&lt;/span&gt;&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://spring.io/projects/spring-cloud-kubernetes&quot;&gt;Spring Cloud Kubernetes Documentation&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config&quot;&gt;Spring Boot Reference Documentation&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring</category>
      <author>100win10</author>
      <guid isPermaLink="true">https://100100e.tistory.com/588</guid>
      <comments>https://100100e.tistory.com/588#entry588comment</comments>
      <pubDate>Wed, 10 Jan 2024 00:01:34 +0900</pubDate>
    </item>
  </channel>
</rss>