<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>StarLord</title>
    <link>https://armyost.tistory.com/</link>
    <description>Sr. DevOps Engineer Blog 입니다. 
</description>
    <language>ko</language>
    <pubDate>Wed, 15 Apr 2026 03:08:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>armyost</managingEditor>
    <image>
      <title>StarLord</title>
      <url>https://tistory1.daumcdn.net/tistory/4533681/attach/7d15e67f6a33419d8f90213e4fce81d1</url>
      <link>https://armyost.tistory.com</link>
    </image>
    <item>
      <title>Backstage) Custom Relation, Custom Entity 적용하기</title>
      <link>https://armyost.tistory.com/545</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Backstage의 Entity 종류는 9 가지가 있고, 각자 고유한 정의를 따른다. Backstage 내의 Entity와 Relation은 Core library에서 어느정도 정의해서 제공하고 있다. 그러다 보니 단순이 app-config.yaml에서 allow되는 Entity 목록에 완전히 새로운걸 추가한다던지 그런 수준의 yaml만 수정해서는 새로운 Entity를 만들수 없다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #1b1b1d; color: #e3e3e3; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-component&quot;&gt;Kind: Component&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-template&quot;&gt;Kind: Template&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-api&quot;&gt;Kind: API&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-group&quot;&gt;Kind: Group&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-user&quot;&gt;Kind: User&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-resource&quot;&gt;Kind: Resource&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-system&quot;&gt;Kind: System&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-domain&quot;&gt;Kind: Domain&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #36baa2;&quot; href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-location&quot;&gt;Kind: Location&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1770727135398&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;Descriptor Format of Catalog Entities | Backstage Software Catalog and Developer Platform&quot; data-og-description=&quot;Documentation on Descriptor Format of Catalog Entities which describes the default data shape and semantics of catalog entities&quot; data-og-host=&quot;backstage.io&quot; data-og-source-url=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-location&quot; data-og-url=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ExQxP/dJMb88F0L8N/o7sJfa5e4iTqa84T8KuMBK/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/bb9tBW/dJMb81fOvVY/b0Jr9IRnSyKwOMmFuIOat1/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900&quot;&gt;&lt;a href=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-location&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://backstage.io/docs/features/software-catalog/descriptor-format/#kind-location&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ExQxP/dJMb88F0L8N/o7sJfa5e4iTqa84T8KuMBK/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/bb9tBW/dJMb81fOvVY/b0Jr9IRnSyKwOMmFuIOat1/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900');&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;Descriptor Format of Catalog Entities | Backstage Software Catalog and Developer Platform&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Documentation on Descriptor Format of Catalog Entities which describes the default data shape and semantics of catalog entities&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;backstage.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;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Core에 커스텀을 입힐 정도의 추가 개발이 필요하다. 많은 여러 시도를 통해 검증되었지만, 이것은 불가피하다.&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;과연 무엇을 옵티마이징 해야할지 여기 공유하겠다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 새로운 Type의 Entity 정의하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1489&quot; data-origin-height=&quot;623&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coTchO/dJMcaa5g8RO/OWWlk5rDKvY4Z9vxXq80M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coTchO/dJMcaa5g8RO/OWWlk5rDKvY4Z9vxXq80M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coTchO/dJMcaa5g8RO/OWWlk5rDKvY4Z9vxXq80M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoTchO%2FdJMcaa5g8RO%2FOWWlk5rDKvY4Z9vxXq80M1%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;1489&quot; height=&quot;623&quot; data-origin-width=&quot;1489&quot; data-origin-height=&quot;623&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;우선 내가 새로이 추가하고 싶은 Type의 Entity는 Feature이며, 이는 다음과 같은 속성을 가진다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770728033958&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Feature entity - Custom entity kind
apiVersion: backstage.io/v1alpha1
kind: Feature
metadata:
  name: user-authentication
  namespace: default
  description: User authentication feature with OAuth2 and JWT support
spec:
  type: capability
  lifecycle: production
  owner: admin
  featureOf:
    - component:default/micro-service-a
  upstreamOf:
    - api:default/example-grpc-api&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`micro-service-a` 라는 Component에 귀속되며 이의 관계는&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;&amp;nbsp;[&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'featureOf'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'hasFeature'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;]&lt;span style=&quot;color: #000000;&quot;&gt; 의 쌍을 가진다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;color: #cccccc; text-align: start;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 `example-grpc-api`라는 API와&lt;/span&gt; &lt;span style=&quot;color: #cccccc;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'upstreamOf'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'upstreamBy'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'downstreamOf'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'downstreamBy'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;] &lt;span style=&quot;color: #000000;&quot;&gt;의 관계를 가지고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 새로운 Entity Type을 사용하기 위해서는 Backend에서 Core를 호출하는 부분에서 수정이 필요하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;관련 Commit 1 : &lt;a href=&quot;https://github.com/armyost/backstage-armyost/commit/74c5cbec521e13f5dd70ae377ea28f936b4d2dc0#diff-be7251ebe458c47f2b1f8691db3216b5b9e4fa66177033f8dcbaec2eef0939b8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/armyost/backstage-armyost/commit/74c5cbec521e13f5dd70ae377ea28f936b4d2dc0&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770729879497&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;debuging &amp;middot; armyost/backstage-armyost@f288b25&quot; data-og-description=&quot;+ &amp;lt;EntityCatalogGraphCard variant=&amp;quot;gridItem&amp;quot; renderNode={CustomRenderNode} height={400} kinds={[&amp;quot;component&amp;quot;, &amp;quot;system&amp;quot;, &amp;quot;api&amp;quot;, &amp;quot;resource&amp;quot;]} relationTypes={[&amp;quot;featureOf&amp;quot;,&amp;quot;hasFeature&amp;quot;]}/&amp;gt;&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/armyost/backstage-armyost/commit/f288b253da40bcd590d332cc80ca94e4413688eb#diff-be7251ebe458c47f2b1f8691db3216b5b9e4fa66177033f8dcbaec2eef0939b8&quot; data-og-url=&quot;https://github.com/armyost/backstage-armyost/commit/f288b253da40bcd590d332cc80ca94e4413688eb&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/el9ilQ/dJMb8SXtGE0/43EVKc4J81xLJ0lgzbRO60/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/L4zPQ/dJMb8PGr3re/CPsagPTu8LBft7cwIsOmZ1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/armyost/backstage-armyost/commit/f288b253da40bcd590d332cc80ca94e4413688eb#diff-be7251ebe458c47f2b1f8691db3216b5b9e4fa66177033f8dcbaec2eef0939b8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/armyost/backstage-armyost/commit/f288b253da40bcd590d332cc80ca94e4413688eb#diff-be7251ebe458c47f2b1f8691db3216b5b9e4fa66177033f8dcbaec2eef0939b8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/el9ilQ/dJMb8SXtGE0/43EVKc4J81xLJ0lgzbRO60/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/L4zPQ/dJMb8PGr3re/CPsagPTu8LBft7cwIsOmZ1/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;debuging &amp;middot; armyost/backstage-armyost@f288b25&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;+ &amp;lt;EntityCatalogGraphCard variant=&quot;gridItem&quot; renderNode={CustomRenderNode} height={400} kinds={[&quot;component&quot;, &quot;system&quot;, &quot;api&quot;, &quot;resource&quot;]} relationTypes={[&quot;featureOf&quot;,&quot;hasFeature&quot;]}/&amp;gt;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;관련 Commit 2: &lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/armyost/backstage-armyost/commit/0b29677ece3ed75c3ea13c8b866b86bb47f51882&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/armyost/backstage-armyost/commit/0b29677ece3ed75c3ea13c8b866b86bb47f51882&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770730019072&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;update feature entity &amp;middot; armyost/backstage-armyost@0b29677&quot; data-og-description=&quot;+ &amp;lt;EntityCatalogGraphCard variant=&amp;quot;gridItem&amp;quot; renderNode={CustomRenderNode} height={400} kinds={[&amp;quot;component&amp;quot;, &amp;quot;system&amp;quot;, &amp;quot;api&amp;quot;, &amp;quot;resource&amp;quot;, &amp;quot;feature&amp;quot;]} /&amp;gt;&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/armyost/backstage-armyost/commit/0b29677ece3ed75c3ea13c8b866b86bb47f51882&quot; data-og-url=&quot;https://github.com/armyost/backstage-armyost/commit/0b29677ece3ed75c3ea13c8b866b86bb47f51882&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KIHTJ/dJMb9iIC0y1/W5oBPsTc5M8kLbfApkZLSK/img.png?width=1200&amp;amp;height=600&amp;amp;face=988_136_1059_214,https://scrap.kakaocdn.net/dn/bp61Q1/dJMb9ee9Pg6/bTcAo29EveSq4NS2IsBpH0/img.png?width=1200&amp;amp;height=600&amp;amp;face=988_136_1059_214&quot;&gt;&lt;a href=&quot;https://github.com/armyost/backstage-armyost/commit/0b29677ece3ed75c3ea13c8b866b86bb47f51882&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/armyost/backstage-armyost/commit/0b29677ece3ed75c3ea13c8b866b86bb47f51882&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KIHTJ/dJMb9iIC0y1/W5oBPsTc5M8kLbfApkZLSK/img.png?width=1200&amp;amp;height=600&amp;amp;face=988_136_1059_214,https://scrap.kakaocdn.net/dn/bp61Q1/dJMb9ee9Pg6/bTcAo29EveSq4NS2IsBpH0/img.png?width=1200&amp;amp;height=600&amp;amp;face=988_136_1059_214');&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;update feature entity &amp;middot; armyost/backstage-armyost@0b29677&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;+ &amp;lt;EntityCatalogGraphCard variant=&quot;gridItem&quot; renderNode={CustomRenderNode} height={400} kinds={[&quot;component&quot;, &quot;system&quot;, &quot;api&quot;, &quot;resource&quot;, &quot;feature&quot;]} /&amp;gt;&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;p data-ke-size=&quot;size16&quot;&gt;다음을 Backend에 추가해주어야 한다.&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;packages/backend/src/plugins/catalogModuleCustomRelationProcessor.ts&lt;/p&gt;
&lt;pre id=&quot;code_1770730055626&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { createBackendModule } from '@backstage/backend-plugin-api';
import { CustomRelationProcessor } from './CustomRelationProcessor';

/**
 * Module to register custom relation processor for the catalog
 * Processes: hasFeature, featureOf custom relations
 */
export const catalogModuleCustomRelationProcessor = createBackendModule({
  pluginId: 'catalog',
  moduleId: 'custom-relation-processor',
  register(env) {
    env.registerInit({
      deps: {
        catalog: catalogProcessingExtensionPoint,
      },
      async init({ catalog }) {
        catalog.addProcessor(new CustomRelationProcessor());
      },
    });
  },
});

export default catalogModuleCustomRelationProcessor;&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;packages/backend/src/plugins/catalogModuleFeatureEntityKind.ts&lt;/p&gt;
&lt;pre id=&quot;code_1770730084443&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { createBackendModule } from '@backstage/backend-plugin-api';
import { CatalogProcessor, CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
import { Entity } from '@backstage/catalog-model';
import { LocationSpec } from '@backstage/plugin-catalog-common';

/**
 * Custom processor that validates and processes Feature entities
 */
class FeatureEntityProcessor implements CatalogProcessor {
  getProcessorName(): string {
    return 'FeatureEntityProcessor';
  }

  async validateEntityKind(entity: Entity): Promise&amp;lt;boolean&amp;gt; {
    return entity.kind === 'Feature';
  }

  async postProcessEntity(
    entity: Entity,
    _location: LocationSpec,
    _emit: CatalogProcessorEmit,
  ): Promise&amp;lt;Entity&amp;gt; {
    // Accept and pass through Feature entities
    if (entity.kind === 'Feature') {
      // Basic validation
      if (!entity.spec) {
        throw new Error('Feature entity must have a spec');
      }

      if (!entity.spec.type) {
        throw new Error('Feature entity must have a spec.type');
      }

      if (!entity.spec.lifecycle) {
        throw new Error('Feature entity must have a spec.lifecycle');
      }

      if (!entity.spec.owner) {
        throw new Error('Feature entity must have a spec.owner');
      }
    }

    return entity;
  }
}

/**
 * Module to register Feature entity kind processor
 */
export const catalogModuleFeatureEntityKind = createBackendModule({
  pluginId: 'catalog',
  moduleId: 'feature-entity-kind',
  register(env) {
    env.registerInit({
      deps: {
        catalog: catalogProcessingExtensionPoint,
      },
      async init({ catalog }) {
        catalog.addProcessor(new FeatureEntityProcessor());
      },
    });
  },
});

export default catalogModuleFeatureEntityKind;&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;packages/backend/src/plugins/CustomRelationProcessor.ts&lt;/p&gt;
&lt;pre id=&quot;code_1770730103332&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { CatalogProcessor, CatalogProcessorEmit, processingResult } from '@backstage/plugin-catalog-node';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import { Entity } from '@backstage/catalog-model';

/**
 * Custom catalog processor that adds featureOf/hasFeature relations
 * based on custom fields in entity spec
 */
export class CustomRelationProcessor implements CatalogProcessor {
  getProcessorName(): string {
    return 'CustomRelationProcessor';
  }

  async preProcessEntity(
    entity: Entity,
    location: LocationSpec,
    emit: CatalogProcessorEmit,
  ): Promise&amp;lt;Entity&amp;gt; {
    // Handle hasFeature field
    const hasFeature = (entity.spec?.hasFeature as string[] | string | undefined);
    if (hasFeature) {
      const features = Array.isArray(hasFeature) ? hasFeature : [hasFeature];
      
      for (const targetRef of features) {
        emit(processingResult.relation({
          source: {
            kind: entity.kind,
            namespace: entity.metadata.namespace || 'default',
            name: entity.metadata.name,
          },
          type: 'hasFeature',
          target: {
            kind: targetRef.split(':')[0] || 'Feature',
            namespace: targetRef.split('/')[0].split(':')[1] || 'default',
            name: targetRef.split('/')[1] || targetRef,
          },
        }));
      }
      
      // Remove from spec after processing to avoid validation errors
      delete entity.spec.hasFeature;
    }

    // Handle featureOf field
    const featureOf = (entity.spec?.featureOf as string[] | string | undefined);
    if (featureOf) {
      const components = Array.isArray(featureOf) ? featureOf : [featureOf];
      
      for (const targetRef of components) {
        emit(processingResult.relation({
          source: {
            kind: entity.kind,
            namespace: entity.metadata.namespace || 'default',
            name: entity.metadata.name,
          },
          type: 'featureOf',
          target: {
            kind: targetRef.split(':')[0] || 'Component',
            namespace: targetRef.split('/')[0].split(':')[1] || 'default',
            name: targetRef.split('/')[1] || targetRef,
          },
        }));
      }
      
      // Remove from spec after processing to avoid validation errors
      delete entity.spec.featureOf;
    }

///// Up/Down Stream feature

    // Handle upstream field
    const upstreamOf = (entity.spec?.upstreamOf as string[] | string | undefined);
    if (upstreamOf) {
      const apis = Array.isArray(upstreamOf) ? upstreamOf : [upstreamOf];
      
      for (const targetRef of apis) {
        emit(processingResult.relation({
          source: {
            kind: entity.kind,
            namespace: entity.metadata.namespace || 'default',
            name: entity.metadata.name,
          },
          type: 'upstreamOf',
          target: {
            kind: targetRef.split(':')[0] || 'Api',
            namespace: targetRef.split('/')[0].split(':')[1] || 'default',
            name: targetRef.split('/')[1] || targetRef,
          },
        }));
      }

      // Remove from spec after processing to avoid validation errors
      delete entity.spec.upstreamOf;
    }

          // Handle upstream field
    const upstreamBy = (entity.spec?.upstreamBy as string[] | string | undefined);
    if (upstreamBy) {
      const apis = Array.isArray(upstreamBy) ? upstreamBy : [upstreamBy];
      
      for (const targetRef of apis) {
        emit(processingResult.relation({
          source: {
            kind: entity.kind,
            namespace: entity.metadata.namespace || 'default',
            name: entity.metadata.name,
          },
          type: 'upstreamBy',
          target: {
            kind: targetRef.split(':')[0] || 'Feature',
            namespace: targetRef.split('/')[0].split(':')[1] || 'default',
            name: targetRef.split('/')[1] || targetRef,
          },
        }));
      }
      
      // Remove from spec after processing to avoid validation errors
      delete entity.spec.upstreamBy;
    }

    // Handle upstream field
    const downstreamOf = (entity.spec?.downstreamOf as string[] | string | undefined);
    if (downstreamOf) {
      const apis = Array.isArray(downstreamOf) ? downstreamOf : [downstreamOf];
      
      for (const targetRef of apis) {
        emit(processingResult.relation({
          source: {
            kind: entity.kind,
            namespace: entity.metadata.namespace || 'default',
            name: entity.metadata.name,
          },
          type: 'downstreamOf',
          target: {
            kind: targetRef.split(':')[0] || 'Api',
            namespace: targetRef.split('/')[0].split(':')[1] || 'default',
            name: targetRef.split('/')[1] || targetRef,
          },
        }));
      }
      
      // Remove from spec after processing to avoid validation errors
      delete entity.spec.downstreamOf;
    }

    // Handle upstream field
    const downstreamBy = (entity.spec?.downstreamBy as string[] | string | undefined);
    if (downstreamBy) {
      const apis = Array.isArray(downstreamBy) ? downstreamBy : [downstreamBy];
      
      for (const targetRef of apis) {
        emit(processingResult.relation({
          source: {
            kind: entity.kind,
            namespace: entity.metadata.namespace || 'default',
            name: entity.metadata.name,
          },
          type: 'downstreamBy',
          target: {
            kind: targetRef.split(':')[0] || 'Feature',
            namespace: targetRef.split('/')[0].split(':')[1] || 'default',
            name: targetRef.split('/')[1] || targetRef,
          },
        }));
      }
      
      // Remove from spec after processing to avoid validation errors
      delete entity.spec.downstreamBy;
    }

    return entity;
  }
}&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;그리고 위 정의한 Custom Processor를 Import 해야한다.&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;packages/backend/src/index.ts&lt;/p&gt;
&lt;pre id=&quot;code_1770730328982&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// custom Feature entity kind
backend.add(import('./plugins/catalogModuleFeatureEntityKind'));

// custom catalog relation processor
backend.add(import('./plugins/catalogModuleCustomRelationProcessor'));&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;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000; text-align: start;&quot;&gt;Entity의 Overview에서 Catalog Graph Card의 속성에서 기본적으로 표시할 Type을 선정할 수 있고 이중에 Feature를 추가하였다.&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;packages/app/src/components/catalog/EntityPage.tsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770728878148&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const overviewContent = (
  &amp;lt;Grid container spacing={3} alignItems=&quot;stretch&quot;&amp;gt;
    {entityWarningContent}
    &amp;lt;Grid item md={6}&amp;gt;
      &amp;lt;EntityAboutCard variant=&quot;gridItem&quot; /&amp;gt;
    &amp;lt;/Grid&amp;gt;
    &amp;lt;Grid item md={6} xs={12}&amp;gt;
      &amp;lt;EntityCatalogGraphCard variant=&quot;gridItem&quot; renderNode={CustomRenderNode} height={400} kinds={[&quot;component&quot;, &quot;api&quot;, &quot;resource&quot;, &quot;feature&quot;]}/&amp;gt;
    &amp;lt;/Grid&amp;gt;
...
  &amp;lt;/Grid&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;packages/backend/src/plugins/FeatureEntityProvider.ts&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770730188371&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Entity } from '@backstage/catalog-model';

/**
 * Feature Entity Kind definition
 * Validates Feature entities in the catalog
 */
export interface FeatureEntityV1alpha1 extends Entity {
  apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
  kind: 'Feature';
  spec: {
    type: string;
    lifecycle: string;
    owner: string;
    system?: string;
    featureOf?: string[];
    upstreamOf?: string[];
    downstreamOf?: string[];
  };
}

/**
 * Type guard for Feature entities
 */
export function isFeatureEntity(entity: Entity): entity is FeatureEntityV1alpha1 {
  return entity.kind === 'Feature';
}&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Backstage에서 호환할 Entity Type 은 다음 필드에서 정의한다. catalog.rules.allow&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: 'Nanum Gothic'; color: #000000;&quot;&gt;app-config.yaml&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770729055592&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;catalog:
  rules:
    - allow: [Component, System, API, Resource, Location, Feature]&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. Catalog Graph에서 새로운 Relation을 추가하고 Default Choice를 수정하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;617&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4EjeY/dJMcaihVCf4/kBI2A8E6KcKtk1APGjWp4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4EjeY/dJMcaihVCf4/kBI2A8E6KcKtk1APGjWp4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4EjeY/dJMcaihVCf4/kBI2A8E6KcKtk1APGjWp4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4EjeY%2FdJMcaihVCf4%2FkBI2A8E6KcKtk1APGjWp4K%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;265&quot; height=&quot;617&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;617&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; font-family: 'Nanum Gothic';&quot;&gt;Catalog Graph에서 표시할 Default Setting은 다음과 같이 정의한다. 기본적으로 온보딩되어 있는 scmIntegrationsApi는 그대로 두며, backstage/plugin-catalog-graph 라이브러리를 사용하고 있는 catalogGraphApiRef 부분부터 추가해주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770729688176&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        defaultRelationTypes: {
          // Don't exclude any custom relations - show them by default
          exclude: ['providesApi', 'consumesApi','apiConsumedBy','apiProvidedBy'],
        },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;이 부분은 Default로 제외할 Relation을 정의한다.&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; font-family: 'Nanum Gothic';&quot;&gt;packages/app/src/apis.ts&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770729224552&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {
  ScmIntegrationsApi,
  scmIntegrationsApiRef,
  ScmAuth,
} from '@backstage/integration-react';
import {
  AnyApiFactory,
  configApiRef,
  createApiFactory,
} from '@backstage/core-plugin-api';
import {
  ALL_RELATIONS,
  ALL_RELATION_PAIRS,
  catalogGraphApiRef,
  DefaultCatalogGraphApi,
} from '@backstage/plugin-catalog-graph';

export const apis: AnyApiFactory[] = [
  createApiFactory({
    api: scmIntegrationsApiRef,
    deps: { configApi: configApiRef },
    factory: ({ configApi }) =&amp;gt; ScmIntegrationsApi.fromConfig(configApi),
  }),
  ScmAuth.createDefaultApiFactory(),
  
  // Insert Custom Relation and default relation setting
  createApiFactory({
    api: catalogGraphApiRef,
    deps: {},
    factory: () =&amp;gt;
      new DefaultCatalogGraphApi({
        // The relations to support
        knownRelations: [...ALL_RELATIONS, 'featureOf', 'hasFeature','upstreamOf','downstreamOf', 'upstreamBy','downstreamBy'],
        // The relation pairs to support
        knownRelationPairs: [
          ...ALL_RELATION_PAIRS,
          ['featureOf', 'hasFeature'],
          ['upstreamOf', 'upstreamBy'],
          ['downstreamOf', 'downstreamBy'],
        ],
        // Select what relations to be shown by default
        defaultRelationTypes: {
          // Don't exclude any custom relations - show them by default
          exclude: ['providesApi', 'consumesApi','apiConsumedBy','apiProvidedBy'],
        },
      }),
  }),
];&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Programming/MSA</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/545</guid>
      <comments>https://armyost.tistory.com/545#entry545comment</comments>
      <pubDate>Tue, 10 Feb 2026 22:35:18 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 심화) 카프카 모니터링 하기</title>
      <link>https://armyost.tistory.com/544</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;클러스터 문제 진단하기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;활성 컨트롤러 수나 컨트롤러 큐 크기와 같은 지표를 모니터링 함으로써 뭔가 문제가 발생했을 때 간단히 알아차릴수 있다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;불완전 복제 파티션 다루기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;해당 브로커가 리더 레플리카를 잡고 있는 파티션 중 팔로워 레플리카가 따라오지 못하고 있는 파티션의 수를 나타낸다. 다수의 브로커가 일정한 수의 불완전 복제 파티션을 가지고 있다는 것은 보통 클러스터의 브로커중 하나가 내려가 있다는 것을 의미하는 경우가 많다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;가장 먼저 해야 할 일은 문제가 단일 브로커에 국한된 것인지 아니면 클러스터 전체에 연관된 것인지를 확인하는 것이다 때로는 이것이 쉽지 않은 문제일 수 있다. 만약 다음 예와 같이 불완전 복제 파티션들이 한 브로커에 몰려 있다면 해당 브로커가 문제일 가능성이 높다. 에러 메시지를 보면 다른 브로커가 해당 브로커로부터 메시지를 복제하는데 문제가 있음을 알 수 있다.&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;클러스터 수준 문제&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;대게 클러스터 문제는 다음 둘중 하나의 유형에 속한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 부하 불균형&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&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; font-family: 'Nanum Gothic';&quot;&gt;첫번재 문제는 가장 찾기 쉽지만, 해결 하는 것은 상당히 복잡할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 파티션의 개수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 리더 파티션의 수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 전 토픽에 있어서의 초당 들어오는 메시지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 전 토픽에 있어서의 초당 들어오는 바이트&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&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; font-family: 'Nanum Gothic';&quot;&gt;클러스터에서 흔히 볼 수 있는 또 다른 문제는 브로커에 들어오는 요청이 처리 가능한 용량을 넘어 가는 경우다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- CPU사용률&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 인바운드 네트워크 속도&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 아웃바운드 네트워크 속도&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 평균 디스크 대기 시간&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&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; font-family: 'Nanum Gothic';&quot;&gt;전 토픽 바이트 인입률은 클러스터 사용량을 보여주는 좋은 가이드 라인이 된다.&lt;/span&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;호스트 수준 문제&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;하드웨어가 제공하는 기능을 사용해서 하드웨어 상태를 모해야 할 것이다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;브로커 지표&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 활성 컨트롤러 수 : 이 지표는 특정 브로커가 현재 클러스터의 컨트롤러 역할을 맡고 있는지를 나타낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 컨트롤러의 큐 크기 : 현재 컨트롤러에서 브로커의 처리를 기다리고 있는 요청의 수를 가리킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 요청 핸들러 유휴 비율 : 카프카는 모든 클라이언트 요청을 처리하기 위해 네트워크, 스레드 풀과 요청 핸들러 스레드 풀, 두개의 스레드 풀을 사용한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 전 토픽 바이트 인입 : 속도는 브로커가 프로듀서 클라이언트로부터 얼마나 많은 메시지 트래픽을 받는지에 대한 측정값으로서 유용하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 전 토픽 바이트 유출 : 전 토픽 바이트 유출 속도는 트래픽의 전체적인 성장세를 보여주는 또 다른 지표다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 전 토픽 메시지 인입 : 메시지 인입 속도는 메시지 크기와 무관하게 초당 들어오는 메시지 수를 보여준다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 파티션 수 : 이는 브로커에 할당된 파티션의 전체 갯수로 자주 변하지는 않는다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 리더 수 : 브로커가 현재 리더를 맡고 있는 파티션의 갯수를 보여준다. 이 지푯값은 모든 브로커에 걸쳐 균동해야 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 오프라인 파티션 : 불완전 복제 파티션 수와 마찬가지로 오프라인 파티션 수는 모니터링에 있어서 매우 중요한 지표다. 현재 리더가 없는 파티션의 갯수를 보여준다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 읽기 요청지표 : 초당 요청 지표는 속도 지표이며, 주어진 시간 단위에 걸쳐 수신되어 처리된 해당 타입 요청의 전체 갯수를 가리킨다.&amp;nbsp;&lt;/span&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;토픽과 파티션별 지표&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 토픽별 지표 : 모든 토픽별 지표의 측정값은 앞에서 설명한 브로커 지표와 매우 비슷하다. 사실, 유일한 차이점은 토픽 이름을 지정해야 한다는 점이며, 지푯값 역시 지정된 토픽에 국한도니 값만을 내놓는다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 파티션별 지표 : 지속적으로 사용하는 걸 기준으로 보자면 파티션별 지표는 토픽별 지표에 비해 덜 유용한 편이다. 몇몇 제한된 상황에서는 유용하게 사용될 수 있다. 특히 파티션 크기 지표는 해당 파티션에 대한 현재 디스크에 저장도니 데이터의 양을 바이트 단위로 보여준다. 따라서 이 값들을 합산하면 하나의 토픽에 저장된 데이터의 양을 알 수 있는데, 이는 카프카를 각각의 클라이언트에 할당할 때 들어갈 자원의 양을 계산하는데 유용할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;로깅&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;kafka.server.ClientQuotaManager 로거다. 이 로거는 프로듀서 혹은 컨슈머 쿼터 작업에 관련된 메시지를 보여주기 위해 사용된다. 로그 압착 스레드의 상태에 관한 정보를 로깅하는 것 역시 도움이 된다. kafka.log.LogCleaner, kafka.log.Cleaner, kafka.log.LogCleanerManager 로거를 Debug 레벨로 활성화함으로써 이 스레드에 대한 상태 정보가 찍히게 할 수 있다. 카프카에서 문제를 디버깅할대 유용한 로거 역시 있다. 그러한 로거 중 하나가 kafka.request.logger다. 이 로거는 Debug또는 Trace 레벨로 켜 놓으면 되는데, 브로커로 들어오는 모든 요청에 대한 정보를 기록한다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;프로듀서 종합 지표&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;우선 record-error-rate 속성은 반드시 경보 설정을 해 놓아야 한다. 이 지표는 언제나 0이어야 하며, 만약 그보다 크다면 프로듀서가 브로커로 메시지를 보내는 와중에 누수가 발생하고 있음을 의미한다. 프로듀서는 백오프backoff 를 해가면서 사전 설정된 수만큼 재시로를 하게 되어 있는데, 만약 재시도 수가 고갈되면 메시지는 폐기된다.&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; font-family: 'Nanum Gothic';&quot;&gt;경보를 설정해 놔야 하는다른 지표는 request-latency-avg 이다. 이것은 브로커가 쓰기 요청을 받을 때까지 걸린 평균 시간이다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;브로커별, 토픽별 지표&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;브로커별 프로듀서 지표에서 가장 중요한 지표는 request-latency-avg이다. 이 지표는 거의 변화가 없지만, 특정 브로커로의 연결에 문제가 있을 경우 알 수 있다. outgoing-byte-rate나 request-latency-avg와 같은 다른 속성은 브로커가 리더를 맡고 있는 파티션이 무엇이냐에 따라 달라지는 경향이 있다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;컨슈머 지표&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;읽기 매니저 지표 : 컨슈머 클라이언트에서는 중요한 지표들이 읽기 매니저 빈에 들어있기 때문에 컨슈머 종합 지표 빈은 상대적으로 덜 유용하다. 읽기 매니저의 경우 모니터링 말고 경보 설정에 모두 사용할 수 있는 속성에 하나 있는데, 바로 fetch-latency-avg이다. 프로듀서 클라이언트의 request-latency-avg와 마찬가지로 이 지표는 브로커로 읽기 요청을 보내는데 걸리는 시간을 보여준다. 다만 이 지표에 경보 설정을 걸어 놓는 것은 문제가 있는데, 지연은 컨슈머 설정 중 fetch.min.bytes와 fetch.max.wait.ms의 영향을 받기 때문이다. 컨슈머 클라이언트가 얼마나 많은 메시지 트래픽을 처리중인지를 알려면 bytes-comsumed-rate 혹은 records-consumed=rate, 가능하면 두 지표를 다 보는것이 좋다. 이 지표들은 클라이언트 인스턴스의 읽기 트래픽을 초당 바이트 수 혹은 초당 메시지 수 형태로 보여준다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;브로커별, 토픽별 지표 : 컨슈머 클라이언트 역시 브로커 연결이나 읽는 토픽에 각각에 대한 지표를 제공한다. request-latency-avg 속성은 읽고 있는 토픽의 메시지 트래픽에 따라 제한적으로 유용하다. incoming-byte-rate와 reqeust-rate 지표는 읽기 매니저에 의해 제공되는 bytes-consumed-rate, records-consumed-rate 지표를 각각 브로커별 초당 바이트와 브로커별 초당 요청수로 세분화한 것이다. 토픽별 지표는 1개 이상의 토픽에서 읽어오고 있을 때 유용하다. 너무 많은 토픽으로부터 읽을 경우 이 지표들은 알아보기가 어렵다. 만약 이 값들을 수집하고 싶다면 가장 중요한 지표는 bytes-consumed-rate, records-consumed-rate, fetch-size-avg다. bytes-consumed-rate 는 특정 포틱에서 읽은 초당 바이트 수 , records-consumed-rate는 초당 메시지 수 , fetch-size-avg는 해당 토픽에 대한 읽기 요청의 평균 크기를 보여준다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;그룹 코디에니터는 동기화 작업에 들어간&amp;nbsp; 평균 시간을 sync-time-avg 지표 속성을 통해 밀리초 단위로 보여준다. 초당 그룹 동기화 수를 보여주는 sync-rate 속성 역시 도움이 된다. 컨슈머 그룹이 안정적일 경우 이값은 대부분의 시간 동안 0이다.&amp;nbsp;&lt;/span&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;랙 모니터링&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;카프카 컨슈머에 있어서 가장 중요하게 모니터링되어야 하는 것은 컨슈머 랙이다. 선호되는 방법은 브로커의 파티션 상태와 컨슈머 상태를 둘다 지켜봄으로써 마지막으로 쓰여진 메시지 오프셋과 컨슈머 그룹이 파티션에 대해 마지막으로 커밋한 오프셋을 추적하는 외부 프로세스를 두는 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>PaaS/MQ</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/544</guid>
      <comments>https://armyost.tistory.com/544#entry544comment</comments>
      <pubDate>Tue, 10 Feb 2026 00:29:08 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 심화) 보안</title>
      <link>https://armyost.tistory.com/543</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;PLAINTEXT&lt;/b&gt; : PLAINTEXT 전송 계층에는 인증이 존재하지 않는다. 사설 네트워크 안에서 인증이나 암호화가 필요없을 정도로 민감하지 않은 데이터를 처리할 때문 적합하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;SSL&lt;/b&gt; : SSL전송 계층은 선택적으로 클라이언트 SSL인증을 수행할 수 있다. 암호화 뿐만 아니라 클라이언트/서버 인증도 지원되기 때문에 안전하지 않은 네트워크에서 적절하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;SASL_PLAINTEXT :&lt;/b&gt; SASL 인증과 PLAINTEXT 전송계층이 합쳐진 것이다. 어떤 SASL메커니즘은 서버 인증 역시 지원한다. 암호화는 지우하지 않기 때문에 사실 네트워크 안에서만 적합하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;SASL_SSL&lt;/b&gt; : SASL 인증과 SSL 전송계층이 합쳐진 것이다. 암호화 뿐만아니라 클라이언트/서버 인증도 지워되기 때문에 안전하지 않은 네트워크에서 적절하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인증&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SSL&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SASL&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;카프카 프로토콜은 SASL을 사용한 인증을 지원하며, 자주 사용되는 SASL 메커니즘을 기본적으로 지원한다. 기본적으로 다음과 같은 SASL 메커니즘과 함께 기존 보안 인프라스트럭처와 통합하기 위한 커스터마이즈 가능한 콜백을 지원한다.&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1) GSSAPI : SASL/GSSAPI 를 사용하는 케르베로스 인증이 지원되며, Active Directory나 OpenLDAP와 같은 케르베로스 서버와 통합하는데 사용될 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;2) PLAIN : 사용자 이름/비밀번호 인증, 보통 외부 비밀번호 저장소를 사용해서 비밀번호를 검증하는 서버측 커스텀 콜백과 함께 사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;3) SCRAM-SHA-256 and SCRAM-SHA-512 : 추가적인 비밀번호 저장소 같은 것을 설정할 필요없이 카프카를 설치하자마자 바로 사용할수 있는 사용자 이름/비밀번호 인증&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;4) OAUTHBEARER : OAuth bearer 토큰을 사용한 인증, 보통 표준화된 OAuth 서버에서 부여된 토큰을 추출하고 검증하는 커스텀 콜백과 함께 사용한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SASL/GSSAPI : &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 메커니즘은 GSS-API의 케르베로스 V5 메커니즘을 사용해서 인증을 수행한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SASL/PLAIN: &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TLS와 함께 사용될 수 있는 단순한 사용자&amp;nbsp; 이름/비밀번호 인증 메커니즘을 정의한다. 카프카는 커스텀 콜백 핸들러를 사용해서 안전한 외부 비밀번호 저장소와 통합할 수 있는 안전한 SASL/PLAIN 구현체를 기본적으로 사용한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SASL/SCRAM : &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;RFC-5802는 SASL/PLAIN처럼 비밀번호를 직접 전송하는 인증 메커니즘에서 발생하는 보안 문제에 대처할 수 있는 안전한 사용자 이름/비밀번호 인증 메커니즘을 제안한다. SCRAM은 암호화 되지 않은 비밀번호가 전송되는 것을 피하고 악의적인 사용자가 다른 사용자로 가장해서 내용을 읽어오는 것이 불가능한 형식으로 저장한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SASL/OAUTHBEARER : &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAUTHBEARER는 OAuth 2.0 베어러 토큰을 사용하되 더 짧은 토큰 유효기관과 제한도니 자우 접근만 허용하는데, 이는 장기 비밀번호를 사용하는 메커니즘에서 발생하는 보안 취약점을 방지하는 효과가 있다. 카프카는 클라이언트 인증에 SASL/OAUTHBEARER를 지원하며, 서드파티 OAuth 서버와의 통합 역지 지원한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 위임토큰 : &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위임토큰은 카프카 브로커와 클라이언트 사이에 공유된 비밀로써, 클라이언트에 SSL키스토어나 케르베로스 키탭 파일을 배포할 필요없는 경량 설정 메커니즘을 제공한다. 위임 토큰은 케르베로스 키 배포센터와 같은 인증 서버의 부하르 ㄹ줄이기 위해 사용될 수 도 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;케르베로스나 OAuth와 같은 보안 메커니즘은 유효기간이 있는 자격 증명을 사용한다.카프카는 기존 자격 증명이 만료되기 전 새로운 자격 증명을 얻어오기 위해 백드라운드에서 돌아가는 로그인 스레드를 사용하지만, 새로운 자격증명은 기본적으로 새로운 연결에만 적용되도록 되어 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;메시지를 카프카 로그에 저장되는 바이트 배열로 변환하기 위해 시리얼라이저를 사용하는 것을 보았다. 시리얼라이저와 디시리얼라이저를 직렬화 도중에 메시지를 암호화 하거나, 역직렬화 도중에 복호화를 수행할 수 있도록 암호화 라이브러리에 통합될 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&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-origin-width=&quot;902&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TrLlh/dJMcadHDhWG/GykxhyxaX9WNTHRMn8ktdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TrLlh/dJMcadHDhWG/GykxhyxaX9WNTHRMn8ktdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TrLlh/dJMcadHDhWG/GykxhyxaX9WNTHRMn8ktdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTrLlh%2FdJMcadHDhWG%2FGykxhyxaX9WNTHRMn8ktdk%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;642&quot; height=&quot;436&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 카프카 프로듀서를 사용해서 메시지를 보낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;2. 프로듀서가 KMS에 저장된 암호화 키를 사용해서 메시지를 암호화한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;3. 암호화된 메시지가 브로커로 전달된다. 브로커는 암호화된 메시지를 파티션 로그에 저장한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;4. 브로커가 암호화된 메시지를 컨슈머로 보낸다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;5. 컨슈머가 KMS에 저장된 암호화 키를 사용해서 메시지를 복호화 시킨다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인가&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AclAuthorizer&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;AclAuthorizer는 접근 제어 목록을 사용해서 카프카 자원에 대한 접근을 세밀하게 제어할 수 있도록 해준다. ACL은 주키퍼에 저장되는데, 요청ㅇ르 빠르게 인가할 수 있게 하기 위해 모든 브로커의 메모리에 캐시한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카프카 ACL과 부여되는 접근 권한&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://docs.confluent.io/platform/current/security/authorization/acls/overview.html#token-resource-type-operations&quot;&gt;https://docs.confluent.io/platform/current/security/authorization/acls/overview.html#token-resource-type-operations&lt;/a&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1770444391901&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;Use access control lists (ACLs) for authorization in Confluent Platform | Confluent Documentation&quot; data-og-description=&quot;If you use Kerberos, your Kafka principal is based on your Kerberos principal (for example, kafka/kafka1.hostname.com@EXAMPLE.COM). By default, Kafka only uses the primary name of the Kerberos principal, which is the name that appears before the slash (/).&quot; data-og-host=&quot;docs.confluent.io&quot; data-og-source-url=&quot;https://docs.confluent.io/platform/current/security/authorization/acls/overview.html#token-resource-type-operations&quot; data-og-url=&quot;https://docs.confluent.io/platform/current/security/authorization/acls/overview.html#token-resource-type-operations&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.confluent.io/platform/current/security/authorization/acls/overview.html#token-resource-type-operations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.confluent.io/platform/current/security/authorization/acls/overview.html#token-resource-type-operations&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Use access control lists (ACLs) for authorization in Confluent Platform | Confluent Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;If you use Kerberos, your Kafka principal is based on your Kerberos principal (for example, kafka/kafka1.hostname.com@EXAMPLE.COM). By default, Kafka only uses the primary name of the Kerberos principal, which is the name that appears before the slash (/).&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.confluent.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;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cluster resource operations&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;div&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;color: #191924; width: 18.1395%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Alter&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.0931%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cluster&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AlterReplicaLogDirs, CreateAcls, DeleteAcls&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.1395%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AlterConfigs&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.0931%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cluster&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AlterConfigs&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.1395%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ClusterAction&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.0931%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cluster&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Fetch (for replication only), LeaderAndIsr, OffsetForLeaderEpoch, StopReplica, UpdateMetadata, ControlledShutdown, WriteTxnMarkers&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.1395%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Create&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.0931%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cluster&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CreateTopics, Metadata&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.1395%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Describe&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.0931%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cluster&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DescribeAcls, DescribeLogDirs, ListGroups&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.1395%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DescribeConfigs&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.0931%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Cluster&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DescribeConfigs&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic resource type operations&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;div&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;color: #191924; width: 18.2558%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Alter&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.093%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.5349%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CreatePartitions&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.2558%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AlterConfigs&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.093%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.5349%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AlterConfigs&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.2558%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Create&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.093%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.5349%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CreateTopics, Metadata&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.2558%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Delete&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.093%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.5349%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DeleteRecords, DeleteTopics&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.2558%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Describe&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.093%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.5349%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ListOffsets, Metadata, OffsetFetch, OffsetForLeaderEpoch&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.2558%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DescribeConfigs&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.093%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.5349%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DescribeConfigs&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.2558%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Read&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.093%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.5349%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Fetch, OffsetCommit, TxnOffsetCommit&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.2558%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Write&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 12.093%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Topic&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 69.5349%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Produce, AddPartitionsToTxn&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Group resource type operations&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;div&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;color: #191924; width: 18.7209%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Delete&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 9.06976%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Group&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 77.4419%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DeleteGroups&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.7209%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Describe&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 9.06976%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Group&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 77.4419%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DescribeGroup, FindCoordinator, ListGroups&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.7209%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Read&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 9.06976%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Group&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 77.4419%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AddOffsetsToTxn, Heartbeat, JoinGroup, LeaveGroup, OffsetCommit, OffsetFetch, SyncGroup, TxnOffsetCommit&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Token resource type operations&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;div&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;color: #191924; width: 17.907%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Describe&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 16.0465%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DelegationToken&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 65.8139%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;DescribeTokens&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #111820;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Transactional ID resource type operations&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;div&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;color: #191924; width: 18.0233%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Describe&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 16.2791%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;TransactionalId&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 65.5813%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;FindCoordinator&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #191924; width: 18.0233%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Write&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 16.2791%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;TransactionalId&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;color: #191924; width: 65.5813%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Produce, AddPartitionsToTxn, AddOffsetsToTxn, EndTxn, InitProducerId, TxnOffsetCommit&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;</description>
      <category>PaaS/MQ</category>
      <category>`</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/543</guid>
      <comments>https://armyost.tistory.com/543#entry543comment</comments>
      <pubDate>Sat, 7 Feb 2026 15:06:57 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 심화) 클러스터간 데이터 미러링하기</title>
      <link>https://armyost.tistory.com/542</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;카프카 클러스터 간의 데이터 복제는 미러링 이라고 부를것이다. 아파치 카프카에는 클러스터간 데이터 복제를 수행하기 위한 툴로 미러메이커를 포함하고 있다.&amp;nbsp;&lt;/span&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;클러스터간 미러링 활용 사례&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;지역 및 중앙 클러스터&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;고전적인 사례로는 수요와 공급에 따라서 가격을 조정해야 하는 회사가 있을 것이다. 이 회사는 사무실을 운영중인 각각의 도시에 데이터 센터를 가지고 각 지역의 수요와 공급에 대한 정보를 수집해서 그에 따라 가격을 조정한다. 이 모든 데이터는 그 후 중앙 클러스터로 미러링되어 비즈니스 분석가들이 회사 단위의 수익 보고를 할 때 사용할 수 있다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;고가용성과 재해복구&lt;/span&gt;&lt;/b&gt;&lt;/h4&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;규제&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;여러 나라에서 사업을 운영하는 회사의 경우 국가별로 다른 법적, 규제적 요구 조건을 따르기 위해 나라마다 있는 카프카 클러스터별로 서로 다른 설정과 정책을 시행해야 할 수 있다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;클라우드 마이그레이션&lt;/span&gt;&lt;/b&gt;&lt;/h4&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;엣지 클러스터로부터의 데이터 집적&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; 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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;다중 클러스터 아키텍처&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;데이터센터간 통신의 현실적 문제들&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 높은 지연&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 제한된 대역폭&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; 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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;허브 엔 스포크 아키텍처&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;허브 앤 스포크 아키텍처는 여러개의 로컬 카프카 클러스터와 한개의 중앙 카프카 클러스터가 있는 상황을 상정한 것이다.&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-origin-width=&quot;817&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CHnKs/dJMcadgz1fy/r1KHtko1soZipUXQbkslz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CHnKs/dJMcadgz1fy/r1KHtko1soZipUXQbkslz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CHnKs/dJMcadgz1fy/r1KHtko1soZipUXQbkslz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCHnKs%2FdJMcadgz1fy%2Fr1KHtko1soZipUXQbkslz1%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;648&quot; height=&quot;350&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;441&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 아키텍처의 단순화된 형태로는 리더와 팔로워 두개의 클러스터만 사용하는 방식이 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 아키텍처의 주도니 장점은 항상 로컬 데이터센터에서 데이터가 생성되고 각각의 데이터센터에 저장된 이벤트가 중앙 데이터센터로 단 한 번만 미러링된다는 점이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;액티브-액티브 아키텍처&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cadvj3/dJMcadAPkPJ/jQT1P8E8zzt54xm1rZklr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cadvj3/dJMcadAPkPJ/jQT1P8E8zzt54xm1rZklr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cadvj3/dJMcadAPkPJ/jQT1P8E8zzt54xm1rZklr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcadvj3%2FdJMcadAPkPJ%2FjQT1P8E8zzt54xm1rZklr1%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;549&quot; height=&quot;240&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 아키텍처의 주된 장점은 인근 데이터센터에서 사용자들의 요청을 처리할 수 있다는 점이다. 또 다른 장점은 데이터 중복과 회복 탄력성이다. 모든 데이터센터가 모든 기능을 동일하게 가지기 때문에 한 데이터센터에 장애가 발생하더라도 사용자 요청을 다른 데이터센터 처리할 수 있는 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 아키텍처의 주된&amp;nbsp; 단점은 데이터를 여러 위치에서 비동기적으로 읽거나 변경할 경우 발생하는 충돌을 피하는 것이 어렵다는 점이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 사용자가 하나의 데이터센터에 이벤트를 쓰고 또 다른 데이터센터로부터는 이벤트를 읽어오는 상황을 생각해보자. 전자에 쓴 데이터가 후자에 아직 도착하지 않았을 수도 있다. 사용자 입장에서는 위시리스트에 책을 하나 추가하고, 위시리스트를 클릭했는데 정작거기에 방금전 추가한 책이 없는 것이다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;액티브-스탠바이 아키텍처&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZGF9u/dJMcacBZhAQ/SKHZcrx6lNLYJUraTMYMr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZGF9u/dJMcacBZhAQ/SKHZcrx6lNLYJUraTMYMr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZGF9u/dJMcacBZhAQ/SKHZcrx6lNLYJUraTMYMr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZGF9u%2FdJMcacBZhAQ%2FSKHZcrx6lNLYJUraTMYMr1%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;259&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;첫번째 클러스터의 모든 데이터를 가지고 있다가 첫 번째 클러스터가 완전히 사용할 수 없게 될 경우 대신 사용할 수 있는 두번째 클러스터가 필요할 수 있다. 혹은 지리적 장애 복구가 필요할 수도 있다.&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1) 재해 복구 계획하기&lt;/span&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;2) 계획에 없던 장애 복구에서의 데이터 유실과 불일치&lt;/span&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;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;3) 장애복구 이후 애플리케이션의 시작 오프셋&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 자동 오프셋 재설정 : 아파치 카프카 컨슈머는 사전에 커밋된 오프셋이 없을 경우 어떻게 작동해야 하는지를 결정하는 설정값을 갖는다. 장애 복구 계획에 이 오프셋을 미러링하는 부분이 빠져 있다면, 다음 두가지 옵션중에서 양자택일 해야 할 것이다. 즉, 데이터의 맨 처음부터 읽기 시작해서 상당한 수의 많은 양의 데이털르 처리할 것인지, 아니면 맨 끝에서 시작해서 알려지지 않은 갯수의 이벤트를 건너뛸 것인지?&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 오프셋 토픽 복제 : 컨슈머는 자신의 오프셋을 __consumer_offset라 불리는 특별한 토픽에 커밋한다. 만약 이 토픽을 DR 클러스터로 미러링해 준다면, DR 클러스터에서 읽기 작업을 시작하는 컨슈머는 이전에 주 클러스터에 마지막으로 커밋한 오프셋으로부터 작업을 재개하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 시간 기반 장애 복구 : 컨슈머가 오전 4시 3분에 해당하는 데이터부터 처리를 재개하도록 할 수 있다. 이 옵션은 장애복구에 있어서 어느정도의 확실성을 보장해야 하는 경우를 권장한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 오프셋 변환&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;4) 장애복구가 끝난 후&lt;/b&gt; : 일관성과 순서 보장이 극도로 중요한 상황에서 가장 간단한 해법은 일단 원래 주 클러스터에 저장된 데이터와 커밋된 오프셋을 완전히 삭제한 뒤 새로운 주 클러스터에서 완전히 새것이 된 새 DR 클러스터로 미러링을 시작하는 것이다.&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;5) 클러스터 디스커버리 관련&lt;/span&gt;&lt;/b&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;스트레치 클러스터&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;스트래치 클러스터는 데이터센터 전체에 문제가 발생했을 때 카프카 클러스터에 장애가 발생하는것을 방지하기 위한 것이다. 하나의 카프카 클러스터를 여러개의 데이터 센터에 걸쳐 설치하는 것이다.&amp;nbsp;&lt;/span&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;아파치 카프카의 미러메이커&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;미러메이커는 데이터베이스가 아닌 다른 카프카 클러스터로부터 데이터를 읽어오기 위해 소스 커넥터를 사용한다. 미러메이커는 카프카의 컨슈머 그룹 관리 프로토콜을 사용하지 않고 태스크에 파티션을 균등하게 배분함으로써 새로운 토픽이나 파티션이 추가되었을때 발생하는 리벨런스로 인해 지연이 튀어오르는 상황을 방지한다. 미러메이커는 원본 클러스터의 각 파티션에 저장된 이벤틀르 대상 클러스터의 동일한 파티션으로 미러링함으로써 파티션의 의미 구조나 각 파티션 안에서의 이벤트 순서를 그대로 유지한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>PaaS/MQ</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/542</guid>
      <comments>https://armyost.tistory.com/542#entry542comment</comments>
      <pubDate>Wed, 4 Feb 2026 22:57:21 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 심화) 데이터 파이프라인 구축하기</title>
      <link>https://armyost.tistory.com/541</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;데이터 파이프라인 구축 시 고려사항&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;적시성&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;하루에 한 번 대량의 데이터를 받는 시스템이 있는 반면 데이턱 생성된 뒤 몇 밀리초 안에 받아야하는 시스템도 있다. 대부분의 데이터 파이프라인은 이 두가지 형태의 중간쯤 어딘가에 위치한다. 이러한 맥락에서 카프카를 이해하는 좋은 방법은 쓰는 쪽과 읽는 쪽 사이의 시간적 민감도에 대한 요구조건을 분리시키는 거대한 버퍼로 생각하는 것이다. 쓰는 쪽에서는 실시간으로 쓸 수 있지만, 읽는 쪽에서는 배치 단위로 읽을 수 있으며, 그 반대로 가능하다. 이것은 백프레셔(Producer가 cONSUMING 가능 이상의 데이터를 발생시킬때) 적용 역시 단순하게 해준다.&amp;nbsp;&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;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;신뢰성&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;데이터 파이프라인은 많은 경우 중요한 비즈니스 시스템에 데이터가 전달되는 통로이기도 하기 때문에, 몇 초간의 장애가 발생하는 것만으로도 전체 시스템에 큰 지장을 줄 수 있다. 데이터 유실을 허용하는 시스템도 있지만, 대부분의 경우 최소 한 번 보장을 요구하는게 보통이기 때문에 원본 시스템에서 발생한 이벤트가 모두 목적지에 도착해야 한다. '정확히 한번' 전달 보장을 요구하는 경우도 자주 볼 수 있다.&amp;nbsp;&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;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;높으면서도 조정 가능한 처리율&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;카프카가 쓰는 쪽과 읽는 쪽 사이에서 버퍼 역할을 하기 때문에 더 이상 프로듀서의 처리율과 컨슈머의 처리율을 묶어서 생각하지 않아도 된다. 카프카는 높은 처리율을 받아낼 수 있는 분산 시스템이다. 카프카 커넥트 API는 작업을 병렬화 하는데 초점을 맞추기 때문에, 시스템 요구 조건에 따라 하나의 노드에서든 수평 확장된 여러개의 노드에서든 아무 상관없이 실행될 수 있다.&amp;nbsp;&lt;/span&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;데이터 형식&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;데이터 파이프라인에서 가장 중요하게 고려해야 할 것 중 하나는 서로 다른 데이터 형식과 자료형을(type)을 적절히 사용하는 것이다. 예를 들어서 에이브로 타입을 사용해서 XML이나 관계형 데이터를 카프카에 적재한 뒤 엘라스틱서치에 쓸때는 JSON 형식으로 HDFS에 쓸때는 파케이 Parguet 형식으로 S3에 쓸때는 csv로 변환해야 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;이 레코드를 어떠한 형식으로든 저장할 수 있도록 장착 가능한 pluggable할 컨버터 역시 지원한다. 카프카의 데이터를 외부 시스템에 쓸 경우, 싱크 커넥터가 외부 시스템에 쓰여지는 데이터의 형식을 책임진다.&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; font-family: 'Nanum Gothic';&quot;&gt;카프카 커넥트는 원본 시스템의 데이터를 카프카로 옮길 때 혹은 카프카의 데이터를 대상 시스템으로 옮길 때 단위 레코드를 변환할 수 있게 해주는 단일 메시지 변환 기능을 탑재하고 있다. 조인이나 집적과 같이 더 복잡한 변환 작업은 카프카 스트림을 사용해서 처리할 수 있는데, 이것은 별도의 장에서 자세히 알아볼 것이다.&amp;nbsp;&lt;/span&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;보안&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;데이터 파이프라인의 관점에서 주로 고려해야할 점은 다음과 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 누가 카프카로 수집되는 데이터에 접근할 수 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 파이프라인을 통과하는 데이터가 암호화 되었다고 확신할 수 있는가? 이것은 여러 데이터 세ㅔ터에 걸쳐 구축된 데이터 파이프라인의 경우 특히 중요하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 누가 파이프라인을 변경할 수 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 만약 파이프라인이 접근이 제한된 곳의 데이터를 읽거나 써야 할 경우 문제없이 인증을 통과할 수 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 개인 식별 정보를 저장하고, 접근하고, 사용할 때 법과 규제를 준수하는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;데이터 전송과정에서 SASL을 사용한 인증과 인가 역시 지워한다. 따라서 토픽이 민감한 정보를 담고 있을 경우, 여기 저장되어 있는 정보가 권한이 없는 누군가에 의해 덜 안전한 시스템으로 전달될 걱정은 할 필요가 없는 것이다. 카프카는 허가 받거나 허가 받지 않은 접근 내역을 추적할 수 있는 감사 로그 역시 지원한다.&amp;nbsp;&lt;/span&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;카프카 커넥트&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;개별 메시지 변환&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;Single Message Transformation (SMT) 는 카프카 커넥트가 메시지를 복사하는 도중에 데이터 변호나 작업의 일부로써, 보통 코드를 작성할 필요없이 수행된다. 조인이나 집적을 포함하곤 하는 더 복잡한 변환의 경우 상태가 있는 카프카 스트림즈 프레임워크를 사용해야 할 것이다. 카프카 스트림즈에 대해서는 나중에 설명한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- Cast : 필드의 데이터 타입을 바꾼다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- MaskField : 특정 필드의 내용물을 null로 채운다. 민감한 정보나 개인 식별 정보를 제거할 때 유용하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- Filter : 특정한 조건에 부합하는 모든 메시지를 제외하거나 포함한다. 기본으로 제공되는 조건으로는 토픽 이름 패턴, 특정 헤더, 툼스톤 메시지 여부를 판별할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- Flatten : 중첩된 자료 구조를 편다. 각 밸류값의 경로 안에 있는 모든 필드의 이름을 이어붙인 것이 새 키값이 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- HeaderFrom : 메시지에 포함되어 있는 필드를 헤더로 이동시키거나 복사한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- InsertHeader : 각 메시지의 헤더에 정적인 문자열을 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- InsertField : 메시지에 새로운 필드를 추가해 넣는다. 오프셋과 같은 메타데이터에서 가져온 값일 수 도 있고, 정적인 값일 수 도 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- RegexRouter : 정규식과 교체할 문자열을 사용해서 목적지 토픽의 이름을 바꾼다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- ReplaceField : 메시지에 포함된 필드를 삭제하거나 이름을 변경한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- TimerstampConverter : 필드의 시간 형식을 바꾼다. 예를 들어서 유닉스 시간값을 문자열로 바꾼다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- TimestampRouter : 메시지에 포함된 타임스템프 값을 기준으로 토픽을 변경한다. 이것은 상크 커넥터에서 특히나 유용한데, 타임스캠프 기준으로 저장된 특정 테이블의 파티션에 메시지를 복사해야 할 경우, 토픽 이름만으로 목적지 시스템의 데이터세트를 찾아야 하기 때문이다.&amp;nbsp;&lt;/span&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;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;컨버터&lt;/span&gt;&lt;/b&gt;&lt;/h3&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;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;카프카 커넥트는 자료 객체를 카프카에 쓸때 사용되는 형식으로 바꿀 수 있도록 컨버터를 지원한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;커넥트가 어떻게 작동하는지를 이해하려면 3개의 기본적인 개념과 함께 이들이 어떻게 상호작용하지는 지를 알아야 한다.&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;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;1) 커넥터와 테스크&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;커넥터는 다음의 세가지 작업을 수행한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 커넥터에서 몇개의 태스크가 실행되어야 하는지 결정한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 데이터 복사 작업을 각 테스트에 어떻게 분할해 줄지 결정한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;- 워커로부터 테스크 설정을 얻어와서 테스크에 전달해준다.&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; font-family: 'Nanum Gothic';&quot;&gt;테스크는 데이터를 실제로 카프카에 넣거나 거져오는 작업을 담당한다. 모든 태스크는 워커로부터 컨텍스트를 받아서 초기화된다. 소스 컨텍스트는 소스테스크가 소스 레코드의 오프셋을 저장할 수 있게 해주는 객체를 포함한다.&amp;nbsp;&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;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;2) 워커&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;커넥터와 태스크를 실행시키는 역할을 맡는 '컨테이너' 프로세스라고 할 수 있다. 워커 프로세스는 커넥터와 그 설정을 같이하는 HTTP 요청을 처리할 뿐만 아니라, 커넥터 설정을 내부 카프카에 토픽에 저장하고, 커넥터와 태스크를 실행시키고, 여기에 적절한 설정값을 전달해주는 역할을 한다. 만약 워커 프로세스가 정지하거나 크래시 날 경우, 커넥트 클러스터 안의 다른 워커들이 이것을 감지해서 해당 워커에서 실행중이던 커넥터와 태스크를 다른 워커로 재할당한다.&amp;nbsp;&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;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;3) 컨버터 및 커넥트 데이터 모델&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;원본 시스템의 이벤트를 읽어와서 Schema, Value 순서쌍을 생성하는 것이다. 싱크 커넥터는 정확히 반대 작업을 수행한다. Schema, Value 순서쌍을 받아온뒤 Schema를 사용해서 해당 값을 파싱하고, 대상 시스템에 쓰는 것이다.&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; font-family: 'Nanum Gothic';&quot;&gt;워커는 설정된 컨버터를 사용해서 이 레코드를 Avro객체나 JSON 객체, 혹은 문자열로 변환한뒤 카프카에 쓴다. 싱크 커넥터에서는 정확히 반대 반향의 처리과정을 거친다. 커넥트 워커는 카프카로부터 레코드를 읽어온뒤, 설정된 컨버터를 사용해서 읽어온 레코드를 카프카에 저장된 형식 커넥트 데이터 API레코드로 변환한다. 이러헥 변환된 데이터는 다시 싱크 커넥터에 전달되어 최종적으로 대상 시스템에 쓰여진다.&amp;nbsp;&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;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;4) 오프셋 관리&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;소스 커넥터의 경우, 커넥터가 커넥트 워커에 리턴하는 레코드에는 논리적인 파티션과 오프셋이 포함된다. 이것은 카프카의 파티션과 오프셋이 아니라 원본 시스템에서 필요로하는 파티션과 오프셋이다. 예를 들어 파일 소스의 경우, 파일이 파티션 역할을, 파일안의 줄 혹은 문자 위치가 오프셋 역할을 할 수 있다. 워커는 이 레코드를 카프카 브로커로 보낸다. 만약 보로커가 해당 레코드를 성공적으로 쓴 뒤 해당 요청에 대한 응답을 보내면, 그제서야 워커는 방금전 카프카로 보낸 레코드에 대한 오프셋을 저장한다. 이러헥 함으로써 커넥터는 재시작 혹은 크래시 발생 후에도 마지막으로 저장되었던 오프셋에서부터 이벤트 처리를 시작할 수 있다. 싱크 커넥터는 비슷한 과정을 정반대 순서로 실행한다. 토픽, 파티션, 오프셋 식별자가 이미 포함되어 있는 카프카 레코드를 읽은뒤 커넥터의 put() 메서드를 호출해서 이 레코드를 대상 시스템에 저장한다. 작업이 성공하면 싱크 커넥터는 커넥터에 주어졌던 오프셋을 카프카에 커밋한다. 평범한 컨슈머 에플리키이션이 하는 것과 완전히 동일한 방법으로 말이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>PaaS/MQ</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/541</guid>
      <comments>https://armyost.tistory.com/541#entry541comment</comments>
      <pubDate>Mon, 2 Feb 2026 00:31:12 +0900</pubDate>
    </item>
    <item>
      <title>Backstage) Relation - Catalog Graph 의 가시성 향상시키기</title>
      <link>https://armyost.tistory.com/540</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 기존의 Catalog Graph 인데, 모든 Kind가 같은 색상으로 아이콘으로 구분되다 보니 가시성이 떨어진다.&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;1280&quot; data-origin-height=&quot;893&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tIRrt/dJMcaaqBesP/wwetupKQVoqbu3rZmz3J7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tIRrt/dJMcaaqBesP/wwetupKQVoqbu3rZmz3J7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tIRrt/dJMcaaqBesP/wwetupKQVoqbu3rZmz3J7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtIRrt%2FdJMcaaqBesP%2FwwetupKQVoqbu3rZmz3J7K%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;1280&quot; height=&quot;893&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;893&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;Custom UI 를 사용하여 가시성을 향상시킬수 있다. 설명은 공식적으로 여기에 소개되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/backstage/backstage/blob/master/plugins/catalog-graph/README.md#customizing-the-ui&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/backstage/backstage/blob/master/plugins/catalog-graph/README.md#customizing-the-ui&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769723017145&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;backstage/plugins/catalog-graph/README.md at master &amp;middot; backstage/backstage&quot; data-og-description=&quot;Backstage is an open framework for building developer portals - backstage/backstage&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/backstage/backstage/blob/master/plugins/catalog-graph/README.md#customizing-the-ui&quot; data-og-url=&quot;https://github.com/backstage/backstage/blob/master/plugins/catalog-graph/README.md&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bl00mE/dJMb9g45WuR/Z8kV2rFyRjkkodtVSskA71/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bkprBw/dJMb9eTKrbc/0SgKScsTzEkbZ7cu0mUTP1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/backstage/backstage/blob/master/plugins/catalog-graph/README.md#customizing-the-ui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/backstage/backstage/blob/master/plugins/catalog-graph/README.md#customizing-the-ui&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bl00mE/dJMb9g45WuR/Z8kV2rFyRjkkodtVSskA71/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bkprBw/dJMb9eTKrbc/0SgKScsTzEkbZ7cu0mUTP1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;backstage/plugins/catalog-graph/README.md at master &amp;middot; backstage/backstage&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Backstage is an open framework for building developer portals - backstage/backstage&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;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;우선 CustomUI Rendering Component를 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;packages/app/src/components/catalog/CustomRenderNode.tsx&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769723132596&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useLayoutEffect, useRef, useState } from 'react';
import { DependencyGraphTypes } from '@backstage/core-components';
import { makeStyles } from '@material-ui/core/styles';
import classNames from 'classnames';

const useStyles = makeStyles(_theme =&amp;gt; ({
  node: {
    stroke: '#000',
    strokeWidth: 2,
    '&amp;amp;.focused': {
      strokeWidth: 3,
    },
    '&amp;amp;.clickable': {
      cursor: 'pointer',
    },
    // Entity Kind specific styles
    '&amp;amp;.system': {
      fill: '#F5DC70',
      stroke: '#F2CE34',
    },
    '&amp;amp;.domain': {
      fill: '#F5DC70',
      stroke: '#F2CE34',
    },
    '&amp;amp;.component': {
      fill: '#85E1FF',
      stroke: '#2196F3',
    },
    '&amp;amp;.service': {
      fill: '#85E1FF',
      stroke: '#2196F3',
    },
    '&amp;amp;.api': {
      fill: '#98FB98',
      stroke: '#4CAF50',
    },
    '&amp;amp;.group': {
      fill: '#FFB366',
      stroke: '#FF9800',
    },
    '&amp;amp;.user': {
      fill: '#E1BEE7',
      stroke: '#9C27B0',
    },
    '&amp;amp;.resource': {
      fill: '#FFCCBC',
      stroke: '#FF6E40',
    },
    '&amp;amp;.template': {
      fill: '#B2DFDB',
      stroke: '#009688',
    },
    '&amp;amp;.unknown': {
      fill: '#BDBDBD',
      stroke: '#757575',
    },
  },
  text: {
    fontSize: 12,
    fontFamily: 'Arial, sans-serif',
    fill: '#000',
    pointerEvents: 'none',
    '&amp;amp;.focused': {
      fontWeight: 'bold',
    },
  },
  clickable: {
    cursor: 'pointer',
  },
}));

export const CustomRenderNode = (
  props: DependencyGraphTypes.RenderNodeProps&amp;lt;any&amp;gt;,
) =&amp;gt; {
  const classes = useStyles();
  const { node } = props;
  const { id } = node;
  
  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);
  const idRef = useRef&amp;lt;SVGTextElement | null&amp;gt;(null);

  useLayoutEffect(() =&amp;gt; {
    // set the width to the length of the ID
    if (idRef.current) {
      let { height: renderedHeight, width: renderedWidth } =
        idRef.current.getBBox();
      renderedHeight = Math.round(renderedHeight);
      renderedWidth = Math.round(renderedWidth);

      if (renderedHeight !== height || renderedWidth !== width) {
        setWidth(renderedWidth);
        setHeight(renderedHeight);
      }
    }
  }, [width, height]);

  const padding = 10;
  const paddedWidth = width + padding * 2;
  const paddedHeight = height + padding * 2;
  
  // Extract kind from node ID (format: &quot;kind:namespace/name&quot;)
  const kind = id.split(':')[0]?.toLowerCase();

  return (
    &amp;lt;g 
      onClick={node.onClick} 
      className={classNames(node.onClick &amp;amp;&amp;amp; classes.clickable)}
    &amp;gt;
      &amp;lt;rect
        className={classNames(
          classes.node,
          kind,
          node.focused &amp;amp;&amp;amp; 'focused',
        )}
        width={paddedWidth}
        height={paddedHeight}
        rx={4}
        ry={4}
      /&amp;gt;
      &amp;lt;text
        ref={idRef}
        className={classNames(classes.text, node.focused &amp;amp;&amp;amp; 'focused')}
        y={paddedHeight / 2}
        x={paddedWidth / 2}
        textAnchor=&quot;middle&quot;
        alignmentBaseline=&quot;middle&quot;
      &amp;gt;
        {id}
      &amp;lt;/text&amp;gt;
    &amp;lt;/g&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;그리고 정의한 Component를 필요한 곳에. import한다. 그리고 이렇게 사용한다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1f1f1f; color: #cccccc;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;&amp;nbsp; &amp;nbsp; renderNode&lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;CustomRenderNode&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;}&lt;/span&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;우선 &quot;/catalog-graph&quot; route를 정의한 곳에 적용해보자&lt;/p&gt;
&lt;div style=&quot;background-color: #1f1f1f; color: #cccccc;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;packages/app/src/App.tsx&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769723343421&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { CustomRenderNode } from './components/catalog/CustomRenderNode';

...

    &amp;lt;Route path=&quot;/catalog-graph&quot; element={&amp;lt;CatalogGraphPage renderNode={CustomRenderNode} /&amp;gt;} /&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 EntityPage에도 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;packages/app/src/components/catalog/EntityPage.tsx&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769723605660&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { CustomRenderNode } from './CustomRenderNode';
...
const overviewContent = (
  &amp;lt;Grid container spacing={3} alignItems=&quot;stretch&quot;&amp;gt;
    ...
    &amp;lt;Grid item md={6} xs={12}&amp;gt;
      &amp;lt;EntityCatalogGraphCard variant=&quot;gridItem&quot; renderNode={CustomRenderNode} height={400} /&amp;gt;
    &amp;lt;/Grid&amp;gt;
    ...
  &amp;lt;/Grid&amp;gt;
);
...
const systemPage = (
  &amp;lt;EntityLayout&amp;gt;
    &amp;lt;EntityLayout.Route path=&quot;/diagram&quot; title=&quot;Diagram&quot;&amp;gt;
      &amp;lt;EntityCatalogGraphCard
        variant=&quot;gridItem&quot;
        renderNode={CustomRenderNode}
        direction={Direction.TOP_BOTTOM}
        title=&quot;System Diagram&quot;
        height={700}
        relations={[
          RELATION_PART_OF,
          RELATION_HAS_PART,
          RELATION_API_CONSUMED_BY,
          RELATION_API_PROVIDED_BY,
          RELATION_CONSUMES_API,
          RELATION_PROVIDES_API,
          RELATION_DEPENDENCY_OF,
          RELATION_DEPENDS_ON,
        ]}
        unidirectional={false}
      /&amp;gt;
    &amp;lt;/EntityLayout.Route&amp;gt;
  &amp;lt;/EntityLayout&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Full SourceCode는 여기서 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/armyost/backstage-armyost&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/armyost/backstage-armyost&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769723939742&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;GitHub - armyost/backstage-armyost&quot; data-og-description=&quot;Contribute to armyost/backstage-armyost development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/armyost/backstage-armyost&quot; data-og-url=&quot;https://github.com/armyost/backstage-armyost&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2kfgW/dJMb9lL6o3k/RtNq9O5upFbtrR7nzC7RH0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bjLsx2/dJMb9jgrZYR/zHvLYwThhiVHBvcT3JBQK0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/armyost/backstage-armyost&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/armyost/backstage-armyost&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2kfgW/dJMb9lL6o3k/RtNq9O5upFbtrR7nzC7RH0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bjLsx2/dJMb9jgrZYR/zHvLYwThhiVHBvcT3JBQK0/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;GitHub - armyost/backstage-armyost&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to armyost/backstage-armyost 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;p data-ke-size=&quot;size16&quot;&gt;이렇게 CustomUI가 씌워졌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2zyWg/dJMcabJPIhA/XcITU1KFyykzeai7AS40q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2zyWg/dJMcabJPIhA/XcITU1KFyykzeai7AS40q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2zyWg/dJMcabJPIhA/XcITU1KFyykzeai7AS40q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2zyWg%2FdJMcabJPIhA%2FXcITU1KFyykzeai7AS40q0%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;853&quot; height=&quot;508&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Programming/MSA</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/540</guid>
      <comments>https://armyost.tistory.com/540#entry540comment</comments>
      <pubDate>Fri, 30 Jan 2026 06:59:27 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 심화) '정확히 한 번' 의미 구조</title>
      <link>https://armyost.tistory.com/539</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이전 챕터에서 신뢰성 보장을 제어할 수 있게 해주는 설정 매개변수들과 모범사례를 보았다. 여기서 우리는 '최소 한번' 전달에 초점을 맞췄다.&amp;nbsp;&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;멱등적 프로듀서&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;간혹 재시도는 메시지 중복을 발생시킨다. 가령 다음과 같은 시나리오에서..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 파티션 리더가 프로듀서로부터 레코드를 받아서 팔로워들에게 성공적으로 복제한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 프로듀서에게 응답을 보내기 전, 파티션 리더가 있는 브로커에 크래시가 발생한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 프로듀서 입장에서는 응답을 받지 못한 채 타임아웃이 발생하고, 메시지를 재전송한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 재전송된 메시지가 새 리더에 도착한다. 하지만 이 메시지는 이미 저장되어 있다.&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;font-family: 'Nanum Gothic';&quot;&gt;이러한 케이스를 대응하기 위해서 우리는 멱등적 프로듀서를 사용한다.&amp;nbsp;&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: 'Nanum Gothic';&quot;&gt;멱등적 프로듀서의 작동 원리&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;멱등적 프로듀서 기능을 켜면 모든 메시지는 고유한 프로듀서 ID와 시퀀스 넘버를 가지게 된다. 해당 브로커에 할당된 모든 파티션들에 쓰여진 마지막 5개 메시지들을 추적하기 위해 이 고유 식별자를 사용한다. 파티션별로 추적되어야 하는 시퀀스 넘버의 수를 제한하고 싶다면 프로듀서의 max.in.flights.requests.per.connection 설정값이 5 이하로 잡혀 있어야한다. (기본값 5)&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: 'Nanum Gothic';&quot;&gt;브로커가 예전에 받은 적이 있는 메시지를 받게 될 경우, 적절한 에러를 발생시킴으로써 중복 메시지를 거부한다.&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;font-family: 'Nanum Gothic';&quot;&gt;과연 멱등적 프로듀서가 어떻게 처리하는지를 생각해보는 것은 의미가 있다. 프로듀서 재시작과 브로커 장애, 두 경우를 생각해보자.&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1) 프로듀서 재시작 Case&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로듀서에 장애가 발생할 경우, 보통 새 프로듀서를 생성해서 장애가 난 프로듀서를 대체한다. 여기서 중요한 점은 프로듀서가 시작될 때 멱등적 프로듀서 기능이 켜져 있을 경우, 프로듀서는 초기화 과정에서 카프카 브로커로부터 프로듀서 ID를 생성받는 다는 점이다. 트랜젝션 기능을 켜지 않았을 경우, 프로듀서를 초기화할 때마다 완전히 새로운 ID가 생성된다. 즉, 프로듀서에 장애가 발생해서 대신 투입된 새 프로듀서가 기존 프로듀서가 이미 전송한 메시지를 다시 전송할 경우, 브로커는 메시지에 중복이 발생했음을 알아차리지 못한다.&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;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2) 브로커 장애 Case&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 브로커 3 입장에서, 어느 시퀀스 넘버까지 쓰여졌는지 어떻게 알고 중복 메시지를 걸러내는가? 리더는 새 메시지가 쓰여질 때마다 인-메모리 프로듀서 상태에 저장된 최근 5개의 시퀀스 넘버를 업데이트한다. 팔로워 레플리카는 리더로부터 새로운 메시지를 복제할 때마다 자체적인 인-메모리 버퍼를 업데이트 한다. 하지만, 여기서 예전 리더가 다시 돌아온다면 어떤 일이 벌어질까? 브로커는 종료되거나 새 세그먼트가 생성될 때마다 프로듀서 상태에 대한 스냅샷을 파일 형태로 저장한다. 브로커가 시작되면 일단 파일에서 최신 상태를 읽어온다. 그러고 나서 현재 리더로부터 복제한 레코드를 사용해서 프로듀서 상태를 업데이트함으로써 최신 상태를 복구한다. 그래서 이 브로커가 다시 리더를 맡을 준비가 될 시점에는 최신 시퀀스 넘버를 가지고 있게 한다.&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: 'Nanum Gothic';&quot;&gt;멱등적 프로듀서의 한계&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;카프카의 멱등적 프로듀서는 프로듀서의 내부 로직으로 인한 재시도가 발생할 경우 생기는 중복만을 방지한다. 동일한 메시지를 가지고 producer,send()를 두번 호출하면 멱등적 프로듀서가 개입하지 않는 만큼 중복된 메시지가 생기게 된다. 프로듀서 입장에서는 전송된 레코드 두개가 실제로는 동일한 레코드인지 확인할 방법이 없기 때문이다. 프로듀서 예외를 잡아서 애플리케이션이 직접 재시도 하는 것보다는 프로듀서에 탑재된 재시도 메커니즘을 사용하는 것이 언제나 더 낫다. 멱등적 프로듀서는 이 패턴을 더 편리하게 만들어 준다.&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: 'Nanum Gothic';&quot;&gt;멱등적 프로듀서 사용법&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 부분은 쉽다. 프로듀서 설정에 enable.idempotence=true 를 추가해주면 끝이다. 만약 프로듀서에 acks=all 설정이 이미 잡혀 있다면, 성능에는 차이가 없을 것이다. 멱등적 프로듀서 기능을 활성화 시키면 다음과 같은 것들이 바뀐다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로듀서 ID를 받아오기 위해 프로듀서 시동 과정에서 API를 하나 더 호출한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전송되는 각각의 레코드 배치에는 프로듀서 ID와 배치 내 첫 메시지의 시퀀스 넘버가 포함된다. 이 새 필드들은 각 메시지 배치에 96 비트를 추가한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;브로커들은 모든 프로듀서 인스턴스에서 들어온 레코드 배치의 시퀀스 넘버를 집중해서 메시지 중복을 방지한다.&amp;nbsp;&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: 'Nanum Gothic';&quot;&gt;트랜잭션 활용 사례&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;금융 애플리케이션은 '정확히 한번' 기능이 정확한 직접 결과를 보장하는데 쓰이는 복잡한 스트림 처리 애플리케이션의 전형적인 예다. 하지만, 카프카 스트림즈 애플리케이션이 '정확히 한 번' 보장을 제공하도록 설정하는 것이 상당히 단순한 만큼 챗본과 같이 더 흔한 활용 사례에서도 이 기능이 활용되는 것을 볼 수 있다.&amp;nbsp;&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: 'Nanum Gothic';&quot;&gt;트랜젝션이 해결하는 문제&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1) 애플리케이션 크래시로 인한 재처리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) 좀비 애플리케이션에 의해 발생하는 재처리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 애플리케이션이 카프카로부터 레코드 배치를 읽어온 직후 뭔가를 하기 전에 멈추거나, 카프카로의 연결이 끊어진다면 어떻게 될까? 앞에서 살펴본 상황과 비슷하게, 하트비트가 끊어지면서 애플리케이션은 죽은 것으로 간주될 것이며, 해당 컨슈머에 할당되어 있던 파티션들은 컨슈머 그룹 내 다른 컨슈머들에게 재할당될 것이다. 파티션을 재할당 받은 컨슈머가 레코드 배치를 다시 읽어서 처리하고, 출력 토픽에 결과를 쓰고 작업을 계속한다. 그 사이, 멈췄던 애플리케이션의 첫번재 인스턴스가 다시 작동할 수 있다.&amp;nbsp;&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: 'Nanum Gothic';&quot;&gt;트랜젝션은 어떻게 '정확히 한번'을 보장하는가?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;카프카 트랜젝션은 원자적 다수 파티션 쓰기 기능을 도입했다. 트랜젝션을 사용해서 워자적 다수 파티션 쓰기를 수행하려면 트랜잭션적 프로듀서를 사용해야한다. 카프카 브로커는 transactional.id에서 producer.id로의 대응 관계를 유지하고 있다가 만약 이미 있는 transactional.id 프로듀서가 initTransactions()를 다시 호출하면 새로운 랜덤 값이 아닌 이전에 쓰던 producer.id값을 할당해준다.&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: 'Nanum Gothic';&quot;&gt;한 클러스터에서 다른 클러스터로 데이터 복제&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 이것이 트랜젝션의 운자성을 보장하지는 않는다. 만약 애플리케이션이 여러개의 레코드와 오프셋을 트랜젝션으로 쓰고, 미러메이커 2.0이 이 레코드들을 다른 카프카 클러스터에 복사한다면 복사 과정에서 트랜젝션 속성이나 보장같은 것은 유실된다. 데이터를 읽어왔는지 알 수 도 없고 보장할 수도 없는 것이다.&amp;nbsp;&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: 'Nanum Gothic';&quot;&gt;트랜젝션 사용법&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;트랜젝션은 브로커 기능이다. 트랜젝션 기능을 사용하는 가장 일반적이고도 권장되는 방법은 카프카 스트림즈에서 exactly-once 보장을 활성화 하는 것이다.&amp;nbsp;&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: 'Nanum Gothic';&quot;&gt;트랜젝션 ID와 펜싱&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로듀서가 사용할 트랜젝션 ID를 선택하는 것은 중요한 뿐 아니라 보기보다 조금 더 어려운 일이다. 트랜젝션 ID를 잘못 할당해 줄 경우 애플리케이션에 에러가 발생하거나 '정확히 한번' 보장을 준수할 수 없게 될 수 도 있다.&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;font-family: 'Nanum Gothic';&quot;&gt;버전 2.5까지, 펜싱을 보장하는 유링방 방법은 트랜젝션 ID를 파티션에 정적으로 대응시켜 보는 것 뿐이었다. 아파치 카프카 2.5에서 소개된 KIP-447 은 펜싱을 수행하는 두번째 방법, 즉 트랜젝션 ID와 컨슈머 그룹 메타데이터를 함께 사용하는 펜싱을 도입하였다.&amp;nbsp;&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: 'Nanum Gothic';&quot;&gt;트랜잭션 성능&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;트랜젝션 초기화와 커밋 요청은 동기적으로 작동하기 때문에 성공적으로 완료되거나, 실패하거나, 타임아웃되거나 할 때까지 어떤 데이터도 전송되지 않는다. 그렇기 때문에 오버헤드는 더 증가한다. 프로듀서에 있어서 트랜젝션 오버헤드는 트랜젝션에 포함된 메시지의 수와는 무관하다는 점을 명심하라.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>PaaS/MQ</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/539</guid>
      <comments>https://armyost.tistory.com/539#entry539comment</comments>
      <pubDate>Mon, 26 Jan 2026 23:47:23 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 심화) 신뢰성 있는 데이터 전달</title>
      <link>https://armyost.tistory.com/538</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;브로커 설정&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;복제 팩터&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;토픽 단위 설정은 replication.factor에, 자동으로 생성되는 토픽들에 적용되는 브로커 단위 설정은 default.replication.factor 설정에 잡아준다.&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이책에서 지금까지는 토픽의 복제 팩터가 3이라고 가정하였다. 이는 각 브로커가 3대의 서로 다른 브로커에 3개 복제된다는 것을 의미한다. 이엇은 합리적인 가정이고 또 카프카의 기본값이기다 하지만, 이설정은 사용자가 고칠수 있는 것이다. 그렇다면 토픽에 몇개의 레플리카가 적절한지 어떻게 결정할 수 있을까? 몇가지의 핵심 고려사항이 있다.&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;font-family: 'Nanum Gothic'; 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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;- 지속성&lt;/b&gt; : 각 레플리카는 파티션 안의 모든 데이터의 복사본이다. 만약 파티션에 레플리카가 하나뿐이고 어떠한 이유에서 디스크가 사용 불가능하게 될 경우 해당 파티션의 모든 데이터는 유실된다. 복사본이 많을 수록 데이터가 유실될 가능성은 줄어든다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;- 처리량&lt;/b&gt; : 레플리카가 추가될 때마다 브로커간 트래픽 역시 늘어난다. 만약 우리가 특정 파티션에 10Mbps 의 속도로 쓰는데 레플리카가 하나뿐이라면 복제 트래픽은 하나도 없을 것이다. 하지만 레플리카가 2개라면 복제 트래픽은 10Mbps가 되고 , 3개라면 20Mbps가 되고, 5개라면 40Mbps가 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;- 종단지연&lt;/b&gt; : 쓰여진 메시지를 컨슈머가 읽을 수 있으려면 모든 인-싱크 레플리카에 복제되어야 한다. 이론적으로, 레플리카 수가 더 많을 수록 이들중 하나가 느려짐으로써 컨슈머까지 함께 느려질 가능성은 높아진다. 실제로는 어떤 이유에서건 특정한 브로커가 느려지면 해당 브로커를 사용하는 모든 클라이언트 역시 복제 팩터에 무관하게 느려질 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;- 비용&lt;/b&gt; : 중요하지 않은 데이터에 대해 복제 팩터를 3 미만으로 잡아주는 가장 일반적인 이유는 바로 이것이다. 더 많은 레플리카를 가질수록 저장소와 네트워크에 들어가는 비용역시 증가한다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;언클린 리더 선출&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&amp;nbsp;unclean.leader.election.enable 설정의 기본값은 false로 잡혀 있다. 즉, 아웃-오브-싱크 레플리카는 리더가 될 수 없도록 되어 있는 것이다. 데이터 유실에 있어 가장 좋은 보장을 제공하는 만큼 이것은 가장 안전한 옵션이다. 즉, 우리가 앞에서 설명한 것과 같은 극단적인 불응 상황에서 일부 파티션들은 수동으로 복구될때까지 사용이 불가능한 상태로 남게 된다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;최소 인-싱크 레플리카&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;만약 토픽에 레플리카가 3개 있고 min.insync.replicas를 2로 잡아 줬다면 프로듀서들은 3개의 레플리카 중 최소 2개가 인-싱크 상태인 파티션에만 쓸수 있다. 만약 3개 레플리카 모두가 인-싱크 상태라면, 모든 것이 정상적으로 작동한다. 이것은 세 개 중 하나의 레플리카가 작동 불능에 빠져도 마찬가지다. 하지만 만약 세 개 중 두개의 레플리카가 작동 불능에 빠질 경우, 브로커는 더이상 쓰기 요청을 받을 수 없을 것이다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;레플리카를 인-싱크 상태로 유지하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;만약 레플리카가 replica.lag.time.max.ms 에 설정된 값보다 더 오랫동안 리더로부터 데이터를 읽어 오지 못하거나, 리더에 쓰여진 최신 메시지를 따라잡지 못하는 경우 동기화가 풀린상태 즉 아웃-오브-싱크 상태가 된다.&amp;nbsp;&lt;/span&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;신뢰성 있는 시스템에서 프로듀서 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;토픽별로 3개의 레플리카를 가지도록 브로커를 설정한 상태에서 언클린 리더 선출 기능을 끈다. 이렇게 되면 카프카 클러스터에 커밋된 메시지는 유실되지 않는다. 하지만 프로듀서가 메시지를 보낼때 acks=1 설정으로 보내도록 설정한다. 프로듀서에서 메시지를 전송해서 리더에는 쓰여졌지만, 아직 인-싱크 레플리카에 반영되지는 않은 상태다. 리더가 프로듀서에게 &quot;메시지가 성공적으로 쓰여졌다&quot;라고 응답을 보낸 직후 크래시가 나서 데이터가 레플리카로 복제되지 않는다. 다른 레플리카들은 여전히 인-싱크 상태로 간주되기 때문에 그중 하나가 리더가 될 것이다. 어떤 컨슈머도 이 메시지를 보지 못한 만큼 시스템의 일관성은 유지된다. 하지만 프로듀서의 입장에서 보면 메시지는 유실된 것이다.&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;카프카에 메시지를 쓰는 애플리케이션을 개발하는 모든 이들이 신경써야 할 중요한 것이 두개가 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;i&gt;- 신뢰성 요구 조건에 맞는 올바른 acks 설정을 사용한다.&amp;nbsp;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;i&gt;- 설정과 코드 모두에서 에러를 올바르게 처리한다.&lt;/i&gt;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;응답 보내기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;프로듀서는 아래 세가지 응답 모드 중 하나를 선택할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- acks=0&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;프로듀서가 네트워크로 메시지를 전송한 시점에서 메시지가 카프카에 성공적으로 쓰여진 것으로 간주된다. 파티션이 오프라인이거나, 리더 선출이 진행중이거나 심지어 전체 카프카 클러스터가 작동 불능인 경우 에러가 발생하지 않을 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- acks=1&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이것은 리더가 메시지를 받아서 파티션 데이터 파일에 쓴 직후 응답 또는 에러를 보낸다는 것을 의미한다. 클라이언트로 응답이 간 상태에서 미처 팔로워로 복제가 완료되기 전에 리더가 정지하거나 크래시 날 경우 데이터가 유실될 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- acks=all&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이것은 리더가 모든 인-싱크 레플리카가 메시지를 받아갈 때가지 기다렸다가 응답하거나 에러를 보낸다는 것을 의미한다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;프로듀서 재시도 설정하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;프로듀서의 에러처리는 두 부분으로 나누어 지는데, 바로 프로듀서가 자동으로 처리해주는 에러와 프로듀서 라이브러리를 사용하는 개발자들이 처리해야 하는 에러다.&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;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;에러 코드는 두 부류로 나뉘어 진다. 즉, 전송을 재시도 하면 해결될 수 있는것과 아닌 것이다. 예를 들어서, 브로커가 LEADER_NOT_AVAILABLE 에러코드를 리턴할 경우, 프로듀서는 전송을 재시도할 수 있다. 그러니까 아마도 새로운 브로커가 리더로 선출된 상활일 것이며 두번째 시도는 성공할 것이다. 즉 LEADER_NOT_AVAILABLE은 재시도 가능한 에러인 것이다. 반대로, 만약 브로커가 INVALID_CONFIG 예외를 리턴할 경우, 같은 메시지를 재전송한다고 해서 설정이 변경되지는 않는다. 이것이 재시도 불가능한 레어의 한 예이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;일반적으로 메시지가 유되지 않는 것이 목표인 경우, 가장 좋은 방법은 재시도 가능한 에러가 발생핬을 경우 프로듀서가 계속해서 메시지 전송을 재시도 하도록 설정하는 것이다. 그리고 재시도에 관한 한 가장 좋은 방법은 3장에서 권장한 것과 같이 재시도 수를 기본 설정값으로 내버려 두고 메시지 전송을 포기할 때까지 대기할수 있는 시간을 지정하는 delivery.time.ms 설정값을 최대로 잡아주는 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;전송 실패한 메시지를 재시도하는 것은 두 메시지가 모두 브로커에 성공적으로 쓰여지는, 결과적으로 메시지가 중복도리 위험을 내포한다. 재시도와 주의 깊은 에러 처리는 각 메시지가 '최소 한 번' 저장되도록 보장할 수 는 있지만, '정확히 한번'은 보장할 수 없다. enable.idempotence=true 설정을 잡아줌으로써 프로듀서가 추가적인 정보를 레코드에 포함할수 있도록, 그리고 이를 활용해서 브로커가 재시도로 인해 중복된 메시지를 건너 뛸 수 있도록 할 수 있다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;추가적인 에러처리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;프로듀서에 탑재된 재시도 기능을 사용하는 것은 메시지 유실없이 다양한 종류의 에러를 올바르게 처리할 수 있는 쉬운 방법이다. 하지만, 개발자 입장에서는 다른 종류의 에러들 역시 처리할 수 있어야 한다. 여기에는 다음과 같은 것들이 포함된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 메시지 크기에 관련되었거나 인가 관련 에러와 같이 재시도가 불가능한 브로커 에러&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 메시지가 브로커에 전송되기 전에 발생한 에러&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 프로듀서가 모든 재전송 시도를 소진했거나 , 재시도 과정에서 프로듀서가 사용하는 가용 메모리가 메시지로 가득차서 발생하는 에러&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; 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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;신뢰성 있는 시스템에서 컨슈머 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;특정 카프카 컨슈머가 작동을 정지하면, 또 다른 컨슈머 입장에서는 어디서부터 작업을 재개해야할지 알아야할 필요가 있다. 컨슈머가 읽어온 오프셋을 '커밋'해야 하는 이유가 여기에 있다. 읽고 있는 각 파티션에대해 어디까지 읽었는지를 저장해 둬야 해당 컨슈머나 다른 컨슈머가 재시작한 뒤에도 어디서부터 작업을 계속해야 할지 알수 있기 때문이다. 컨슈머가 메시지를 누락할 수 있는 경우는 대게 읽기는 했지만 아직 처리는 완료되지 않은 이벤트들의 오프셋을 커밋하는 경우다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;신뢰성 있는 처리를 위해 중요한 컨슈머 설정&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;우리가 원하는 수준의 신뢰성을 갖는 컨슈머를 설정하기 위해 알아두어야 하는 컨슈머 속성은 다음과 같이 네 개가 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- &lt;b&gt;group.id&lt;/b&gt; : 같은 그룹 ID를 갖는 두개의 컨슈머가 같은 토픽을 구독할 경우, 각각의 컨슈머에는 해당 토픽 전체 파티션의 서로 다른 부분집합이 할당되므로 각각은 서로 다른 부분의 메시지만을 읽게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;-&lt;b&gt; auto.offset.reset&lt;/b&gt; : earliest를 사용한다면, 유효한 오프셋이 없는 한 컨슈머는 파티션의 맨 앞에서부터 읽기를 시작하게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- &lt;b&gt;enable.auto.commit :&lt;/b&gt; 오프셋을 커밋하게 할 것인가, 아니면 코드에서 직접 오프셋을 커밋하게 할 것인가? 자동 오프셋 커밋의 주된 이점은 애플리케이션에서 컨슈머를 사용할 때 걱정거리가 한개 준다는 점이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- &lt;b&gt;auto.commit.interval.ms&lt;/b&gt;는 5초마다 커밋하는 것이 기본값이다. 일반적으로 자주 커밋할 수 록 오버헤드 역시 늘어나지만 컨슈머가 정지햇을 때 발생할 수 있는 중복의 수는 줄어든다.&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;컨슈머에서 명시적으로 오프셋 커밋하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;더 세밀한 제어가 필요해서 오프셋 커밋을 직접 수행하기로 했다면, 이러한 결정이 정확성과 성느에 미치는 영향에도 신경을 써야한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 메시지 처리 먼저, 오프셋 커밋은 나중에&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;만약 풀링 루프에서 모든 처리를 하고 루프 사이의 상태는 저장하지 않는다면, 이것은 쉽다. 자동 오프셋 커밋 설정을 사용하거나 폴링 루프틔 끝에서 오프셋을 커밋하거나 아니면 루프안에서 일정한 주기로 오프셋을 커밋함으로써 오버헤드와 중복 처리 회피 사이의 요구 조건의 균형을 맞찬다던가 하면 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;-&lt;b&gt; 커밋 빈도는 성능과 크래시 발생시 중복 개수 사이의 트레이드 오프다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;커밋 작업은 상당한 성능 오버헤드를 수반한다. 이것은 acks=all 설정과 함께 쓰기 작업을 수행하는 것과 비슷한데, 다만 특정 컨슈머 그룹의 모든 오프셋 커밋이 동일한 브로커로 간다는 점이 다르다. 커밋 주기는 성능과 중복 발생의 요구조건 사이에서 균형을 맞춰야 한다. 메시지를 읽어올 때마다 커밋하는 방식은 매우 낮은 빈도로 메시지가 들어오는 토픽에나 사용할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 정확한 시점에 정확한 오프셋을 커밋하자&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;언제나 처리가 완료된 메시지의 오프셋을 커밋하는 것이 중요하다는 걸 기억하자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 리벨런스&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;할당된 파티션이 해제되기 전에 오프셋을 커밋하고, 새로운 파티션이 할당되었을때 애플리케이션이 보유하고 있던 상태를 삭제해주는 작업을 포기한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 컨슈머는 재시도를 해야할 수 도 있다.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;만약 레코드 #30 처리에 실패한 상태에서 #31 처리에 성공할 경우 #31의 오프셋을 커밋하면 안된다. 이것은 #30을 포함한 #31까지의 모든 레코드를 처리했다고 표시하는 것과 같은 결과를 초래하는데, 이는 우리가 원하는 것이 아닐 것이다.&amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;설정 검증하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;org.apache.kafka.tools 패키지에는 VerifiableProducer, VerifiableConsumer 클래스가 포함되어 있는데, 이들 각각은 명령줄 툴 형태로든 자동화된 테스팅 프레임워크에 포함된 형태로든 실행이 가능하다. 검증용 프로듀서에는 일반적인 프로듀서와 똑같이 acks.retries.delivery.timeout.ms 등의 설정값을 잡아줄 수 있을 뿐더러 메시지를 쓰는 속도 역시 정해줄 수 있다. 그리고 나서 검증용 프로듀서를 실행시키면 브로커에 전송된 각 메시지마다 성공 혹은 에러를 출력한다.&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;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;애플리케이션 검증하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;다양한 장애 상황에 대해 테스트를 수행할 것을 권장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 클라이언트가 브로커 중 하나와 연결이 끊어짐&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 클라이언트와 브로커 사이의 긴 지연&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 디스크 꽉 참&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 디스크 멈춤&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 리더 선출&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 브로커 롤링 재시작&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 컨슈머 롤릴 재시작&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; 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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;프로덕션 환경에서 신뢰성 모니터링 하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;그렇다고 해서 데이터가 예상한 대로 흐르고 있는지 프로덕션 시스템을 지속적으로 모니터링 하는 것을 그냥 넘어가서는 안된다. 이벤트 전송 도중에 발생하는 프로듀서 에러 포그 역시 모니터링 해주어야 한다. 남은 재시도 횟수가 0인 이벤트가 보인다면, 프로듀서에서 재시도 횟수가 고갈된 것이다. 컨슈머 쪽에서 가장 중요한 지표는 컨슈머 렉 이다. 이 지표는 컨슈머가 브로커 내 파티션에 커밋된 가장 최신 메시지에서 얼마나 뒤떨어져 있는지를 가리킨다. 랙은 계속해서 약간 오르락 내리락 하게 되어있다. 여기서 중욯나 것은 컨슈머가 점점 더 뒤쳐지는게 아니라, 계속해서 따라붙는 것이다. 컨슈머 랙 값이 어느정도 오르락내리락 하는 만큼, 이 지표에 전통적인 정보를 설정하는 것은 약간 어려울 수 있다. kafka.server:type=BrokerTopicMetrics.name=FailedProduceRequestsPerSec와 kafka.server:type=BrokerTopicMetrics,name=FailedFetchRequestsPerSec지표값을 수집할 것을 권장한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>PaaS/MQ</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/538</guid>
      <comments>https://armyost.tistory.com/538#entry538comment</comments>
      <pubDate>Fri, 23 Jan 2026 01:41:44 +0900</pubDate>
    </item>
    <item>
      <title>Istio Proxy Debug 모드로 전환하기</title>
      <link>https://armyost.tistory.com/537</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;간혹 Istio Proxy Level에서 트러블 슈팅이 필요한 경우가 있다. 이럴때는 Istio Proxy 로깅이 필요한데 별도의 조치가 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768807403313&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# istio-proxy 컨테이너 쉘 접속
kubectl -n {해당 NS} exec -it {해당 POD} -c istio-proxy -- /bin/sh

# 로그 레벨을 debug로 수정
curl -XPOST http://localhost:15000/logging?level=debug&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;</description>
      <category>PaaS/Kubernetes</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/537</guid>
      <comments>https://armyost.tistory.com/537#entry537comment</comments>
      <pubDate>Mon, 19 Jan 2026 16:24:36 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 심화) 카프카 내부 알고리즘</title>
      <link>https://armyost.tistory.com/536</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;클러스터 멤버쉽&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;카프카는 현재 클러스터의 멤버인 브로커들의 목록을 유지하기 위해 아파치 주키퍼를 사용한다. 각 브로커는 브로커 설정 파일에 정의되었거나 아니면 자동으로 생성된 고유한 식별자를 가진다.&amp;nbsp;&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;컨트롤러&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컨트롤러는 일반적인 카프카 브로커의 기능에 더해서 파티션 리더를 선출하는 역할을 추가적으로 맡는다. 브로커가 컨트롤러가 되면, 클러스터 메타데이터 고나리와 리더 선출을 시작하기 전에 먼저 주키퍼로부터 최신 레플리카 상태 맵을 읽어온다. 브로커가 클러스터를 나갔다는 사실을 컨트롤러가 알아차리면, 컨트롤러는 해당 브로커가 리더를 맡고 있었던 모든 파티션에 대해 새로운 브로커를 할당해주게 된다. 컨트롤러는 새로운 리더가 필요한 모든 파티션을 순회해 가면서 새로운 리더가 될 브로커를 결정한다.(단순히 해당 파티션의 레플리카 목록에서 바로 다음 레플리카가 새 브로커가 된다.)&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;KRaft: 카프카의 새로운 래프트 기반 컨트롤러&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2019년부터 아파치 카프카 커뮤니티는 야심찬 프로젝트를 시작했다. 바로 주키퍼 기반 컨트롤러로부터 탈피해서 래프트 기반 컨트롤러 쿼럼으로 옮겨가는 것이다. 'KRaft'라고 불리는 새로운 컨트롤러의 프리뷰 버전은 2.8에&amp;nbsp; 포함되었으며 3.3부터는 실험적이라는 수식어를 떼고 정식으로 프로더거션 환경에서 사용 가능한 기능이 되었다. 래프트 알고리즘을 사용함으로써, 컨트롤러 노드들은 외부 시스템에 의존하지 않고 자체적으로 리더를 선출할 수 있게 될 것이다. 메타데이터 로그의 리더 역할을 맡고 있는 컨트롤러는 액티브 컨트롤러라고 부른다.&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;font-family: 'Nanum Gothic';&quot;&gt;컨트롤러는 다른 브로커에 변경사항을 밀어내지 않는다. 대신, 다른 브로커들이 새로 도입된 MetadataFetch API를 사용해서 액티브 컨트롤러로부터 변경 사항을 당겨온다.&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;복제&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;각 토픽은 1개 이상의 파티션으로 분할되며, 각 파티션은 다시 다수의 레플리카를 가질 수 있다. 각각의 레플리카는 브로커에 저장되는데, 대게 하나의 브로커는 수백 개에서 심지어 수천개의 레플리카를 저장한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;- 리더 레플리카&lt;/b&gt; : 각 파티션에는 리더 역할을 하는 레플리카가 하나씩 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;- 팔로워 레플리카&lt;/b&gt; : 파티션에 속한 모든 레플리카 중에서 리더 레플리카를 제외한 나머지를 팔로워 레플리카라고 한다. 이들이 주로 하는 일은 리더 레플리카로 들어온 최근 메시지들을 복제함으로써 최신 상태를 유지하는 것이다. 만약 해당 파티션의 리더 레플리카에 크래쉬가 날 경우, 팔로워 레플리카 중 하나가 파티션의 새 리터 파티션으로 승격된다. 파티션이 처음 생성되던 시점에서는 리더 레플리카가 모든 브로커에 걸쳐 균등하게 분포되기 때문에 '선호'라는 표현이 붙었다. 결과적으로, 클러스터내의 모든 파티션에 대해 선호 리더가 실제 리더가 될 경우 부하가 브로커 사이에 균등하게 분배될 것이라고 예상할 수 있다. 카프카 에는 auto.leader.rebalance.enable=true 설정이 기본적으로 잡혀있다. 이 설정은 선호 리더가 현재 리더가 아니지만, 현재 리더와 동기화가 되기 있을 경우 리더 선출을 실행시킴으로써 선호 리더를 현재 리더로 만들어 준다.&amp;nbsp;&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;파티션 할당&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 레플리카들을 가능한 한 브로커 간에 고르게 분산시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 각 파티션에 대해 각각의 레플리카는 서로 다른 브로커에 배치되도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;- 만약 브로커에 랙 정보가 설정되어 있다면가능한 한 각 파티션의 레플리카들을 서로 다른 랙에 할당한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;새 파티션을 저장할 디렉토리를 결정해야 한다. 이 작업은 파티션별로 독립적으로 수행되며, 규칙은 매우 간단하다. 즉, 각 디렉토리에 저장되어 있는 파티션의 수를 센 뒤, 가장 적은 파티션이 저장된 디렉토리에 새 파티션을 저장하는 것이다.&amp;nbsp;&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;인덱스&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;브로커는 오프셋 100에 해당하는 메시지가 저장된 위치르 ㄹ빠르게 찾아서 해당 오프셋에서 부터 메시지를 읽기 시작할 수 있어야 한다. 브로커가 주어진 오프셋의 메시지를 빠르게 찾을 수 있도록 하기 위해 카프카는 각 파티션에 대해 오프셋을 유지한다. 이 인덱스는 오프셋과 세그먼트 파일 및 그 안에서의 위치를 매핑한다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;압착&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;카프카는 두 가지 보존 정책을 허용한다. 보존 정책에서는 지정된 보존 기한 보다 더 오래된 이벤트들을 삭제한다. 압착 보존 정책에서는 토픽에서 각 키의 가장 최근값만 저장하도록 한다.&amp;nbsp;&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;압착의 작동원리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;파티션을 압착하기 위해서 클리너 스레드는 파티션의 더티 영역을 읽어서 인-메모리 맵을 생성한다. 클린 세그먼트들을 오래된 것부터 읽어들이면서 오프셋 맵의 내용과 대조한다. 키 값이 현재 오프셋 맵에 저장되어 있는지 확인한다.&amp;nbsp;&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-origin-width=&quot;714&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/woHvB/dJMcaaxgaA5/WbimznBvmyerLIyE9TEK3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/woHvB/dJMcaaxgaA5/WbimznBvmyerLIyE9TEK3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/woHvB/dJMcaaxgaA5/WbimznBvmyerLIyE9TEK3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwoHvB%2FdJMcaaxgaA5%2FWbimznBvmyerLIyE9TEK3K%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;210&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnQpZo/dJMcadOd02d/XTtCgtk5oWX6ZTCKI7QzVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnQpZo/dJMcadOd02d/XTtCgtk5oWX6ZTCKI7QzVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnQpZo/dJMcadOd02d/XTtCgtk5oWX6ZTCKI7QzVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnQpZo%2FdJMcadOd02d%2FXTtCgtk5oWX6ZTCKI7QzVK%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;700&quot; height=&quot;505&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;505&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;font-family: 'Nanum Gothic';&quot;&gt;토픽은 언제 압착되는가?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;삭제 정책이 현재의 액티브 세그먼트를 절대로 삭제하지 않는 것과 마찬가지로, 압착 정책 역시 현재의 액티브 세그먼트를 절대로 압착하지 않는다. 액티브 세그먼트가 아닌 세그먼트에 저장되어 있는 메시지 만이 압착의 대상이 된다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>PaaS/MQ</category>
      <author>armyost</author>
      <guid isPermaLink="true">https://armyost.tistory.com/536</guid>
      <comments>https://armyost.tistory.com/536#entry536comment</comments>
      <pubDate>Wed, 14 Jan 2026 23:00:22 +0900</pubDate>
    </item>
  </channel>
</rss>