<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>꾸준하게, 차근차근</title>
    <link>https://jinalim-dev.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 23 Jun 2026 18:24:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jn4624</managingEditor>
    <item>
      <title>[트러블슈팅 회고] JPA 엔티티 단순 조회만 했는데 update 쿼리가 남발된 이유</title>
      <link>https://jinalim-dev.tistory.com/113</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(@DynamicUpdate, @Converter, dirty checking, equals/hashCode, Hibernate deep copy 내부 동작까지)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바쁜 주말을 보내고, 피로와 싸우며 출근을 짠! 했는데 출근과 동시에 이슈가 발생했다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 피로고 나발이고 활기찬 오전을 보냈다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 상황: &quot;조회만 했는데 update 쿼리가 나온다?&quot;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;금요일 운영에 반영한 Search API가 update 쿼리를 대량으로 쏟아내는 현상이 발생했다...  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 JPA로 엔티티를 여러 번 조회했을 뿐인데, 데이터베이스에 update 쿼리가 조회 횟수만큼 쏟아졌다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;코드 상황은 이랬다:&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 클래스에 @DynamicUpdate 어노테이션이 선언되어 있었다.&lt;/li&gt;
&lt;li&gt;필드 중 하나는 @Converter를 적용한 커스텀 클래스로 구성되어 있었다.&lt;/li&gt;
&lt;li&gt;해당 커스텀 클래스에 equals/hashCode를 재정의하지 않았다.&lt;/li&gt;
&lt;li&gt;Search API 로직상 데이터는 전혀 변경하지 않았고, 단순 조회 로직만 반복 실행되는 구조였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데도 로그에는 select뿐만 아니라 불필요한 update 쿼리가 계속 실행되어 데이터베이스에 부하를 주고 있었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 원인 추적&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 코드를 수정하면서 어딘가에 데이터를 변경하는 로직이 잘못 들어갔나 싶어 흐름을 몇 번이고 따라갔지만 데이터 변경 로직은 전혀 보이지 않았다.   완전 멘붕...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹장님의 감사하고 죄송하고 감사한 도움으로 원인을 빠르게 파악하여 문제를 해결하게 되었지만 왜 의도한 대로 동작하지 않은건지 궁금해서 JPA의 내부 동작, Hibernate의 dirty checking까지 들여다보기 시작했다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Hibernate의 스냅샷 생성 및 dirty checking 구조&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;우선 hydratedState, loadedState, dirty checking에 대해서 알아보자.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1. hydratedState의 역할&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hibernate가 데이터베이스에서 엔티티를 로드할 때 각 필드의 값을 일단 hydratedState라는 Object[] 배열에 임시로 저장한다.&lt;/li&gt;
&lt;li&gt;이 hydratedState는 엔티티가 실제로 생성되기 전에 데이터베이스에서 가져온 &quot;원시 값&quot;의 임시 보관소다.&lt;/li&gt;
&lt;li&gt;@Converter가 붙은 필드 역시, 변환된 커스텀 객체로 이 배열에 포함된다.&lt;/li&gt;
&lt;li&gt;hydratedState 자체는 dirty checking에 직접 사용되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2. loadedState(스냅샷) 생성 이유과 과정&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 객체가 생성되면, Hibernate는 hydratedState를 deep copy(깊은 복사)해서 loadedState라는 배열로 저장한다.&lt;/li&gt;
&lt;li&gt;이 loadedState가 &quot;처음 데이터베이스에서 엔티티를 로드했을 때의 값&quot;이자 dirty checking의 기준(스냅샷)이 된다.&lt;/li&gt;
&lt;li&gt;왜 deep copy가 필요할까?
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;뮤터블 객체(예: List, Map, 커스텀 객체)가 hydratedState와 엔티티 필드에서 동일한 객체(참조)를 공유하게 되면, 트랜잭션 중 엔티티를 변경하면 hydreatedState까지 같이 바뀌어 dirty checking이 무의미해지기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Hibernate는 이를 방지하기 위해 deep copy(=별도 객체 생성)로 loadedState를 만들어 변경감지 기준을 분리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.3. dirty checking의 기준&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dirty checking은 flush(트랜잭션 커밋 전) 시점에 loadedState(스냅샷)와 엔티티 인스턴스의 현재 값을 equals()로 비교한다.&lt;/li&gt;
&lt;li&gt;만약 값이 달라졌다면 dirty(변경)로 간주하여 update 쿼리를 실행한다.&lt;/li&gt;
&lt;li&gt;hydratedState는 오직 loadedState 생성을 위한 중간 단계일 뿐, dirty checking의 비교 대상은 &quot;loadedState(스냅샷)&quot;와 &quot;현재 엔티티 인스턴스&quot;다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.4. 관련 Hibernate 내부 구조&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;deep copy는 내부적으로 각 타입별 전략이 적용된다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이뮤터블(불변) 객체는 값만 복사(참조 유지)&lt;/li&gt;
&lt;li&gt;뮤터블(가변) 객체는 복제 객체 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관련 클래스 체계&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;AbstractStandardBasicType -&amp;gt; JavaTypeDesciptor -&amp;gt; MutabilityPlan&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 트러블의 근본 원인 - @Converter + equals/hashCode 미구현&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 현상과 Hibernate 내부 구조를 연결해보면, 문제의 진짜 원인은 아래와 같았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Converter로 데이터베이스 값을 읽을 때마다 매번 새로운 커스텀 객체가 생성된다.&lt;/li&gt;
&lt;li&gt;하지만 해당 커스텀 클래스에 equals/hashCode가 미구현이면 loadedState(스냅샷, 조회 시 객체)와 현재 엔티티(트랜잭션 중 새로 생성된 객체)의 참조값이 항상 다르게 되어버린다.&lt;/li&gt;
&lt;li&gt;Hibernate의 dirty checking(=loadedState와 현재 객체의 equals() 비교)은 참조가 다르면 무조건 &quot;dirty&quot;로 판단한다.&lt;/li&gt;
&lt;li&gt;그 결과, 실제로 값은 안 바뀌었는데도 불구하고 update 쿼리가 남발된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. @DynamicUpdate의 역할&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 헷갈릴 수 있는 점 한가지!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@DynamicUpdate는 dirty checking의 수행 여부와는 관계 없다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@DynamicUpdate는 &quot;실제로 변경된 필드만 update 쿼리에 포함&quot;되도록 쿼리를 최적화하는 기능이다.&lt;/li&gt;
&lt;li&gt;dirty checking(변경 감지)은 @DynamicUpdate와 무관하게 트랜잭션이 readOnly가 아니라면 항상 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 동작 흐름 용어 요약 및 flush 시 비교 순서&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6.1. 용어 정리&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;hydratedState&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;데이터베이스에서 로딩한 엔티티의 &quot;원시값&quot;을 담은 임시 배열&lt;/li&gt;
&lt;li&gt;dirty checking에 직접 사용되지 않고, loadedState 생성용 원본&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;loadedState&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;hydratedState를 deep copy해서 만든 &quot;처음 로딩된 시점의 값&quot;&lt;/li&gt;
&lt;li&gt;dirty checking의 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dirty checking(변경 감지)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;flush 시점에 loadedState(스냅샷)와 엔티티 인스턴스의 현재 값을 equals()로 비교해 변경 여부를 판단&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6.2. flush(커밋) 시 비교 순서&lt;/b&gt;&lt;/h4&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;flush(트랜잭션 커밋 전)&lt;/li&gt;
&lt;li&gt;loadedState(스냅샷)와 엔티티 인스턴스의 현재 값이 equals()로 비교됨&lt;/li&gt;
&lt;li&gt;값이 다르면 dirty로 간주해 update 쿼리 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. 해결 방법&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;커스텀 클래스에 equals/hashCode 반드시 구현&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 객체의 실제 값이 같으면 동등하게 판단할 수 있도록 equals/hashCode를 구현해야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;조회만 필요한 트랜잭션에는 readOnly 옵션 명시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional(readOnly = true)로 선언하면 Hibernate는 dirty checking 자체를 생략한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. 여기서 문득 드는 궁금증?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hydratedState가 원시 데이터를 Object 배열에 임시 저장하고 이를 기반으로 loadedState를 생성한다고 했다  &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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;엔티티 인스턴스는 어떻게 만들어지는가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hibernate가 데이터베이스에서 엔티티를 로드할 때,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 컬럼(필드)의 값을 읽어서 hydratedState 배열에 저장한다.&lt;/li&gt;
&lt;li&gt;이 hydratedState 배열에 들어 있는 값을 이용해서
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;엔티티 객체의 각 필드에 값을 주입해 실제 엔티티 인스턴스를 생성한다.&lt;/li&gt;
&lt;li&gt;즉, hydratedState 배열에 있는 값이 엔티티의 생성자/필드/Setter 등에 그대로 할당된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그리고 hydratedState의 값을 deep copy하여 loadedState 배열(스냅샷)로 만들어 dirty checking의 기준을 만든다.&lt;/li&gt;
&lt;/ul&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;엔티티 인스턴스는 데이터베이스 -&amp;gt; hydratedState -&amp;gt; 엔티티 생성&lt;/li&gt;
&lt;li&gt;loadedState(스냅샷)는 hydratedState -&amp;gt; deep copy -&amp;gt; loadedState(스냅샷)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 생성된다고 한다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 이슈로 세상 혼자 난리났었지만, 그래도 너무 좋은 경험이었던 것 같다.   (하지만 두번의 실수는 안돼  &amp;zwj;♂️ &amp;zwj;♂️ &amp;zwj;♂️)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 사소한 걸 사용하게 되더라도 실무에서의 경험이 부족하다면 사이드 이펙트를 먼저 확인하고, JPA가 관련되었다면 실행되는 쿼리도 꼭 확인하는 습관을 가져야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늘 나를 의심하고, 내 코드를 의심하고, 두 번, 세 번 확인하고, 늘 겸손하자  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 잘못된 내용이 있다면 댓글 감사드리겠습니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;END.&lt;/b&gt;&lt;/h2&gt;</description>
      <category>Spring</category>
      <category>@Converter</category>
      <category>@DynamicUpdate</category>
      <category>dirty checking</category>
      <category>equals/hashcode</category>
      <category>Hibernate</category>
      <category>hydratedstate</category>
      <category>jpa 엔티티 단순 조회만 했는데 update 쿼리가 나발된 이유</category>
      <category>loadedstate</category>
      <category>readonly</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/113</guid>
      <comments>https://jinalim-dev.tistory.com/113#entry113comment</comments>
      <pubDate>Mon, 14 Jul 2025 23:17:24 +0900</pubDate>
    </item>
    <item>
      <title>[Error] No matching tests found in any candidate test task.</title>
      <link>https://jinalim-dev.tistory.com/112</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사 업무에 적응하기도 바쁘지만, 바쁘다고 자기계발에 소홀하면 안되지!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 덕에 점점 일만 벌리고 있는 것 같지만  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친한 동생과 내년 상반기를 목표로 토이 프로젝트를 진행하기로 했고, 퇴근 후 틈틈이 개발해서 테스트 코드 작성 단계까지 도달했다. (나는 TDD가 잘 안돼  )&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 작성하고 실행하는데 아래와 같은 에러 로그가 찍히면서 테스트 코드가 정상 동작하지 않는 것!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PQnPZ/btsO5m1pl0n/QDyUKQh1w1kWRoWCs1ZRZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PQnPZ/btsO5m1pl0n/QDyUKQh1w1kWRoWCs1ZRZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PQnPZ/btsO5m1pl0n/QDyUKQh1w1kWRoWCs1ZRZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPQnPZ%2FbtsO5m1pl0n%2FQDyUKQh1w1kWRoWCs1ZRZK%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;121&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;121&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 해결 방법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.1. IntelliJ 설정 수정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT에게 물어보고(비록 헛소리로 하나도 도움이 되지 않았음  ), 구글링도 해보고 해서 찾은 첫번째 방법이 IntelliJ 설정을 변경하는 것이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-07-04 오후 11.28.59.png&quot; data-origin-width=&quot;1946&quot; data-origin-height=&quot;1424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqgIk5/btsO4OqyrTf/kEdRsPl5dZaZVy6T12PrFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqgIk5/btsO4OqyrTf/kEdRsPl5dZaZVy6T12PrFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqgIk5/btsO4OqyrTf/kEdRsPl5dZaZVy6T12PrFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqgIk5%2FbtsO4OqyrTf%2FkEdRsPl5dZaZVy6T12PrFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1946&quot; height=&quot;1424&quot; data-filename=&quot;스크린샷 2025-07-04 오후 11.28.59.png&quot; data-origin-width=&quot;1946&quot; data-origin-height=&quot;1424&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;b&gt;Gradle (Default) -&amp;gt; IntelliJ IDEA로 변경&lt;/b&gt;하는 것!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 변경하니 에러는 사라졌고, 테스트 코드도 문제 없이 실행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 맡은 도메인 개발이 거의 마무리되어 오늘 PR까지 생성하고 마무리하는게 목표였는데 코드를 정리하는데 무언가 이상했다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 잘 설정해둔 Q클래스가 외부 경로에 생성되어 있질 않나, QueryDSL의 Q클래스가 동일 경로에 두 번 이상 생성되어 중복 생성 시도로 인한 에러가 발생하질 않나  &amp;zwj; &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;거기다 테스트 코드를 실행할 때마다 JUnit Run Configurations가 중복해서 생성되고 있는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인을 정확하게 분석하지는 못했지만, 예상컨대 테스트 코드를 실행할 때마다 빌드와 실행이 누적된게 아닐까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다보니 설정도 꼬이고 코드도 꼬이고 내 머리도 꼬이고  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 방법이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.2. build.gradle 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StackOverflow에 게시되어 있는 &lt;b&gt;IntelliJ 2019.1 업데이트로 인해 JUnit 테스트가 중단되었다&lt;/b&gt;는 글을 보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 댓글은 IntelliJ 설정 변경 방법과 똑같은 내용이었고, 그 다음으로 눈에 띈 댓글이 현재 작성 중인 build.gradle 설정 방법이다.&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;build.gradle에 아래와 같이 설정을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1751640379731&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test {
	useJUnitPlatform()
}&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;위와 같이 설정하니 JUnit Run Configurations도 중복 생성되지 않았고, 꼬이고 꼬였던 QueryDSL 이슈도 말끔히 사라졌다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 게시글은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/55405441/intelij-2019-1-update-breaks-junit-tests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/55405441/intelij-2019-1-update-breaks-junit-tests&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하고 다시 게시글을 확인해보니 생각보다 오래된 게시글이었다??  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 궁금증&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.1. 이전 설정을 적용하면?&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;2019.1 업데이트로 인해 JUnit 테스트가 중단되었는데 올해 상반기에 생성한 프로젝트는 이런 문제가 없었단 말이지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상반기에 생성한 프로젝트에서 테스트 관련 설정을 옮겨와 적용해봤다.&lt;/p&gt;
&lt;pre id=&quot;code_1751641222876&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tasks.named('test') {
	useJUnitPlatform()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 된다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐야??&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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;3.2. 에러가 맞을까?&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Initializr 홈페이지에 접속해서 프로젝트를 하나 생성해봤는데....&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-07-05 오전 12.12.22.png&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xctO0/btsO54sgqDM/pKjgh4EVD26MSezmPvm7m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xctO0/btsO54sgqDM/pKjgh4EVD26MSezmPvm7m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xctO0/btsO54sgqDM/pKjgh4EVD26MSezmPvm7m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxctO0%2FbtsO54sgqDM%2FpKjgh4EVD26MSezmPvm7m0%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;1926&quot; height=&quot;968&quot; data-filename=&quot;스크린샷 2025-07-05 오전 12.12.22.png&quot; data-origin-width=&quot;1926&quot; data-origin-height=&quot;968&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;/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;동생이 QueryDSL 설정하면서 테스트 관련 설정을 지웠나봐  &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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 참고&lt;/b&gt;&lt;/h2&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;2019.1 이전에는 Gradle이 JUnit 4 Runner로 테스트를 실행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이후 버전부터는 Gradle이 JUnit Platform(5) 기반으로 테스트를 실행하는 것이 표준이 되면서, 명확하게 useJUnitPlatform()을 지정하지 않으면 Gradle이 JUnit 4/5 혼합 프로젝트에서 테스트를 찾지 못하거나 에러가 발생할 수 있었다.&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;IntelliJ의 Run/Debug도 &quot;JUnit Platform&quot;기반으로 동작하기 때문에 반드시 useJUnitPlatform()을 지정해야 프로젝트 전반에서 일관된 테스트 실행을 보장 받을 수 있었다.&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;따라서 이러한 이유로 Gradle이 모든 테스트를 JUnit 5(Platform) 기반으로 실행하도록 강제하는 설정이 IntelliJ 2019.1+의 테스트 정책 변화와 맞물려 반드시 필요한 설정이 된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;END.&lt;/b&gt;&lt;/h2&gt;</description>
      <category>Error</category>
      <category>no matching tests found in any candidate test task.</category>
      <category>usejunitplatform</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/112</guid>
      <comments>https://jinalim-dev.tistory.com/112#entry112comment</comments>
      <pubDate>Sat, 5 Jul 2025 00:06:39 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] FeignClient의 재시도(Retry), 너란 녀석!</title>
      <link>https://jinalim-dev.tistory.com/111</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 입사하고 첫 서비스가 운영에 배포되는 날이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출근하자마자 각 서버의 인스턴스들을 모니터링하고, 모든 서비스가 배포된 후 운영 테스트가 진행되었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한참 운영 테스트가 진행되고 있는 와중에 서비스에서 예외가 발생했다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인 결과, 데이터 취득을 위해 타 서비스로 API를 요청하는데 이 과정에서 400(Bad Request) 응답 코드를 받게 된 것이었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 요청을 받는 서비스 측의 문제로 문제가 해결되기를 기다릴 수밖에 없었지만, 문득 의아한 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책상 위 과정에서 예외가 발생할 경우 2회 재시도가 발생했어야 하는데? 로그상으로는 1회 요청으로 끝난 것이다.  &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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Feign의 기본 Retryer 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Feign으로 API 통신 중, 장애 상황이나 네트워크 불안정으로 재시도(Retry) 설정을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이번 상황에서는 재시도(Retry)가 일어나지 않았다.  &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;FeignClient에서는 재시도 횟수, 간격을 지정한 Retryer를 Bean으로 등록한 설정 클래스를 @FeignClinet 어노테이션의 configuration 속성에 설정해주면 예외가 발생했을 경우 재시도(Retry)를 시도한다.&lt;/p&gt;
&lt;pre id=&quot;code_1750336963691&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public Retryer retryer() {
    // (initialInterval, maxInterval, maxAttempts)
    return new Retryer.Default(100, 1000, 3); // 최대 3회 시도
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 위 예제와 같이 설정해두면 API 요청이 실패할 경우 무조건 재시도를 할 줄 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Feign의 Retryer와 ErrorDecoder, 그리고 상태 코드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Feign의 재시도는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 예외(IOException 등)&lt;/li&gt;
&lt;li&gt;RetryableException&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지가 발생할 때만 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 상태 코드는 보통&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ErrorDecoder에서 FeignException으로 변환&lt;/li&gt;
&lt;li&gt;4xx, 5xx 둘 다 그냥 FeignException 발생&lt;/li&gt;
&lt;li&gt;RetryableException이 아니라서, Retryer는 재시도 안함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Retryer는 FeignException에는 반응하지 않고, RetryableException일 때만 재시도를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 공식 문서 Search-Search&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud OpenFeign 공식 문서에도 아래처럼 명시되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-19 오후 10.03.35.png&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;89&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/es0SRX/btsOJGSoNHl/Bd4fHG3U4I4WuFnfKPQr3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/es0SRX/btsOJGSoNHl/Bd4fHG3U4I4WuFnfKPQr3K/img.png&quot; data-alt=&quot;Spring Cloud OpenFeign 공식문서(2024.06. 기준)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/es0SRX/btsOJGSoNHl/Bd4fHG3U4I4WuFnfKPQr3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fes0SRX%2FbtsOJGSoNHl%2FBd4fHG3U4I4WuFnfKPQr3K%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;825&quot; height=&quot;89&quot; data-filename=&quot;스크린샷 2025-06-19 오후 10.03.35.png&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;89&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Spring Cloud OpenFeign 공식문서(2024.06. 기준)&lt;/figcaption&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;Spring Cloud OpenFeign은 기본적으로 재시도가 꺼져 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retryer를 등록해야만 &quot;네트워크 오류(IOException)&quot;와 &quot;RetryableException&quot;에 대해 재시도가 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4xx, 5xx는 커스텀 ErrorDecoder에서 RetryableException을 던지지 않는 한 기본적으로 재시도되지 않는다. 라는 의미!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 만약 특정 상태 코드에서만 재시도하려면?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우엔 ErrorDecoder를 커스터마이징해서 특정 상태 코드에서만 RetryableException을 직접 throw 해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1750338826783&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CustomErrorDecoder implements ErrorDecoder {
	private final ErrorDecoder defaultDecoder = new Default();

	@Override
	public Exception decode(String methodKey, Response response) {
		if (response.status() == 400) {
			System.out.println(&quot;### [ErrorDecoder] &quot; + response.status() + &quot;에서 RetryableException 발생&quot;);
			return new RetryableException(
				response.status(),
				&quot;retry on &quot; + response.status(),
				response.request().httpMethod(),
				(Long) null,
				response.request()
			);
		}
		return defaultDecoder.decode(methodKey, response);
	}
}&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;그리고 Config에 등록&lt;/p&gt;
&lt;pre id=&quot;code_1750338837602&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class FeignConfig {
	@Bean
	public Retryer retryer() {
		return new Retryer.Default(100, 1000, 3);
	}

	@Bean
	public ErrorDecoder errorDecoder() {
		return new CustomErrorDecoder();
	}
}&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;이렇게 하면 400에서만 재시도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 그럼 테스트를 해봐야지!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위한 나머지 코드를 작성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1750340639609&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FeignClient(
	name = &quot;test-api&quot;,
	url = &quot;http://localhost:8080&quot;,
	configuration = FeignConfig.class
)
public interface TestFeignClient {
	@GetMapping(&quot;/remote/test&quot;)
	String callRemote();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1750340653552&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
public class TestController {
	private int callCount = 0;

	@GetMapping(&quot;/remote/test&quot;)
	public ResponseEntity&amp;lt;String&amp;gt; testApi() {
		callCount++;
		System.out.println(&quot;#### [Server] 호출 횟수: &quot; + callCount);
		return ResponseEntity.status(400).body(&quot;Bad Gateway&quot;);
	}

	private final TestFeignClient testFeignClient;

	@GetMapping(&quot;/feign/test&quot;)
	public String feignTest() {
		try {
			return testFeignClient.callRemote();
		} catch (Exception e) {
			System.out.println(&quot;### [FeignClient] 최종 예외 발생: &quot; + e.getClass().getSimpleName() + &quot; / &quot; + e.getMessage());
			throw e;
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 코드를 작성하고 포스트맨으로 호출한 결과!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-19 오후 10.45.21.png&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnzNRc/btsOIXneDqK/e1FW4kMWRn5MpKG7ZMCn01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnzNRc/btsOIXneDqK/e1FW4kMWRn5MpKG7ZMCn01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnzNRc/btsOIXneDqK/e1FW4kMWRn5MpKG7ZMCn01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnzNRc%2FbtsOIXneDqK%2Fe1FW4kMWRn5MpKG7ZMCn01%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;913&quot; height=&quot;397&quot; data-filename=&quot;스크린샷 2025-06-19 오후 10.45.21.png&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;397&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;최초 1회 + 재시도 2회를 시도한 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 드는 궁금증  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 운영 테스트 중 발생한 예외에서 ErrorDecoder를 커스터마이징 + 등록하지 않아서 재시도가 정말 안된게 맞을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CustomErrorDecoder를 주석 처리하고 다시 요청을 시도해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-19 오후 10.50.15.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S7zMQ/btsOH8CBFwb/ur0D3FC36sSeNuK1Xdkzu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S7zMQ/btsOH8CBFwb/ur0D3FC36sSeNuK1Xdkzu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S7zMQ/btsOH8CBFwb/ur0D3FC36sSeNuK1Xdkzu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS7zMQ%2FbtsOH8CBFwb%2Fur0D3FC36sSeNuK1Xdkzu0%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;906&quot; height=&quot;290&quot; data-filename=&quot;스크린샷 2025-06-19 오후 10.50.15.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;290&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;정말 Feign의 기본 Retryer는 4xx 응답코드에 대해선 재시도를 하지 않는다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿌듯한 삽질이었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;END.&lt;/b&gt;&lt;/h2&gt;</description>
      <category>Spring</category>
      <category>errordecoder</category>
      <category>feignClient</category>
      <category>retryableexception</category>
      <category>retryer</category>
      <category>Spring Cloud OpenFeign</category>
      <category>재시도</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/111</guid>
      <comments>https://jinalim-dev.tistory.com/111#entry111comment</comments>
      <pubDate>Thu, 19 Jun 2025 22:54:38 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 람다/스트림에서 외부 변수 값 변경, Exception 누적 처리</title>
      <link>https://jinalim-dev.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘도 어김 없이 문제 상황에 부딪혔고 이를 해결하면서 알게 된 내용들을 정리하려고 한다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발은 매일 매일이 배움의 시간이라는걸 뼈저리게 느끼며 삽질 시작!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java로 배치 처리를 하다 보면 스트림(stream().forEach())나 람다식을 사용할 일이 참 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 스케줄러 내부, 람다식 사용에서 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 람다식을 통해 코어 로직을 호출하고 해당 로직에서 예외가 발생할 경우 예외와 함께 알림 처리를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모니터링을 통해 점진적으로 처리량을 늘려가야 할테지만, 현재 한번에 처리되는 데이터의 양은 200건으로 설정되어 있고, 그럴 일은 없겠지만 최악의 경우 200건 모두 예외가 발생하면 알림 또한 200개가 전송될 여지가 있기 때문에 예외를 누적하여 단건의 알림만 전송되도록 로직을 수정하게 됐다.&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;알림을 통해 모든 예외 내용을 한 눈에 파악하긴 힘들고, 어차피 로그나 DB를 확인해야 하니 예외는 최초 예외만 포함해서 알림 메시지를 구성하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;pre id=&quot;code_1750255220134&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Exception firstErrorException = null;
list.forEach(item -&amp;gt; {
    try {
        process(item);
    } catch (Exception e) {
        firstErrorException = e; // 컴파일 에러!
    }
});&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;위와 같이 최초 예외를 보관하기 위해 외부에 Exception 변수를 선언하고, 람다 안에서 해당 변수의 초기화를 진행했다.&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;&quot;변수는 final이거나 effectively final이어야 한다&quot;&lt;/b&gt;는 친절한(?) 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 잘못된 코드와 컴파일 에러 메시지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 컴파일러는 Local veriable firstErrorException defined in an enclosing scope must be final or effectively final 이라는 에러 메시지를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;람다식 안에서는 외부의 일반 변수에 값을 다시 할당할 수 없다는 것!!!!!!!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 람다/스트림에서 외부 변수 값 변경이 불가능한 이유&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자바의 람다 캡처(closure) 원칙&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 람다는 외부(감싸고 있는) 메서드의 지역 변수를 &lt;b&gt;&quot;캡처&quot;&lt;/b&gt;해서 내부에서 쓸 수 있게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 변수는 &lt;b&gt;&quot;final&quot; 혹은 &quot;effectively final(사실상 최종값)&quot;만 허용!&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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다식이 실행되는 시점에는, 외부 변수의 라이프사이클(생명 주기)이 이미 끝났을 수도 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 비유하자면, 람다는 &lt;b&gt;&quot;외부 변수의 복사본&quot;&lt;/b&gt;을 안고 들어가기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 값이 계속 바뀔 수 있다면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시성 문제&lt;/li&gt;
&lt;li&gt;예측 불가능한 결과&lt;/li&gt;
&lt;li&gt;의도치 않은 Side Effect&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;그래서 변경이 불가능한(final, effectively final) 변수만 허용하는거다.&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;&quot;final&quot; 혹은 &quot;effectively final&quot;이란?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;final:&lt;/b&gt; 선언 시점에 &quot;한 번만 값이 할당&quot;되고, 바뀌지 않는 변수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;effectively final:&lt;/b&gt; final 키워드는 안 붙었지만, 실제로 값이 한 번도 바뀌지 않는 변수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다/스트림에서는 final 또는 effectively final 변수만 읽기 가능, 값 변경은 불가다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 가능한 해결방법과 각각의 장단점&lt;/b&gt;&lt;/h2&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;final 배열(혹은 컬렉션) 활용&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750255724979&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final Exception[] firstException = {null};
list.forEach(item -&amp;gt; {
    try {
        process(item);
    } catch (Exception e) {
        firstException[0] = e;
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 쉽게 적용 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 배열 인덱스로 값을 넣고 빼는 코드가 다소 직관적이지 않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;AtomicReference 활용&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750255891704&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AtomicReference&amp;lt;Exception&amp;gt; firstException = new AtomicReference&amp;lt;&amp;gt;(null);
list.forEach(item -&amp;gt; {
    try {
        process(item);
    } catch (Exception e) {
        firstException.compareAndSet(null, e);
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 람다/스트림에서도 가독성 좋게 값을 변경 가능하고, 동시성에도 안전하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; AtomicReference를 처음 보는 사람에게는 생소할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;for-each(전통적 반복문)로 대체&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750255965378&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Exception firstException = null;
for (Item item : list) {
    try {
        process(item);
    } catch (Exception e) {
        firstException = e;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 가장 단순하고 누구나 이해하기 쉽다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 람다식의 장점(함수형 스타일, 메서드 체이닝 등)을 쓸 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 그래서 나의 선택은?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민 끝에 AtomicReference를 활용하는 방식을 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코어 로직의 성공/실패 카운트도 누적해야 하고 최초 발생한 예외도 처리해야 했으며 가독성을 포기할 수 없었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 이외의 나머지 예외를 건너뛰려면 조건문도 들어가야 하니까 메서드 호출 한줄이면 될 코드를 3줄로 만들 필요가 있을까 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Atomic은 겸사겸사 동시성에도 안전하니까  &lt;/p&gt;
&lt;pre id=&quot;code_1750256299889&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum ProcessStatus { SUCCESS, FAIL }

Map&amp;lt;ProcessStatus, Integer&amp;gt; resultCount = new EnumMap&amp;lt;&amp;gt;(ProcessStatus.class);
AtomicReference&amp;lt;Exception&amp;gt; firstException = new AtomicReference&amp;lt;&amp;gt;(null);

list.forEach(item -&amp;gt; {
    try {
        process(item);
        resultCount.merge(ProcessStatus.SUCCESS, 1, Integer::sum);
    } catch (Exception e) {
        resultCount.merge(ProcessStatus.FAIL, 1, Integer::sum);
        firstException.compareAndSet(null, e);
    }
});&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;따라서 성공/실패 카운트는 EnumMap과 merge로 관리하고, 예외는 AtomicReference로 관리하는 패턴을 적용했다(추후 EnumMap과 HashMap의 차이에 대한 내용도 정리도 필요할 것 같...  ).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;아참!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하면서 궁금했던 점!&lt;/p&gt;
&lt;pre id=&quot;code_1750256562239&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AtomicReference&amp;lt;Exception&amp;gt; firstException = new AtomicReference&amp;lt;&amp;gt;(null);&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;AtomicReference&amp;lt;Exception&amp;gt;을 생성할 때 null로 초기화하는 이유가 궁금해서 찾아봤다!&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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AtomicReference&amp;lt;Exception&amp;gt;은 Exception 객체(혹은 아무 객체나)를 저장하는 참조형 래퍼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치, 반복문, 람다 등에서 &lt;b&gt;&quot;예외가 발생하지 않았다&quot;는 상태를 &quot;null(아직 예외가 없다)&quot;로 표현한 것&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;위의 나의 선택에서 사용한 것처럼 최초 한 번만 예외를 저장하고 싶어 compareAndSet을 사용하게 된다면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에 firstException은 null로 초기화&lt;/li&gt;
&lt;li&gt;처음 예외가 발생하면 null에서 e로 바뀜&lt;/li&gt;
&lt;li&gt;그 이후 발생하는 예외는 이미 값이 있으므로 저장되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;compareAndSet(expectedValue, newValue)의 의미&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 인자(expectedValue): 현재 값이 이것과 같다면...&lt;/li&gt;
&lt;li&gt;두 번째 인자(newValue): &quot;해당 값으로 바꿔라&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 의미로 firstException의 현재 값이 null이면 e(예외 객체)로 값을 변경하기 때문에 생성할 때 null로 초기화하는 것이다.&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;AtomicReference&amp;lt;Exception&amp;gt;의 초기값을 null로 두는건 &quot;아직 예외가 발생하지 않았다&quot;는 상태를 표현하고, 최초로 예외가 생겼을 때만 값을 저장하기 위함&lt;/b&gt;으로 이해하면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 삽질은 어제의 삽질보다 짧았다   괜히 뿌듯하군  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;END.&lt;/b&gt;&lt;/h2&gt;</description>
      <category>Java</category>
      <category>atomicreference</category>
      <category>compareandset</category>
      <category>local veriable firsterrorexception defined in an enclosing scope must be final or effectively final</category>
      <category>람다/스트림에서 외부 변수 값 변경</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/110</guid>
      <comments>https://jinalim-dev.tistory.com/110#entry110comment</comments>
      <pubDate>Wed, 18 Jun 2025 23:37:01 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스케줄러에서 ShedLock 실행 누락, ThreadPoolTaskScheduler, 그리고 millisecond의 함정</title>
      <link>https://jinalim-dev.tistory.com/109</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 도메인 서비스를 개발하면서 데이터 처리, 재처리 등을 위한 스케줄러를 구현하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄러는 이전 직장에서도 자주 구현해왔던지라 익숙했지만, 다중 인스턴스 환경에서의 구현은 처음이고 ThreadPoolTaskScheduler, ShedLock 활용도 처음이라 의도한대로 동작하는지 출근하면 로그를 모니터링하는 습관이 생길 정도였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 왜 슬픈 예감은 틀린 적이 없는지...  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 주기의 스케줄러 로그가 누락되는 현상을 두 눈으로 확인한 후에 내 머릿속은 바빠지기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 1개의 로그가 안찍혔지? 왜 2개만 로그가 찍힌거지? 내가 잘못 본건가?  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 시스템 구성 및 문제 상황&lt;/b&gt;&lt;/h2&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;ThreadPoolTaskScheduler를 활용해 스케줄러를 관리했고,&lt;/li&gt;
&lt;li&gt;스케줄러 작업마다 @SchedulerLock(ShedLock, DB 기반 분산 락)을 적용했다.&lt;/li&gt;
&lt;li&gt;1분마다 실행되어야 하는 스케줄러가 3개, 3초마다 실행되는 스케줄러가 2개 있었다.&lt;/li&gt;
&lt;li&gt;ThreadPoolTaskScheduler의 pool size는 4로 설정되어 있었다(사내 템플릿 프로젝트의 기본 설정).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는, 1분마다 실행되어야 하는 스케줄러 3개가 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄러가 &lt;b&gt;&quot;매번 정확히 3개가 동시에 실행되어야 하는데, 어떤 시점에는 2개만 실행되고 어떤 시점에는 1개만 실행되는 등 실행 누락이 반복적으로 나타나는 것&quot;&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그로 보면 아래와 같은 상황이 자주 보였다.&lt;/p&gt;
&lt;pre id=&quot;code_1750168371679&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;00:15:00.057 scheduler-1 실행
00:15:00.058 scheduler-2 실행
00:15:00.064 scheduler-3 실행

00:16:00.079 scheduler-2 실행
00:16:00.079 scheduler-3 실행
// scheduler-1 누락

00:22:00.035 scheduler-1 실행
00:22:00.035 scheduler-3 실행
00:22:00.035 scheduler-2 실행

00:23:00.055 scheduler-1 실행
// scheduler-3 누락
// scheduler-2 누락&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;즉, 3개의 스케줄러가 1분마다 반드시 실행되어야 하는데, 간헐적으로 한두개가 누락되는 현상이 계속 반복되었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 첫 번째 시도: ThreadPoolTaskScheduler의 pool size 증가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 ThreadPoolTaskScheduler의 pool size가 부족해서 동시에 여러 스케줄러가 실행될 때 스레드 할당이 안되어 누락되는건 아닐까 의심했다.&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;pool size가 4로 설정되어 있었고, 1분마다 실행되는 스케줄러가 3개, 3초마다 실행되는 스케줄러가 2개이니 최대 5개가 동시에 겹치는 경우 pool size가 부족할 수도 있다고 판단했다.&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;그래서 pool size를 5로 늘려서 배포해봤지만, 문제는 여전히 해결되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 패턴이 그대로 반복되었고, 여전히 스케줄러가 누락되는 일이 사라지지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ThreadPoolTaskScheduler는 pool size만큼의 스레드를 미리 생성해두고,&lt;/li&gt;
&lt;li&gt;동시에 여러 예약 작업을 실행할 때 각 작업에 빈 스레드를 할당해준다.&lt;/li&gt;
&lt;li&gt;pool size가 넉넉하면, 단순 스레드 경쟁 때문에 스케줄러가 누락되는 일은 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 작업 자체가 아주 짧게 끝나고 있어서 pool size 부족 문제가 아니라는 점을 다시 한 번 확인하게 되었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 원인 분석: ShedLock(@SchedulerLock) 설정과 실행 타이밍&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ThreadPoolTaskScheduler의 설정에는 문제가 없다는 결론에 도달하면서 자연스럽게 @SchedulerLock(ShedLock)의 설정을 다시 들여다보게 되었다.&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;@SchedulerLock의 기본 원리&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 스케줄러마다 name을 지정해서 DB에 락을 건다.&lt;/li&gt;
&lt;li&gt;락을 잡은 인스턴스만 해당 작업을 수행하고, 락이 풀릴 때까지는 같은 name의 다른 작업(심지어 같은 인스턴스라도)은 실행되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;lockAtLeastFor가 핵심&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 상황에서는 각 스케줄러의 @SchedulerLock에 lockAtLeastFor = &quot;PT1M&quot;, lockAtMostFor = &quot;PT2M&quot;을 설정했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정의 의미는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;작업이 아무리 빨리 끝나도 락은 최소 1분 동안 유지된다&lt;/b&gt;는 것이다.&lt;/li&gt;
&lt;li&gt;즉, 스케줄러가 1분마다 실행된다고 하더라도, 락이 1분간 유지되고 있는 동안에는 ShedLock이 락을 잡지 못해 작업이 스킵(누락)된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 실행 누락의 진짜 원인: 밀리초(millisecond) 오차와 락 해제 타이밍&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 실제 로그를 보면 &quot;정확히 1분이 지난 시점&quot;에 다음 작업이 실행을 시도하는데도 락을 못 잡는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;00:15:00.057에 작업이 실행되고 락이 걸림 (lock_until = 00:16:00.057)&lt;/li&gt;
&lt;li&gt;00:16:00.079에 다음 작업이 락을 잡으려고 시도 -&amp;gt; 분명 1분이 지난 시점인데, 락을 못 잡고 실행이 누락된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 무엇일까?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실제 락 만료 타이밍과 트리거 오차&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락 해제 타이밍(lock_until)과, 실제 @Scheduled가 트리거되는 타이밍 사이에는 서버/DB 시스템간 millisecond 단위의 clock drift(시계 오차), 또는 DB write/read 지연, JVM GC/스레드 스케줄링 오차 등 다양한 현실적 요소로 인해 정확히 1분이 보장되지 않는다.&lt;/li&gt;
&lt;li&gt;예를 들어 DB lock_until 값이 00:16:00.057인데, 애플리케이션의 시스템 시계가 DB보다 10~20ms 느리거나, DB와 네트워크 전송/처리 지연 등으로 실제 lock_until보다 미묘하게 오차가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;이럴 경우, 00:16:00.079에 락을 잡으러 왔지만 DB에서는 lock_until보다 아직 current_time이 작거나 같다고 판단, 락 획득에 실패하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ShedLock의 락 체크 로직&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ShedLock은 락을 잡으려 할 때 현재 시간(current_time)이 lock_until보다 &quot;확실히 큰 경우&quot;에만 락을 획득한다.&lt;/li&gt;
&lt;li&gt;1ms라도 current_time이 lock_until보다 작거나 같으면 락을 잡지 못하고 바로 실행이 누락된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 아주 미세한 차이(0.01초, 0.02초)가 실제 스케줄러의 실행 누락으로 이어질 수 있다.&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;실제로 ShedLock 테이블의 갱신되는 정보를 확인하기 위해 새로고침을 여러번 해본 결과 누락된 스케줄러의 시간만 갱신이 되지 않고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 최종 해결: lockAtLeastFor 설정값 조정&lt;/b&gt;&lt;/h2&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;lockAtLeastFor 설정값을 스케줄러 주기(1분)보다 더 짧게, 스케줄러 데이터 처리 예상 시간을 고려하여 PT2S로 조정하여 락이 항상 다음 실행 트리거 전에 미리 해제될 수 있도록 했다.&lt;/li&gt;
&lt;li&gt;실제로 lockAtLeastFor를 2초로 조정한 뒤에는 스케줄러가 빠짐없이 1분마다 모두 정상적으로 실행되는 것을 로그로 확인할 수 있었다.  &amp;zwj; &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lockAtLeastFor는 &lt;b&gt;&quot;실제 작업 소요 시간 + 1~2초 여유&quot; 정도로만 설정&lt;/b&gt;하고, &lt;b&gt;lockAtMostFor는 장애 복구를 고려해 넉넉하게 설정&lt;/b&gt;하는 것이 효율적인 듯하다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 이번 삽질을 통해 배운 점을 정리해보자!&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ThreadPoolTaskScheduler의 pool size는 단순히 스레드 경쟁 문제만을 해결할 뿐, lockAtLeastFor 등 스케줄러 락 설정 이슈까지 해결해주지 않는다.  &lt;/li&gt;
&lt;li&gt;@SchedulerLock의 lockAtLeastFor 설정값이 실행 주기와 같거나 더 길면 DB/서버의 millisecond 단위 오차, 네트워크 지연 등 여러 현실적 변수로 인해 스케줄러 실행이 주기적으로 누락될 수 있다.  &lt;/li&gt;
&lt;li&gt;정확히 &quot;1분&quot; 같은 딱 맞는 수치는 항상 위험하며, lockAtLeastFor를 스케줄러 주기보다 몇 초 이상 충분히 작게 잡는 것이 안전하다.  &lt;/li&gt;
&lt;li&gt;ShedLock/분산 락 기반 스케줄러를 사용할 때는 시스템간 시계 동기화(NTP 등), DB/서버 간 시간차, 네트워크 상황 등 현실적인 인프라 환경을 반드시 고려해야 한다.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 설계와 개발도 아직 경험이 많이 부족하지만, 이런 수치를 결정하는 것에 대한 경험은 전무하다 보니 이런 결과를 초래하게 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 이런 시행착오를 겪으며 성장해나가리라 믿는다!!!!!!!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘의 삽질 일기는 여기서 이만  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;END.&lt;/b&gt;&lt;/h2&gt;</description>
      <category>Spring</category>
      <category>@schedulerlock</category>
      <category>lockatleastfor</category>
      <category>shedlock</category>
      <category>threadpooltaskscheduler</category>
      <category>스케줄러실행누락</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/109</guid>
      <comments>https://jinalim-dev.tistory.com/109#entry109comment</comments>
      <pubDate>Tue, 17 Jun 2025 23:30:36 +0900</pubDate>
    </item>
    <item>
      <title>[Error] java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to...</title>
      <link>https://jinalim-dev.tistory.com/108</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서는 공통 인터페이스에 제네릭을 사용해 도메인마다 다른 타입을 주입받는 구조를 자주 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조도 깔끔하고 재사용성도 높아서 자주 쓰이는 방식인데, 문제는 FeignClient에 이 구조를 적용하려고 할 때 발생한다.&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;실무로는 처음 다뤄보는 FeignClient, 하루하루를 고군분투하는 이제 막 입사한 한달차 새내기 개발자는 이 구조가 적용되어 있다는 사실을 인지하지 못한채 로컬 테스트 도중 예외를 만나게 되었고, 디버깅과 구글링으로 삽질을 하다 깨달음에 도달했다.&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;따라서 이번 글에서는 삽질의 깨달음을 잊지 않기 위해서 FeignClient에서 제네릭 타입이 유지되지 않는 이유, 그로 인해 만났던 예외, 이를 해결하기 위한 방법에 대해 정리하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 상황: ResponseEntity&amp;lt;Page&amp;gt; 응답에서 캐스팅 예외 발생&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1746713807859&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ResponseEntity&amp;lt;Page&amp;lt;ResponseDTO&amp;gt;&amp;gt; response = feignClient.getPagedData();&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;FeignClient에서 위와 같이 응답을 받아 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746714059348&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.dto.ResponseDTO...&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;그리고 응답 바디를 꺼내서 Page.getContent()로 아이템을 순회하려고 했더니 위와 같은 예외가 발생했다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제의 핵심&lt;/b&gt;은 ResponseDTO로 역직렬화되길 기대했던 객체가 실제로는 Gson의 기본 Map 타입인 LinkedTreeMap으로 반환되었다는 점이었다. 이건 Java의 제네릭 타입 소거(Type Erasure)와 Gson의 동작 방식이 맞물리면서 생긴 런타임 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 왜 이런 시련이 생긴건데?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java는 제네릭 타입을 컴파일 타임까지만 유지하고, 런타임에는 모두 소거된다. 즉, Page&amp;lt;ResponseDTO&amp;gt;는 런타임에 단순한 Page로만 인식된다는 의미이다.&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;FeignClient는 내부적으로 JSON을 역직렬화할 때 Gson(혹은 Jackson)을 사용하는데, 이 과정에서 제네릭 타입 정보가 사라졌기 때문에 내부의 ResponseDTO는 역직렬화되지 않고 기본 타입인 LinkedTreeMap으로 변환된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래와 같은 구조로 데이터를 직접 꺼내고 캐스팅하려고 하면 예외가 발생한다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746714519762&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final Page&amp;lt;ResponseDTO&amp;gt; responsePage = response.getBody();
responsePage.getContent().forEach(item -&amp;gt; &quot;비즈니스 로직 처리 중&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 문제 해결 전략: Page&amp;lt;?&amp;gt; -&amp;gt; ObjectMapper 수동 변환&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 응답을 받을 때 명확한 제네릭 타입을 지정하지 않고, 먼저 Page&amp;lt;?&amp;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;1단계: Page&amp;lt;?&amp;gt;로 바디 받기&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1746714705493&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final ResponseEntity&amp;lt;Page&amp;lt;ResponseDTO&amp;gt;&amp;gt; response = feignClient.getPagedData();
final Page&amp;lt;?&amp;gt; rawPage = response.getBody();&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;이 시점에서 rawPage.getContent()에 있는 요소들은 모두 LinkedTreeMap이다.&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;2단계: ObjectMapper로 content 리스트를 수동 변환&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1746714891926&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;ResponseDTO&amp;gt; content = rawPage.stream()
    .map(item -&amp;gt; objectMapper.convertValue(item, ResponseDTO.class))
    .collect(Collectors.toList());&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;이렇게 하면 LinkedTreeMap을 기대한 ResponseDTO 타입으로 안전하게 변환할 수 있다. 핵심은 &lt;b&gt;ObjectMapper의 convertValue를 이용해 수동 역직렬화를 수행하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 제네릭 타입을 그대로 믿지 말자!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FeignClient와 제네릭을 함께 사용하면 겉보기에는 타입이 잘 유지될 것 같지만, 실제로 IDE에서도 유지되는 것으로 보여줬다구  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제로는 런타임 타입 소거로 인해 역직렬화 문제가 발생할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Page&amp;lt;T&amp;gt;, List&amp;lt;T&amp;gt; 같은 제네릭 컬렉션을 직접 응답으로 받을 때&lt;/li&gt;
&lt;li&gt;FeignClient 내부에서 Gson 또는 Jackson이 사용하는 디폴트 역직렬화 타입이 Map일 때&lt;/li&gt;
&lt;li&gt;Gson 사용시 기본적으로 LinkedTreeMap, Jackson 사용시 LinkedHashMap으로 매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 땐, 제네릭을 포기하고, Object로 받은 다음, 명시적으로 타입을 변환해주는 접근이 훨씬 안전하려나  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;END.&lt;/b&gt;&lt;/h2&gt;</description>
      <category>Error</category>
      <category>feignClient</category>
      <category>java.lang.classcastexception: com.google.gson.internal.linkedtreemap cannot be cast to</category>
      <category>런타임 타입 소거</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/108</guid>
      <comments>https://jinalim-dev.tistory.com/108#entry108comment</comments>
      <pubDate>Thu, 8 May 2025 23:45:48 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] AWS ElasticCache를 활용하자! (2)</title>
      <link>https://jinalim-dev.tistory.com/98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자, 드디어 Redis의 마지막 챕터에 도달했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3일을 Redis를 위해 달려왔다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 계기로 나도 Redis를 활용해서 조회 성능을 개선할 수 있는 개발자로 성장하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. ElasticCache 잘 생성된거 맞아?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 대시보드에 들어가서 상태를 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.03.35.png&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo1IQ3/btsKj1H0n4r/Yk3UHrd3de8cBMqd6boFc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo1IQ3/btsKj1H0n4r/Yk3UHrd3de8cBMqd6boFc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo1IQ3/btsKj1H0n4r/Yk3UHrd3de8cBMqd6boFc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo1IQ3%2FbtsKj1H0n4r%2FYk3UHrd3de8cBMqd6boFc1%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;1263&quot; height=&quot;440&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.03.35.png&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;440&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;상태가 Avaliable로 변경된 걸 보니 잘 생성된 것 같다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. EC2에서 ElastiCache 접속해보자!&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.05.17.png&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drgwQc/btsKkGbWCEW/xRPA3kFLXXq9xZSkClx6w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drgwQc/btsKkGbWCEW/xRPA3kFLXXq9xZSkClx6w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drgwQc/btsKkGbWCEW/xRPA3kFLXXq9xZSkClx6w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrgwQc%2FbtsKkGbWCEW%2FxRPA3kFLXXq9xZSkClx6w1%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;1266&quot; height=&quot;764&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.05.17.png&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;764&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;기본 엔드포인트에서 Port만 빼고 복사하자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 엔드포인트: 모든 권한을 가지고 있는 주소&lt;/li&gt;
&lt;li&gt;리더 엔드포인트: 읽기 전용 주소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.07.26.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdngw1/btsKl3Kvn23/RHrccXjGBbaHflB2UC9VkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdngw1/btsKl3Kvn23/RHrccXjGBbaHflB2UC9VkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdngw1/btsKl3Kvn23/RHrccXjGBbaHflB2UC9VkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcdngw1%2FbtsKl3Kvn23%2FRHrccXjGBbaHflB2UC9VkK%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;914&quot; height=&quot;53&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.07.26.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729876105861&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# EC2에서 ElastiCache 접속하는 명령어
redis-cli -h instagram-cache-server.iaxvmk.ng.0001.apn2.cache.amazonaws.com

# ElastiCache 통신 확인 명령어
ping&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 주소의 Redis에 접속하기 위해서는 -h 옵션을 붙여줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 로컬에서도 접속이 될까?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.09.48.png&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;19&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GMPz8/btsKk6BuVBa/oVTtbrm10jkMlj9KCqPee1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GMPz8/btsKk6BuVBa/oVTtbrm10jkMlj9KCqPee1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GMPz8/btsKk6BuVBa/oVTtbrm10jkMlj9KCqPee1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGMPz8%2FbtsKk6BuVBa%2FoVTtbrm10jkMlj9KCqPee1%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;764&quot; height=&quot;19&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.09.48.png&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;19&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서는 EC2 ElastiCache에 접속되지 않는다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이유&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElastiCache는 같은 VPC에서만 접속이 가능하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 그럼 EC2랑 ElastiCache랑 VPC가 같아?&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;EC2의 VPC&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.16.03.png&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/orbQH/btsKk3Y3L7T/dWCUwRxP5qJRpETcl5YqF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/orbQH/btsKk3Y3L7T/dWCUwRxP5qJRpETcl5YqF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/orbQH/btsKk3Y3L7T/dWCUwRxP5qJRpETcl5YqF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2ForbQH%2FbtsKk3Y3L7T%2FdWCUwRxP5qJRpETcl5YqF1%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;967&quot; height=&quot;506&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.16.03.png&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;506&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ElastiCache의 VPC&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.15.46.png&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUg006/btsKlOGPRvS/nIRMKYJmmKJ1YoNgOVUCMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUg006/btsKlOGPRvS/nIRMKYJmmKJ1YoNgOVUCMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUg006/btsKlOGPRvS/nIRMKYJmmKJ1YoNgOVUCMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUg006%2FbtsKlOGPRvS%2FnIRMKYJmmKJ1YoNgOVUCMK%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;867&quot; height=&quot;326&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.15.46.png&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;326&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;VPC가 같았군  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Spring Boot랑 연결하자!&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;application.yml 수정&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1729876859824&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# prod 환경
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://instagram-db.cfqanhnkrusz.ap-northeast-2.rds.amazonaws.com:3306/mydb
    username: admin
    password: password
  data:
    redis:
      host: instagram-cache-server.iaxvmk.ng.0001.apn2.cache.amazonaws.com
      port: 6379&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;배포 환경에 redis 연동 정보를 추가하고 ElastiCache의 기본 엔드포인트 정보를(Port 제거) 작성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2에 배포하기 위해서 Github Repository에 Push하고 EC2에서 Pull 받자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. Spring Boot 프로젝트 실행&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElastiCache는 Docker로 띄우지 않을거기 때문에 기존 방식으로 실행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.35.45.png&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyx8JF/btsKljggpKH/CR5qujuKG8Rf1XDvsMOHe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyx8JF/btsKljggpKH/CR5qujuKG8Rf1XDvsMOHe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyx8JF/btsKljggpKH/CR5qujuKG8Rf1XDvsMOHe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdyx8JF%2FbtsKljggpKH%2FCR5qujuKG8Rf1XDvsMOHe1%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;1214&quot; height=&quot;459&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.35.45.png&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729877866880&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 테스트 생략하고 Spring Boot 빌드하는 명령어
./gradlew clean build -x test

# 빌드 파일이 있는 곳으로 디렉토리 이동하는 명령어
cd build/libs

# Spring Boot 실행하는 명령어
java -jar -Dspring.profiles.active=prod redis-in-spring-0.0.1-SNAPSHOT.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. API 테스트가 빠질 수 없지&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.40.58.png&quot; data-origin-width=&quot;2490&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uRA7O/btsKlE5gfHs/sE1lh6ieHBcaAo5SVLPMUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uRA7O/btsKlE5gfHs/sE1lh6ieHBcaAo5SVLPMUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uRA7O/btsKlE5gfHs/sE1lh6ieHBcaAo5SVLPMUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuRA7O%2FbtsKlE5gfHs%2FsE1lh6ieHBcaAo5SVLPMUK%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;2490&quot; height=&quot;622&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.40.58.png&quot; data-origin-width=&quot;2490&quot; data-origin-height=&quot;622&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;/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;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.40.41.png&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ea8dPj/btsKku3LCaf/dw1d4hZ3GCg8TfKS7ofNA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ea8dPj/btsKku3LCaf/dw1d4hZ3GCg8TfKS7ofNA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ea8dPj/btsKku3LCaf/dw1d4hZ3GCg8TfKS7ofNA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fea8dPj%2FbtsKku3LCaf%2Fdw1d4hZ3GCg8TfKS7ofNA0%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;1211&quot; height=&quot;103&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.40.41.png&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;103&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;최초 요청시 Redis에 데이터가 없어 데이터베이스를 통해 데이터를 취득하고 Redis에 해당 데이터를 저장한 것을 확인할 수 있다.&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;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.42.39.png&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AgFeg/btsKlxefW8s/V8wVoB73GPZZ8GypeZT3jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AgFeg/btsKlxefW8s/V8wVoB73GPZZ8GypeZT3jK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AgFeg/btsKlxefW8s/V8wVoB73GPZZ8GypeZT3jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAgFeg%2FbtsKlxefW8s%2FV8wVoB73GPZZ8GypeZT3jK%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;1210&quot; height=&quot;104&quot; data-filename=&quot;스크린샷 2024-10-26 오전 2.42.39.png&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;104&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;두번째 요청시 기존에 저장되어 있는 데이터가 Redis에 존재해 데이터베이스 SQL이 찍히지 않은 것을 알 수 있다.&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;이로써 ElastiCache 도 Cache Aside 전략의 흐름대로 동작하는 것을 알 수 있게 되었다.  &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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729878404780&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;비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편) 강의 | JSCODE 박재성 - 인프런&quot; data-og-description=&quot;JSCODE 박재성 | 비전공자 입장에서도 쉽게 이해할 수 있고, 실전에서 바로 적용 가능한 'Redis 입문/실전 (조회 성능 최적화편)' 강의를 만들어봤습니다!,   에라이, 못 해먹겠네!비전공자로 개발&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; data-og-url=&quot;https://www.inflearn.com/course/비전공자-redis-입문-성능-최적화&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3PQlY/hyXlQ7jGhY/oTyWD8s8VCGh1Z4Hiyu1l1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/qjxfp/hyXpEqAQML/aak7J29n2koy9BNxlWl9K1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/D3wtp/hyXlVguMTN/U71Hiq5y0nSm7HMpkTHrO0/img.png?width=736&amp;amp;height=479&amp;amp;face=0_0_736_479&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3PQlY/hyXlQ7jGhY/oTyWD8s8VCGh1Z4Hiyu1l1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/qjxfp/hyXpEqAQML/aak7J29n2koy9BNxlWl9K1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/D3wtp/hyXlVguMTN/U71Hiq5y0nSm7HMpkTHrO0/img.png?width=736&amp;amp;height=479&amp;amp;face=0_0_736_479');&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;비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편) 강의 | JSCODE 박재성 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JSCODE 박재성 | 비전공자 입장에서도 쉽게 이해할 수 있고, 실전에서 바로 적용 가능한 'Redis 입문/실전 (조회 성능 최적화편)' 강의를 만들어봤습니다!,   에라이, 못 해먹겠네!비전공자로 개발&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Redis</category>
      <category>aws elasticache 활용</category>
      <category>ec2 elasticache 접속</category>
      <category>spring boot + elasticache</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/98</guid>
      <comments>https://jinalim-dev.tistory.com/98#entry98comment</comments>
      <pubDate>Sat, 26 Oct 2024 02:48:49 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] AWS ElasticCache를 활용하자! (1)</title>
      <link>https://jinalim-dev.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현업에서는 EC2에 Redis를 직접 설치해서 사용하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 RDS를 활용하는 것처럼 Redis도 AWS의 ElastiCache를 활용해서 사용하고 있다고 한다.&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;따라서 AWS ElasticCache를 마지막으로 활용해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 아키텍처 구성&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이전 아키텍처&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.webp&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n1HuX/btsKk3xUF1Z/72zWBKmedTPDQ3tcRVRsB1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n1HuX/btsKk3xUF1Z/72zWBKmedTPDQ3tcRVRsB1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n1HuX/btsKk3xUF1Z/72zWBKmedTPDQ3tcRVRsB1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn1HuX%2FbtsKk3xUF1Z%2F72zWBKmedTPDQ3tcRVRsB1%2Fimg.webp&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;497&quot; height=&quot;311&quot; data-filename=&quot;1.webp&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 아키텍처 구조는 취준생 레벨 또는 비용을 극단적으로 관리해야 하는 초기 스타트업에 적합한 구성이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ElasticCache 도입 아키텍처&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.webp&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9gTUp/btsKldtxRfL/QayzqYkS03psAMCZ7eWpOK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9gTUp/btsKldtxRfL/QayzqYkS03psAMCZ7eWpOK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9gTUp/btsKldtxRfL/QayzqYkS03psAMCZ7eWpOK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9gTUp%2FbtsKldtxRfL%2FQayzqYkS03psAMCZ7eWpOK%2Fimg.webp&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;498&quot; height=&quot;350&quot; data-filename=&quot;2.webp&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이런 식으로 구성하는게 실제 서비스 현업에 가까운 아키텍처 구조라 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 현업에 가까운 배포 환경을 경험해야 하지 않겠는가?&lt;/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;지금부터 AWS 인프라 구성을 변경해서 ElastiCache를 도입해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. ElastiCache 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.17.28.png&quot; data-origin-width=&quot;1265&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciEZgm/btsKkoI9Af6/CLutFR9jmfOt0KIBmzRmU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciEZgm/btsKkoI9Af6/CLutFR9jmfOt0KIBmzRmU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciEZgm/btsKkoI9Af6/CLutFR9jmfOt0KIBmzRmU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciEZgm%2FbtsKkoI9Af6%2FCLutFR9jmfOt0KIBmzRmU1%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;1265&quot; height=&quot;295&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.17.28.png&quot; data-origin-width=&quot;1265&quot; data-origin-height=&quot;295&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;홈페이지에서 ElastiCache를 검색해서 사이트를 이동하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.18.25.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsx0Hu/btsKlGvesrl/S1AsL8WqGLh5N4YkRZ5lpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsx0Hu/btsKlGvesrl/S1AsL8WqGLh5N4YkRZ5lpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsx0Hu/btsKlGvesrl/S1AsL8WqGLh5N4YkRZ5lpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsx0Hu%2FbtsKlGvesrl%2FS1AsL8WqGLh5N4YkRZ5lpk%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;1250&quot; height=&quot;342&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.18.25.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;342&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;지금 시작을 클릭하고 Redis OSS를 선택하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.21.08.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjUFo3/btsKlb3APpz/kwRP79s3pk7SpYOIKz93yK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjUFo3/btsKlb3APpz/kwRP79s3pk7SpYOIKz93yK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjUFo3/btsKlb3APpz/kwRP79s3pk7SpYOIKz93yK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjUFo3%2FbtsKlb3APpz%2FkwRP79s3pk7SpYOIKz93yK%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;1250&quot; height=&quot;658&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.21.08.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;658&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;/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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터는 여러 개의 Cache 서버들을 묶고 있는 하나의 그룹을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 하나의 Cache 서버를 노드(Node)라고 부른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.webp&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgq5uU/btsKkdBl1gN/1Y7tzAKzSNT8VUYMzNbdo0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgq5uU/btsKkdBl1gN/1Y7tzAKzSNT8VUYMzNbdo0/img.webp&quot; data-alt=&quot;출처: AWS 공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgq5uU/btsKkdBl1gN/1Y7tzAKzSNT8VUYMzNbdo0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgq5uU%2FbtsKkdBl1gN%2F1Y7tzAKzSNT8VUYMzNbdo0%2Fimg.webp&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;1626&quot; height=&quot;510&quot; data-filename=&quot;3.webp&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: AWS 공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.26.43.png&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvoHAa/btsKjYEuz6i/rZX0JdPApkoaVhjkYAqmhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvoHAa/btsKjYEuz6i/rZX0JdPApkoaVhjkYAqmhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvoHAa/btsKjYEuz6i/rZX0JdPApkoaVhjkYAqmhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvoHAa%2FbtsKjYEuz6i%2FrZX0JdPApkoaVhjkYAqmhk%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;1252&quot; height=&quot;335&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.26.43.png&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;335&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이번에도 강사님 정하신 instagram-cache-server로 정했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.30.16.png&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnlM3q/btsKlWECKtI/2P69AJDr0GO1zBW6wYeGRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnlM3q/btsKlWECKtI/2P69AJDr0GO1zBW6wYeGRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnlM3q/btsKlWECKtI/2P69AJDr0GO1zBW6wYeGRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnlM3q%2FbtsKlWECKtI%2F2P69AJDr0GO1zBW6wYeGRk%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;1252&quot; height=&quot;584&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.30.16.png&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;584&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;위치는 AWS 클라우드를 선택하고 다중 AZ는 체크를 해제, 자동 장애 조치는 사용하는 것으로 선택한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다중 AZ와 자동 장애 조치에 대해서 알고 넘어가자&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중 AZ: 리전에 재난이 발생해 서비스가 중단될 수 있는 상황을 미연에 방지하고나 여러 리전에 Cache 서버를 분산해 세팅해두는 것을 의미한다.&lt;/li&gt;
&lt;li&gt;자동 장애 조치: failover라고도 하는데 클러스터 내부에서 특정 노드에 장애가 발생하면 정상 노드로 교체해주는 기능으로, 자동으로 장애를 처리해주는 기능이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.34.09.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCyAV/btsKjMYnfnb/SbjaZUNix1J9YuKZoidcKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCyAV/btsKjMYnfnb/SbjaZUNix1J9YuKZoidcKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCyAV/btsKjMYnfnb/SbjaZUNix1J9YuKZoidcKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCyAV%2FbtsKjMYnfnb%2FSbjaZUNix1J9YuKZoidcKK%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;1250&quot; height=&quot;719&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.34.09.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;719&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;클러스터 설정 부분에서 노드 유형을 t3.micro로 설정해준다. 프리티어이기 때문에!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제본 개수는 클러스터 내 Cache 서버의 개수를 의미하는데 0으로 지정할 경우 failover 처리가 불가능하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElastiCache는 노드(Node)당 가격을 책정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 failover는 1개 이상의 복제본 개수를 선택해야 처리가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소한의 비용으로 테스트를 진행하기 위해 복제본 개수를 0으로 지정했지만, 실제로는 1개 이상 만드는 경우가 많다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.38.45.png&quot; data-origin-width=&quot;1249&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IsJhR/btsKlw0IJ9x/HB7d6gkBEVYF4uENmIVea0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IsJhR/btsKlw0IJ9x/HB7d6gkBEVYF4uENmIVea0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IsJhR/btsKlw0IJ9x/HB7d6gkBEVYF4uENmIVea0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIsJhR%2FbtsKlw0IJ9x%2FHB7d6gkBEVYF4uENmIVea0%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;1249&quot; height=&quot;600&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.38.45.png&quot; data-origin-width=&quot;1249&quot; data-origin-height=&quot;600&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분도 나는 강사님이 정하신 instagram-subnet-group으로 정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 설정은 다 했으니 이제 다음 단계로 넘어가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ElastiCache에 접근할 수 있도록 보안 그룹을 설정해줘야 해서 EC2로 이동해서 보안 그룹을 생성하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.43.10.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vWrvh/btsKlmKOzfv/KnXMM7fkWH5DOt6Xh0vUp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vWrvh/btsKlmKOzfv/KnXMM7fkWH5DOt6Xh0vUp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vWrvh/btsKlmKOzfv/KnXMM7fkWH5DOt6Xh0vUp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWrvh%2FbtsKlmKOzfv%2FKnXMM7fkWH5DOt6Xh0vUp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1264&quot; height=&quot;289&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.43.10.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;289&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.45.59.png&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mVXAv/btsKjLkP3b1/30JTYerot6xvZUPQIvAhf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mVXAv/btsKjLkP3b1/30JTYerot6xvZUPQIvAhf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mVXAv/btsKjLkP3b1/30JTYerot6xvZUPQIvAhf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmVXAv%2FbtsKjLkP3b1%2F30JTYerot6xvZUPQIvAhf0%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;1251&quot; height=&quot;840&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.45.59.png&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;840&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 인바운드 규칙 소스 부분은 Anywhere-IPv4를 선택한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.48.26.png&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHsiIc/btsKkhpW3Lq/19bsmQlg95flN3sYNKuNhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHsiIc/btsKkhpW3Lq/19bsmQlg95flN3sYNKuNhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHsiIc/btsKkhpW3Lq/19bsmQlg95flN3sYNKuNhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHsiIc%2FbtsKkhpW3Lq%2F19bsmQlg95flN3sYNKuNhk%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;1251&quot; height=&quot;589&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.48.26.png&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;589&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;다시 ElastiCache로 돌아와서 위에 생성한 보안 그룹을 선택해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.49.51.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLCd3z/btsKk7f3I1M/VyOonObQ8l88uohNNqm291/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLCd3z/btsKk7f3I1M/VyOonObQ8l88uohNNqm291/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLCd3z/btsKk7f3I1M/VyOonObQ8l88uohNNqm291/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLCd3z%2FbtsKk7f3I1M%2FVyOonObQ8l88uohNNqm291%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;1250&quot; height=&quot;215&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.49.51.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;215&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;/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;마지막 단계는 검토 및 생성하는 단계로 바로 ElastiCache를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.52.31.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S81TT/btsKkfFJQMR/JYIaBER9HmYwkhr1cWW7Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S81TT/btsKkfFJQMR/JYIaBER9HmYwkhr1cWW7Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S81TT/btsKkfFJQMR/JYIaBER9HmYwkhr1cWW7Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS81TT%2FbtsKkfFJQMR%2FJYIaBER9HmYwkhr1cWW7Fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1264&quot; height=&quot;443&quot; data-filename=&quot;스크린샷 2024-10-26 오전 1.52.31.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;443&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729875220362&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;비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편) 강의 | JSCODE 박재성 - 인프런&quot; data-og-description=&quot;JSCODE 박재성 | 비전공자 입장에서도 쉽게 이해할 수 있고, 실전에서 바로 적용 가능한 'Redis 입문/실전 (조회 성능 최적화편)' 강의를 만들어봤습니다!,   에라이, 못 해먹겠네!비전공자로 개발&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; data-og-url=&quot;https://www.inflearn.com/course/비전공자-redis-입문-성능-최적화&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3PQlY/hyXlQ7jGhY/oTyWD8s8VCGh1Z4Hiyu1l1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/qjxfp/hyXpEqAQML/aak7J29n2koy9BNxlWl9K1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/D3wtp/hyXlVguMTN/U71Hiq5y0nSm7HMpkTHrO0/img.png?width=736&amp;amp;height=479&amp;amp;face=0_0_736_479&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3PQlY/hyXlQ7jGhY/oTyWD8s8VCGh1Z4Hiyu1l1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/qjxfp/hyXpEqAQML/aak7J29n2koy9BNxlWl9K1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/D3wtp/hyXlVguMTN/U71Hiq5y0nSm7HMpkTHrO0/img.png?width=736&amp;amp;height=479&amp;amp;face=0_0_736_479');&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;비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편) 강의 | JSCODE 박재성 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JSCODE 박재성 | 비전공자 입장에서도 쉽게 이해할 수 있고, 실전에서 바로 적용 가능한 'Redis 입문/실전 (조회 성능 최적화편)' 강의를 만들어봤습니다!,   에라이, 못 해먹겠네!비전공자로 개발&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Redis</category>
      <category>aws elasticache 활용</category>
      <category>elasticache생성</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/97</guid>
      <comments>https://jinalim-dev.tistory.com/97#entry97comment</comments>
      <pubDate>Sat, 26 Oct 2024 01:55:50 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] Docker Compose로 Redis + Spring Boot 띄우기 (AWS)</title>
      <link>https://jinalim-dev.tistory.com/96</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;배포 환경의 인프라를 구성했으니 AWS EC2에서 Docker Compose로 Redis + Spring Boot를 한 번에 띄워봐야하지 않겠는가!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Dockerfile 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 내에서 사용할 Dockerfile을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 환경에서 사용할 파일과 배포 환경에서 사용할 파일을 구분하기 위해 이번엔 Dockerfile-prod로 생성하자.&lt;/p&gt;
&lt;pre id=&quot;code_1729868876660&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM openjdk:17-jdk

COPY build/libs/*SNAPSHOT.jar app.jar

# 배포 환경 profiles를 지정해줘야 하기 때문에 명령어가 추가되었다
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;-Dspring.profiles.active=prod&quot;, &quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. compose.yml 파일 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compose.yml 파일도 로컬 환경과 배포 환경을 구분하기 위해 compose-prod.yml로 생성하자.&lt;/p&gt;
&lt;pre id=&quot;code_1729869182950&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  api-server:
    build:
      # Dockerfile이 위치한 경로 특정
      context: .
      # Dockerfile을 임의로 지정하였기 때문에 파일명을 명시
      dockerfile: ./Dockerfile-prod
    ports:
      - 8080:8080
    depends_on:
      cache-server:
        condition: service_healthy
  cache-server:
    image: redis
    ports:
      - 6379:6379
    healthcheck:
      test: [&quot;CMD&quot;, &quot;redis-cli&quot;, &quot;ping&quot;]
      interval: 5s
      retries: 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Github Repository에 Push&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내 변경 사항을 Github Repository에 올려야 EC2에서 내려 받을 수 있기 때문에 Repository에 변경 사항을 Push 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.18.28.png&quot; data-origin-width=&quot;2466&quot; data-origin-height=&quot;1352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/78FE8/btsKkdH96oY/Tu18Y6pC8IwHmdgbfT9Fh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/78FE8/btsKkdH96oY/Tu18Y6pC8IwHmdgbfT9Fh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/78FE8/btsKkdH96oY/Tu18Y6pC8IwHmdgbfT9Fh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F78FE8%2FbtsKkdH96oY%2FTu18Y6pC8IwHmdgbfT9Fh0%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;2466&quot; height=&quot;1352&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.18.28.png&quot; data-origin-width=&quot;2466&quot; data-origin-height=&quot;1352&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. EC2에서 Git Pull 받기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push한 설정 파일들을 EC2에 내려 받아 적용해야 하기 때문에 인스턴스에 접속해서 변경 사항을 Pull 받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.20.15.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbqgLu/btsKlHVbThQ/uo6ZtU37oZt0sAn52bkKO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbqgLu/btsKlHVbThQ/uo6ZtU37oZt0sAn52bkKO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbqgLu/btsKlHVbThQ/uo6ZtU37oZt0sAn52bkKO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbqgLu%2FbtsKlHVbThQ%2Fuo6ZtU37oZt0sAn52bkKO0%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;410&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.20.15.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;410&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. EC2에 Docker 설치&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2에서 Docker를 실행시켜야 하기 때문에 Docker 설치를 진행한다.&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;Docker 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.32.56.png&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRQtyc/btsKkbDv2Kg/LEYhbkgRi4NMmT3u8HxEH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRQtyc/btsKkbDv2Kg/LEYhbkgRi4NMmT3u8HxEH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRQtyc/btsKkbDv2Kg/LEYhbkgRi4NMmT3u8HxEH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRQtyc%2FbtsKkbDv2Kg%2FLEYhbkgRi4NMmT3u8HxEH0%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;1208&quot; height=&quot;290&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.32.56.png&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729870441564&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# EC2에 Docker 설치하는 명령어
sudo apt-get update &amp;amp;&amp;amp; \
	sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common &amp;amp;&amp;amp; \
	curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - &amp;amp;&amp;amp; \
	sudo apt-key fingerprint 0EBFCD88 &amp;amp;&amp;amp; \
	sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot; &amp;amp;&amp;amp; \
	sudo apt-get update &amp;amp;&amp;amp; \
	sudo apt-get install -y docker-ce &amp;amp;&amp;amp; \
	sudo usermod -aG docker ubuntu &amp;amp;&amp;amp; \
	newgrp docker &amp;amp;&amp;amp; \
	sudo curl -L &quot;https://github.com/docker/compose/releases/download/2.27.1/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose &amp;amp;&amp;amp; \
	sudo chmod +x /usr/local/bin/docker-compose &amp;amp;&amp;amp; \
	sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose&lt;/code&gt;&lt;/pre&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;Docker 및 Docker compose 설치 확인&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.35.51.png&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ea0UrC/btsKk8eWiA3/nwoqHzG80Cd8hHu8KeHE20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ea0UrC/btsKk8eWiA3/nwoqHzG80Cd8hHu8KeHE20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ea0UrC/btsKk8eWiA3/nwoqHzG80Cd8hHu8KeHE20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fea0UrC%2FbtsKk8eWiA3%2FnwoqHzG80Cd8hHu8KeHE20%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;580&quot; height=&quot;71&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.35.51.png&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729870600047&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Docker 버전 확인 명령어
docker -v

# Docker compose 버전 확인 명령어
docker compose version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 기존에 실행되고 있는 Redis 종료&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.38.48.png&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJmpr/btsKlYJcnkP/mUjJod4Kvmt5mOALkMRF00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJmpr/btsKlYJcnkP/mUjJod4Kvmt5mOALkMRF00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJmpr/btsKlYJcnkP/mUjJod4Kvmt5mOALkMRF00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJmpr%2FbtsKlYJcnkP%2FmUjJod4Kvmt5mOALkMRF00%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;1209&quot; height=&quot;208&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.38.48.png&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729870792748&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# EC2에서 Redis 중지하는 명령어
sudo systemctl stop redis

# EC2에서 Redis 상태 확인하는 명령어
sudo systemctl status redis&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. Docker 컨테이너로 띄워보자!&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spring Boot 다시 빌드&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.46.12.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qgbSd/btsKkpgXH8Z/oAW11H4eBcOVknnq6ptBhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qgbSd/btsKkpgXH8Z/oAW11H4eBcOVknnq6ptBhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qgbSd/btsKkpgXH8Z/oAW11H4eBcOVknnq6ptBhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqgbSd%2FbtsKkpgXH8Z%2FoAW11H4eBcOVknnq6ptBhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;84&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.46.12.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729871218761&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 테스트 생략하고 Spring Boot 빌드하는 명령어
./gradlew clean build -x test&lt;/code&gt;&lt;/pre&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;Docker 컨테이너로 띄우기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.48.43.png&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;681&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br3ggp/btsKljAxWtJ/E6d1T4z63vU0NXTQGAlqh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br3ggp/btsKljAxWtJ/E6d1T4z63vU0NXTQGAlqh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br3ggp/btsKljAxWtJ/E6d1T4z63vU0NXTQGAlqh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr3ggp%2FbtsKljAxWtJ%2FE6d1T4z63vU0NXTQGAlqh1%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;1205&quot; height=&quot;681&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.48.43.png&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;681&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729871375203&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 배포 환경 파일을 명시해서 Docker 컨테이너로 띄우는 명령어
docker compose -f compose-prod.yml --build -d&lt;/code&gt;&lt;/pre&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;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.52.43.png&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQliL4/btsKkvBwoIi/jcRRkl9kfkRPvxRy7kQJZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQliL4/btsKkvBwoIi/jcRRkl9kfkRPvxRy7kQJZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQliL4/btsKkvBwoIi/jcRRkl9kfkRPvxRy7kQJZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQliL4%2FbtsKkvBwoIi%2FjcRRkl9kfkRPvxRy7kQJZK%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;1201&quot; height=&quot;118&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.52.43.png&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;118&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. 마지막으로 API 확인해야지!&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.54.49.png&quot; data-origin-width=&quot;2466&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqOH3o/btsKkhwKonS/rXU6pTKyZbt0BB5PLulVq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqOH3o/btsKkhwKonS/rXU6pTKyZbt0BB5PLulVq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqOH3o/btsKkhwKonS/rXU6pTKyZbt0BB5PLulVq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqOH3o%2FbtsKkhwKonS%2FrXU6pTKyZbt0BB5PLulVq0%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;2466&quot; height=&quot;616&quot; data-filename=&quot;스크린샷 2024-10-26 오전 12.54.49.png&quot; data-origin-width=&quot;2466&quot; data-origin-height=&quot;616&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729871741637&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;비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편) 강의 | JSCODE 박재성 - 인프런&quot; data-og-description=&quot;JSCODE 박재성 | 비전공자 입장에서도 쉽게 이해할 수 있고, 실전에서 바로 적용 가능한 'Redis 입문/실전 (조회 성능 최적화편)' 강의를 만들어봤습니다!,   에라이, 못 해먹겠네!비전공자로 개발&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; data-og-url=&quot;https://www.inflearn.com/course/비전공자-redis-입문-성능-최적화&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tUmsw/hyXlM4Rv6J/RdFJkNNvdwNIT3e4Sb2Fe1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bJ6yiP/hyXpzvYELH/W0Twhk6CZ5KwFvkyjynE5k/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/PR0gw/hyXlMKxPHL/AaN9M3dBrC9YgmfxKsdUpk/img.png?width=736&amp;amp;height=479&amp;amp;face=0_0_736_479&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tUmsw/hyXlM4Rv6J/RdFJkNNvdwNIT3e4Sb2Fe1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bJ6yiP/hyXpzvYELH/W0Twhk6CZ5KwFvkyjynE5k/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/PR0gw/hyXlMKxPHL/AaN9M3dBrC9YgmfxKsdUpk/img.png?width=736&amp;amp;height=479&amp;amp;face=0_0_736_479');&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;비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편) 강의 | JSCODE 박재성 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JSCODE 박재성 | 비전공자 입장에서도 쉽게 이해할 수 있고, 실전에서 바로 적용 가능한 'Redis 입문/실전 (조회 성능 최적화편)' 강의를 만들어봤습니다!,   에라이, 못 해먹겠네!비전공자로 개발&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Redis</category>
      <category>aws ec2</category>
      <category>Docker Compose</category>
      <category>docker 컨테이너로 띄우기</category>
      <category>redis + spring boot 함께</category>
      <category>redis 설치</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/96</guid>
      <comments>https://jinalim-dev.tistory.com/96#entry96comment</comments>
      <pubDate>Sat, 26 Oct 2024 00:56:39 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] Docker Compose로 Redis + Spring Boot 띄우기 (로컬)</title>
      <link>https://jinalim-dev.tistory.com/95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발을 하다 보면 Docker를 자주 활용하는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이번엔 Docker Compose로 Redis와 Spring Boot를 한 번에 띄울 수 있게 구성을 진행해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Dockerfile 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 프로젝트 root 디렉토리에 Dockerfile을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1729865814299&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# JDK17을 사용해서
FROM openjdk:17-jdk

# 빌드한 파일을 복사해와서
COPY build/libs/*SNAPSHOT.jar app.jar

# 복사한 파일을 기반으로 실행
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. compose.yml 파일 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일도 Spring Boot 프로젝트 root 디렉토리에 생성하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1729866216026&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services: 
  api-server:
    # Dockerfile을 기준으로 빌드를 할거라 Dockerfile의 경로 설정
    build: .
    ports:
      - 8080:8080
    depends_on:
      cache-server:
        # Redis가 정상적으로 구동한 뒤에 api 서버를 구동한다는 설정
        condition: service_healthy
  cache-server:
    image: redis
    ports:
      - 6379:6379
    # Redis가 정상적으로 구동되었음을 확인하는 설정
    healthcheck:
      test: [&quot;CMD&quot;, &quot;redis-cli&quot;, &quot;ping&quot;]
      interval: 5s
      retries: 10&lt;/code&gt;&lt;/pre&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;Redis가 정상적으로 구동한 뒤에 api 서버를 구동해야 하는 이유&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api서버가 구동되면서 Redis와의 연결을 시도하는데 이때, Redis가 구동되기 전이라면 Error가 발생해 정상적인 서버 구동이 어렵기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 기존에 실행되고 있는 Redis 종료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트 충돌이 일어나기 때문에 기존에 실행해두었던 Redis를 종료해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.29.54.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DPFaI/btsKli2Emve/D4TDJKd9SI6hwPcuyasDrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DPFaI/btsKli2Emve/D4TDJKd9SI6hwPcuyasDrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DPFaI/btsKli2Emve/D4TDJKd9SI6hwPcuyasDrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDPFaI%2FbtsKli2Emve%2FD4TDJKd9SI6hwPcuyasDrK%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;436&quot; height=&quot;138&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.29.54.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729866775766&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 로컬에서 실행되고 있는 Redis 종료 명령어
brew services stop redis

# 정상 종료되었는지 확인하는 명령어
brew services info redis&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. application.yml 수정&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1729867019496&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# local 환경
spring:
  profiles:
    default: local
  datasource:
    url: jdbc:mysql://host.docker.internal:3306/mydb # 호스트 컴퓨터의 주소로 변경
    username: root
    password: [password]
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  data:
    redis:
      host: cache-server # compose에 명시한 이름으로 변경
      port: 6379&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Docker 컨테이너로 띄워보자!&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spring Boot 다시 빌드&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.42.01.png&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6nOSt/btsKjGKNfnH/vB53vvQWhPpyzhjhlrVwA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6nOSt/btsKjGKNfnH/vB53vvQWhPpyzhjhlrVwA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6nOSt/btsKjGKNfnH/vB53vvQWhPpyzhjhlrVwA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6nOSt%2FbtsKjGKNfnH%2FvB53vvQWhPpyzhjhlrVwA0%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;591&quot; height=&quot;330&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.42.01.png&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729867405233&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 테스트 생략하고 Spring Boot 빌드하는 명령어
./gradlew clean build -x test&lt;/code&gt;&lt;/pre&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;Docker 컨테이너로 띄우기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.44.00.png&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bThW6V/btsKkWMnjgJ/I264qnVI1rjiLugwVjudYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bThW6V/btsKkWMnjgJ/I264qnVI1rjiLugwVjudYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bThW6V/btsKkWMnjgJ/I264qnVI1rjiLugwVjudYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbThW6V%2FbtsKkWMnjgJ%2FI264qnVI1rjiLugwVjudYK%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;1137&quot; height=&quot;313&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.44.00.png&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.44.20.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;89&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTvmWj/btsKlyxsCj1/sZrw7WwxMsT1BFpHc083N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTvmWj/btsKlyxsCj1/sZrw7WwxMsT1BFpHc083N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTvmWj/btsKlyxsCj1/sZrw7WwxMsT1BFpHc083N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTvmWj%2FbtsKlyxsCj1%2FsZrw7WwxMsT1BFpHc083N1%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;1138&quot; height=&quot;89&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.44.20.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;89&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729867617270&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Docker 컨테이너로 띄우는 명령어
docker-compose up --build -d&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 컨테이너로 띄우기 전에 Docker 데몬이 실행되고 있는지 확인을 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 이런 메세지를 만나게 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.45.45.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOv1FU/btsKlZH5z8V/2Qhu0vRKu0XhovDQvz9jRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOv1FU/btsKlZH5z8V/2Qhu0vRKu0XhovDQvz9jRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOv1FU/btsKlZH5z8V/2Qhu0vRKu0XhovDQvz9jRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOv1FU%2FbtsKlZH5z8V%2F2Qhu0vRKu0XhovDQvz9jRK%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;1144&quot; height=&quot;47&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.45.45.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;47&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker Desktop을 실행하여 Docker 데몬을 활성화하기&lt;/li&gt;
&lt;li&gt;docker 서비스 시작하기&lt;/li&gt;
&lt;/ul&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;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.48.05.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIuaJ5/btsKjHpoolD/8KaISsIk9Kbylv0FqeVHHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIuaJ5/btsKjHpoolD/8KaISsIk9Kbylv0FqeVHHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIuaJ5/btsKjHpoolD/8KaISsIk9Kbylv0FqeVHHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIuaJ5%2FbtsKjHpoolD%2F8KaISsIk9Kbylv0FqeVHHK%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;1146&quot; height=&quot;134&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.48.05.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729867732066&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Docker 상태 확인을 위한 명령어
docker ps&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;Docker Desktop을 실행해 Docker 데몬을 활성화했다면 Docker Desktop에서도 확인이 가능하다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.49.51.png&quot; data-origin-width=&quot;2722&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6KOoQ/btsKl0AemXY/POVZ9NOM6pSnjwJp0kbtm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6KOoQ/btsKl0AemXY/POVZ9NOM6pSnjwJp0kbtm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6KOoQ/btsKl0AemXY/POVZ9NOM6pSnjwJp0kbtm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6KOoQ%2FbtsKl0AemXY%2FPOVZ9NOM6pSnjwJp0kbtm1%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;2722&quot; height=&quot;288&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.49.51.png&quot; data-origin-width=&quot;2722&quot; data-origin-height=&quot;288&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. API 확인까지 해야지?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 잘 띄워졌다고 해도 API가 정상 동작하는지까지 확인해봐야 한다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.52.20.png&quot; data-origin-width=&quot;2470&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3FehU/btsKj5XEpsW/lgTmBTwuD7JjwPB0TgEt9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3FehU/btsKj5XEpsW/lgTmBTwuD7JjwPB0TgEt9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3FehU/btsKj5XEpsW/lgTmBTwuD7JjwPB0TgEt9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3FehU%2FbtsKj5XEpsW%2FlgTmBTwuD7JjwPB0TgEt9K%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;2470&quot; height=&quot;624&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.52.20.png&quot; data-origin-width=&quot;2470&quot; data-origin-height=&quot;624&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker의 실시간 로그를 확인하는 방법을 알아두자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발자에게 로그란 중요한 것이니까!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.56.30.png&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWpiSp/btsKlmRzb9U/cqNa6IX7soXO4ToMJoiT6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWpiSp/btsKlmRzb9U/cqNa6IX7soXO4ToMJoiT6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWpiSp/btsKlmRzb9U/cqNa6IX7soXO4ToMJoiT6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWpiSp%2FbtsKlmRzb9U%2FcqNa6IX7soXO4ToMJoiT6k%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;1143&quot; height=&quot;267&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.56.30.png&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1729868231907&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Docker의 실시간 로그 확인하는 명령어
docker compose logs -f&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 요청을 처리한 로그를 찾아봤는데 실시간으로 문제 없이 확인되는 것을 알 수 있다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.55.49.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDgzYc/btsKkmkvsSs/pyldMn0MB2U3A8VZITpH00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDgzYc/btsKkmkvsSs/pyldMn0MB2U3A8VZITpH00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDgzYc/btsKkmkvsSs/pyldMn0MB2U3A8VZITpH00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDgzYc%2FbtsKkmkvsSs%2FpyldMn0MB2U3A8VZITpH00%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;1144&quot; height=&quot;152&quot; data-filename=&quot;스크린샷 2024-10-25 오후 11.55.49.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;152&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729868350879&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;비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편) 강의 | JSCODE 박재성 - 인프런&quot; data-og-description=&quot;JSCODE 박재성 | 비전공자 입장에서도 쉽게 이해할 수 있고, 실전에서 바로 적용 가능한 'Redis 입문/실전 (조회 성능 최적화편)' 강의를 만들어봤습니다!,   에라이, 못 해먹겠네!비전공자로 개발&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; data-og-url=&quot;https://www.inflearn.com/course/비전공자-redis-입문-성능-최적화&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tUmsw/hyXlM4Rv6J/RdFJkNNvdwNIT3e4Sb2Fe1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bJ6yiP/hyXpzvYELH/W0Twhk6CZ5KwFvkyjynE5k/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/PR0gw/hyXlMKxPHL/AaN9M3dBrC9YgmfxKsdUpk/img.png?width=736&amp;amp;height=479&amp;amp;face=0_0_736_479&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-redis-%EC%9E%85%EB%AC%B8-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tUmsw/hyXlM4Rv6J/RdFJkNNvdwNIT3e4Sb2Fe1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bJ6yiP/hyXpzvYELH/W0Twhk6CZ5KwFvkyjynE5k/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/PR0gw/hyXlMKxPHL/AaN9M3dBrC9YgmfxKsdUpk/img.png?width=736&amp;amp;height=479&amp;amp;face=0_0_736_479');&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;비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편) 강의 | JSCODE 박재성 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JSCODE 박재성 | 비전공자 입장에서도 쉽게 이해할 수 있고, 실전에서 바로 적용 가능한 'Redis 입문/실전 (조회 성능 최적화편)' 강의를 만들어봤습니다!,   에라이, 못 해먹겠네!비전공자로 개발&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Redis</category>
      <category>Docker Compose</category>
      <category>docker 컨테이너로 띄우기</category>
      <category>redis + spring boot 함께</category>
      <author>jn4624</author>
      <guid isPermaLink="true">https://jinalim-dev.tistory.com/95</guid>
      <comments>https://jinalim-dev.tistory.com/95#entry95comment</comments>
      <pubDate>Sat, 26 Oct 2024 00:00:19 +0900</pubDate>
    </item>
  </channel>
</rss>