<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>To Be Continued</title>
    <link>https://kinim329.tistory.com/</link>
    <description>kinim329 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 14 May 2026 23:01:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>kinim329</managingEditor>
    <item>
      <title>Terraform을 사용하는 이유</title>
      <link>https://kinim329.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어제는 Terraform이 인프라를 코드로 관리하는 도구라는 것을 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Terraform을 왜 사용하는지에 대해 정리해보려고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 인프라를 직접 만들 때의 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;268&quot; data-start=&quot;225&quot; data-ke-size=&quot;size16&quot;&gt;AWS에서 EC2를 만든다고 하면 콘솔에 들어가서 직접 설정을 선택해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;327&quot; data-start=&quot;270&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스 타입을 고르고, VPC와 서브넷을 선택하고, 보안 그룹을 만들고, 필요한 포트를 열어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;367&quot; data-start=&quot;329&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 크게 어렵지 않지만, 서버가 여러 개가 되면 문제가 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;429&quot; data-start=&quot;369&quot; data-ke-size=&quot;size16&quot;&gt;어떤 설정으로 만들었는지 기억하기 어렵고, 사람이 직접 클릭해서 만들다 보면 설정이 조금씩 달라질 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;478&quot; data-start=&quot;431&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 A 서버는 8080 포트가 열려 있는데, B 서버는 빠져 있을 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;524&quot; data-start=&quot;480&quot; data-ke-size=&quot;size16&quot;&gt;이런 차이는 나중에 배포나 운영 과정에서 예상하지 못한 오류로 이어질 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;524&quot; data-start=&quot;480&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; Terraform을 사용하면 좋은 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;587&quot; data-start=&quot;551&quot; data-ke-size=&quot;size16&quot;&gt;Terraform을 사용하면 인프라 설정을 코드로 남길 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;651&quot; data-start=&quot;589&quot; data-ke-size=&quot;size16&quot;&gt;즉, EC2를 몇 개 만들지, 어떤 인스턴스 타입을 사용할지, 어떤 보안 그룹을 적용할지 등을 파일에 작성한다.&lt;/p&gt;
&lt;p data-end=&quot;694&quot; data-start=&quot;653&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 인프라 설정이 코드로 남기 때문에 나중에 다시 확인하기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;751&quot; data-start=&quot;696&quot; data-ke-size=&quot;size16&quot;&gt;또한 Git으로 관리할 수 있기 때문에 누가 언제 어떤 인프라 설정을 변경했는지도 추적할 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;751&quot; data-start=&quot;696&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 원하는 상태를 기준으로 동작한다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;819&quot; data-start=&quot;775&quot; data-ke-size=&quot;size16&quot;&gt;Terraform의 중요한 특징은 &amp;ldquo;원하는 상태&amp;rdquo;를 기준으로 동작한다는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;897&quot; data-start=&quot;821&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 코드에 EC2 인스턴스가 1개 필요하다고 작성되어 있는데 실제 AWS에는 없다면, Terraform은 EC2를 새로 생성한다.&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;899&quot; data-ke-size=&quot;size16&quot;&gt;반대로 코드에서 인스턴스 타입을 변경하면 Terraform은 현재 상태와 코드의 차이를 비교해서 변경이 필요한 부분을 알려준다.&lt;/p&gt;
&lt;p data-end=&quot;1004&quot; data-start=&quot;972&quot; data-ke-size=&quot;size16&quot;&gt;이때 사용하는 명령어가 terraform plan이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1091&quot; data-start=&quot;1034&quot; data-ke-size=&quot;size16&quot;&gt;이 명령어를 실행하면 실제로 적용하기 전에 어떤 리소스가 생성, 수정, 삭제될지 미리 확인할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1121&quot; data-start=&quot;1093&quot; data-ke-size=&quot;size16&quot;&gt;그리고 문제가 없으면 다음 명령어로 실제 적용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; Terraform State &lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1217&quot; data-start=&quot;1172&quot; data-ke-size=&quot;size16&quot;&gt;Terraform은 현재 인프라 상태를 기억하기 위해 State 파일을 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;1255&quot; data-start=&quot;1219&quot; data-ke-size=&quot;size16&quot;&gt;이 파일에는 Terraform이 관리하는 리소스 정보가 저장된다.&lt;/p&gt;
&lt;p data-end=&quot;1311&quot; data-start=&quot;1257&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 EC2 인스턴스를 만들었는지, 어떤 보안 그룹을 만들었는지 같은 정보가 들어간다.&lt;/p&gt;
&lt;p data-end=&quot;1368&quot; data-start=&quot;1313&quot; data-ke-size=&quot;size16&quot;&gt;Terraform은 이 State 파일과 코드, 실제 인프라 상태를 비교해서 변경 사항을 판단한다.&lt;/p&gt;
&lt;p data-end=&quot;1404&quot; data-start=&quot;1370&quot; data-ke-size=&quot;size16&quot;&gt;그래서 State 파일은 Terraform에서 매우 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;1476&quot; data-start=&quot;1406&quot; data-ke-size=&quot;size16&quot;&gt;혼자 테스트할 때는 로컬에 State 파일을 둘 수 있지만, 팀 프로젝트에서는 S3 같은 원격 저장소에 보관하는 경우가 많다.&lt;/p&gt;
&lt;p data-end=&quot;1514&quot; data-start=&quot;1478&quot; data-ke-size=&quot;size16&quot;&gt;그래야 여러 사람이 같은 인프라 상태를 기준으로 작업할 수 있다.&lt;/p&gt;</description>
      <category>Study</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/87</guid>
      <comments>https://kinim329.tistory.com/87#entry87comment</comments>
      <pubDate>Wed, 13 May 2026 23:50:58 +0900</pubDate>
    </item>
    <item>
      <title>Terraform 개념 정리</title>
      <link>https://kinim329.tistory.com/86</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Terraform이란&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terraform은 인프라를 코드로 관리할 수 있게 해주는 도구이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버, 네트워크, 데이터베이스, 보안 그룹 같은 인프라 자원을 직접 콘솔에서 클릭해서 만드는 것이 아니라, 코드로 작성해서 생성하고 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식을 IAC라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IaC는 Infrastructure as Code의 약자이고 한국어로는 코드형 인프라라고 볼 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기존 방식의 문제점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;314&quot; data-start=&quot;259&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 AWS EC2 서버를 만든다고 하면 보통은 AWS 콘솔에 들어가서 직접 설정을 선택한다.&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;316&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스 타입을 고르고, 키 페어를 선택하고, 보안 그룹을 만들고, 포트를 열고, VPC나 서브넷을 설정한다.&lt;/p&gt;
&lt;p data-end=&quot;434&quot; data-start=&quot;379&quot; data-ke-size=&quot;size16&quot;&gt;처음 한 번은 괜찮지만 서버가 여러 개가 되거나, 같은 환경을 다시 만들어야 할 때 문제가 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;487&quot; data-start=&quot;436&quot; data-ke-size=&quot;size16&quot;&gt;어떤 설정으로 만들었는지 기억하기 어렵고, 사람이 직접 설정하다 보면 실수도 생길 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;487&quot; data-start=&quot;436&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Terraform을 사용하면&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;555&quot; data-start=&quot;515&quot; data-ke-size=&quot;size16&quot;&gt;erraform을 사용하면 이런 인프라 설정을 코드로 작성할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;587&quot; data-start=&quot;557&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 내용을 코드로 작성할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;app_server&quot; {
  ami           = &quot;ami-xxxxxx&quot;
  instance_type = &quot;t2.micro&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;733&quot; data-start=&quot;702&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 AWS EC2 인스턴스를 만들겠다는 의미이다.&lt;/p&gt;
&lt;p data-end=&quot;784&quot; data-start=&quot;735&quot; data-ke-size=&quot;size16&quot;&gt;즉, Terraform 파일을 작성하고 실행하면 코드에 적힌 내용대로 인프라가 생성된다.&lt;/p&gt;
&lt;h3 data-end=&quot;784&quot; data-start=&quot;735&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; Terraform과 GitHub Actions의 차이&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1370&quot; data-start=&quot;1333&quot; data-ke-size=&quot;size16&quot;&gt;Terraform은 서버나 네트워크 같은 인프라를 만드는 도구이다.&lt;/p&gt;
&lt;p data-end=&quot;1418&quot; data-start=&quot;1372&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions는 빌드, 테스트, 배포 같은 작업을 자동화하는 도구이다.&lt;/p&gt;
&lt;p data-end=&quot;1434&quot; data-start=&quot;1420&quot; data-ke-size=&quot;size16&quot;&gt;즉, 둘의 역할은 다르다.&lt;/p&gt;
&lt;p data-end=&quot;1499&quot; data-start=&quot;1436&quot; data-ke-size=&quot;size16&quot;&gt;Terraform은 &amp;ldquo;서버를 만든다&amp;rdquo;에 가깝고, GitHub Actions는 &amp;ldquo;코드를 서버에 배포한다&amp;rdquo;에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;1597&quot; data-start=&quot;1501&quot; data-ke-size=&quot;size16&quot;&gt;그래서 실제 프로젝트에서는 Terraform으로 EC2, 보안 그룹 같은 인프라를 만들고, GitHub Actions로 애플리케이션을 배포하는 식으로 함께 사용할 수 있다.&lt;/p&gt;</description>
      <category>Study</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/86</guid>
      <comments>https://kinim329.tistory.com/86#entry86comment</comments>
      <pubDate>Tue, 12 May 2026 22:32:06 +0900</pubDate>
    </item>
    <item>
      <title>Jenkins 개념 정리</title>
      <link>https://kinim329.tistory.com/85</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jenkins란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins는 CI/CD를 자동화해주는 도구이다.&lt;/p&gt;
&lt;p data-end=&quot;128&quot; data-start=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 코드를 GitHub에 push하면, Jenkins가 그 변경사항을 감지해서 자동으로 빌드, 테스트, 배포 같은 작업을 실행할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;161&quot; data-start=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 원래는 개발자가 직접 해야 하는 작업이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;161&quot; data-start=&quot;130&quot;&gt;코드 pull 받기&lt;/li&gt;
&lt;li data-end=&quot;161&quot; data-start=&quot;130&quot;&gt;프로젝트 빌드하기&lt;/li&gt;
&lt;li data-end=&quot;161&quot; data-start=&quot;130&quot;&gt;테스트 실행하기&lt;/li&gt;
&lt;li data-end=&quot;161&quot; data-start=&quot;130&quot;&gt;서버에 배포하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins를 사용하면 이 과정을 자동화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Push &amp;rarr; Jenkins 실행 &amp;rarr; Build &amp;rarr; Test &amp;rarr; Deploy&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 사용할까?&lt;/h3&gt;
&lt;p data-end=&quot;379&quot; data-start=&quot;333&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트가 커질수록 매번 직접 빌드하고 배포하는 것은 번거롭고 실수도 생기기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;425&quot; data-start=&quot;381&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 배포할 때 테스트를 깜빡하거나, 잘못된 파일을 서버에 올릴 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;472&quot; data-start=&quot;427&quot; data-ke-size=&quot;size16&quot;&gt;Jenkins는 정해진 절차대로 자동 실행되기 때문에 이런 실수를 줄일 수 있다.&lt;/p&gt;</description>
      <category>Study</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/85</guid>
      <comments>https://kinim329.tistory.com/85#entry85comment</comments>
      <pubDate>Mon, 11 May 2026 23:24:10 +0900</pubDate>
    </item>
    <item>
      <title>로그 레벨</title>
      <link>https://kinim329.tistory.com/84</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 로그 레벨이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;55&quot; data-start=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;로그 레벨은 로그의 중요도나 목적에 따라&lt;br /&gt;로그를 구분해서 남기는 기준이다.&lt;/p&gt;
&lt;p data-end=&quot;82&quot; data-start=&quot;57&quot; data-ke-size=&quot;size16&quot;&gt;모든 로그가 같은 의미를 가지는 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;140&quot; data-start=&quot;84&quot; data-ke-size=&quot;size16&quot;&gt;단순히 흐름을 확인하기 위한 로그도 있고,&lt;br /&gt;문제가 발생했을 때 반드시 확인해야 하는 로그도 있다.&lt;/p&gt;
&lt;p data-end=&quot;206&quot; data-start=&quot;142&quot; data-ke-size=&quot;size16&quot;&gt;그래서 로그를 남길 때는 상황에 맞게&lt;br /&gt;info, warn, error 같은 레벨을 구분해서 사용한다.&lt;/p&gt;
&lt;h3 data-end=&quot;206&quot; data-start=&quot;142&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 로그 레벨을 나누는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;289&quot; data-start=&quot;231&quot; data-ke-size=&quot;size16&quot;&gt;로그 레벨을 나누지 않고 모든 내용을 똑같이 남기면&lt;br /&gt;중요한 로그와 덜 중요한 로그를 구분하기 어렵다.&lt;/p&gt;
&lt;p data-end=&quot;368&quot; data-start=&quot;291&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 정상적인 요청 처리 로그와&lt;br /&gt;실제 장애가 발생한 로그가 같은 수준으로 남는다면,&lt;br /&gt;문제가 생겼을 때 원인을 찾기 힘들어진다.&lt;/p&gt;
&lt;p data-end=&quot;395&quot; data-start=&quot;370&quot; data-ke-size=&quot;size16&quot;&gt;로그 레벨을 나누면 다음과 같은 장점이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;395&quot; data-start=&quot;370&quot;&gt;정상 흐름과 오류 상황을 구분할 수 있다&lt;/li&gt;
&lt;li data-end=&quot;395&quot; data-start=&quot;370&quot;&gt;운영 환경에서 필요한 로그만 확인할 수 있다&lt;/li&gt;
&lt;li data-end=&quot;395&quot; data-start=&quot;370&quot;&gt;문제가 발생했을 때 중요한 로그를 빠르게 찾을 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; info&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;542&quot; data-start=&quot;503&quot; data-ke-size=&quot;size16&quot;&gt;info는 애플리케이션의 정상적인 주요 흐름을 기록할 때 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;601&quot; data-start=&quot;544&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 계좌 생성을 요청했거나,&lt;br /&gt;매수 요청이 정상적으로 들어온 경우에 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;log.info(&quot;계좌 생성 요청 userId={}&quot;, userId);
log.info(&quot;매수 요청 accountId={}, stockCode={}&quot;, accountId, stockCode);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;770&quot; data-start=&quot;724&quot; data-ke-size=&quot;size16&quot;&gt;info는 에러가 아니라&lt;br /&gt;정상적으로 진행된 중요한 이벤트를 남기는 용도이다.&lt;/p&gt;
&lt;h3 data-end=&quot;784&quot; data-start=&quot;777&quot; data-section-id=&quot;1xww3wv&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;warn&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;836&quot; data-start=&quot;786&quot; data-ke-size=&quot;size16&quot;&gt;warn은 당장 시스템이 멈추는 에러는 아니지만,&lt;br /&gt;주의가 필요한 상황에서 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;897&quot; data-start=&quot;838&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 요청 값이 이상하거나,&lt;br /&gt;예상과 다른 상황이 발생했지만 처리는 가능한 경우에 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;log.warn(&quot;잔액 부족으로 매수 실패 accountId={}, balance={}&quot;, accountId, balance);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1052&quot; data-start=&quot;984&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 서버가 죽은 것은 아니지만,&lt;br /&gt;사용자의 요청이 정상적으로 처리되지 않았기 때문에&lt;br /&gt;나중에 확인할 필요가 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;1067&quot; data-start=&quot;1059&quot; data-section-id=&quot;xwcdjx&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;error&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1121&quot; data-start=&quot;1069&quot; data-ke-size=&quot;size16&quot;&gt;error는 실제 예외가 발생했거나,&lt;br /&gt;기능이 정상적으로 처리되지 못한 경우에 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;1184&quot; data-start=&quot;1123&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 DB 저장 실패, 외부 API 호출 실패,&lt;br /&gt;예상하지 못한 예외가 발생한 경우에 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;monkey&quot;&gt;&lt;code&gt;try {
    accountService.createAccount(userId);
} catch (Exception e) {
    log.error(&quot;계좌 생성 실패 userId={}&quot;, userId, e);
    throw e;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1411&quot; data-start=&quot;1334&quot; data-ke-size=&quot;size16&quot;&gt;error 로그에는 예외 객체 e를 함께 넘기는 것이 좋다.&lt;br /&gt;그래야 어떤 예외가 발생했는지와 어디서 발생했는지 확인할 수 있다.&lt;/p&gt;</description>
      <category>Study</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/84</guid>
      <comments>https://kinim329.tistory.com/84#entry84comment</comments>
      <pubDate>Sun, 10 May 2026 23:21:23 +0900</pubDate>
    </item>
    <item>
      <title>로그 찍기의 중요성</title>
      <link>https://kinim329.tistory.com/83</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 로그란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;68&quot; data-start=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;로그는 애플리케이션이 실행되는 동안&lt;br /&gt;어떤 일이 발생했는지 기록해두는 정보이다.&lt;/p&gt;
&lt;p data-end=&quot;95&quot; data-start=&quot;70&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같은 내용을 남길 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1778337968346&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;log.info(&quot;계좌 생성 요청 userId={}&quot;, userId);
log.error(&quot;계좌 생성 실패 userId={}&quot;, userId, e);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 코드가 실행됐는지 확인하는 용도뿐만 아니라,&lt;br /&gt;나중에 문제가 발생했을 때 원인을 찾기 위한 기록으로 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 왜 로그가 중요할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;318&quot; data-start=&quot;283&quot; data-ke-size=&quot;size16&quot;&gt;개발 중에는 디버거를 사용해서 코드를 한 줄씩 확인할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;380&quot; data-start=&quot;320&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 서버에서는 사용자의 요청이 계속 들어오고,&lt;br /&gt;문제가 언제 어디서 발생했는지 바로 알기 어렵다.&lt;/p&gt;
&lt;p data-end=&quot;408&quot; data-start=&quot;382&quot; data-ke-size=&quot;size16&quot;&gt;이때 로그가 없으면 다음과 같은 문제가 생긴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;요청이 들어왔는지 모름&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;어디서 실패했는지 모름&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;어떤 데이터로 실패했는지 모름&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;예외가 왜 발생했는지 모름&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;505&quot; data-start=&quot;481&quot; data-ke-size=&quot;size16&quot;&gt;결국 코드를 다시 추측하면서 확인해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;560&quot; data-start=&quot;507&quot; data-ke-size=&quot;size16&quot;&gt;반대로 로그가 잘 남아 있으면&lt;br /&gt;문제가 발생한 흐름을 따라가면서 원인을 빠르게 찾을 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;560&quot; data-start=&quot;507&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;601&quot; data-start=&quot;574&quot; data-ke-size=&quot;size16&quot;&gt;계좌 생성 기능에서 에러가 발생했다고 가정해보자.&lt;/p&gt;
&lt;p data-end=&quot;628&quot; data-start=&quot;603&quot; data-ke-size=&quot;size16&quot;&gt;로그가 없다면 단순히 이런 결과만 보게 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;500 Internal Server Error&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;696&quot; data-start=&quot;669&quot; data-ke-size=&quot;size16&quot;&gt;하지만 로그를 남겨두면 다음처럼 확인할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;monkey&quot;&gt;&lt;code&gt;log.info(&quot;계좌 생성 요청 userId={}, competitionId={}&quot;, userId, competitionId);

try {
    accountService.createAccount(userId, competitionId);
} catch (Exception e) {
    log.error(&quot;계좌 생성 실패 userId={}, competitionId={}&quot;, userId, competitionId, e);
    throw e;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1031&quot; data-start=&quot;968&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 어떤 사용자의 요청에서 문제가 발생했는지,&lt;br /&gt;어떤 대회 계좌를 생성하다 실패했는지 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;1031&quot; data-start=&quot;968&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 주의할 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1324&quot; data-start=&quot;1298&quot; data-ke-size=&quot;size16&quot;&gt;로그는 많이 찍는다고 무조건 좋은 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1359&quot; data-start=&quot;1326&quot; data-ke-size=&quot;size16&quot;&gt;너무 많은 로그는 오히려 중요한 정보를 찾기 어렵게 만든다.&lt;/p&gt;
&lt;p data-end=&quot;1407&quot; data-start=&quot;1361&quot; data-ke-size=&quot;size16&quot;&gt;또한 비밀번호, 토큰, 개인정보 같은 민감한 값은&lt;br /&gt;절대 로그에 남기면 안 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;// 좋지 않은 예
log.info(&quot;로그인 요청 password={}&quot;, password);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1511&quot; data-start=&quot;1475&quot; data-ke-size=&quot;size16&quot;&gt;로그는 문제를 해결하기 위한 정보만 적절히 남기는 것이 중요하다.&lt;/p&gt;</description>
      <category>Study</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/83</guid>
      <comments>https://kinim329.tistory.com/83#entry83comment</comments>
      <pubDate>Sat, 9 May 2026 23:47:56 +0900</pubDate>
    </item>
    <item>
      <title>대회 종료 시 최종 자산 정산 고도화</title>
      <link>https://kinim329.tistory.com/82</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;antcamp은 가상 주식 대회 플랫폼이다. 대회가 종료되면 모든 참가자의 최종 총자산을 계산해 순위를 확정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총자산은 아래 공식으로 계산된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;fix&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;총자산 = 현금 잔액 + (보유 종목별 확정가 &amp;times; 보유 수량)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 확정가는 대회 종료 시점의 실제 주가여야 하므로, 한투(한국투자증권) API를 호출해 가격을 가져와야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐싱 전 이미지 삽입&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 구현하면 참가자 수 &amp;times; 보유 종목 수만큼 한투 API를 호출하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 참가자 100명이 각자 5개 종목을 보유하고 있다면 최대 500번 API를 호출해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식에는 두 가지 문제가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;한투 API에는 **호출 횟수 제한(Rate Limit)**이 있어 초과 시 오류가 발생한다.&lt;/li&gt;
&lt;li&gt;외부 API 호출이 많을수록 정산 처리 시간이 길어진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 &amp;mdash; Redis 캐싱으로 중복 호출 제거&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐싱 후 이미지 삽입&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 종목은 어느 참가자가 보유하든 대회 종료 시점의 가격이 동일하다. 삼성전자를 50명이 들고 있어도 그 시점의 가격은 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점을 활용해 Redis를 캐시로 사용했다. 종목 가격을 처음 조회할 때만 한투 API를 호출하고, 이후 같은 종목이 나오면 저장해둔 값을 그대로 꺼내 쓴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1778224901793&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String cacheKey = &quot;stock:price:&quot; + competitionId + &quot;:&quot; + stockCode;
Long price = redisTemplate.opsForValue().get(cacheKey);

if (price == null) {
    // 캐시에 없을 때만 한투 API 호출
    price = stockPriceClient.getPriceAt(stockCode, endedAt).getData().longValue();
    redisTemplate.opsForValue().set(cacheKey, price, 1, TimeUnit.HOURS);
}
priceCache.put(stockCode, price); // 인메모리 Map에도 저장해 Redis 조회도 줄임&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리가 끝나면 Redis에 저장해둔 가격 데이터를 즉시 삭제해 불필요한 데이터가 남지 않도록 했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&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&gt;한투 API 호출 수&lt;/td&gt;
&lt;td&gt;참가자 수 &amp;times; 보유 종목 수&lt;/td&gt;
&lt;td&gt;고유 종목 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100명 &amp;times; 5종목 기준&lt;/td&gt;
&lt;td&gt;최대 500번&lt;/td&gt;
&lt;td&gt;최대 20번&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 데이터를 반복해서 가져올 필요가 없다는 점에서 출발한 단순한 아이디어지만, API 호출 횟수를 크게 줄여 Rate Limit 위험을 없애고 정산 속도도 높일 수 있었다.&lt;/p&gt;</description>
      <category>Project</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/82</guid>
      <comments>https://kinim329.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 8 May 2026 16:22:32 +0900</pubDate>
    </item>
    <item>
      <title>Redis로 분산락 거는 방법</title>
      <link>https://kinim329.tistory.com/81</link>
      <description>&lt;p data-end=&quot;42&quot; data-start=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;어제는 분산락이 왜 필요한지 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;87&quot; data-start=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 Redis를 사용해서 분산락을 어떻게 거는지 간단히 정리해보려고 한다.&lt;/p&gt;
&lt;h3 data-end=&quot;87&quot; data-start=&quot;44&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;락의 기준 정하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;128&quot; data-start=&quot;91&quot; data-ke-size=&quot;size16&quot;&gt;분산락을 걸 때는 먼저 무엇을 기준으로 잠글지 정해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;174&quot; data-start=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;주식 매수 기능에서는 같은 계좌의 잔액이 동시에 변경되면 문제가 생길 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;201&quot; data-start=&quot;176&quot; data-ke-size=&quot;size16&quot;&gt;그래서 계좌 ID 기준으로 락을 걸 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;lock:account:{accountId}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;273&quot; data-start=&quot;241&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 계좌 ID가 1이면 다음 key를 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;lock:account:1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Redis로 락 걸기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;352&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;Redis에서는 보통 SET NX EX 방식을 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SET lock:account:1 random-value NX EX 3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;436&quot; data-start=&quot;408&quot; data-ke-size=&quot;size16&quot;&gt;NX는 key가 없을 때만 저장한다는 의미이다.&lt;/p&gt;
&lt;p data-end=&quot;464&quot; data-start=&quot;438&quot; data-ke-size=&quot;size16&quot;&gt;이미 같은 key가 있으면 락 획득에 실패한다.&lt;/p&gt;
&lt;p data-end=&quot;501&quot; data-start=&quot;466&quot; data-ke-size=&quot;size16&quot;&gt;EX 3은 3초 뒤에 락이 자동으로 사라지게 하는 설정이다.&lt;/p&gt;
&lt;p data-end=&quot;546&quot; data-start=&quot;503&quot; data-ke-size=&quot;size16&quot;&gt;서버가 작업 중 죽어도 락이 계속 남는 것을 막기 위해 만료 시간이 필요하다.&lt;/p&gt;
&lt;h3 data-end=&quot;546&quot; data-start=&quot;503&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;처리 흐름&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요청이 들어온다.&lt;/li&gt;
&lt;li&gt;Redis에 lock key 생성을 시도한다.&lt;/li&gt;
&lt;li&gt;성공하면 로직을 실행한다.&lt;/li&gt;
&lt;li&gt;끝나면 락을 해제한다.&lt;/li&gt;
&lt;li&gt;실패하면 이미 처리 중인 요청으로 본다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Study</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/81</guid>
      <comments>https://kinim329.tistory.com/81#entry81comment</comments>
      <pubDate>Thu, 7 May 2026 23:50:20 +0900</pubDate>
    </item>
    <item>
      <title>분산락</title>
      <link>https://kinim329.tistory.com/80</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;분산락이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산락은 여러 서버나 여러 프로세스가 동시에 같은 자원에 접근하지 못하도록 막는 잠금 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 synchronized나 Java Lock은 하나의 애플리케이션 안에서는 동작하지만, 서버가 여러 대로 늘어나면 한계가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 서버가 1대라면 아래처럼 처리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;synchronized void buyStock() {
    // 매수 처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 서버가 2대 이상이면 문제가 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 요청 1 &amp;rarr; asset-service 서버 A&lt;/li&gt;
&lt;li&gt;사용자 요청 2 &amp;rarr; asset-service 서버 B&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 서버 A와 서버 B는 서로의 메모리를 공유하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 서버 A에서 락을 걸어도 서버 B는 그 사실을 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때&amp;nbsp; Redis같은 외부 저장소를 이용해서 여러 서버가 공통으로 확인할 수 있는 락을 거는 방식이 분산락이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 필요할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 환경에서는 같은 데이터에 동시에 접근하는 상황이 자주 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 주식 매수 요청이 거의 동시에 두번 들어왔다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 잔액이 10000원이고, 주식 1주 가격이 7000원이라면 정상적으로는 한 번만 매수되어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 동시에 요청이 들어오면 두 요청이 모두 같은 잔액을 조회할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 A: 잔액 10000원 조회&lt;/li&gt;
&lt;li&gt;요청 B: 잔액 10000원 조회&lt;/li&gt;
&lt;li&gt;요청 A: 7000원 매수 가능 판단&lt;/li&gt;
&lt;li&gt;요청 B: 7000원 매수 가능 판단&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 실제 잔액보다 더 많은 금액이 사용될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 막기 위해 특정 사용자 계좌나 특정 주식 보유 정보에 대해 한 번에 하나의 요청만 처리되도록 잠금을 걸어야 한다.&lt;/p&gt;</description>
      <category>Study</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/80</guid>
      <comments>https://kinim329.tistory.com/80#entry80comment</comments>
      <pubDate>Wed, 6 May 2026 22:19:26 +0900</pubDate>
    </item>
    <item>
      <title>N+1문제</title>
      <link>https://kinim329.tistory.com/79</link>
      <description>&lt;p data-end=&quot;49&quot; data-start=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 개발에서 자주 언급되는 N+1 문제에 대해 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;132&quot; data-start=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;N+1 문제는 어떤 데이터를 한 번 조회한 뒤,&lt;br /&gt;그 데이터와 관련된 정보를 가져오기 위해 추가 요청이 여러 번 반복되는 문제를 말한다.&lt;/p&gt;
&lt;p data-end=&quot;193&quot; data-start=&quot;134&quot; data-ke-size=&quot;size16&quot;&gt;여기서 N은 처음 조회한 데이터의 개수이고,&lt;br /&gt;+1은 처음 데이터를 조회한 요청 1번을 의미한다.&lt;/p&gt;
&lt;p data-end=&quot;289&quot; data-start=&quot;195&quot; data-ke-size=&quot;size16&quot;&gt;즉, 처음 목록을 가져오기 위한 요청 1번과&lt;br /&gt;목록에 포함된 각 데이터의 추가 정보를 가져오기 위한 요청 N번이 더해져&lt;br /&gt;총 N+1번의 요청이 발생하는 상황이다.&lt;/p&gt;
&lt;h3 data-end=&quot;289&quot; data-start=&quot;195&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예시로 이해하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;347&quot; data-start=&quot;308&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 게시글 목록과 작성자 정보를 화면에 보여줘야 한다고 해보자.&lt;/p&gt;
&lt;p data-end=&quot;365&quot; data-start=&quot;349&quot; data-ke-size=&quot;size16&quot;&gt;먼저 게시글 목록을 가져온다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;게시글 목록 조회 1번&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;428&quot; data-start=&quot;393&quot; data-ke-size=&quot;size16&quot;&gt;조회 결과가 5개라면 다음과 같은 게시글이 있다고 볼 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;게시글 1
게시글 2
게시글 3
게시글 4
게시글 5&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;527&quot; data-start=&quot;473&quot; data-ke-size=&quot;size16&quot;&gt;그런데 각 게시글의 작성자 이름도 필요하다면,&lt;br /&gt;게시글마다 작성자 정보를 다시 가져올 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;게시글 1의 작성자 조회
게시글 2의 작성자 조회
게시글 3의 작성자 조회
게시글 4의 작성자 조회
게시글 5의 작성자 조회&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;634&quot; data-start=&quot;612&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 요청은 다음과 같이 발생한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;게시글 목록 조회 1번
작성자 정보 조회 5번

총 6번 요청&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;732&quot; data-start=&quot;684&quot; data-ke-size=&quot;size16&quot;&gt;처음 조회한 게시글이 5개라서 1 + 5,&lt;br /&gt;즉 N+1 문제가 발생한 것이다.&lt;/p&gt;
&lt;p data-end=&quot;732&quot; data-start=&quot;684&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;732&quot; data-start=&quot;684&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 문제가 될까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;780&quot; data-start=&quot;752&quot; data-ke-size=&quot;size16&quot;&gt;데이터가 적을 때는 큰 문제가 없어 보일 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;813&quot; data-start=&quot;782&quot; data-ke-size=&quot;size16&quot;&gt;하지만 게시글이 5개가 아니라 100개라면 어떻게 될까?&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;게시글 목록 조회 1번
작성자 정보 조회 100번

총 101번 요청&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;901&quot; data-start=&quot;867&quot; data-ke-size=&quot;size16&quot;&gt;게시글이 1000개라면 요청은 1001번까지 늘어날 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;935&quot; data-start=&quot;903&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 요청이 많아지면 다음과 같은 문제가 생길 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;응답 속도 저하
서버 부하 증가
데이터베이스 부하 증가
불필요한 네트워크 비용 증가&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1057&quot; data-start=&quot;997&quot; data-ke-size=&quot;size16&quot;&gt;즉, N+1 문제는 기능이 동작하지 않는 오류라기보다는&lt;br /&gt;성능을 떨어뜨리는 구조적인 문제에 가깝다.&lt;/p&gt;
&lt;h3 data-end=&quot;1057&quot; data-start=&quot;997&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결 방향&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1430&quot; data-start=&quot;1371&quot; data-ke-size=&quot;size16&quot;&gt;N+1 문제를 해결하는 핵심은&lt;br /&gt;반복해서 가져올 정보를 한 번에 가져오도록 구조를 바꾸는 것이다.&lt;/p&gt;
&lt;h4 data-end=&quot;1462&quot; data-start=&quot;1432&quot; data-section-id=&quot;hhqnn7&quot; data-ke-size=&quot;size20&quot;&gt;1. 처음 조회할 때 필요한 정보를 함께 가져오기&lt;/h4&gt;
&lt;p data-end=&quot;1525&quot; data-start=&quot;1464&quot; data-ke-size=&quot;size16&quot;&gt;게시글 목록을 가져올 때 작성자 정보도 함께 가져오면&lt;br /&gt;작성자 정보를 게시글마다 다시 조회하지 않아도 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;게시글 + 작성자 정보를 한 번에 조회&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1585&quot; data-start=&quot;1562&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 요청 흐름이 다음처럼 바뀐다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;기존 방식:
게시글 목록 조회 1번
작성자 조회 N번

개선 방식:
게시글과 작성자 정보를 함께 조회 1번&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1692&quot; data-start=&quot;1665&quot; data-section-id=&quot;ih81ba&quot; data-ke-size=&quot;size20&quot;&gt;2. 여러 개의 추가 정보를 묶어서 가져오기&lt;/h4&gt;
&lt;p data-end=&quot;1753&quot; data-start=&quot;1694&quot; data-ke-size=&quot;size16&quot;&gt;각 게시글마다 작성자를 하나씩 조회하는 대신,&lt;br /&gt;필요한 작성자 ID를 모아서 한 번에 조회할 수도 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;작성자 ID 1, 2, 3, 4, 5를 모아서 한 번에 조회&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1823&quot; data-start=&quot;1802&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 요청이 다음처럼 줄어든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;기존 방식:
작성자 조회 5번

개선 방식:
작성자 목록 조회 1번&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;1907&quot; data-start=&quot;1881&quot; data-section-id=&quot;1rcty0l&quot; data-ke-size=&quot;size26&quot;&gt;3. 화면에 정말 필요한 데이터만 가져오기&lt;/h2&gt;
&lt;p data-end=&quot;1945&quot; data-start=&quot;1909&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 모든 관련 정보를 가져오는 것이 항상 좋은 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1990&quot; data-start=&quot;1947&quot; data-ke-size=&quot;size16&quot;&gt;필요하지 않은 정보까지 많이 가져오면&lt;br /&gt;그 자체로 성능 문제가 될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2064&quot; data-start=&quot;1992&quot; data-ke-size=&quot;size16&quot;&gt;따라서 화면이나 기능에서 실제로 필요한 데이터가 무엇인지 먼저 확인하고,&lt;br /&gt;그 데이터만 적절한 방식으로 가져오는 것이 중요하다.&lt;/p&gt;</description>
      <category>Study</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/79</guid>
      <comments>https://kinim329.tistory.com/79#entry79comment</comments>
      <pubDate>Tue, 5 May 2026 23:50:20 +0900</pubDate>
    </item>
    <item>
      <title>Kafka 역직렬화 에러 해결하기</title>
      <link>https://kinim329.tistory.com/78</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 상황&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asset-service에서 Kafka Consumer를 실행하던 중 다음과 같은 에러가 발생했다&lt;/p&gt;
&lt;pre id=&quot;code_1777903928119&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Error deserializing VALUE for partition competition.ticked-0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 핵심 원인은 아래 코드였다&lt;/p&gt;
&lt;pre id=&quot;code_1777904021294&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;No type information in headers and no default type provided&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Kafka 연결 문제나 Producer 설정 문제라고 생각했지만, 로그를 확인해보니 실제 원인은 Consumer가 Kafka 메시지를 객체로 변환하지 못하는 문제였다.&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;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asset-service에는 competition.ticked 토픽을 구독하는 Consumer가 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1777904574333&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Component
@RequiredArgsConstructor
public class CompetitionTickedEventConsumer {

    private final RankingService rankingService;

    @KafkaListener(
            topics = &quot;${topics.competition.ticked}&quot;,
            groupId = &quot;${spring.kafka.consumer.group-id}&quot;
    )
    public void handleCompetitionTicked(CompetitionTicked payload) {
        rankingService.updateRanking(payload.competitionId());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;931&quot; data-start=&quot;873&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 Kafka에서 메시지를 받아 CompetitionTicked 객체로 바로 변환하려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;1016&quot; data-start=&quot;933&quot; data-ke-size=&quot;size16&quot;&gt;문제는 Spring Kafka의 JsonDeserializer가 이 메시지를 어떤 클래스 타입으로 변환해야 하는지 알 수 없었다는 점이다.&lt;/p&gt;
&lt;h3 data-end=&quot;1016&quot; data-start=&quot;933&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 이런 문제가 발생했을까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1073&quot; data-start=&quot;1043&quot; data-ke-size=&quot;size16&quot;&gt;Kafka 메시지는 내부적으로 바이트 형태로 저장된다.&lt;/p&gt;
&lt;p data-end=&quot;1166&quot; data-start=&quot;1075&quot; data-ke-size=&quot;size16&quot;&gt;Producer가 객체를 Kafka에 보낼 때는 객체를 JSON 같은 형태로 변환해서 보내고, Consumer는 다시 그 JSON을 Java 객체로 변환해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1188&quot; data-start=&quot;1168&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 각각 다음과 같이 부른다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;Producer: 직렬화
Consumer: 역직렬화&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1257&quot; data-start=&quot;1232&quot; data-ke-size=&quot;size16&quot;&gt;이번 문제는 Consumer 쪽에서 발생했다.&lt;/p&gt;
&lt;p data-end=&quot;1301&quot; data-start=&quot;1259&quot; data-ke-size=&quot;size16&quot;&gt;즉, 메시지는 Kafka에 들어왔지만 Consumer가 다음을 알지 못했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;이 JSON을 어떤 Java 클래스에 담아야 하지?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1365&quot; data-start=&quot;1345&quot; data-ke-size=&quot;size16&quot;&gt;그래서 다음과 같은 에러가 발생했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;No type information in headers and no default type provided&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1451&quot; data-start=&quot;1440&quot; data-ke-size=&quot;size16&quot;&gt;의미는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Kafka 메시지 헤더에 타입 정보가 없고,
Consumer 설정에도 기본 타입이 지정되어 있지 않다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결방법 1&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1617&quot; data-start=&quot;1563&quot; data-ke-size=&quot;size16&quot;&gt;가장 간단한 해결 방법은 application.yml에 기본 역직렬화 타입을 지정하는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;1653&quot; data-start=&quot;1619&quot; data-ke-size=&quot;size16&quot;&gt;CompetitionTicked의 패키지가 다음과 같다면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;io.antcamp.assetservice.domain.event.payload.CompetitionTicked&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1752&quot; data-start=&quot;1731&quot; data-ke-size=&quot;size16&quot;&gt;yml에 아래 설정을 추가할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;spring:
  kafka:
    consumer:
      group-id: asset-service
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring.json.trusted.packages: &quot;*&quot;
        spring.json.value.default.type: io.antcamp.assetservice.domain.event.payload.CompetitionTicked&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2269&quot; data-start=&quot;2194&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 설정하면 Consumer는 Kafka 메시지를 읽을 때 기본적으로 CompetitionTicked 타입으로 변환하려고 한다.&lt;/p&gt;
&lt;h3 data-end=&quot;2269&quot; data-start=&quot;2194&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결방법 2&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2366&quot; data-start=&quot;2309&quot; data-ke-size=&quot;size16&quot;&gt;다만 asset-service가 앞으로 여러 Kafka 이벤트를 받을 수 있다면 문제가 생길 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2399&quot; data-start=&quot;2368&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 토픽을 모두 구독한다고 가정한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;competition.started
competition.finished
competition.ticked&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2551&quot; data-start=&quot;2474&quot; data-ke-size=&quot;size16&quot;&gt;각 토픽의 payload 타입이 다르다면 spring.json.value.default.type 하나로 모든 메시지를 처리하기 어렵다.&lt;/p&gt;
&lt;p data-end=&quot;2631&quot; data-start=&quot;2553&quot; data-ke-size=&quot;size16&quot;&gt;이 경우에는 Kafka 메시지를 먼저 String으로 받고, Consumer 내부에서 직접 DTO로 변환하는 방식이 더 명확할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2640&quot; data-start=&quot;2633&quot; data-ke-size=&quot;size16&quot;&gt;yml 설정:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;spring:
  kafka:
    consumer:
      group-id: asset-service
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2926&quot; data-start=&quot;2914&quot; data-ke-size=&quot;size16&quot;&gt;Consumer 코드:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Slf4j
@Component
@RequiredArgsConstructor
public class CompetitionTickedEventConsumer {

    private final RankingService rankingService;
    private final ObjectMapper objectMapper;

    @KafkaListener(
            topics = &quot;${topics.competition.ticked}&quot;,
            groupId = &quot;${spring.kafka.consumer.group-id}&quot;
    )
    public void handleCompetitionTicked(String message) {
        try {
            CompetitionTicked payload =
                    objectMapper.readValue(message, CompetitionTicked.class);

            rankingService.updateRanking(payload.competitionId());
        } catch (Exception e) {
            log.error(&quot;competition.ticked 이벤트 변환 실패. message={}&quot;, message, e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3707&quot; data-start=&quot;3650&quot; data-ke-size=&quot;size16&quot;&gt;이 방식은 각 Consumer마다 어떤 DTO로 변환할지 코드에서 직접 확인할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;3707&quot; data-start=&quot;3650&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내가 선택한 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;177&quot; data-start=&quot;100&quot; data-ke-size=&quot;size16&quot;&gt;이번 상황에서는 해결방법 2인 Kafka 메시지를 String으로 받은 뒤 ObjectMapper로 직접 변환하는 방식을 선택했다.&lt;/p&gt;
&lt;p data-end=&quot;239&quot; data-start=&quot;179&quot; data-ke-size=&quot;size16&quot;&gt;asset-service는 앞으로 하나의 Kafka 이벤트만 받는 것이 아니라 여러 이벤트를 받을 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;267&quot; data-start=&quot;241&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 이벤트를 받을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;competition.started
competition.finished
competition.ticked&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;378&quot; data-start=&quot;342&quot; data-ke-size=&quot;size16&quot;&gt;각 이벤트는 역할이 다르고, payload 구조도 다를 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;479&quot; data-start=&quot;380&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황에서 spring.json.value.default.type으로 기본 타입을 하나만 지정하면 모든 Kafka 메시지를 하나의 DTO 타입으로 변환하려고 할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;522&quot; data-start=&quot;481&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이벤트마다 payload 타입이 다르면 이 방식은 관리하기 어렵다.&lt;/p&gt;
&lt;p data-end=&quot;600&quot; data-start=&quot;524&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Consumer에서는 메시지를 먼저 String으로 받고, 각 Consumer 내부에서 필요한 DTO로 직접 변환하도록 했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Slf4j
@Component
@RequiredArgsConstructor
public class CompetitionTickedEventConsumer {

    private final RankingService rankingService;
    private final ObjectMapper objectMapper;

    @KafkaListener(
            topics = &quot;${topics.competition.ticked}&quot;,
            groupId = &quot;${spring.kafka.consumer.group-id}&quot;
    )
    public void handleCompetitionTicked(String message) {
        try {
            CompetitionTicked payload =
                    objectMapper.readValue(message, CompetitionTicked.class);

            rankingService.updateRanking(payload.competitionId());
        } catch (Exception e) {
            log.error(&quot;competition.ticked 이벤트 변환 실패. message={}&quot;, message, e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1384&quot; data-start=&quot;1324&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 각 Consumer가 어떤 이벤트를 어떤 DTO로 변환하는지 코드에서 명확하게 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;1384&quot; data-start=&quot;1324&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1445&quot; data-start=&quot;1398&quot; data-ke-size=&quot;size16&quot;&gt;이번 문제는 Kafka 연결 문제가 아니라 Consumer 역직렬화 문제였다.&lt;/p&gt;
&lt;p data-end=&quot;1529&quot; data-start=&quot;1447&quot; data-ke-size=&quot;size16&quot;&gt;Kafka 메시지는 정상적으로 들어왔지만, Consumer가 JSON 메시지를 어떤 Java 객체로 변환해야 하는지 알 수 없어서 에러가 발생했다.&lt;/p&gt;
&lt;p data-end=&quot;1546&quot; data-start=&quot;1531&quot; data-ke-size=&quot;size16&quot;&gt;핵심 에러는 다음과 같았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;No type information in headers and no default type provided&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1638&quot; data-start=&quot;1621&quot; data-ke-size=&quot;size16&quot;&gt;해결 방법은 크게 두 가지였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;1. application.yml에 spring.json.value.default.type을 지정한다.
2. 메시지를 String으로 받은 뒤 ObjectMapper로 직접 변환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1810&quot; data-start=&quot;1756&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 여러 Kafka 이벤트를 받을 가능성이 있기 때문에 두 번째 방법을 선택했다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;1888&quot; data-start=&quot;1812&quot; data-ke-size=&quot;size16&quot;&gt;이벤트마다 payload 구조가 다를 수 있으므로, 각 Consumer에서 필요한 DTO로 직접 변환하는 방식이 더 명확하다고 판단했다.&lt;/p&gt;</description>
      <category>Project</category>
      <author>kinim329</author>
      <guid isPermaLink="true">https://kinim329.tistory.com/78</guid>
      <comments>https://kinim329.tistory.com/78#entry78comment</comments>
      <pubDate>Mon, 4 May 2026 23:33:28 +0900</pubDate>
    </item>
  </channel>
</rss>