<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>힘차게, 열심히 공대생</title>
    <link>https://thalals.tistory.com/</link>
    <description>민돌v      

 Back-end Developer   


</description>
    <language>ko</language>
    <pubDate>Fri, 17 Apr 2026 03:07:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>민돌v</managingEditor>
    <image>
      <title>힘차게, 열심히 공대생</title>
      <url>https://tistory1.daumcdn.net/tistory/4668589/attach/3ad46547a54648dbbcfce0a2aff49d97</url>
      <link>https://thalals.tistory.com</link>
    </image>
    <item>
      <title>☁️ AWS DynamoDB 기본 개념 공부</title>
      <link>https://thalals.tistory.com/496</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt; AWS DynamoDB 를 처음 접하는 작성자의 공부용 기록 페이지입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마도 연초에?&amp;nbsp; 감사하게도 회사에서 AWS DynamoDB 교육을 지원해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 교육을 들었을 때는, NoSQL 도 익숙하지 않은 상황이었고 DDB가 가지는 특징들 또한 이해가 잘 가지않아 제대로 소화하지 못하고있었는데, 사내에서 DDB 를 주력 테이블로 하는 작은 서비스의 테이블 설계를 맡게 되어 AWS 공식문서 내용과 제 생각을 글로 정리해 보았습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;[목차]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;AWS DynamoDB 란&lt;/li&gt;
&lt;li&gt;DynamoDB 스키마 구조&lt;/li&gt;
&lt;li&gt;DynamoDB 테이블 디자인 시 주의해야할 점
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;RDMBS 와의 차이점&lt;/li&gt;
&lt;li&gt;DynamoDB Access Pattern&lt;/li&gt;
&lt;li&gt;DynamoDB 성능을 제어하는 일반 원칙&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;DynamoDB 파티션 키 설계 (DynamoDB PK, SK Design)&lt;/li&gt;
&lt;li&gt;개인적인 생각&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✔️&amp;nbsp; AWS DynamoDB 란&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;DynamoDB는 AWS에서 제공하는 서버리스 기반 Key-Value 형식의 NoSQL 데이터베이스입니다.&lt;br /&gt;AWS 에서 2004년 아마존 닷컴의 대용량 트랙픽으로 인한 전체 서버다운 문제를 개선하기 위해 만들어진 DB 입니다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS 에서 관리하는 Serverless DataBase&lt;/li&gt;
&lt;li&gt;Key - Value 형식으로 이루어진 비정형 NoSQL
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Key-Value DataBase 특장점 (링크 - &lt;a href=&quot;https://aws.amazon.com/ko/nosql/key-value/#topic-0&quot;&gt;AWS)&lt;/a&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;테이블 조인 불필요&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키-값 데이터베이스는 리소스를 많이 사용하는 테이블 조인을 수행할 필요가 없습니다. 유연성이 뛰어나기 때문에 필요한 모든 정보를 단일 테이블에 저장할 수 있습니다. 이것이 키-값 스토어가 성능이 높은 이유 중 하나입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ACID 트랜잭션 지원&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자가 테이블 내에서 그리고 전체 테이블에 걸쳐 모두 변경하거나 전혀 변경하지 않고 간편하게 조정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;DynamoDB는&amp;nbsp;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html&quot;&gt;트랜잭션당 100개의 작업&lt;/a&gt;을 지원하여 개발자 생산성을 향상합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transactions.html&quot;&gt;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transactions.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  DynamoDB History 의 목적 (생성 원인)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;NoSQL journey&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;2004 : 아마존 닷컴의 전체 서버가 다운 됨 (DB 트래픽 급증)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 쿼리의 70% 가 단일 테이블에서 단일 쿼리를 조회하고 있음 &amp;rarr; 70% 데이터는 RDBMS 가 아니어도 되겠네? 에서 다이나모디비의 시작점&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2007 : DynamoDB 논문이 나왔지만, 현재 다이나모 디비와는 매우 상이함&lt;/li&gt;
&lt;li&gt;2012.1 : AWS 서비스 사용화&lt;/li&gt;
&lt;li&gt;Today : Tier 0 service (AWS 의 사용 서비스 중 근간이 되는 서비스 중 하나가 되었다)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tier 0 Service 대부분 버전이 없다. (근간이 되는 서비스가 버전 up 되면 전체가 업데이트 되어야하기 때문에, 정책으로 가져가고 있다고 합니다.)&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;✔️&lt;/span&gt; DynamDB 의 스키마 구조&lt;/h2&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;테이블 이름 (table name)&lt;/li&gt;
&lt;li&gt;파티션 키 (PK, Primary Key) &amp;rarr; Hash&lt;/li&gt;
&lt;li&gt;정렬 키 (SK, Primary Key) &amp;rarr; Range&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선택 요소&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Data-Item Attributes
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;key-value 형태의 데이터 필드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Secondary Indexes
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GSI (Global Secondary Index): 별도의 Partition Key / Sort Key 구조 정의 가능&lt;/li&gt;
&lt;li&gt;LSI (Local Secondary Index): 테이블의 PK는 공유, Sort Key만 다른 구조 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Provisioned / On-Demand Capacity
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기/쓰기 성능 모델 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TTL (Time To Live)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 속성을 만료 시각으로 지정해 자동 삭제 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Streams
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경 이벤트 캡처 &amp;rarr; Lambda, Kinesis 등과 연동&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1091&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sakkX/btsQAoCwNds/qoUzFyHZ7DQQ9qucbxsPn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sakkX/btsQAoCwNds/qoUzFyHZ7DQQ9qucbxsPn0/img.png&quot; data-alt=&quot;스키마 예시 - 출처: https://www.alexdebrie.com/posts/dynamodb-single-table/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sakkX/btsQAoCwNds/qoUzFyHZ7DQQ9qucbxsPn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsakkX%2FbtsQAoCwNds%2FqoUzFyHZ7DQQ9qucbxsPn0%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;1091&quot; height=&quot;339&quot; data-origin-width=&quot;1091&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스키마 예시 - 출처: https://www.alexdebrie.com/posts/dynamodb-single-table/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;✔️&lt;/span&gt;&amp;nbsp; NoSQL DynomoDB 테이블 디자인 시 유의 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. RDMBS 와는 다른 관점이 필요&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RDBMS&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NoSQL 설계는 RDBMS 설계와는 다른 사고방식을 요구합니다.&lt;/li&gt;
&lt;li&gt;RDBMS의 경우, 접근 패턴을 고려하지 않고도 정규화된 데이터 모델을 생성할 수 있습니다.&lt;/li&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;NoSQL (DynomoDB Schema)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DynamoDB 애플리케이션에서는 가능한 한 적은 수의 테이블을 유지해야 한다고 합니다.
&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;권한 관리의 필요성 낮아지고 (AWS Serverless이기 때문에 명시된 문장 같음)&lt;/li&gt;
&lt;li&gt;오버헤드가 줄어든다.&lt;/li&gt;
&lt;li&gt;백업 비용도 절감&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DynomoDB 가 해결해야 할 질문을 파악하기 전까지는 시작해서는 안된다.. !(공식문서 명시)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 문제&lt;/li&gt;
&lt;li&gt;애플리케이션 사용 사례&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. DynamoDB Access Pattern&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;다이나모 디비는, AWS 서버를 통해 조회해야하기 때문에 관리하는 Access Pattern 이 존재하고 권장하는 걸로 보임&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 크기&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장 및 조회 (Write &amp;amp; Read) 가 얼마나 많은 데이터로 한번에 요청되는지 먼저 파악하는게 스키마를 분할하는데 도움이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 형태&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RDBMS는 조회할 때 조인하고 선택하여, 조회할 데이터 포맷을 구성한다.&lt;/li&gt;
&lt;li&gt;NoSQL은 저장할 때 조회 포맷을 염두에 두고 저장해야한다. (그대로 조회 - 데이터 포맷을 정형화하지 않는 이유)&lt;/li&gt;
&lt;li&gt;❗️NoSQL의 속도와 확장성을 높이는 핵심 요소..!!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 속도&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;논리적 파티션(PK)을 통해 어떻게 I/O 요청을 분산할지에 대한 고민이 필요.&lt;/li&gt;
&lt;li&gt;DynamoDB는 쿼리 처리에 사용 가능한 물리적 파티션 수를 늘리고 이러한 파티션에 데이터를 효율적으로 분산함으로써 확장합니다.&lt;/li&gt;
&lt;li&gt;최대 쿼리 부하가 어느 정도일지 미리 파악하면 I/O 용량을 최대한 활용하기 위한 데이터 분할 방법을 결정하는 데 도움될 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  3. 데이터 속도에 대하여&lt;br /&gt;분할 파티션을 위한 설명인데, 물리적인 분할 (샤딩)은 한번 진행하면 되돌릴 수 없으므로 가능한 하지 않는게 좋다는 내부 직원의 설명을 들은 적이 있습니다.&lt;br /&gt;물리적인 분할 (샤딩)은 데이터가 분할되어 저장되기 시작하면, 특정 샤딩 키(?) 지점에 데이터가 몰리거나 트래픽이 몰리지 않도록 샤딩 키를 설계하는 부분부터, 설계 복잡도 및 관리의 복잡도가 급격하게 늘어나기 때문에 되도록이면 샤딩을 하지 않는 방향으로 설계하는게 좋다고 합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. DynamoDB 성능을 제어하는 일반 원칙&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;관련 데이터를 함께 보관해라 (참조 지역성)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 DynamoDB 애플리케이션에서는 가능한 한 적은 수의 테이블을 유지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대용량 시계열 데이터나 액세스 패턴이 매우 다른 데이터세트가 있는 경우는 예외&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;역인덱스가 있는 단일 테이블은 일반적으로 간단한 쿼리를 통해 애플리케이션에 필요한 복잡한 계층적 데이터 구조를 생성하고 검색할 수 있도록 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정렬 순서를 사용해라 (SK 를 의미 ?)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정렬이 가능하다면 관련 항목을 그룹화(파티션) 하여 효율적으로 쿼리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쿼리를 분산해라&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트래픽이 파티션에 잘 분산되도록 데이터 키를 설계하고, 핫 스팟을 피해라&lt;/li&gt;
&lt;li&gt;(생각) 참조 지역성에 있는 데이터만 같은 파티션 키를 사용하도록 하라는 말로 해석됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;글로벌 보조 인덱스를 사용해라&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;글로벌 보조 인덱스를 생성하면, 기본 테이블 조회 쿼리보다 다양하게 쓸 수 있다고 합니다.&lt;/li&gt;
&lt;li&gt;테이블 스키마의 속성(attribute) 로 조회 필터링을 걸기 위해서는 GSI 설정이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  글로벌 보조 인덱스 (GSI)&lt;br /&gt;공식 문서에서는 GSI 를 권장하는 것 처럼 나와있는데, AWS 에 제시되어있는 GSI 의 가격을 보면 1개의 Item attribute(이하 필드) 에 GSI 를 설정할 때 마다 테이블 1개를 더 사용하는 것과 같다고 나와있습니다.&lt;br /&gt;즉, GSI 는 PK(hash) 와 SK(range) 로 설정하지 않은 값에 대해서는 조회 쿼리의 조건으로 사용될 수 없는 특징으로 인해 어쩔수 없이 추가 조회 필터가 필요할 때만 사용하는게 맞다고 생각합니다.&lt;br /&gt;&lt;br /&gt;이러한 부분때문에 처음 스키마 설계 시 실제 조회할 쿼리를 먼저 생각하고 스키마를 설계해야하는 부분이 다른 DB와의 매우 큰 차이점으로 느켜졌습니다.. (매우 어려움,,)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;✔️&lt;/span&gt; DynomoDB 파티션 키 설계 (DynomoDB PK, SK Design)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Amazon DynamoDB 테이블의 각 항목을 고유하게 식별하는 기본 키는 단순 키(파티션 키만)이거나 복합 키(파티션 키와 정렬 키 결합)일 수 있습니다.&lt;/blockquote&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;DynamoDB 테이블의 모든 파티션은 초당 최대 3,000개의 읽기 단위와 1,000개의 쓰기 단위를 제공하도록 설계되었습니다.&lt;/li&gt;
&lt;li&gt;최대 4KB 크기 초당 1개의 최종 일관성 (ACID 트랜잭션) 읽기 보장 (Write 는 1KB)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  파티션 키 (PK)&lt;/b&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;&lt;span data-token-index=&quot;0&quot;&gt;DynamoDB에서&amp;nbsp;작업 부하를 분산하기위한 파티션 키&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;테이블 기본키의 파티션 키는, 데이터가 저장되는 논리적 파티션을 결정합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블의 싱글 테이블로 사용하더라도, 파티션 키를 이용하면 물리적 I/O는 분리되어 있지는 않지만 - I/O 요청 자체를 분산하여 요청할 수 있도록 하기 때문이라고 생각되어집니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-data-upload.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 - AWS Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  정렬 키 (SK)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;DynamoDB에서 데이터를 구성하기 위한 정렬 키&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;정렬 키를 신중하게 설계하면&amp;nbsp;begins_with,&amp;nbsp;between,&amp;nbsp;&amp;gt;,&amp;nbsp;&amp;lt;등의 연산자를 사용한 범위 쿼리를 통해 자주 필요한 관련 항목 그룹을 검색할 수 있습니다.&lt;/li&gt;
&lt;li&gt;복합 정렬 키를 사용하면 계층 구조의 모든 수준에서 쿼리할 수 있는 데이터의 계층적(일대다) 관계를 정의할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex ) [country]#[region]#[state]#[county]#[city]#[neighborhood]&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;✔️&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;DynomoDB 에 대한 생각 정리&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AWS 다이나모 디비는 Serverless 시스템으로 굉장히 강력한 확장성과 편리성을 제공하지만, 그만큼 무척 비싸고 까다롭습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AWS 자체에서 DDB를 싱글 테이블로 구성하는걸 권장하는 만큼 설계를 굉장히 조심스럽게 해야하며, 특히 PK 와 SK를 꼼꼼하게 설정하여야만 NoSQL로써 DDB의 장점을 살릴 수 있다는 생각이 들었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; 즉 DDB 의 스키마는 RDBMS 처럼 데이터의 정합성을 논리적으로 설계하는게 아닌,&lt;br /&gt;실제 데이터가 어떻게 조회될 지, 실제 사용 시나리오와 필요한 데이터를 추출할 수 있는 쿼리를 먼저 고민해보아야 후회없는 테이블 스키마 설계가 가능할 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;☁️ AWS DynomoDB&lt;br /&gt;기본 개념 공식문서로 톺아보기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&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;개념과 목적 : &lt;a href=&quot;https://youtu.be/t_8NmwAy4D0&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://youtu.be/t_8NmwAy4D0&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;설계 : &lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=I7zcRxHbo98&quot;&gt;https://www.youtube.com/watch?v=I7zcRxHbo98&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dynamo DB Single table vs multi table
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/database/single-table-vs-multi-table-design-in-amazon-dynamodb/&quot;&gt;https://aws.amazon.com/ko/blogs/database/single-table-vs-multi-table-design-in-amazon-dynamodb/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Single Table Design
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html&quot;&gt;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DynomoDB 비용 최적화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-cost-optimization.html&quot;&gt;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-cost-optimization.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파티션 키 분산 설계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-design.html&quot;&gt;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-design.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스키마 구조
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SettingUp.html&quot;&gt;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SettingUp.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.html&quot;&gt;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DynamoDB 설계 예시 코드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alexdebrie/awesome-dynamodb?tab=readme-ov-file#videos&quot;&gt;https://github.com/alexdebrie/awesome-dynamodb?tab=readme-ov-file#videos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aws-samples/aws-dynamodb-examples/tree/master/schema_design/SchemaExamples/SocialNetwork&quot;&gt;https://github.com/aws-samples/aws-dynamodb-examples/tree/master/schema_design/SchemaExamples/SocialNetwork&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DataBase/DynamoDB</category>
      <category>AWS DDB</category>
      <category>DDB 설계하기</category>
      <category>DDB 장점</category>
      <category>DDB란</category>
      <category>DynamoDB 단점</category>
      <category>DynamoDB 설계</category>
      <category>DynamoDB 유의점</category>
      <category>다이나모디비</category>
      <category>다이나모디비 비용</category>
      <category>다이나모디비란</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/496</guid>
      <comments>https://thalals.tistory.com/496#entry496comment</comments>
      <pubDate>Tue, 16 Sep 2025 00:46:03 +0900</pubDate>
    </item>
    <item>
      <title>#100일 챌린지 - Fake it till you make it. | 오츠카 아미</title>
      <link>https://thalals.tistory.com/495</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;#100일 챌린지, 작은 도전이 만들어낸 큰 변화&quot; 를 읽고 기억하고 싶은 책의 내용과 개인적인 생각을 정리한 글 입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;465&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmJicR/btsP8OWCyVf/ynwgRUTqAwGrSkPKfG67b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmJicR/btsP8OWCyVf/ynwgRUTqAwGrSkPKfG67b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmJicR/btsP8OWCyVf/ynwgRUTqAwGrSkPKfG67b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmJicR%2FbtsP8OWCyVf%2FynwgRUTqAwGrSkPKfG67b0%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;181&quot; height=&quot;281&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;465&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;책을 좋아하시는 옆자리 동료분이 빌려주신 책이었고, 정말 가벼운 마음으로 읽었는데 생각보다 재밌고 의외의 동기부여를 얻을 수 있었던 추천할 만한 책입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주제는 AI, ChatGPT를 사용한 비전공자의 개발 여행기 혹은 성장기에 가깝지만, ChatGPT를 이용하여 성장하는 과정보다,&lt;br /&gt;그 과정에서 드러나는 저자의 행동과 생각이 재밌었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자는 준비가 된 사람이었고, 스스로의 장점과 결과물을 표현하는 것은 정말 중요하다는 걸 다시 한번 느낍니다.&lt;br /&gt;내 생각과 감정을 공개하는 건 무서운 일이지만, 꾸준한 용기가 필요하다는 걸 느끼며 책의 내용을 정리합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 게으름을 피우기 위한 도구 ChatGPT&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자의 ChatGPT 에 대한 첫 인상은, &quot;게으름을 피우기 위한 도구&quot; 정도로 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기의 프롬프트로 보통 &lt;b&gt;해줘&lt;/b&gt; 혹은 &lt;b&gt;만들어줘&lt;/b&gt; 정도의 뉘앙스로 시작합니다. 하지만 ChatGPT 를 사용해 다양한 문제를 해결해 나아갈수록 ChatGPT가 문제 해결의 주체에서 점점 도구로 사용되어지는게 인상깊었습니다.&lt;/p&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;어떻게 해야해&lt;/li&gt;
&lt;li&gt;뭐가 잘못 됬어&lt;/li&gt;
&lt;li&gt;문제가 이런데, 해결하기위한 방법이 뭐야&lt;/li&gt;
&lt;li&gt;어떻게 동작하는거야&lt;/li&gt;
&lt;li&gt;왜 그렇게 해야해&lt;/li&gt;
&lt;li&gt;나는 이렇게 생각하니까 이런 방식으로 다시 조언해줘&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;100일 챌린지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 맥락에서 저자가 100일 챌린지를 진행하면서, 점차 프로그래밍을 만드는 것에서 구현하고 설계를 고민하는 방향으로 나아가는 과정도 인상깊었습니다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전체 구조를 파악 -&amp;gt; 개선점 탐구&lt;/li&gt;
&lt;li&gt;가독성과 확정성을 고려함&lt;/li&gt;
&lt;li&gt;문제점과 개선 방안에 대한 생각을 메모&lt;/li&gt;
&lt;li&gt;효율적인 관리를 위한 아키텍처 설계&lt;/li&gt;
&lt;li&gt;ChatGPT 에 대한 의존도는 줄고, 설계와 구현을 주도함&lt;/li&gt;
&lt;li&gt;작업 효율을 위한 도구로 사용하되, 결과물을 검토함&lt;/li&gt;
&lt;li&gt;리팩토링 - 기능을 모듈화, 클래스의 책임을 분리하여 모둘 단위의 구현을 ChatGPT 로 사용함&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️&lt;span&gt; 제텔 카스텐 메모법&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;사실 기억하고 있다기보다는, 전부 메모해 두고 있어요.&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자에 대한 저의 관점이 변하기 시작한 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자는 굉장히 빠르게 논문을 작성하곤 했는데, 어느날 교수가 &quot;너는 어떻게 그런걸 다 기억하고있는거야&quot; 라고 물었을 떄, &quot;기억이 아닌 메모&quot; 라고 대답한 부분이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서는 비교적 짧게 언급하고 지나가는데, 저자는 기억이 하기 싫은 나를 위해 제 2의 뇌로 사용할 수 있는 제텔카스텐 메모법을 사용한다고 언급합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr5hIF/btsQc55sDRd/2C6f5UtdsnVDXtPT4dIht0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr5hIF/btsQc55sDRd/2C6f5UtdsnVDXtPT4dIht0/img.png&quot; data-alt=&quot;#100일 챌린지 - p134&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr5hIF/btsQc55sDRd/2C6f5UtdsnVDXtPT4dIht0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr5hIF%2FbtsQc55sDRd%2F2C6f5UtdsnVDXtPT4dIht0%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;500&quot; height=&quot;457&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;#100일 챌린지 - p134&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;제텔카스텐(Zettelkasten) 메모법은 독일 사회학자 니클라스 루만이 사용한 메모 방식으로,&amp;nbsp;독립된 아이디어들을 각각의 메모에 적고, 이 메모들을 상호 연결하여 지식을 확장하고 새로운 글쓰기를 돕는 상향식 메모 방식입니다.&lt;/blockquote&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;제텔카스텐 메모법의 장점은, 메모를 하기 위한 접근 편이성을 현저하게 낮추어 더 많이, 더 자주 메모할 수 있도록 해주는데 중점을 두는 것으로 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 잘 모르지만 이 책에서 가장 인상깊게 읽었던 부분으로 쉽지는 않겠지만 충분히 시도해볼만 가치가 있고 잘 사용한다면 큰 도움이 되지 않을까 생각이 들었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;면접 질문 - &quot;팀으로 뭔가를 이뤄낸 경험이 있나요?&quot;&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;/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;br /&gt;이런 고민을 가지고있기에 저자의 속마음에 스스로를 투영하게되어 반성하게 되는 부분이였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저자가 생각하는 이상적인 리더쉽 상&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;스스로 앞장 서 나가고, 팀을 끌고가는 이상적인 인물&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저자의 답변&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;졸업 발표에서 스케줄 조율과 의견 정리 발표 조율을 맡아 교수님께 좋은 피드백을 받음&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저자의 속마음&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;80명 규모의 디스코드 커뮤니티 운영, 직접 커뮤니티 생성. 홍보, 방침 및 규칙 정의, 적절한 역할 배분을 통한 운영 자동화, 규칙의 표준화를 통한 문제 최소화&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;끝으로&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;성장이란 한창일 때는 느껴지지 않는다. 한참 시간이 지나고 나서야 본인이 성장했다는 걸 깨닫고는 한다.&quot;&lt;br /&gt;&quot;내가 하고 싶은 일을 나만의 방식으로 즐기고 표현하는 것&quot;&lt;/blockquote&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;br /&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;100일 챌린지가 끝나고, 100일간의 8,123 개의 프롬프트에 대한 데이터 정리를 즉시 아르바이트생을 뽑아 데이터 정리를 시킬 수 있을 만큼의 작업 메뉴얼을 먼저 갖추고 일을 시작했으며&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;br /&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;책의 초반 챗지피티를 며칠 사용하지 않았을 떄 이미 사용 기록 시간이 100시간(? 정확이 기억이 나지 않네요)을 넘겼다는 단 1줄의 문장이, 너무 아무렇지 않게 지나가는게 어쩌구니가 없을 정도로 인상깊었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이 책이 인기있는 이유는 ChatGPT 를 이용했다는 것이 아닌, 한 명의 준비된 사람이 수면위로 들어나는 과정을 보여주었기 때문이 아닐까 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝! 열심히 하자 !&lt;/p&gt;</description>
      <category>  개발자 책 읽기/독후감</category>
      <category>100일 챌린지</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/495</guid>
      <comments>https://thalals.tistory.com/495#entry495comment</comments>
      <pubDate>Sun, 31 Aug 2025 22:54:21 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot]  @TransactionalEventListener &amp;quot;AFTER_COMMIT&amp;quot; 의 Transaction 처리 범위</title>
      <link>https://thalals.tistory.com/493</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;해당글은 spring boot3.4.x, kotiln 2.1.x 환경에서 작성되었습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 오랜만에 쓰는 글인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Spring Boot 의 @TransactionlEventListener 를 사용했을 때, 이벤트는 발행되지만 Listener 가 이벤트를 수신받아 동작을 정상적으로 수행하지 못했던 이슈에 대해 간단하게 정리해보고자 합니다.&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;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;문제 원인 파악하기&lt;/li&gt;
&lt;li&gt;결론 및 해결방안&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확하게는 이벤트의 수행시점을 After Commit 으로 두었을 때 문제가 생겼습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@TransactionlEventListener(phase = TransactionPhase.AFTER_COMMIT)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;수신받은 리스너에서 로직 수행&lt;/li&gt;
&lt;li&gt;다시 이벤트 발행 (phase = After Commit)&lt;/li&gt;
&lt;li&gt;리스너에서 수신은 받지만, 동작을 수행하지 않음 (트리거 조건이 맞지않음)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 첫 번째 트랜잭션&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Transactional
fun firstTransaction(){
    //... 
    eventPublisher.publish(event)
    //... 
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 두 번째 트랜잭션&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Listener
public class CustomEventListener(){
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void litener(Event event) {
        service.run(event.deliveryId());
    }
}

// Service
public class ApiService(){
    @Transactional
    public void run(Event event) {
        //...
        publisher.publish(event)
        //...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 마지막 리스너&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;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Async
@TransactionalEventListener
fun on(event: Event) {
    println(&quot;자라나라 머리머리&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 문제 원인 파악&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;문제의 원인은 생각보다 간단했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 첫 번째 시도&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음, 왜 안되는걸까 고민하다가 마지막 수신받는 부분을 @EventListner 로 수정하니 실행되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 두 번째 시도&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트의 발행과 수신에 문제가 없다면 @TransactionalEventListner 의 트리거가 문제일까?&lt;/li&gt;
&lt;li&gt;phase 를 Before Commit 으로 수정하니 물론 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 세 번째 시도&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드의 시작에서부터 이벤트가 2번 발행되니, 마지막으로 수신받는 리스너에서, 트랜잭션의 커밋 시점을 못 읽는게 아닐까?&lt;/li&gt;
&lt;li&gt;두번째 트랜잭션이 시작되는 리스너에서 비동기(@Asyn) 로 끊으니&lt;/li&gt;
&lt;li&gt;트랜잭션이 분리되면서 해당 스레드에서 발행하는 이벤트는 정상적으로 수행이 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) ✔️ 네 번째 시도&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;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k8xEe/btsOOK08pee/i2claPLWE5HG8joeTj5Lgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8xEe/btsOOK08pee/i2claPLWE5HG8joeTj5Lgk/img.png&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1076&quot; data-is-animation=&quot;false&quot; style=&quot;width: 37.7292%; margin-right: 10px;&quot; data-widthpercent=&quot;38.17&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8xEe/btsOOK08pee/i2claPLWE5HG8joeTj5Lgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8xEe%2FbtsOOK08pee%2Fi2claPLWE5HG8joeTj5Lgk%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;1636&quot; height=&quot;1076&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T4m4U/btsOOx11ftS/QOLs57pgaYSSKQCRCj2ODK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T4m4U/btsOOx11ftS/QOLs57pgaYSSKQCRCj2ODK/img.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;588&quot; data-is-animation=&quot;false&quot; style=&quot;width: 61.108%;&quot; data-widthpercent=&quot;61.83&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T4m4U/btsOOx11ftS/QOLs57pgaYSSKQCRCj2ODK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT4m4U%2FbtsOOx11ftS%2FQOLs57pgaYSSKQCRCj2ODK%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;1448&quot; height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;NOTE: The transaction will have been committed already, but the transactional resources might still be active and accessible. As a consequence, any data access code triggered at this point will still &quot;participate&quot; in the original transaction, allowing to perform some cleanup (with no commit following anymore!), unless it explicitly declares that it needs to run in a separate transaction. Hence: Use PROPAGATION_REQUIRES_NEW for any transactional operation that is called from here.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;참고: 트랜잭션은 이미 커밋되었지만 트랜잭션 리소스는 여전히 활성화되어 액세스할 수 있습니다. 따라서 이 시점에서 트리거된 모든 데이터 액세스 코드는 원래 트랜잭션에 여전히 &quot;참여&quot;하여 별도의 트랜잭션에서 실행해야 한다고 명시적으로 선언하지 않는 한(더 이상 커밋 팔로우 없이!) 정리를 수행할 수 있습니다. 따라서 여기서 호출되는 모든 트랜잭션 작업에는 PROGRAPION_REQUEED_NEW를 사용하세요.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 결론 및 해결방안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  즉, 하나의 스레드에서 이벤트가 수행될 때 트랜잭션이 1번 커밋되었다면, 이후의 커밋시점에 대해 이벤트를 트리거하기 위해서는 명시적으로 트랜잭션을 분리, 생성하여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 첫 번째 트랜잭션&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;@Transactional
fun firstTransaction(){
    //... 
    eventPublisher.publish(event)
    //... 
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 두 번째 트랜잭션 &lt;span style=&quot;color: #ee2323;&quot;&gt;(여기서 트랜잭션을 분리해야 합니다.)&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Listener
public class CustomEventListener(){
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void litener(Event event) {
        service.run(event.deliveryId());
    }
}

// Service
public class ApiService(){
    @Transactional
    public void run(Event event) {
        //...
        publisher.publish(event)
        //...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)&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;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Async
@TransactionalEventListener
fun on(event: Event) {
    println(&quot;자라나라 머리머리&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;잠깐 시간이 남아서 오랜만에 글을 썼는데, 많이 버겁네요 후&lt;br /&gt;앞으로 열심히 써보겠습니다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;열정열정열정~&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;[SpringBoot]&amp;nbsp;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;  @TransactionalEventListener&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;&quot;AFTER_COMMIT&quot;&amp;nbsp;의&amp;nbsp;Transaction&amp;nbsp;처리&amp;nbsp;범위&lt;/u&gt;&lt;/h4&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>after commi</category>
      <category>after_commit 동작</category>
      <category>event listener trigger</category>
      <category>리스너 커밋</category>
      <category>스프링</category>
      <category>이벤트 리스너 커밋</category>
      <category>이벤트리스너</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/493</guid>
      <comments>https://thalals.tistory.com/493#entry493comment</comments>
      <pubDate>Mon, 23 Jun 2025 23:49:27 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] RestClient 와 HTTPInterface 로 통신하기</title>
      <link>https://thalals.tistory.com/492</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;코드는 &lt;a href=&quot;https://github.com/thalals/SpringStudy/tree/main/rest-client&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github&lt;/a&gt; 에 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestClient Spring Boot 3.2.x Release 된 서비스입니다.&lt;br /&gt;RestClient 는 외부 서비스와 통신을 제공해주며, 기존의 WebClient 의 WebFlux에 대한 의존성에 대한 아쉬운점을 보안하여 나온 서비스 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 제가 아는 Spring 에서 제공해주는 HTTP 호출 도구는 아래의 흐름을 가집니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;RestTemplate (2009. Spring 3.0 - Deprecated)&lt;/li&gt;
&lt;li&gt;WebClient (2017. spring 5.0에 도입)&lt;/li&gt;
&lt;li&gt;RestClient (2024.10. spring 6.1, spring boot 3.2)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 을 제외하고, HTTP 통신 라이브러리들은 아래와 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Apache HttpClient (2009)&lt;/li&gt;
&lt;li&gt;Netflix OpenFeign (2012)&lt;/li&gt;
&lt;li&gt;Square OkHttp (2013)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring RestClient 가 나오기전에는, 비동기 처리 방식인 WebFlux에 대한 부담감이 존재하기때문에 주로 `OpenFeign` 이나 `OkHttp` 라이브러리를 주로 사용해온 것으로 보여집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Spring 에서 정식으로 동기식 호출도구이며 직관적인 RestClient 를 출시했기 때문에, 외부 라이브러리에 대한 의존성을 줄이는 방향이 아무래도 관리에 편하기 때문에 해당 기능에대해 공부해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[목차]&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;RestClient 사용 방법
&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;Get( ) / Post( ) 요청&lt;/li&gt;
&lt;li&gt;RestClient 에러 핸들링&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;HTTP Interface 와 함께 사용하기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HTTPInterface 란&lt;/li&gt;
&lt;li&gt;HTTPInterface 사용하기&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. RestClient 사용 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 객체생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestClient 인터페이스를 사용하여 생성할 수 있습니다.&lt;br /&gt;SpringFramework 에서 지원하는 도구이기때문에 의존성을 추가할 필요가 없습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import org.springframework.web.client.RestClient

class RestClientConfig {

    fun getRestClient(): RestClient {
        return RestClient.create()
    }

    fun getRestClient2(): RestClient {
        return RestClient.builder()
            .baseUrl(&quot;/&quot;)
            .build()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) Get( ) / Post( ) 요청&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;api 는 해당 사이트에서 제공해주는 더미 url 로 테스트하였습니다.&lt;br /&gt;https://jsonplaceholder.typicode.com/&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 직관적으로 외부 API를 호출 할 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun getApi() {
    val restClient = getRestClient()
    ResponseEntity result = restClient.get()
        .uri(&quot;https://jsonplaceholder.typicode.com/posts&quot;)
        .retrieve()
        .toEntity(String::class.java)

    println(&quot;Response status: &quot; + result.statusCode)
    println(&quot;Response headers: &quot; + result.headers)
    println(&quot;Contents: &quot; + result.body)
}

//Json 객체 Mapping
val pet = restClient.get()
  .uri(&quot;https://petclinic.example.com/pets/{id}&quot;, id) 
  .accept(APPLICATION_JSON) 
  .retrieve()
  .body&amp;lt;Pet&amp;gt;()
  
//응답값이 존재하지 않을 때 ex) POST
val response = restClient.post() 
  .uri(&quot;https://petclinic.example.com/pets/new&quot;) 
  .contentType(APPLICATION_JSON) 
  .body(pet) 
  .retrieve()
  .toBodilessEntity()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3) RestClient 에러 핸들링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;기본적으로 RestClement는 4xx 또는 5xx 상태 코드로 응답을 검색할 때 RestClementException의 하위 클래스를 실행합니다. 이 동작은 onStatus를 사용하여 재정의할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;fun getApiError() {
    val restClient = getRestClient()

    //404
    val result = restClient.get()
        .uri(&quot;https://jsonplaceholder.typicode.com/donot&quot;)
        .retrieve()
        .onStatus(HttpStatusCode::is4xxClientError) { _, response -&amp;gt;
            throw RuntimeException(response.statusText)
        }
        .toEntity(String::class.java)

    println(&quot;Response status: &quot; + result.statusCode)
    println(&quot;Response headers: &quot; + result.headers)
    println(&quot;Contents: &quot; + result.body)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RestClient Exchange&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;더 고급 시나리오의 경우, RestClient는 retrieve() 대신 사용할 수 있는 exchange() 메서드를 통해 기본 HTTP 요청 및 응답에 대한 액세스를 제공합니다. &lt;br /&gt;exchange()를 사용할 때는 상태 처리기가 적용되지 않습니다. Exchange 함수에서 이미 전체 응답에 대한 액세스를 제공하므로 필요한 오류 처리를 수행할 수 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;fun getApiExchange() {
    val restClient = getRestClient()

    //404
    val result = restClient.get()
        .uri(&quot;https://jsonplaceholder.typicode.com/donot&quot;)
        .exchange { request, response -&amp;gt;
            if (response.getStatusCode().is4xxClientError()) {
                throw RuntimeException(response.statusText)
            } else {
                //return Custom Response Object
                val pet: Pet = convertResponse(response)
                pet
            }
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2.&amp;nbsp; HTTP Interface 와 함께 사용하기&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1) HTTPInterface 란&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;✔️ HttpInterface는 인터페이스 기반 선언형 API 입니다.&lt;/blockquote&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring&amp;nbsp;프레임워크에서는&amp;nbsp;@HttpExchange&amp;nbsp;메서드를&amp;nbsp;사용하여&amp;nbsp;HTTP&amp;nbsp;서비스를&amp;nbsp;Java&amp;nbsp;인터페이스로&amp;nbsp;정의할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;&lt;br /&gt;이러한 인터페이스를 HttpServiceProxyFactory에 전달하여 RestClient 또는 WebClient와 같은 HTTP 클라이언트를 통해 요청을 수행하는 프록시를 생성할 수 있습니다. 서버 요청 처리를 위해 @Controller에서 인터페이스를 구현할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  즉, 앞에서 본 RestClient 와 HTTP Interface 를 함께 사용하면 &lt;b&gt;선언적 API의 간결함과 RestClient의 유연성을 모두 활용할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) HTTPInterface 사용하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@HttpExchange 어노테이션을 명시한 Interface로 어떤 URL을 사용하여 통신할지 명시해줍니다.&lt;/li&gt;
&lt;li&gt;현재까지는 Spring에서 실질적으로 HTTP 통신의 역할을 해줄 프록시객체 생성을 지원해주지 않기때문에, 통신 로직을 담당할 Proxy 객체를 직접 구현해 주어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚙️ HttpInterface&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@HttpExchange
interface DemoHttpInterface {
    @GetExchange(&quot;/posts&quot;)
    fun getPosts()
}

// GetExchange..
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@HttpExchange(method = &quot;GET&quot;)
public @interface GetExchange {
...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚙️ Proxy 객체 Bean 등록&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
class DemoHttpInterfaceProxy {
    @Bean
    fun restClient(): RestClient =
        RestClient
            .builder()
            .baseUrl(&quot;https://jsonplaceholder.typicode.com&quot;)
            .defaultHeaders { headers -&amp;gt;
                headers.contentType = MediaType.APPLICATION_JSON
                headers.accept = listOf(MediaType.APPLICATION_JSON)
                headers.setBasicAuth(&quot;jwt-token&quot;)
            }.build()

    @Bean
    fun demoHttpInterface(
        @Qualifier(&quot;restClient&quot;) restClient: RestClient,
    ): DemoHttpInterface {
        val adapter = RestClientAdapter.create(restClient)
        val factory = HttpServiceProxyFactory.builderFor(adapter).build()

        return factory.createClient(DemoHttpInterface::class.java)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚙️ Test&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@SpringBootTest
class HttpInterfaceTest(
    @Autowired
    var demoHttpInterface: DemoHttpInterface,
) {
    @Test
    fun testGet() {
        val posts = demoHttpInterface.getPosts()
        println(posts)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcC3QF/btsMDPKDQ1f/6pwYrGOXihTsHjUuao4UK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcC3QF/btsMDPKDQ1f/6pwYrGOXihTsHjUuao4UK1/img.png&quot; data-alt=&quot;야호~&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcC3QF/btsMDPKDQ1f/6pwYrGOXihTsHjUuao4UK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcC3QF%2FbtsMDPKDQ1f%2F6pwYrGOXihTsHjUuao4UK1%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;800&quot; height=&quot;262&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;야호~&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;이렇게하면 HTTPInterface 로 통신할 서비스를 명시적으로 선언 및 관리하고, 각각의 Proxy 객체를 통신 서비스(도메인)의 성격에 맞게 핸들링할 수 있습니다.&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;간만에 끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;[Spring Boot]&lt;br /&gt;  RestClient 와 HTTPInterface&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mangkyu.tistory.com/291&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mangkyu.tistory.com/291&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>httpinterface 란</category>
      <category>java</category>
      <category>java http</category>
      <category>restclient란</category>
      <category>spring</category>
      <category>spring api 통신</category>
      <category>Spring Http</category>
      <category>spring java 외부 통신</category>
      <category>스프링 api 통신</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/492</guid>
      <comments>https://thalals.tistory.com/492#entry492comment</comments>
      <pubDate>Fri, 7 Mar 2025 09:38:58 +0900</pubDate>
    </item>
    <item>
      <title>Spring Bean 공부하기 (Been 생명주기, Scope, 권장 사용 방법)</title>
      <link>https://thalals.tistory.com/491</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 지금까지 잘 모르고 사용했던 Spring Bean 에 대해 공부해보고자 합니다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Spring Bean 이란&lt;/li&gt;
&lt;li&gt;Bean 의 생명 주기와 Bean Scope&lt;/li&gt;
&lt;li&gt;Bean 의 사용 이유&lt;/li&gt;
&lt;li&gt;Spring Bean 의 권장 사용 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Spring 에서의 Bean 이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Framework 에서의 Bean 은 Spring Boot 의 핵심 기술 중 하나인 제어의 역전(IOC)와 의존성 주입(DI)와 밀첩한 관계를 가집니다.&lt;br /&gt;Spring Framework는 IOC Container 를 통해 객체 주입(생성)에 대한 의존성을 코드 구현부에서 직접 가져가는게 종속성 주입(DI)을 통해 객체의 호출부에서는 어떤 객체가 생성되는지 신경 쓰지 않게하여 객체간의 결합도를 낮춰 설계할 수 있도록 해주는 기술을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 이때 Spring IOC Container(&lt;span style=&quot;color: #191e1e; text-align: start; background-color: #dddddd;&quot;&gt;org.springframework.context.ApplicationContext)&lt;/span&gt;&amp;nbsp;에서 관리되는 객체를 우리는 Bean 혹은 Bean 객체라고 부릅니다.&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;Pojo 객체를 IOC Contanier 에 Bean 객체로서 관리하도록 지정하는 방법은 @Configuration, @Baen 등의 어노테이션을 통해 런타임 시 객체를 생성해 &lt;span style=&quot;background-color: #dddddd; color: #191e1e; text-align: start;&quot;&gt;ApplicationContext&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 에서 관리하도록 지정할 수 있습니다.&amp;nbsp;&lt;br /&gt;&lt;/span&gt;(참고 : &lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/java.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/core/beans/java.html&lt;/a&gt;)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Bean 생명 주기와 Bean Scope&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ Bean Life Cycle&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Spring 실행&lt;/li&gt;
&lt;li&gt;Spring IOC 컨테이너 생성 (Application Context)&lt;/li&gt;
&lt;li&gt;Component Scan &amp;rarr; Bean 등록&lt;/li&gt;
&lt;li&gt;의존성 주입&lt;/li&gt;
&lt;li&gt;Bean 초기화 콜백 메서드 실행&lt;/li&gt;
&lt;li&gt;Spring 종료 전 소멸 콜백 메서드 실행&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(Bean 콜백 메서드에 관해서는 코드를 까보지 않아 잘 모릅니다,, 김영한님 강의에 나오길래 일단 적어봤어요)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;프로세스 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ Component Scan&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;Component Scan 은 @Component 주석이 붙어있는 클래스를 찾아 Bean 객체로 등록하는 과정입니다.&lt;/li&gt;
&lt;li&gt;결국 Component Scan 이란, 특정 객체를 Spring IOC Container 에서 관리하도록 객채를 생성하여 넣어줘~ 라는 과정입니다.&lt;/li&gt;
&lt;li&gt;그렇다면, 외부 라이브러리를 사용하여 생성하는 객체를 Bean 으로 등록하고 싶을때는 어떻게 해야할까요? &lt;br /&gt;  이럴 때 @Bean 어노테이션을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 Bean 사용 방법에 대해서는 조금 더 아래에서 공부해보겠습니다.&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;✔️ Bean Scope&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기본적으로는 Singleton 입니다.&lt;/b&gt; 한번 빈 객체로 등록되면 객체는 재 생성 되지않고 생성된 객체를 주입받아 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;프로토타입 : prototype scope 로 설정하면 의존성을 주입 받을 때마다 IOC Container 에서 객체를 생성합니다. 기본적으로 초기화까지 진행하고 주입된 객체에 대한 관리(할당 해제)는 관여하지 않습니다.&lt;/li&gt;
&lt;li&gt;그 외에 request, session, application, websocket 의 범위마다로 Bean 의 생명주기를 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScopeDescription (&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/p&gt;
&lt;table id=&quot;beans-factory-scopes-tbl&quot; style=&quot;background-color: #ffffff; color: #191e1e; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 11.9767%;&quot;&gt;&lt;span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-singleton&quot;&gt;singleton&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 87.907%;&quot;&gt;&lt;span&gt;(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 11.9767%;&quot;&gt;&lt;span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-prototype&quot;&gt;prototype&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 87.907%;&quot;&gt;&lt;span&gt;Scopes a single bean definition to any number of object instances.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 11.9767%;&quot;&gt;&lt;span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-request&quot;&gt;request&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 87.907%;&quot;&gt;&lt;span&gt;Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring&lt;span&gt;&amp;nbsp;&lt;/span&gt;ApplicationContext.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 11.9767%;&quot;&gt;&lt;span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-session&quot;&gt;session&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 87.907%;&quot;&gt;&lt;span&gt;Scopes a single bean definition to the lifecycle of an HTTP&lt;span&gt;&amp;nbsp;&lt;/span&gt;Session. Only valid in the context of a web-aware Spring&lt;span&gt;&amp;nbsp;&lt;/span&gt;ApplicationContext.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 11.9767%;&quot;&gt;&lt;span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-application&quot;&gt;application&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 87.907%;&quot;&gt;&lt;span&gt;Scopes a single bean definition to the lifecycle of a&lt;span&gt;&amp;nbsp;&lt;/span&gt;ServletContext. Only valid in the context of a web-aware Spring&lt;span&gt;&amp;nbsp;&lt;/span&gt;ApplicationContext.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 11.9767%;&quot;&gt;&lt;span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/web/websocket/stomp/scope.html&quot;&gt;websocket&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 87.907%;&quot;&gt;&lt;span&gt;Scopes a single bean definition to the lifecycle of a&lt;span&gt;&amp;nbsp;&lt;/span&gt;WebSocket. Only valid in the context of a web-aware Spring&lt;span&gt;&amp;nbsp;&lt;/span&gt;ApplicationContext.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Bean 의 사용 이유&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개인적인 생각 정리 입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Framework 에서 제공하는 IOC 컨테이너를 통한 의존성 주입을 위해 Bean 을 사용하는 목적이 가장 크다고 생각되긴 합니다.&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;Bean 객체는 Default 로 Singleton Scope 를 가집니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;OkHttpClient 와 같은 외부 라이브러리 객체를 코드단에서 직접 생성하지 않고 Bean으로 등록하였을 때, 객체 인스턴스가 여러 개 생김으로 써 발생하는 다양한 문제점(메모리, 라이브러리 커넥셔 풀 등등,,)에 대해 고민할 필요가 없어지게됩니다.&lt;/li&gt;
&lt;li&gt;(추가로, 의존성을 외부에서 주입하기 때문에 특정 라이브러리를 필요하는 기능을 인터페이스로 추출한다면 라이브러리 변경에도 자유로울 수 있겠네요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 메모리 관점에서 객체를 Singleton 으로 관리할 수 있다는 것 또한 Bean 의 주요한 장점이라고 생각됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Spring Bean 의 권장 사용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️ Config&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bean 으로 등록할 객체를 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;✔️ Bean 등록과정&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bean 의 Default Scope 는 Singleton 이기 때문에 &lt;span style=&quot;color: #a6bc00;&quot;&gt;@Configuration&lt;/span&gt;이 붙어있는 클래스를 Component Scan 과정에서 탐색하고&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #a6bc00;&quot;&gt;@Bean&lt;/span&gt;이 붙어있는 메서드의 응답값을 IOC Contanier 에 등록합니다. (메서드는 1번만 실행)&lt;/li&gt;
&lt;li&gt;이때 IOC 컨테이너에 등록되는 Bean 객체의 이름은 메서드 이름입니다. (name 속성으로 지정 가능)&lt;/li&gt;
&lt;li&gt;만약 아래와 같이 같은 응답객체를 Bean 으로 등록하였을 때 &lt;span style=&quot;color: #a6bc00;&quot;&gt;@Primay&lt;/span&gt; 주석을 이용하여 Default Bean 객체를 지정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class OkHttpClientConfig {

    @Primary
    @Bean
    public OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
            .connectTimeout(60, TimeUnit.SECONDS)
            .build();
    }

    @Bean
    public OkHttpClient okHttpClientLongConnection() {
        return new OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.MINUTES)
            .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️ Bean 주입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bean 객체는 의존성 주입이 가능하기 때문에 원하는 객체를 정의합니다.&lt;/li&gt;
&lt;li&gt;만약 &lt;span style=&quot;color: #a6bc00;&quot;&gt;@Primary&lt;/span&gt; 가 붙은 Default Bean 이 아닌 속성의 객체를 주입하고 싶다면 &lt;span style=&quot;color: #a6bc00;&quot;&gt;@Qualifer&lt;/span&gt; 주석을 통해 주입할 Bean 의 이름을 지정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TestService {

    private final OkHttpClient okHttpClient;

    @Qualifier(&quot;okHttpClientLongConnection&quot;)
    private final OkHttpClient okHttpClientLongConnection;

    public void hello() throws IOException {
        var response = okHttpClient.newCall(null).execute();
        var response2 = okHttpClientLongConnection.newCall(null).execute();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗️ 만약 생성자 주입을 Lombok의 &lt;span style=&quot;color: #a6bc00;&quot;&gt;@RequiredArgsConstructor&lt;/span&gt; 사용한다면 아래와 같은 오류가 발생합니다. 이는 롬복에 &lt;span style=&quot;color: #a6bc00;&quot;&gt;@Qualifer&lt;/span&gt; 가 붙은 주석을 런타임시 실제 생성자로 변환할 때 함께 복사하지 못해서 나오는 에러입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Lombok does not copy the annotation 'org.springframework.beans.factory.annotation.Qualifier' into the constructor&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4QEQA/btsL3OyeYA0/kC6b3clZENu05orUXHKKU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4QEQA/btsL3OyeYA0/kC6b3clZENu05orUXHKKU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4QEQA/btsL3OyeYA0/kC6b3clZENu05orUXHKKU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4QEQA%2FbtsL3OyeYA0%2FkC6b3clZENu05orUXHKKU1%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;1102&quot; height=&quot;352&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗️아래와 같은 별도의 설정 파일(lombok.config) 을 통해 &lt;span style=&quot;color: #a6bc00;&quot;&gt;@Qualifier&lt;/span&gt; 어노테이션도 함께 복사하도록 세팅이 가능합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;# lombok.config
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;각각 다른 객체가 주입된 결과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFWYi6/btsL3goj47y/zdeG7RPCrERrB8yWw4tsDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFWYi6/btsL3goj47y/zdeG7RPCrERrB8yWw4tsDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFWYi6/btsL3goj47y/zdeG7RPCrERrB8yWw4tsDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFWYi6%2FbtsL3goj47y%2FzdeG7RPCrERrB8yWw4tsDk%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;609&quot; height=&quot;114&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. Spring Bean 의 다른 사용방법 (권장 x)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 아래와 같이도 Bean 을 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;@Configuration 또한 @Component 를 포함하기에 해당 클래스도 Bean 객체로 등록되어 있습니다.&lt;/li&gt;
&lt;li&gt;또한 @Bean 이 명시된 메서드의 응답값이 Bean 객체로 지정되어 있기 때문에 해당 메서드를 실행해도, 실제로 메서드가 실행되지 않으며 메서드 이름으로 Bean 객체를 찾아서 반환합니다. (싱글톤)&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TestService {

    private final OkHttpClientConfig okHttpClientConfig;

    public void hello2() {
        OkHttpClient okHttpClient1 = okHttpClientConfig.okHttpClient();
        OkHttpClient okHttpClient2 = okHttpClientConfig.okHttpClientLongConnection();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 이러한 방식은 Spring 에서 권장하는 방식이 아니라고 합니다. &lt;br /&gt;@Configuration 은 @Bean 을 관리하는 목적으로만 사용하고 Bean 객체를 직접 주입받는게 싱글 톤을 명확하게 보장하는 방식이라고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝읕&lt;/p&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>@bean vs @configuration</category>
      <category>@bean 과 @component</category>
      <category>@bean 왜 사용</category>
      <category>@bean 장점</category>
      <category>bean 이란</category>
      <category>spring</category>
      <category>Spring Bean</category>
      <category>spring bean 개념</category>
      <category>spring boot</category>
      <category>spring boot @bean</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/491</guid>
      <comments>https://thalals.tistory.com/491#entry491comment</comments>
      <pubDate>Wed, 29 Jan 2025 00:54:17 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot WebClient Mocking 하기</title>
      <link>https://thalals.tistory.com/490</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;상당히 오랜만에 글을 쓰네영&lt;br /&gt;반성하고 열심히 재밌게 다시 써보겠습니다 야호&lt;br /&gt;코드는 &lt;a href=&quot;https://github.com/thalals/SpringStudy/tree/main/WebClient&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 회사로 이직한지 1달이 되었습니다.&lt;br /&gt;여느 회사나 마찬가지로 제가 모르는 코드를 수정해야하기 때문에 테스트 코드는 저에게 필수적인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Spring Boot WebFlux 와 함께 나온 WebClient 를 Mocking 하는 방법에 대해 기록해보고자 합니다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;WebClient 를 Mocking 하려는 이유&lt;/li&gt;
&lt;li&gt;WebClient Mocking 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. WebClient를 Mocking 하려는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 WebClient 자체를 Mocking 하는 상황이 좋은 상황은 아니라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드단에서 더 좋은 방향은 WebClient 를 사용하는 부분을 추상화하고 호출부에서 추상화하는(or 책임을 가지는) 클래스를 의존하는 방향이면, WebClient 를 Mocking 할 필요가 아예 없어지겠죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 코딩이라는게! 항상 이상적인 방향으로 갈 수는 없겠죠! (&lt;s&gt;코딩 잘하고 싶다!&lt;/s&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 주어진 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 저에게 주어진 상황은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;WebClient 의 기본적인 객체를 생성하는 util 클래스가 존재&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;어플리케이션의 하나의 작업 단위(집합)를 정의하고 도메인의 각 동작을 조율하는 역할&quot;&lt;/b&gt; 해야할 객체에 동작을 수행하는 비즈니스로직이 결합 &amp;rarr; 많은 의존성과 책임을 가짐&lt;/li&gt;
&lt;li&gt;WebClientUtil 클래스에서 객체를 생성해화 커스텀한 설정을 거쳐 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;타 서비스를 호출하고 로직을 수정해야하는 상황&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;해당 부분을 따로 추출하기에는 너무 많은 변경점이 생김&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;빠른 요구사항을 적용하고 테스트하기위해서 WebClient 자체에 대한 Mocking 테스트가 필요&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;⚙️ 예시코드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래는 간단하게 외부 의존성을 가지는 Service 객체를 만들어 보았습니다.&lt;/li&gt;
&lt;li&gt;  목표는 WebClient 호출 결과값으로 인한 Exception 처리에 대한 테스트 코드를 작성입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class WebClientLegacyService {

    private final JpaRepository repository;
	private final WebClientUtil webclientUtil;
    
    public boolean callWebClient() {

        String string = webclientUtil.getBaseUrl(&quot;/test/할수/없는/주소지롱&quot;)
            .put()
            .retrieve()
            .onStatus(HttpStatusCode::is4xxClientError, clientResponse -&amp;gt;
                Mono.error(new RuntimeException(&quot;400 에러 입니다&quot;))
            )
            .onStatus(HttpStatusCode::is5xxServerError, clientResponse -&amp;gt;
                Mono.error(new RuntimeException(&quot;500 에러 입니다.&quot;)))
            .bodyToMono(String.class)
            .block();

        return Objects.nonNull(string);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. WebClient Mocking 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebClient 를 Mocking 하는 방법도 여러가지가 존재합니다. 저는 그 중에 2가지를 사용해보고자 시도했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MockWebServer 를 사용하는 방법&lt;/li&gt;
&lt;li&gt;Mockito 를 사용하는 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) MockWebServer 로 WebClient Mokcing&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 MockWebServer 를 사용해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ MockWebServer 란&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트위터의 공동 창업자인 잭 도시(Jack Dorsety)가 그의 친구와 함께 설립한 Square(현 Block) 에서 만든 okhttp 오픈소스 엔터프라이즈에 속한 라이브러리 중 하나입니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;OkHttp&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-testid=&quot;commit-group-title&quot;&gt;okhttp 는 HTTP 통신을 간편하게 구현할 수 있도록 다양한 기능을 제공해주는 Java 라이브러리입니다. Retrofit와 Okio 라이브러리를 사용하고있습니다.&lt;/li&gt;
&lt;li data-testid=&quot;commit-group-title&quot;&gt;많은 자료를 찾아보지않아지만, Spring 라이브러리인 WebClient 에 비해 사용 방법이나 가독성, Webflux 에 대한 러닝 커브가 존재하지 않아 꽤 괜찮은 라이브러리로 보여집니다.&lt;/li&gt;
&lt;li data-testid=&quot;commit-group-title&quot;&gt;다만, Spring Boot 3.2 부터 RestCleint 를 지원하기 때문에 3.2 이상의 버전을 사용한다면 굳이 OkHttp 를 사용할 이유 또한 없어보이긴 합니다.&lt;/li&gt;
&lt;li data-testid=&quot;commit-group-title&quot;&gt;벤치마크 수행결과에 의하면 약 30%정도 WebClient 보다 빠르다라는 결과가 존재하기도 하네요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMBHp5/btsLMExDgjw/Th1uTrhLKLa6uBmKP0HeX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMBHp5/btsLMExDgjw/Th1uTrhLKLa6uBmKP0HeX1/img.png&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;295&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.198%; margin-right: 10px;&quot; data-widthpercent=&quot;49.78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMBHp5/btsLMExDgjw/Th1uTrhLKLa6uBmKP0HeX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMBHp5%2FbtsLMExDgjw%2FTh1uTrhLKLa6uBmKP0HeX1%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;395&quot; height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zUrar/btsLMImBIG3/4zfmEypLTKkCRjsbILFk01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zUrar/btsLMImBIG3/4zfmEypLTKkCRjsbILFk01/img.png&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;302&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.6393%;&quot; data-widthpercent=&quot;50.22&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zUrar/btsLMImBIG3/4zfmEypLTKkCRjsbILFk01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzUrar%2FbtsLMImBIG3%2F4zfmEypLTKkCRjsbILFk01%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;408&quot; height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;출처 : https://github.com/ok2c/httpclient-benchmark/wiki&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-testid=&quot;commit-group-title&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;MockWebServer&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;MockWebServer는 OkHttp 라이브러리에서 제공하는 테스트 유틸리티로, 네트워크 요청을 테스트 환경에서 실행하기 위해 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;MockWebServer 네트워크 요청에 대해 예상되는 응답을 미리 정의해 두고, 해당 응답을 반환하여 실제 서버 없이도 클라이언트 코드가 서버와 통신하는 것처럼 동작하도록 테스트 환경을 구성하도록 해주는 도구입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;MockWebServer의 Junit5 가능&amp;nbsp; Oct 24, 2020 에 처음 커밋되었고, 최근(2025.1.14 기준) 최근까지도 지속해서 라이브러리가 관리되어지는 모습이 보여져서 꽤 믿음이 갑니다. (&lt;span style=&quot;color: #99cefa;&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;심지어 코틀린으로 구현된 라이브러리 입니다..!&lt;/span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1297&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boEdb2/btsLMH2dOnJ/UvC2amlkx9UThhQbdLkOz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boEdb2/btsLMH2dOnJ/UvC2amlkx9UThhQbdLkOz1/img.png&quot; data-alt=&quot;mvnrepository&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boEdb2/btsLMH2dOnJ/UvC2amlkx9UThhQbdLkOz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboEdb2%2FbtsLMH2dOnJ%2FUvC2amlkx9UThhQbdLkOz1%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;1297&quot; height=&quot;163&quot; data-origin-width=&quot;1297&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;mvnrepository&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;Star 수도 꽤 많고 무엇보다 테스트 환경에서 실행속도에 영향을 주지않아서 가벼운 마음으로 사용해보고자 합니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmwjA8/btsLM52D8KO/QvX2PSfMJWIZ3gX4Sn9q6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmwjA8/btsLM52D8KO/QvX2PSfMJWIZ3gX4Sn9q6K/img.png&quot; data-alt=&quot;https://github.com/square/okhttp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmwjA8/btsLM52D8KO/QvX2PSfMJWIZ3gX4Sn9q6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmwjA8%2FbtsLM52D8KO%2FQvX2PSfMJWIZ3gX4Sn9q6K%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;1241&quot; height=&quot;220&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/square/okhttp&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ MockWebServer&amp;nbsp; Test Code 작성하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️&lt;i&gt; gradle.build&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;// https://mvnrepository.com/artifact/com.squareup.okhttp3/mockwebserver
testImplementation 'com.squareup.okhttp3:mockwebserver:5.0.0-alpha.14'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️ Test Config&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상의 웹서버 포트를 만들어 mocking 하고 응답값을 전달해주는 방식입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@ExtendWith(MockitoExtension.class)
class WebClientLegacyServiceTest {

    private static MockWebServer mockWebServer;
    private static WebClient mockWebClient;

    @Mock
    JpaRepository repository;
    @Mock
    WebClientUtil webClientUtil;

    @InjectMocks
    WebClientLegacyService webClientLegacyService;

    @BeforeEach
    void setUp() throws IOException {

        mockWebServer = new MockWebServer();
        mockWebServer.start();

        mockWebClient = WebClient.builder()
            .baseUrl(mockWebServer.url(&quot;/&quot;).toString())
            .build();
    }

    @AfterEach
    void tearDown() throws IOException {
        mockWebServer.shutdown();
    }
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️ Test  Code&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@ExtendWith(MockitoExtension.class)
class WebClientLegacyServiceTest {

    @Test
    @DisplayName(&quot;요청 성공 테스트&quot;)
    void success() {

        // given
        String bodyJson = &quot;&quot;&quot;
                {
                 &quot;result&quot;: true,
                 &quot;message&quot;: &quot;&quot;
                 }
            &quot;&quot;&quot;;

        MockResponse mockResponse = new MockResponse()
            .setBody(bodyJson)
            .addHeader(&quot;Content-Type&quot;, &quot;application/json&quot;)
            .setResponseCode(200);

        mockWebServer.enqueue(mockResponse);

        when(webClientUtil.get(anyString())).thenReturn(mockWebClient);

        // when
        boolean result = webClientLegacyService.callWebClient();

        // then
        Assertions.assertTrue(result);

    }

    @Test
    @DisplayName(&quot;실패 테스트 - 400 error&quot;)
    void fail400() {

        // given
        String bodyJson = &quot;&quot;&quot;
                {
                 &quot;result&quot;: false
                 }
            &quot;&quot;&quot;;

        MockResponse mockResponse = new MockResponse()
            .setBody(bodyJson)
            .addHeader(&quot;Content-Type&quot;, &quot;application/json&quot;)
            .setResponseCode(400);

        mockWebServer.enqueue(mockResponse);

        when(webClientUtil.get(anyString())).thenReturn(mockWebClient);

        // then
        assertThatThrownBy(() -&amp;gt; webClientLegacyService.callWebClient())
            .isInstanceOf(RuntimeException.class)
            .hasMessage(&quot;400 에러 입니다&quot;);

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tsd5o/btsLM3wZsTI/SrVr36OC5lVVVuBNeQZL1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tsd5o/btsLM3wZsTI/SrVr36OC5lVVVuBNeQZL1k/img.png&quot; data-alt=&quot;빠르다..!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tsd5o/btsLM3wZsTI/SrVr36OC5lVVVuBNeQZL1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftsd5o%2FbtsLM3wZsTI%2FSrVr36OC5lVVVuBNeQZL1k%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;343&quot; height=&quot;118&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빠르다..!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 마무리하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 테스트를위한 라이브러리를 추가하고싶지는 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, Mockito를 이용한 WebClient 를 Mocking 하는 방식이 너무 복잡하고 손이많이가,, 이것 또한 자원 낭비가 아닐까 하고 찾아보고 적용해보니 테스트코드상에서는 러닝커브 없이 간다하고 가볍게 수행할 수 있었습니다.&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;이건 다른 이야기지만, 성능상으로 OkHttp 가 WebClient 보다 높은 성능을 보인다고 하지만 내부적으로 &lt;s&gt;Direct buffer memory 를 사용하여 빠른 성능을 보이지만, JVM Heap 을 사용하는게 아니라 GC 의 대상이 되지 않아&lt;/s&gt; 명시적으로 사용자가 메모리를 반환해 주지않으면 메모리 누수 (Native Memory Leak Detection) 의 위험성이 존재한다고 합니다.&lt;/li&gt;
&lt;li&gt;이건 직접 코드를 까본게 아니라서 한번 살펴볼 필요는 있어보이네요. 코드를 살펴본 후 내용 업데이트 하겠습니다&lt;/li&gt;
&lt;li&gt;&amp;rarr; 이전에는 대용량 데이터의 전달을 위해 Nio.ByteBuffer를 사용했는지 모르겠지만, 현재는 Okio 라이브러리에서 생성한 별도의 Buffer 객체를 사용하여 Connect 시 사용하는 것 같습니다.(아마도,,, 정말 어렵네요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJg0Kn/btsLM2ycjmp/agb6Rq06NS2p8s4TgNB0Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJg0Kn/btsLM2ycjmp/agb6Rq06NS2p8s4TgNB0Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJg0Kn/btsLM2ycjmp/agb6Rq06NS2p8s4TgNB0Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJg0Kn%2FbtsLM2ycjmp%2Fagb6Rq06NS2p8s4TgNB0Sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;417&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/figure&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;MockWebServer는 테스트 코드에서만 사용되며 가볍고 빠르며, 적용하기 쉬워서 매우 적합해 보인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 이만!&lt;br /&gt;끝!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://square.github.io/okhttp/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://square.github.io/okhttp/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-mocking-webclient&quot;&gt;https://www.baeldung.com/spring-mocking-webclient&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;mockwebserver 가이드 : &lt;a href=&quot;https://github.com/square/okhttp/tree/master/mockwebserver&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/square/okhttp/tree/master/mockwebserver&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Test-Driven Develop</category>
      <category>OKHttp</category>
      <category>okhttp mocking</category>
      <category>okhttp 성능</category>
      <category>server mock</category>
      <category>spring</category>
      <category>springboot</category>
      <category>webclient mocking</category>
      <category>webclient test</category>
      <category>webclient 테스트</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/490</guid>
      <comments>https://thalals.tistory.com/490#entry490comment</comments>
      <pubDate>Tue, 14 Jan 2025 23:04:14 +0900</pubDate>
    </item>
    <item>
      <title>[DataBase] 트랜잭션 공부하기</title>
      <link>https://thalals.tistory.com/489</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 오늘은 트랜잭션에 대해서 공부해보고자 합니다.&lt;br&gt;공부하기 전에 목차를 정해야겠죠&lt;br&gt;&amp;nbsp;&lt;br&gt;[목차]&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;트랜잭션이란 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;트랜잭션이 필요한 이유&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;트랜잭션의 특징&lt;/li&gt; 
 &lt;li&gt;트랜잭션의 격리 수준 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;트랜잭션의 격리 수준이 있는 이유&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;트랜잭션의 동작 과정 (Query)&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;시작!&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 트랜잭션이란 (Transaction)&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 트랜잭션의 &lt;s&gt;사전적&lt;/s&gt;(개인적) 정의&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;트랜잭션이란, 작업의 완전성을 보장해주기 위한 시스템입니다.&lt;/li&gt;&lt;li&gt;어떤 작업을 처리하기위한 작은 작업단위들이 세팅되었을 때, 이 작업 세트들이 모두 논리적으로 묶여 하나의 작업 단위로 구성되어&lt;br&gt;모든 논리적인 작업이 성공되었을 때 -&amp;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;h3 data-ke-size=&quot;size23&quot;&gt;✔️&amp;nbsp;트랜잭션이 필요한 이유&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 작업을 완료하기 위해서는 하위 작업들의 연속된 성공이 보장되어야 합니다.&lt;br&gt;예를 들어 &lt;b&gt;&quot;9시까지 출근한다&quot; 라는 작업&lt;/b&gt;을 성공하기 위해서는 아래와 같은 작업들이 모두 성공해야합니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;6:30 에 기상한다.&lt;/li&gt;&lt;li&gt;7:00 까지 씻는다.&lt;/li&gt;&lt;li&gt;7:05 에 버스를 탄다.&lt;/li&gt;&lt;li&gt;7:30 에 지하철을 탄다.&lt;/li&gt;&lt;li&gt;8:50 에 사무실에 도착한다.&lt;/li&gt;&lt;li&gt;9:00 전에 출근도장을 찍는다.&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;→ 이 모든 작업이 순차적으로 성공해야만, 작업이 완료되었다라고 보았을 때 1가지라도 실패하면 작업은 실패햐애합니다.&lt;br&gt;예를들어, &quot;9시 까지 출근한다&quot; 라는 작업이후 &quot;9:10 에 중대한 회의를 시작한다.&quot; 라는 작업이 실행되어야 한다면 선행 작업이 실패했을 때 후행 작업이 실행되면 안됩니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;6:30 에 기상한다.&lt;/li&gt;&lt;li&gt;7:00 까지 씻는다.&lt;/li&gt;&lt;li&gt;&lt;s&gt;7:05 에 버스를 탄다.&amp;nbsp;&lt;/s&gt; → 버스를 놓쳤다. (이후의 하위 작업은 의미가 없어짐)&lt;/li&gt;&lt;li&gt;7:30 에 지하철을 탄다.&lt;/li&gt;&lt;li&gt;8:50 에 사무실에 도착한다.&lt;/li&gt;&lt;li&gt;9:00 전에 출근도장을 찍는다.&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;→&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 선행작업(9:00) 출근을 하지 못하였지만, &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;선행작업(정상출근)이 실패한 것을 모르고 후행 작업 (9:10 회의) 가 저 없이 진행된다면,, 대참사가 일어납니다 !! (아마도요)&lt;/span&gt;&lt;br&gt;→&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;선행작업이 실패했다면, 다시 과거로 돌아가 (Roll Back) 작업을 성공시킨 후 후행 작업을 실행하여야 합니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 트랜잭션의 특징&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 트랜잭션이 무엇이고, 왜 필요한지 생각해보았으니 &lt;b&gt;&quot;트랜잭션이란 시스템에서 가져야하는 특징&quot;&lt;/b&gt; 에 대해 정리하겠습니다.&lt;br&gt;트랜잭션이란 개념은 나온지 오래되었고 가져야하는 특징에 대해 이미 정의가 되어있습니다. &lt;br&gt;흔히 ACID 라고 합니다.&lt;/p&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;/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;1. 원자성 Atomicity&lt;/h4&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트랜잭션의 작업단위가 가장 작은 단위여야 한다.&amp;nbsp;&lt;/blockquote&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;이 의미는, 트랜잭션으로 설정된 모든 작업이 성공되어야 DB 에 반영(Commit) 되어야 하며, 작업 셋 중 1가지로 실패하게 되면 DB (데이터 변경)에 반영되어서는 안됩니다.&lt;/li&gt;&lt;li&gt;흔히 &quot;트랜잭션에 포함된 작업은 전부 수행되거나 전부 수행되지 않아야한다&quot; 라고 표현하기도 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt; &lt;b&gt; &quot;트랜잭션의 작업단위가 가장 작은 작업의 단위가 되어야 한다.&quot;&lt;/b&gt; 는 다른 의미로 &lt;br&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;b&gt;&quot;트랜잭션의 범위를 최소화 하라&quot;&lt;/b&gt; 는 말과 같게 느껴집니다.&lt;br&gt;&amp;nbsp;→ 트랜잭션 또한 자원입니다.&lt;br&gt;트랜잭션을 유지하기 위해서는 DB 커넥션을 유지해야 하며, I/O 자원도 잡아 먹습니다.&lt;br&gt;또한, 작업의 편의성을 위해 트랜잭션을 길게 가져간다면, 실패했을 경우 Rollback 에 대한 자원 소모가 커집니다.&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 일관성 Constency&lt;/h4&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트랜잭션을 수행하기 전이나 후나 데이터베이스는 항상 일관된 상태를 유지해야한다.&lt;/blockquote&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&quot;트랜잭션의 수행 전, 후에 데이터 모델의 모든 제약 조건(기본키, 외래 키, 도메인, 도메인 제약조건 등)을 만족해야한다.&quot; 입니다. 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;예를 들자면 
    &lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
     &lt;li&gt;A 에서 B로 돈을 이체했을 때 A와 B 돈의 총합은 같아야 한다.&lt;/li&gt; 
     &lt;li&gt;A 의 외래 키를 참조하는 B 테이블이 있을 때, A의 외래 키가 변경되면 B의 참조 값도 변경되어야 한다.&lt;/li&gt; 
    &lt;/ol&gt; &lt;/li&gt; 
   &lt;li&gt;즉, 데이터가 일관되게 보관되고 저장되어야한다. 정도로 이해됩니다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 격리성 (고립성) Isolation&lt;/h4&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트랜잭션 수행 시, 다른 트랜잭션이 작업에 영향을 주어서는 안된다.&lt;/blockquote&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;데이터베이스는 기본적으로 클라이언트(호출부)가 같은 데이터를 공유(공유자원)하고 그 자원을 효율적으로 관리하는 것을 목표로 합니다.&lt;/li&gt;&lt;li&gt;따라서 같은 자원을 함께 사용할 때가 존재합니다. → 즉 여러 트랜잭션이 동시에 수행되어야 합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;이때 트랜잭션은 상호 간의 존재를 모르고 독립적으로 수행되어야 자원의 일관성(or 정합성) 을 유지할 수 있습니다.&lt;/b&gt;&lt;/li&gt;&lt;li&gt;트랜잭션간의 간섭이 일어나지 않도록 하기위해 공유자원에 접근하는 트랜잭션에 대한 제어가 필요합니다. (격리 필요)&lt;/li&gt;&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 영속성 (지속성) Durability&lt;/h4&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트랜잭션의 작업이 성공하여 일어진 데이터의 변경은 영구히 반영되어야한다.&lt;/blockquote&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;Commit 이 완료된 트랜잭션(성공한 작업)에 의한 데이터 변경은 그 즉시 데이터베이스의 데이터에 반영되어야 합니다.&lt;/li&gt;&lt;li&gt;다른 트랜잭션의 간섭에 영향을 받지않고&lt;b&gt;, 반영된 데이터가 그 다음에 실행되는 트랜잭션에 정상적으로 변경된 값이 조회되어야만 합니다.&lt;/b&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;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 트랜잭션의 격리 수준&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 격리 수준이란, 트랜잭션이 가져야하는 4가지 특징(ACID) 중 Isolation(고립성) 을 지키기 위해 시스템으로 고안해 낸 몆가지 방법들 입니다.&lt;br&gt;목차에는 [트랜잭션의 격리 수준이 있는 이유] 라고 적었지만 &lt;br&gt;DBMS 에서 공유자원에 접근하는 트랜잭션간의 동시성을 제어하기 위한 조치 방법이 &quot;트랜잭션 격리수준&quot; 입니다.&lt;br&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;(동시성에 대하여 공부하고 싶다면 해당 게시물 참고 → &lt;/i&gt;&lt;a href=&quot;https://thalals.tistory.com/485&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;i&gt;https://thalals.tistory.com/485&lt;/i&gt;&lt;/span&gt;&lt;/a&gt;&lt;i&gt;)&lt;/i&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;공유자원에 동시에 접근하는 프로세스들에 대하여 접근을 &lt;b&gt;얼마나 고립&lt;/b&gt;시킬것인지, &lt;b&gt;얼마나 허용&lt;/b&gt;시킬지에 따라서 성능과 안전성이 반비례하여 달라집니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;격리수준은 크게 아래의 4가지로 나뉩니다.&lt;br&gt;위로 갈수록 &lt;u&gt;격리수준이 높고 성능이 동시성 처리 성능이 낮으&lt;/u&gt;며, 아래로 내려갈수록 &lt;u&gt;격리수준이 낮고 동시성 처리 성능이 뛰어납니다.&lt;/u&gt;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;SERIALIZABLE&lt;/li&gt;&lt;li&gt;REPEATABLE READ → (MySQL default)&lt;/li&gt;&lt;li&gt;READ COMMITTED → (Oracle default)&lt;/li&gt;&lt;li&gt;READ UNCOMMITTED&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;(  다만, Real MySQL 8.0에 의하면 SERIALIZABLE 수준이 아니면 크게 성능의 개선이나 저하는 발생하지 않는다고 합니다.)&lt;br&gt;이제 각 격리수준이 얼마나 격리하는 것인지, 또한 각 수준별로 어떠한 문제점이 발생하길래 4가지나 시스템적으로 나누어 놓았는지 알아보도록 하겠씁니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. SERIALIZABLE&amp;nbsp;&lt;/h3&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;가장 단순하면서도 엄격한 격리수준입니다.&lt;/blockquote&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;트랜잭션이 Update 시 뿐만 아니라, Select 시에도 레코드 락을 걸어 읽기 작업 시에도 공유 잠금(읽기 잠금)을 획득해야 데이터에 접근이 가능합니다.&lt;/li&gt;&lt;li&gt;즉, 한 트랜잭션에서 Read/Write 하는 데이터에는 어떠한 트랜잭션도 절대 접근할 수 없습니다.&lt;/li&gt;&lt;li&gt;가장 엄격한 만큼 트랜잭션 격리 수준에서 발생하는 3가지 부정합 문제 중 어떠한 것도 일어나지 않지만, 동시성 제어 처리 성능이 떨어집니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;  Real MySQL 8.0 에 의하면 아래와 같은 말이 나옵니다,, phantom read 현상에 대해서는 아래 REPEATABLE READ 격리 수준에서 살펴보고자 합니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;InnoDB 스토리지 엔진 에서는 갭락과 넥스트 키 락 덕분에 REPEATABLE READ 격리 수준에서도 이미 PHANTOM READ (유령 읽기) 부정합 문제가 발생하지 않기 때문에 굳이 SERIALIZABLE을 사용할 필요성은 없어 보인다.&quot;&lt;br&gt;&lt;br&gt;&quot;다만, 엄밀하게 말해서 &quot;SELECT ... FOR UPDATE&quot; 또는 &quot;SELECT ... FOR SHARE&quot; 쿼리의 경우 REPEATABLE READ 격리 수준에서 PHANTOM READ 현상이 발생할 수 있다.&quot;&lt;br&gt;&lt;br&gt;&quot;하지만 레코드의 변경 이력(언두 로그)에 잠금을 걸 수는 없기 때문에, 이러한 잠그을 동반한 SELECT 쿼리는 예외적인 상황으로 볼 수 있다.&quot;&lt;/blockquote&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. REPEATABLE READ&lt;/h3&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;반복된 읽기? 이게 무엇일까 - 반복해서 읽어도 데이터 값이 일관성을 유지한다는게 아닐까?&lt;br&gt;(REPEATABLE READ 격리 수준은 MySQL의 InnoDB 스토리지 엔진에서 기본을 사용되는 격리 수준입니다.)&amp;nbsp;&lt;/blockquote&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;MySQL InnoDB 스토리지 엔진에서는 &lt;u&gt;트랜잭션이 Rollback 될 가능성&lt;/u&gt;에 대비해 데이터가 &lt;b&gt;트랜잭션에 의해 변경되기 전의 값을 &lt;/b&gt;&lt;br&gt;&lt;b&gt;&quot;언두(Undo)&quot; 공간에 백업&lt;/b&gt;해두고 실제 레코드 값을 변경합니다.&lt;/li&gt;&lt;li&gt;REAPATABLE READ 격리 수준에서는 &lt;b&gt;트랜잭션마다 번호를 부여&lt;/b&gt;하고, &lt;b&gt;언두 공간에 데이터를 백업할 떄 값을 변경한 트랜잭션 번호를 함께 저장&lt;/b&gt;합니다.&lt;/li&gt;&lt;li&gt;즉, 트랜잭션의 버전마다 변경되는 데이터를 관리하는 &lt;b&gt;MVCC&lt;/b&gt;(Multi Version Concurrency Control) 방식을 사용합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;REPEATABLE READ 수준에서는&lt;/b&gt; 언두 영역에 백업된 이전 데이터를 이용해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;동일 트랜잭션 내에서는 반복해서 데이터를 조회하더라도 동일한 결과값을 보여줄 수 있도록 보장&lt;/b&gt;&lt;/span&gt;합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;→ InnoDB 스토리지 엔진에서는 불필요하다고 판단하는 시점에 주기적으로 언두영역의 백업된 데이터를 삭제합니다.&lt;br&gt;→ 다만, &lt;u&gt;장시간 트랜잭션을 종료하지 않으면 언두 영역이 백업된 데이터로 무한히 커져 백업 레코드가 많아지면, DB 서버의 처리 성능이 저하될 수 있습니다.&lt;/u&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ REPEATABLE READ 격리 수준에서 트랜잭션이 동작하는 과정&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 A 와 B는 같은 테이블을 바라봅니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;사용자 B 의 트랜잭션이 시작하여 10번이라는 번호를 부여 받습니다.&lt;/li&gt; 
 &lt;li&gt;이후, 사용자 A의 트랜잭션이 시작하여 12번이라는 번호를 부여받습니다. 
  &lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
   &lt;li&gt;트랜잭션 12번은 테이블의 특정값을 변경합니다. (emp=500000 레코드, Lara → Toto)&lt;/li&gt; 
   &lt;li&gt;이때 REPEATABLE READ 격리 수준에서는 트랜잭션 12번으로 변경되기 전의 데이터를 언두 영역에 저장합니다. (이전 트랜잭션 번호와 함께)&lt;/li&gt; 
   &lt;li&gt;트랜잭션 12번이 Commit 되고 실제 데이터에 반영됩니다.&lt;/li&gt; 
  &lt;/ol&gt; &lt;/li&gt; 
 &lt;li&gt;사용자 B의 트랜잭션(10번) 이 다시 emp=5000 레코드의 값을 조회합니다.&lt;/li&gt; 
 &lt;li&gt;실제 데이터는 사용자 A에 의해 변경되었지만, 사용자 B의 트랜잭션(10번)은 부여받은 자신의 트랜잭션보다 작은 트랜잭션 번호에서 변경한 이력만 바라봅니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;  REPEATABLE READ 격리 수준에서는 이러한 MVCC 방식으로 동일 트랜잭션 내에서는 동일한 결과값이 조회될 수 있도록 보장합니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzcAGQ/btsKeRZmiDi/XXEvjHGXUDFcJqfT8afh21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzcAGQ/btsKeRZmiDi/XXEvjHGXUDFcJqfT8afh21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzcAGQ/btsKeRZmiDi/XXEvjHGXUDFcJqfT8afh21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzcAGQ%2FbtsKeRZmiDi%2FXXEvjHGXUDFcJqfT8afh21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;589&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;589&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ REPEATABLE READ 격리 수준에서 발생하는 부정합 문제 (PHANTOM READ)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;REPEATABLE READ 격리 수준은 SERIALIZABLE 만큼 엄격한 동시성제어가 아니기 때문에 부정합 문제가 발생합니다.&lt;br&gt;&lt;b&gt;&quot;PHANTOM READ (유령 읽기)&quot;&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;유령읽기 : 이름 그대로 존재하지 않는 데이터를 읽는다는 의미입니다.&lt;/li&gt;&lt;li&gt;아래의 그림과 같이, 사용자 B의 트랜잭션이 유지되고 있을 때 다른 트랜잭션에 데이터를 Insert 한 상황입니다.&lt;/li&gt;&lt;li&gt;이후 트랜잭션 10 번에서 &quot;SELECT .. FOR UPDATE&quot; 와 같이 락(Lock)을 걸어 조회할 때 문제가 생깁니다.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;언두 영역에는 락을 걸 수 없기 때문에, 변경이 일어난 실제 테이블(당연히 트랜잭션 10번 보다 최신화 상태)을 바라보게 되고&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;쿼리 결과가 달라지게 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;이렇게 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다, 안보였다 하는 현상을 PHANTOM READ 라고 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;정리 : 언두영역에는 Lock 을 걸 수 없기 때문에, Lock 을 거는 쿼리를 수행할 때 실제 레코드를 보게 되고 이때 버전이 맞지 않으므로 데이터 부정합이 발생한다.&lt;/blockquote&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oIv2w/btsKfMpoeFU/t4PWtP0GS3T3Afm1N4bP1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oIv2w/btsKfMpoeFU/t4PWtP0GS3T3Afm1N4bP1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oIv2w/btsKfMpoeFU/t4PWtP0GS3T3Afm1N4bP1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoIv2w%2FbtsKfMpoeFU%2Ft4PWtP0GS3T3Afm1N4bP1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;575&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. READ COMMITTED&lt;/h3&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;커밋된 데이터만 읽겠다.&lt;br&gt;오라클 DBMS 에서 기본으로 사용되는 격리 수준입니다.&lt;/blockquote&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;READ COMMITED 격리수준은, 어떤 트랜잭션에서 데이터를 변경했더라도 Commit이 완료된 데이터만 다른 트랜잭션에서 조회할 수 있습니다.&lt;/li&gt;&lt;li&gt;READ COMMITED 격리수준에서도 마찬가지로 데이터를 변경할 때 언두(Undo)영역에 변경 이전의 데이터를 저장하여 다른 트랜잭션에 의해 변경된 데이터가 commit 되기 전이라면 조회하지 이전 데이터를 조회합니다.&lt;/li&gt;&lt;li&gt;다만, REPEATABLE READ 와 다르게 Commit 에 완료되면 Commit 된 데이터를 조회합니다.&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4nNcX/btsKgeZ1TB4/3F2tTRsc8UaeTUj396yAPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4nNcX/btsKgeZ1TB4/3F2tTRsc8UaeTUj396yAPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4nNcX/btsKgeZ1TB4/3F2tTRsc8UaeTUj396yAPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4nNcX%2FbtsKgeZ1TB4%2F3F2tTRsc8UaeTUj396yAPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;534&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ REPEATABLE READ 격리 수준에서 발생하는 부정합 문제 (NON-REPEATABLE READ)&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;b&gt;특정 시점에 다른 트랜잭션에 의해 데이터가 변경 후 commit 이 안료되면, 작업 중인 트랜잭션에 데이터를 조회하는 결과값이 달라집니다.&lt;/b&gt;&lt;/li&gt;&lt;li&gt;즉, 항상 같은 결과를 가져와야 한다는 &quot;REPEATABLE READ&quot; 정합성에 위배되는 문제가 발생합니다.&lt;/li&gt;&lt;li&gt;이를 NON-REPEATABLE READ 부정합 문제라 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;489&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDMec6/btsKe9rYv0Q/fRbybgJdGif6P4tcJHteB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDMec6/btsKe9rYv0Q/fRbybgJdGif6P4tcJHteB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDMec6/btsKe9rYv0Q/fRbybgJdGif6P4tcJHteB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDMec6%2FbtsKe9rYv0Q%2FfRbybgJdGif6P4tcJHteB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;489&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;489&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. READ UNCOMMITED&lt;/h3&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;가장 낮은 수준의 격리 수준입니다.&lt;br&gt;3가지 부정합 문제가 발생하며, MySQL 에서는 최소한 READ COMMITED 이상의 격리 수준을 사용할 것을 권장합니다.&lt;/blockquote&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;READ UNCOMMITED 격리 수준에서는 아래 그림과 같이 Commit 이나 Rollback 여부에 상관없이 모든 트랜잭션의 변경 내용이 조회됩니다.&lt;/li&gt;&lt;li&gt;이렇게 되면, 다른 트랜잭션에 의해 변경되 값을 조회한 트랜잭션이 존재하고 값을 변경한 트랜잭션이 Rollback 된다면 문제가.. 생기겠죠?&lt;/li&gt;&lt;li&gt;이처럼 &lt;b&gt;어떤 트랜잭션에서 처리한 작업이 완료되지 않았음에도 다른 트랜잭션에서 볼 수 있는 현상을 &quot;더티 리드(Dirty Read)&quot;라고 합니다.&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebvGyv/btsKeBo8gmt/atIwa9k97p7MGtMlt92Kvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebvGyv/btsKeBo8gmt/atIwa9k97p7MGtMlt92Kvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebvGyv/btsKeBo8gmt/atIwa9k97p7MGtMlt92Kvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebvGyv%2FbtsKeBo8gmt%2FatIwa9k97p7MGtMlt92Kvk%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;500&quot; height=&quot;507&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;507&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  트랜잭션 격리 수준에 의한 부정합 문제 정리 표&lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Q4oH/btsKdRDx6RO/l2VqjsQbZIlFhovtk60juK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Q4oH/btsKdRDx6RO/l2VqjsQbZIlFhovtk60juK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Q4oH/btsKdRDx6RO/l2VqjsQbZIlFhovtk60juK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Q4oH%2FbtsKdRDx6RO%2Fl2VqjsQbZIlFhovtk60juK%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;1094&quot; height=&quot;326&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 트랜잭션의 동작 과정&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 에서 트래잭션을 실행할 때 실질적으로 날라가는 쿼리를 간단하게 알아보았습니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;자동커밋 해제&lt;br&gt; 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;기본적으로 데이터베이스는 자동커밋으로 설정되어 있습니다.&lt;/li&gt; 
   &lt;li&gt;즉, 쿼리가 실행될 때 데이터베이스에 즉각적으로 반영됩니다.&lt;/li&gt; 
   &lt;li&gt;트랜잭션은 즉각적으로 반영되지 않도록 하는 것이기 때문에, 자동 커밋을 해제하고 수동 커밋 모드로 변경하는 것이 트랜잭션의 시작입니다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;쿼리 실행&lt;br&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;이때 실행한 쿼리는 해당 세션 내에서만 동작하는 것이기 때문에 다른 트랜잭션에서는 해당 트랜잭션의 쿼리 실행 결과에 대해서 조회할 수 없습니다. (커밋 전)&lt;/li&gt; 
  &lt;/ol&gt; &lt;/li&gt; 
 &lt;li&gt;커밋, 롤백&lt;br&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;문제가 발생했다면 롤백&lt;/li&gt; 
  &lt;/ol&gt; &lt;/li&gt; 
&lt;/ol&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;set autocommit false; // 수동커밋 모드로 설정
# set autocommit true; //자동 커밋 모드로 설정
 
UPDATE user SET name='lora' WHERE user_id=1;

#성공 시
commit;
#실패 시
rollback;&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;Spring 에서 트랜잭션을 실행할 때도 하이버네이트 내부적으로도 동일하게 DBCP에서 커넥션을 가져올 때 setAutoCommit() 메소를 호출하여&lt;br&gt;autocommit = false 하는 쿼리가 날아갑니다. (성능 최적화 가능 지점)&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bME3Cu/btsKldtxmYI/1BZVaxgfDZfMlb4YQ9rs0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bME3Cu/btsKldtxmYI/1BZVaxgfDZfMlb4YQ9rs0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bME3Cu/btsKldtxmYI/1BZVaxgfDZfMlb4YQ9rs0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbME3Cu%2FbtsKldtxmYI%2F1BZVaxgfDZfMlb4YQ9rs0k%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;777&quot; height=&quot;445&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;자세한 링크 첨부하고 마무리하겠습니당&lt;br&gt;&lt;a href=&quot;https://pkgonan.github.io/2019/01/hibrnate-autocommit-tuning&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://pkgonan.github.io/2019/01/hibrnate-autocommit-tuning&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;끝! 아 졸려!&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&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;Real MySQL 8.0&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://mangkyu.tistory.com/30&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://mangkyu.tistory.com/30&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://dkswnkk.tistory.com/555&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://dkswnkk.tistory.com/555&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description>
      <category>DataBase/DB</category>
      <category>database</category>
      <category>db</category>
      <category>mysql</category>
      <category>transaction</category>
      <category>격리수준</category>
      <category>트랜잭션</category>
      <category>트랜잭션 동작과정</category>
      <category>트랜잭션 커밋</category>
      <category>트랜잭션 파헤치기</category>
      <category>트랜잭션이란</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/489</guid>
      <comments>https://thalals.tistory.com/489#entry489comment</comments>
      <pubDate>Sat, 26 Oct 2024 00:18:38 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] 다중 인스턴스에서 스케줄링 중복 실행 제어 하기 (@Scheduled Lock - shed lock)</title>
      <link>https://thalals.tistory.com/488</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;다중 인스턴스 환경에서 스케줄링 중복 실행을 제어하는 @ShedLock 에 대해 정리하는 글입니다.&lt;br /&gt;코드는 깃허브에 있습니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 에서는 정해진 시간마다 지정한 메소드가 실행되도록 스케줄링 기능을 지원하는 @Scheduled 어노테이션이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스케줄링 기능을 Spring 에서 구현하였을 때, 단일 인스턴스 (1개 서버) 배포환경에서는 신경써줘야할게 없지만 다중 인스턴스 (n 개의 서버, scale-out) 배포 환경일 경우&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;n 개의 인스턴스 환경에 배포되어있는 &lt;br /&gt;n 개의 프로그램이 특정 시간에 &lt;br /&gt;n 번의 스케줄링을 체크하여&lt;br /&gt;n 번의 기능을 수행합니다.&lt;br /&gt;&lt;br /&gt;즉, 중복이 일어날 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제점을 해결하기 위해서는 아래와 같은&amp;nbsp;처리를 해주어야합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;1번만 작업을 해도 된다.&lt;/li&gt;
&lt;li&gt;혹은 특정 스레드에서 내가 작업하고 있으니 건들지 말아라.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친절하고 다행이도 Spring Boot 에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다중 인스턴스 환경에서 스케줄러가 1번만 돌게 하기 위해 락을 거는 행위 &quot;lukas krecan&quot; 이라는 사람이 만들어둔 오픈소스인 @ShedLock 어노테이션으로 테이블 락을 이용해 &quot;&lt;u&gt;&lt;i&gt;건들지 말아라&lt;/i&gt;&lt;/u&gt;&quot; 를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ShedLock 이란&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예약된 작업이 동시에 1번만 실행되도록 합니다.&lt;/li&gt;
&lt;li&gt;작업이 시작될때 별도의 테이블에서 lock 을 획득합니다.&lt;/li&gt;
&lt;li&gt;Jdbc 템플릿으로 구현되기 때문에 DB에 종속적이지 않습니다. 다만 &lt;b&gt;shedlock 이라는 테이블이 필요&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;다른 노드(스레드)에서는 한 작업이 이미 다른 노드에서 실행 중이라면(lock 점유)&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; 다른 노드에서의 실행은 기다리지 않고 건너 뜁니다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;if one task is already being executed on one node, execution on other nodes does not wait, it is simply skipped.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  ShedLock 은 분산 스케줄러가 아닙니다.&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;ShedLock 은 단지 lock 을 이용할 뿐 동기화를 지원하지 않습니다.&lt;/li&gt;
&lt;li&gt;따라서 ShedLock 은 병렬로 실행할 준비가 되지 않았지만, 안전하고 반복적으로 실행할 수 있는 Scheduled 된 작업에서만 사용해야합니다.&lt;/li&gt;
&lt;li&gt;분산 스케줄러가 필요한 경우 db-sxheduler, JobRunr 등을 사용해야 합니다.&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;b&gt;  또한 ShedLock 의 Lock은 시간기반이며, 노드들의 시간이 동기화된다고 가정합니다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;따라서 ShedLock 의 LockProvider 를 구성할 때&lt;br /&gt;공식 라이브러리 가이드에서 usingDbTime을 활성화 하는 것을 강력하게 권장합니다.&lt;br /&gt;이 옵션이 활성화 되지 않으면 서버의 시간을 기준으로 Lock의 시계가 동작하기 때문에 서버간 시계가 동기화되어 있지 않은 상황을 방지할 수 있습니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1728543697916&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;

...
@Bean
public LockProvider lockProvider(DataSource dataSource) {
            return new JdbcTemplateLockProvider(
                JdbcTemplateLockProvider.Configuration.builder()
                .withJdbcTemplate(new JdbcTemplate(dataSource))
                .usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2
                .build()
            );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ShedLock 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ShedLock 은 @Scheduled 어노테이션과 함께 @SchedulerLock 어노테이션이 붙은 메소드에 구현됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;Dependcy&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;//shedlock
implementation 'net.javacrumbs.shedlock:shedlock-spring:5.16.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.16.0'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;Config&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = &quot;PT10S&quot;)
public class ScheduledConfig {

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(
            JdbcTemplateLockProvider.Configuration.builder()
                .withTableName(&quot;shed_lock_t&quot;)
                .withColumnNames(new ColumnNames(&quot;task_name&quot;, &quot;lock_until_tmstamp&quot;, &quot;locked_at_tmstamp&quot;, &quot;locked_by&quot;))
                .withJdbcTemplate(new JdbcTemplate(dataSource))
                .usingDbTime()
                .build()
        );
    }

    //스케줄러 멀티 스레드로 실행
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();

        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix(&quot;my-scheduler-thread-&quot;);
        scheduler.initialize();

        return scheduler;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;Scheduled Task&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component
@Slf4j
public class ScheduledTask {

    @Scheduled(fixedRate = 1000)
    @SchedulerLock(name = &quot;scheduledTaskName&quot;)
    public void taskOne() throws InterruptedException {
        log.info(&quot;{}, Task one start. ⚙️&quot;, LocalDateTime.now());
        sleep(2000);
        log.info(&quot;{}, Task one done. ⚙️⚙️&quot;, LocalDateTime.now());
    }

    @Scheduled(fixedRate = 1000)
    @SchedulerLock(name = &quot;scheduledTaskName&quot;)
    public void taskTwo() throws InterruptedException {
        log.info(&quot;{}, Task two start️. ⭐️&quot;, LocalDateTime.now());
        sleep(2000);
        log.info(&quot;{}, Task two done. ⭐⭐️️&quot;, LocalDateTime.now());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D7cGW/btsJ0hDLUno/zq2PfdeJ99ujfqsNd4Kl3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D7cGW/btsJ0hDLUno/zq2PfdeJ99ujfqsNd4Kl3k/img.png&quot; data-alt=&quot;'&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D7cGW/btsJ0hDLUno/zq2PfdeJ99ujfqsNd4Kl3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD7cGW%2FbtsJ0hDLUno%2Fzq2PfdeJ99ujfqsNd4Kl3k%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;1764&quot; height=&quot;264&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; ShedLock 테이블에 name 레코드가 존재한다면 기존의 동작은 중복 실행되지 않습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Not Use ShedLock&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중 인스턴스는 아니지만 굳이 가정하고 본다면 - Shed Lock 을 사용하지 않는다면, 아래의 사진과 같이 n 개의 스레드가 중복 실행될 것 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component
@Slf4j
public class ScheduledTask {

    @Scheduled(fixedRate = 1000)
    public void taskOne() throws InterruptedException {
        log.info(&quot;{}, Task one start. ⚙️&quot;, LocalDateTime.now());
        sleep(2000);
        log.info(&quot;{}, Task one done. ⚙️⚙️&quot;, LocalDateTime.now());
    }

    @Scheduled(fixedRate = 1000)
    public void taskTwo() throws InterruptedException {
        log.info(&quot;{}, Task two start️. ⭐️&quot;, LocalDateTime.now());
        sleep(2000);
        log.info(&quot;{}, Task two done. ⭐⭐️️&quot;, LocalDateTime.now());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EtBxZ/btsJ1aRtLy3/LV5T0KEk0TEf55U9bczwmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EtBxZ/btsJ1aRtLy3/LV5T0KEk0TEf55U9bczwmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EtBxZ/btsJ1aRtLy3/LV5T0KEk0TEf55U9bczwmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEtBxZ%2FbtsJ1aRtLy3%2FLV5T0KEk0TEf55U9bczwmK%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;1764&quot; height=&quot;310&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;310&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ShedLock 동작 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LockProvider 구현체인 JdbcTemplateStorageAccessor.class 에 구현되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://songjb.tistory.com/m/36&quot;&gt;https://songjb.tistory.com/36&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 블로그 내용 요약 정리&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스케줄러가 실행되면 executeWithLock 메서드를 실행하고 먼저 Provider를 통해서 lock을 획득하게 됩니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;shad lock 테이블의 key 값인 name 컬럼으로 lock 을 걸 컬럼 name 이 존재하는지 확인합니다. (조회)&lt;/li&gt;
&lt;li&gt;존재하지 않는다면 lock 레코드를 Insert 합니다.&lt;/li&gt;
&lt;li&gt;INSERT IGNORE INTO 문을 사용하여 레코드를 생성합니다.&lt;/li&gt;
&lt;li&gt;만약 두 개의 인스턴스에서 동일한 스케줄러를 실행한다면, 두 번째로 실행한 스케줄러에서 삽입된 중복된 레코드는 무시됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;처음 락 레코드를 삽입한다면 TRUE 반환, 기존 락 레코드가 존재하는경우 해당 레코드의 락 종료시간을 확입합니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;락 &quot;&lt;i&gt;종료 시간 &amp;lt;= 현재 시간 인 경우&lt;/i&gt;&quot;&lt;/li&gt;
&lt;li&gt;&quot;lock_until&quot; 컬럼 시간 변경 (Update)&lt;/li&gt;
&lt;li&gt;True 를 반환함으로써 lock 획득&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;만약 동일 스케줄러가 실행된다면 락 &quot;&lt;i&gt;종료 시간 &amp;gt; 현재 시간&lt;/i&gt;&quot;이 작기 때문에 UPDATE 되지 않고 0 반환 (수정 쿼리 결과 값)&lt;/li&gt;
&lt;li&gt;0이 반환된다면 updateRecord가 false를 반환하게되고 false가 반환되면 lock메서드가 Optional.empty()를 반환하게 되면서 스케줄러가 실행되지 않게된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 처음에는 Lock 이고 별도의 테이블과 컬럼을 사용한다고 하여 Mysql 의 Named Lock 과 동일한거 아닌가? 라는 생각도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shed Lock 의 동작과정을 보니, Lock 을 획득하기위해 대기하지 않는다는 점과 READ 와 (INSERT or UPDTE) 2개의 쿼리로 스케줄러의 중복을 방지할 수 있다는 점에서 큰 성능저하를 야기시키지도 않을 것 같네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 잦은 테이블 접근은 지양해야겠지만 간단하게 다중 인스턴스에서 중복 Task 실행을 막기에는 좋은 선택지가 되어줄 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;shedlock 동작 과정 : &lt;a href=&quot;https://songjb.tistory.com/m/36&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://songjb.tistory.com/m/36&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;shed lock open repoistory : &lt;a href=&quot;https://github.com/lukas-krecan/ShedLock&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/lukas-krecan/ShedLock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>scheduler lock</category>
      <category>shad lock</category>
      <category>shadlock</category>
      <category>spring</category>
      <category>spring schedul</category>
      <category>다중 인스턴스 락</category>
      <category>스케줄러</category>
      <category>스케줄러 락</category>
      <category>스케줄링 lock</category>
      <category>스프링</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/488</guid>
      <comments>https://thalals.tistory.com/488#entry488comment</comments>
      <pubDate>Thu, 10 Oct 2024 21:08:39 +0900</pubDate>
    </item>
    <item>
      <title>인텔리제이 개꿀 플러그인 (변수 하이라이트 및 토글 - Grep Console)</title>
      <link>https://thalals.tistory.com/487</link>
      <description>&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rqEFE/btsJNYcjISW/PsYu55PiQ7O5KGT3Z8I6DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rqEFE/btsJNYcjISW/PsYu55PiQ7O5KGT3Z8I6DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rqEFE/btsJNYcjISW/PsYu55PiQ7O5KGT3Z8I6DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrqEFE%2FbtsJNYcjISW%2FPsYu55PiQ7O5KGT3Z8I6DK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;371&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;371&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;인텔리제이 플로그인에서 Grep Console 다운&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xlaWG/btsJNWZTfrm/a0JI3GIjpeKkTiZMQkYcEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xlaWG/btsJNWZTfrm/a0JI3GIjpeKkTiZMQkYcEK/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;387&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;26.43&quot; data-filename=&quot;blob&quot; style=&quot;width: 26.1186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xlaWG/btsJNWZTfrm/a0JI3GIjpeKkTiZMQkYcEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxlaWG%2FbtsJNWZTfrm%2Fa0JI3GIjpeKkTiZMQkYcEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0iS4j/btsJMAqeCJz/BSRg0tQtKZdTJfUA4QIV3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0iS4j/btsJMAqeCJz/BSRg0tQtKZdTJfUA4QIV3K/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;139&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;73.57&quot; data-filename=&quot;blob&quot; style=&quot;width: 72.7186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0iS4j/btsJMAqeCJz/BSRg0tQtKZdTJfUA4QIV3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0iS4j%2FbtsJMAqeCJz%2FBSRg0tQtKZdTJfUA4QIV3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;139&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>기타 애매한 것</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/487</guid>
      <comments>https://thalals.tistory.com/487#entry487comment</comments>
      <pubDate>Thu, 26 Sep 2024 16:52:50 +0900</pubDate>
    </item>
    <item>
      <title>2024 항해 데브랩 후기</title>
      <link>https://thalals.tistory.com/486</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 2024.08.31일에 항해 데브랩 컨퍼런스에 다녀왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 네트워킹 활동이 있었지만, 발표 세션만을 간단하게 정리하며 남겨보고자 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RUVEw/btsJopPFj8O/E6ky0faNnQotDxgV5NJmQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RUVEw/btsJopPFj8O/E6ky0faNnQotDxgV5NJmQ1/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;397&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 54.8973%; margin-right: 10px;&quot; data-widthpercent=&quot;55.54&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RUVEw/btsJopPFj8O/E6ky0faNnQotDxgV5NJmQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRUVEw%2FbtsJopPFj8O%2FE6ky0faNnQotDxgV5NJmQ1%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;500&quot; height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfBJOR/btsJnW74bjD/NjERRkQl2OBSialtBI4g9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfBJOR/btsJnW74bjD/NjERRkQl2OBSialtBI4g9k/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;496&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 43.9399%;&quot; data-widthpercent=&quot;44.46&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfBJOR/btsJnW74bjD/NjERRkQl2OBSialtBI4g9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfBJOR%2FbtsJnW74bjD%2FNjERRkQl2OBSialtBI4g9k%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;500&quot; height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[목차]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;AI와 자동화로 주니어 개발자 키우기 - 이동욱님&lt;/li&gt;
&lt;li&gt;책임 분리의 마법: 깔끔한 폴더 구조 만들기 - 테오님&lt;/li&gt;
&lt;li&gt;클린 아키텍처: 무한 성장하는 시스템의 비밀 - 허재님&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwBgn1/btsJmHJvvvR/6UTZEvghsX8MTG5uj42hyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwBgn1/btsJmHJvvvR/6UTZEvghsX8MTG5uj42hyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwBgn1/btsJmHJvvvR/6UTZEvghsX8MTG5uj42hyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwBgn1%2FbtsJmHJvvvR%2F6UTZEvghsX8MTG5uj42hyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;490&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;490&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. AI 와 자동화로 주니어 개발자 키우기&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개발바닥의 연예인 - 인프랩 CTO 향로(이동욱)님의 발표였습니다.&lt;br /&gt;스타트업으로 시작한 서비스 회사에서, 인재풀을 확보하고 개발팀이 잘 성장하기 위해 고민했던 과정과 결과에 대해 이야기해 주셨습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이동욱 CTO님이 처음 인프랩에 들어가서 인원을 채용할 때 몆가지 기준을 세워 시니어를 뽑기로 결정했지만, 그 당시 인력풀이 너무 비쌌으며 마땅한 인재를 찾기가 어려우셨다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 상황에서 고민을 하시다, 꼭 필요한 조건만을 남기고 다시 생각해보니 &lt;b&gt;시니어 개발자 채용이 아닌 같이 일하기 좋은 동료&lt;/b&gt;를 뽑는다로 방향을 바꿨습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;✔️ 인프랩에서 원했던 인재풀 &amp;rarr; 시니어&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;✔️ 좋은 시니어 이전의, 같이 일하기 좋은 동료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;조직과 제품의 비전 align&lt;/li&gt;
&lt;li&gt;높은 Self Motivation&lt;/li&gt;
&lt;li&gt;조직과 제품에 대한 적절한 결정&lt;/li&gt;
&lt;li&gt;적당한 수준의 기술력&lt;/li&gt;
&lt;li&gt;리더쉽과 매니지먼트&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;조직과 제품의 비전 align&amp;nbsp; (배우기 힘듬 - 필수)&lt;/li&gt;
&lt;li&gt;높은 Self Motivation&amp;nbsp; &amp;nbsp;(배우기 힘듬 - 필수)&lt;/li&gt;
&lt;li&gt;조직과 제품에 대한 적절한 결정&amp;nbsp; (와서 배울 수 있음)&lt;/li&gt;
&lt;li&gt;적당한 수준의 기술력 (와서 배울 수 있음)&lt;/li&gt;
&lt;li&gt;리더쉽과 매니지먼트&amp;nbsp; (와서 배울 수 있음)&lt;/li&gt;
&lt;/ol&gt;
&lt;span&gt;&amp;rarr; 대신, 시니컬하거나 태도가 불량한 사람은 절대 뽑지 않음&lt;/span&gt;&lt;br /&gt;&lt;span&gt;+ (연차 대비 훌륭한 전문성) 은 당연한 거라고 함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 원하는 인재풀을 바꾸다보니, 경력이 중요해지지 않았고 저연차(주니어) 혹은 신입을 많이 뽑게 되었다고 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;하지만, 이렇게 뽑아도 2년안에 퇴사한다면??&lt;span&gt;&quot;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;보통 어느 조직에 들어가든 1년을 훈련 시켜야 1인분을 하기 시작하는데, 이 적응시간을 줄이자&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  인프랩에서 AI를 활용해 온보딩 및 적응시간을 줄였던 방법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 입사하면 팀의 미션 공유함 (인프랩 개발팀의 미션과 가치)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.inflab.com/20231117-devteam-value/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tech.inflab.com/20231117-devteam-value/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725272465503&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;인프랩 개발팀의 미션과 가치&quot; data-og-description=&quot;안녕하세요 인프랩의 향로입니다. 최근 인프랩 팀은 적극적으로 개발자 채용을 하고 있습니다. 인프랩 채용 공고 지원하시는 분들 입장에서는 인프랩 개발팀은 어떤 것을 추구할까, 나와&amp;hellip;&quot; data-og-host=&quot;tech.inflab.com&quot; data-og-source-url=&quot;https://tech.inflab.com/20231117-devteam-value/&quot; data-og-url=&quot;https://tech.inflab.com/20231117-devteam-value/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bfSY8F/hyWVTi6Tkl/J9gKR2oowOML7jt8pTZ0e0/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/bCUHAd/hyWZinZTSz/YCkfMvL2EcGT5ptsoo6cd1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/qTMus/hyWY705uUl/No11iGNV8SNNA73khtwpcK/img.png?width=351&amp;amp;height=351&amp;amp;face=0_0_351_351&quot;&gt;&lt;a href=&quot;https://tech.inflab.com/20231117-devteam-value/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.inflab.com/20231117-devteam-value/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bfSY8F/hyWVTi6Tkl/J9gKR2oowOML7jt8pTZ0e0/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/bCUHAd/hyWZinZTSz/YCkfMvL2EcGT5ptsoo6cd1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/qTMus/hyWY705uUl/No11iGNV8SNNA73khtwpcK/img.png?width=351&amp;amp;height=351&amp;amp;face=0_0_351_351');&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;인프랩 개발팀의 미션과 가치&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 인프랩의 향로입니다. 최근 인프랩 팀은 적극적으로 개발자 채용을 하고 있습니다. 인프랩 채용 공고 지원하시는 분들 입장에서는 인프랩 개발팀은 어떤 것을 추구할까, 나와&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.inflab.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 잦은 주기의 피드백 환경 조성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히, 신규 입사자를 위한 온보딩을 위해 &quot;서포터&quot;를 두기 마련인데 인프랩은 사람도 적었고 &quot;서포터가 존재하지 않을 때&quot; 도 도움을 받을 수 있는 환경을 고민했고 이를 AI 를 활용해서 해결했다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;Sonar Cube 는 PR 시 해당 PR 코드에 대한 Sonar Rule 에 위반하는 &lt;b&gt;악취 코드&lt;/b&gt;를 선별해줍니다.&lt;/li&gt;
&lt;li&gt;또한 PR이 올라온 후, 수정되는 시간 즉 &amp;rarr; &lt;b&gt;PR 내부의 기술 부채&lt;/b&gt;를 해결하는데 얼마나 시간이 걸렸는지 등을 기록해주어 회고시 많은 도움이 되었다고 합니다.&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;size18&quot;&gt;&lt;b&gt;나. 코드 레빗 AI (&lt;span style=&quot;background-color: #ffffff; color: #17181a; text-align: start;&quot;&gt;CodeRabbit)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 레빗 또한&amp;nbsp;PR(풀 요청) 검토 프로세스를 간소화하는 데 도움을 주기 위해 설계된 AI 기반 코드 검토 도구입니다. (Saas)&lt;/li&gt;
&lt;li&gt;소나 큐브는, rule 기반으로 코드 &lt;b&gt;레빗은 AI 기반&lt;/b&gt;으로 동작하는 분석 도구이기때문에 &lt;b&gt;코드 수정 방향성 제시, 테스트 코드 제안&lt;/b&gt; 등 실질적으로 서포터의 코드 리뷰역할을 담당하였다고 합니다.&lt;/li&gt;
&lt;li&gt;또한 &lt;b&gt;PR 코드에대한 시퀀스 다이어그램&lt;/b&gt;을 만들어주어, 실질적인 코드리뷰어(사람)가 더 빠르게 구조를 파악할 수 있어 도움이 되었다고 합니다.&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #17181a; text-align: start;&quot;&gt;다. DORA 메트릭스&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 생산성에 대해 분석하고 해당 통계를 대시보드로 보여주는 모니터링 툴입니다.&lt;/li&gt;
&lt;li&gt;개발자의 생산성을 숫자로 표기할 수는 없지만, 계획된 기획 기간을 지키지 못했을 때 병목 지점을 파악할 수 있는 회고의 지표로 사용했다고 합니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;PR 반영이 늦었는지&lt;/li&gt;
&lt;li&gt;빌드 시간이 늦었는지&lt;/li&gt;
&lt;li&gt;커밋이 늦었는지&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;인프랩 테크 블로그 : &lt;a href=&quot;https://tech.inflab.com/20240221-dora-metric-with-devlake/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tech.inflab.com/20240221-dora-metric-with-devlake/&lt;/a&gt;&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #17181a; text-align: start;&quot;&gt;라. 언제든지 답변 받을 수 있는 환경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MetaSearch : 회사 내부 문서들을 통합하여 검색할 수 있는 환경&lt;/li&gt;
&lt;li&gt;AI Slack Bot : 사내 wiki, Jira, 용어정리 스프레드(사내 비지니스 용어)를 참고하여 답변할수있는 Slack Bot&lt;/li&gt;
&lt;li&gt;AI 데일리 분석 노트 : 하루 매출, 인프런 상세페이지 pv 와 uv, 학습 수, 첫 구매자 수, 신규 강의 수 등등에 대한 통계&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&amp;nbsp;이런 결과를 위해 &lt;b&gt;&amp;ldquo;문서화 강조&amp;rdquo;&lt;/b&gt; : AI Bot이 문서화를 토대로 답변하기 때문에, 한번의 문서화가 팀원 전체의 생산성을 개선&lt;/p&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;구두 논의 내용또한 슬랙에 기록&lt;/li&gt;
&lt;li&gt;문서 최신화 환경이 구성됨 (신규 입사자가 업무를 진행하면서 Bot 의 답변이 이상혀면 본인이 최신 문서를 수정함)&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 선순환 구조&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3) 직접 구축보단 오픈소스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 프로세스를 직접 만들기 보다는 프레임워크 버전 업데이트에 대응이 가능한 오픈소스를 100% 활용하여 사용한다고 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqtTh0/btsJonqOUTK/jSCYePVLM58HAKkr5mwTw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqtTh0/btsJonqOUTK/jSCYePVLM58HAKkr5mwTw1/img.png&quot; data-alt=&quot;향로님&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqtTh0/btsJonqOUTK/jSCYePVLM58HAKkr5mwTw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqtTh0%2FbtsJonqOUTK%2FjSCYePVLM58HAKkr5mwTw1%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;400&quot; height=&quot;340&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;향로님&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 프론트엔드 개발자 관점으로 보는 관심사의 분리와 좋은 폴더 구조&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;테오의 스프린트로 알고있었던 &quot;테오님&quot;의 발표셨습니다.&lt;br /&gt;Front-end 의 발전에 따라 과심사의 분리가 어떻게 이루어져 나갔고&lt;br /&gt;좋은 폴더구조를 확립하기위한 &quot;프론트의 진화 패러다임?&quot; 에 대한 흐름을 알 수있었던 발표였습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 관심사 분리의 시작&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할이 다른데 역할에 유리한 방식대로 분리하자 (HTML, CSS, JS)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 컴포넌트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수평이 아닌 수직으로 관심사를 분리하자&lt;/li&gt;
&lt;li&gt;하나의 컴포넌트 안에, 디자인 개발 문서 등등을 다 넣음&lt;/li&gt;
&lt;li&gt;역할이 아닌 기능 중심의 모듈화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 여기서 더하여 계층적 관심사가 필요 (컴포넌트로 관심사를 분리했지만, 컴포넌트가 너무 많아지면 관리가 힘들어짐) &lt;br /&gt;&amp;rarr; 아토믹 디자인 패턴&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YmX9U/btsJoWlY6BK/Z4Ueo1cZp5h2S0GSKR3xx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YmX9U/btsJoWlY6BK/Z4Ueo1cZp5h2S0GSKR3xx1/img.png&quot; data-alt=&quot;https://atomicdesign.bradfrost.com/chapter-2/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YmX9U/btsJoWlY6BK/Z4Ueo1cZp5h2S0GSKR3xx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYmX9U%2FbtsJoWlY6BK%2FZ4Ueo1cZp5h2S0GSKR3xx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;462&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://atomicdesign.bradfrost.com/chapter-2/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 의존성 단방향 관리가 중요해짐&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터와 view 간의 의존성 분리를 하여 더 계층적 분리를 하기 시작&lt;/li&gt;
&lt;li&gt;서버 데이터 관리 (하나의 기능 혹은 패러다임으로 해결하려 하지 않고, 관심사를 더 세부적으로 나누어 계층을 분리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이렇게 만들어진 폴더 구조&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BKjks/btsJo4K1Yc4/YMe8tyW53Vpq58868m53M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BKjks/btsJo4K1Yc4/YMe8tyW53Vpq58868m53M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BKjks/btsJo4K1Yc4/YMe8tyW53Vpq58868m53M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBKjks%2FbtsJo4K1Yc4%2FYMe8tyW53Vpq58868m53M1%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;840&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 관심의 방향성&lt;/h3&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;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이렇게 나누어진 2가지 관점을&lt;/span&gt;&lt;b&gt;&amp;nbsp;모듈(수직)과 레이어(수평)&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;라고 부르기 시작했다고 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;+ 클린 아키텍처(데이터의 흐름을 관심사로 하는 새로운 관심사 계층)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 프롭스? 되어야한다?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UoIzK/btsJm7ic8mS/aiVAAwPAbz1pd8BFKik9jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UoIzK/btsJm7ic8mS/aiVAAwPAbz1pd8BFKik9jK/img.png&quot; data-alt=&quot;하지만 멋지게 분류했지만 개발하다보면, 다시 관심사가 흩어짐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UoIzK/btsJm7ic8mS/aiVAAwPAbz1pd8BFKik9jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUoIzK%2FbtsJm7ic8mS%2FaiVAAwPAbz1pd8BFKik9jK%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;1296&quot; height=&quot;834&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;하지만 멋지게 분류했지만 개발하다보면, 다시 관심사가 흩어짐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) FSD 아키텍쳐&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 폴더 구조는 1개다!&lt;/li&gt;
&lt;li&gt;&amp;nbsp;현대는 멋지게 분류했지만 특정 관심사로는 갈기갈기 찢어져, 파편화가 됨&lt;/li&gt;
&lt;li&gt;한번 더 관심사의 분리가 필요해졌고, 최근 떠오르는 아키텍처가 FSD 아키텍처라 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uXUHq/btsJosMuBc9/zvG6BNMccKNhQX3JbZpMX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uXUHq/btsJosMuBc9/zvG6BNMccKNhQX3JbZpMX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uXUHq/btsJosMuBc9/zvG6BNMccKNhQX3JbZpMX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuXUHq%2FbtsJosMuBc9%2FzvG6BNMccKNhQX3JbZpMX0%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;578&quot; height=&quot;321&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ui 컴포넌트와 도메인 컴포넌트는 다르다&lt;/li&gt;
&lt;li&gt;컴포넌트 분리를 관심사에 따라서 분리하자&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ljfil/btsJnGxy16T/O5tqAEJLffiBCoibzCEtIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ljfil/btsJnGxy16T/O5tqAEJLffiBCoibzCEtIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ljfil/btsJnGxy16T/O5tqAEJLffiBCoibzCEtIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fljfil%2FbtsJnGxy16T%2FO5tqAEJLffiBCoibzCEtIK%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;800&quot; height=&quot;578&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;578&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 클린 아키텍처 : 무한 성장하는 시스템의 비밀&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;항해 플러스 멘토로 계신 무신사 백엔드 개발자 - 허재님의 발표입니다.&lt;br /&gt;&quot;코드를 어떻게 짜야하는가&quot; 가 세션의 주된 주제였고&lt;br /&gt;유연한 코드를 위한 아키텍처 설계에 대한 이론 설명이였습니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좋은 코드란 ?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구조화되어있고, 각잡혀있는 이쁜 궁전 ??&lt;/li&gt;
&lt;li&gt;아니다~ 남의 집 따라하면 그 끝은 남의 집이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;우리만의 규칙을 세우고, 규칙을 따라야한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좋은 설계란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미래를 멀리 보지 마라&lt;/li&gt;
&lt;li&gt;처음부터 개쩌는 코드를 짤려고,, 구조 생각하고 클래스 생각하고,,,, 하지말고&lt;/li&gt;
&lt;li&gt;&lt;b&gt;언제든 변경 가능한 구조를 짤 수 있어야한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클린 아키텍처&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로버트 C 마틴은 : 클린아키텍처에 대해 예제를 넣지 않았다.&lt;/li&gt;
&lt;li&gt;스스로 생각하며 클린 아키텍처란 무엇인지 생각해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;허재 님의 3가지 가이드라인&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;허재님은 코드를 작성할때 클린하고 유연한 구조를 잡기위해 3가지 기준을 세우셨다고 합니다.&lt;br /&gt;&lt;br /&gt;1. 비즈니스 로직을 보호하자&lt;br /&gt;2. 도메인을 충분히 숙성시킨 후 책임을 부여하자&lt;br /&gt;3. 모든 객체에는 책임이 있어야한다. 함부로 많은 책임을 부여하지 말자&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AhG81/btsJpmR0b70/KPQT2MgnpX3hDmmn8lNoK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AhG81/btsJpmR0b70/KPQT2MgnpX3hDmmn8lNoK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AhG81/btsJpmR0b70/KPQT2MgnpX3hDmmn8lNoK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAhG81%2FbtsJpmR0b70%2FKPQT2MgnpX3hDmmn8lNoK1%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;500&quot; height=&quot;251&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 비즈니스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 레이어드 아키텍처 :&lt;/b&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;✔️ DIP 를 지켜 &lt;b&gt;비지니스 로직을 지키기 위해서&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;비지니스 로직은 외부 상황에서 보호하기 위해서 infra 를 분리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddX09a/btsJnFS5crY/hfmDmTHGBN84YAsEkjM35k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddX09a/btsJnFS5crY/hfmDmTHGBN84YAsEkjM35k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddX09a/btsJnFS5crY/hfmDmTHGBN84YAsEkjM35k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddX09a%2FbtsJnFS5crY%2FhfmDmTHGBN84YAsEkjM35k%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;500&quot; height=&quot;309&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 그럼 비즈니스는 어떻게 가둬야할까&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단방향으로 의존성이 흐르게 설계하자&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2FJVI/btsJnalKRBT/P3RcifvY1eHazHkfaeDtNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2FJVI/btsJnalKRBT/P3RcifvY1eHazHkfaeDtNk/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;355&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.2089%; margin-right: 10px;&quot; data-widthpercent=&quot;49.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2FJVI/btsJnalKRBT/P3RcifvY1eHazHkfaeDtNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2FJVI%2FbtsJnalKRBT%2FP3RcifvY1eHazHkfaeDtNk%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;500&quot; height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8EASV/btsJnW1hNX1/KgzlJSR7PrjsXbnulbR9bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8EASV/btsJnW1hNX1/KgzlJSR7PrjsXbnulbR9bK/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;352&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.6283%;&quot; data-widthpercent=&quot;50.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8EASV/btsJnW1hNX1/KgzlJSR7PrjsXbnulbR9bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8EASV%2FbtsJnW1hNX1%2FKgzlJSR7PrjsXbnulbR9bK%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;500&quot; height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Interface : 외부의 진입으로 부터의 명세&lt;/li&gt;
&lt;li&gt;Use-Case : 요구사항 구현을 위한 선언적 구조?&lt;/li&gt;
&lt;li&gt;Domain (User, Post, Reservation 등등,,)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유즈케이스 : 도메인 서비스 = 1:1 (이런 구조여야 책임 분리를 잘한 것)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;서비스는 진입점&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴포넌트가 실제 비지니스 로직&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;흔히 Domain 에 비지니스 로직을 두는데, 이를 한단계 내려 Compnent 로 두었다고 하셨습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Componet
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스의 재사용이 가능한 비지니스 로직 (각각의 기능을 세부적으로 나눈 것)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;infrastructure
&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;인프라(DB) 가 바뀌는 경우가 거의 없긴한데, 현실세계의 문제를 강결합으로 풀지 않기 위해 컴포넌트를 나누고 인프라 의존성 공부를 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bflYoQ/btsJovbl2q8/8AOoloiyeObrnJLWUTyoGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bflYoQ/btsJovbl2q8/8AOoloiyeObrnJLWUTyoGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bflYoQ/btsJovbl2q8/8AOoloiyeObrnJLWUTyoGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbflYoQ%2FbtsJovbl2q8%2F8AOoloiyeObrnJLWUTyoGK%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;500&quot; height=&quot;350&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 숙성&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;비지니스를 설계할때, 충분히 성숙된 도메인이었을 때 도메인으로 내보내야한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;콘서트 예약 시스템에서 [예약] 시스템을 도메인으로 보았다면 (domain - reservation, user, concert)&lt;/li&gt;
&lt;li&gt;&amp;lsquo;여기서 만약 숙박 예약이 추가된다면??&lt;/li&gt;
&lt;li&gt;위의 구조에서 reservation 은 예약이 아니라, 콘서트 예약이다.&lt;/li&gt;
&lt;li&gt;즉 예약이 될만큼 충분히 숙성되지 않았다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;핵심은 책임&lt;/li&gt;
&lt;li&gt;함부로 일반화 시키지 말자&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) 책임&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&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;컴포넌트를 갈아끼워서 요구사항을 충족할 수 있도록&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;모든 객체에는 책임이 있어야한다.&lt;/li&gt;
&lt;li&gt;도메인은 충분히 숙성시켜야 한다. 함부로 도메인에 책임을 과하게 주면 안된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yIQ2b/btsJnYY6Fy1/PAmJDKnKXSMj8CVZfVlKh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yIQ2b/btsJnYY6Fy1/PAmJDKnKXSMj8CVZfVlKh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yIQ2b/btsJnYY6Fy1/PAmJDKnKXSMj8CVZfVlKh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyIQ2b%2FbtsJnYY6Fy1%2FPAmJDKnKXSMj8CVZfVlKh1%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;500&quot; height=&quot;294&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다시, 좋은설계란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발을 말랑말랑하게&lt;/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 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;rarr; 그러기 위해서는 선택되지 않은 역할을 도메인에 함부로 주는 실수를 해서는 안된다. 라고 생각됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실 세계에서의 더 많은, 자세한 책임을 준다는 &quot;디테일한 역할을 부여하는 것&quot; 이고 소프트웨어 세계에서 많은 책임을 준다는 &quot;추상적으로 역할을 부여한다&quot; 이 차이를 조심해야할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;핵심은 도메인에 대한 &quot;비지니스 로직을 분리하자&quot;, &quot;도메인은 충분히 숙성시킨 후 추출이 가능해지면 상위 도메인으로 추출하자&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Component 에 비지니스 로직을 부여한다는 것은, 코드를 보지 않아 잘 이해가 가지 않았지만 infrastructure에 대한 접근이 Compnent 에서만 가능한걸로 보아 Entity 에 대한 책임을 가지고 그를 이용해 도메인 레이어에서 재상용이 가능하게 만든 것이 아닌가,, 조심스럽게 유추해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다소 아쉬운 점이 있었던 컨퍼런스였지만, 좋은기회로 가게되어 포스팅으로 남겨보았습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;</description>
      <category>  공대생은 성장 중/세미나</category>
      <category>개발자행사</category>
      <category>데브랩</category>
      <category>스파르타코딩</category>
      <category>항해</category>
      <category>항해 dev</category>
      <category>항해 데브랩</category>
      <category>항해 컨퍼런스 후기</category>
      <category>항해 후기</category>
      <category>항해99</category>
      <category>항해플러스</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/486</guid>
      <comments>https://thalals.tistory.com/486#entry486comment</comments>
      <pubDate>Mon, 2 Sep 2024 20:18:51 +0900</pubDate>
    </item>
    <item>
      <title>동시성 제어 문제에 대한 고찰 (With. Spring, JAVA, MySQL, Redis, Kafka)</title>
      <link>https://thalals.tistory.com/485</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Spring 3.0, JAVA 17, MySQL 환경에서 문제를 풀어갑니다.&lt;br /&gt;코드는 &lt;a href=&quot;https://github.com/thalals/SpringStudy/tree/main/concurrency&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;⚙️깃허브&lt;/a&gt;에 있습니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;동시 다발적인 호출에도 정확한 차감이 이뤄지도록 구현이 되어야 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마전 받았던 기업과제의 요구사항 이었습니다.&lt;br /&gt;요점은 동시성 문제의 백엔드 관점에서의 해결인데, 동시성 문제를 직접 다뤄본 경험은 처음이라 공부를 조금 더 해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 정리했던 [&lt;a href=&quot;https://thalals.tistory.com/370&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;  재고시스템으로 알아보는 동시성이슈 해결방법&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;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;동시성 문제를 해결하기 위해서는 무엇이 필요한가&lt;/li&gt;
&lt;li&gt;동시성 문제의 해결 방법들
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Thread Access Lock
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;synchronized&lt;/li&gt;
&lt;li&gt;Redis + kafka&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;DB Lock
&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;낙관적 락&lt;/li&gt;
&lt;li&gt;네임드 락&lt;/li&gt;
&lt;li&gt;분산락 (Redis)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;Lettuce&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;Redisson&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;RedLock&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;그 외 방법들&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MVCC&lt;/li&gt;
&lt;li&gt;TCC&lt;/li&gt;
&lt;li&gt;saga&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 동시성 문제란 무엇인가&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;동시성 (Concurrency) : 동시에 실행 중인 것처럼 행동하는 것&lt;/blockquote&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;b&gt;[동시성을 제어하지 못했을 때 생기는 문제]&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;경쟁상태(Race condition)&lt;/b&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;span style=&quot;color: #ee2323;&quot;&gt;데이터 모순성(Inconsistency)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;교착상태(DeadLock)&lt;/b&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;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 손상(Data corruption)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;두 개 이상의 스레드가 동시에 같은 데이터에 접근하여 값을 변경할 때, 예상치 못한 데이터의 변형이 발생하는 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;DBMS의 갱신 손실 (lost&amp;nbsp;&lt;/span&gt;update)&lt;/span&gt; : 공유자원에 동시에 데이터 변경이 들어와 작업중인 트랜잭션에 다른 트랜잭션이 데이터를 덮어씌우는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;연쇄복귀(Cascading Rollback)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두개의 트랜잭션이 같은 데이터를 갱신하는 작업을 진행하는 과정에서 하나의 트랜잭션이 실패하면 원자성에 의해 두 트랜잭션 모두 복귀하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비완료 의존성(Uncommitted Dependency)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한개의 트랜잭션이 실패하였을때, 이 트랜재션이 회복하기전에 다른 트랜잭션이 실패한 수행 결과를 참조하는 경우&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;등등,,&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 동시성 문제를 해결하기 위해서는 무엇이 필요한가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 공유자원에 대해 동시에 제어하려는 것을 순차적으로 접근시킬 수 있게 하면 되지 않을까?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;작업의 단위를 고립 시키는 것&lt;/li&gt;
&lt;li&gt;데이터(공유 자원)의 접근을 고립시키는 것&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&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;전체 수량과 남은 재고를 가지는 Ticket 도메인&lt;/li&gt;
&lt;li&gt;티켓 예약시 생성되는 TicketReservation 도메인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ticket&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Entity
@Slf4j
@Getter
@NoArgsConstructor
public class Ticket {

    @Id
    private Long id;
    @Column
    private Integer quantity;
    @Column
    private Integer stock;

    public Ticket(Long id, Integer quantity) {
        this.id = id;
        this.quantity = quantity;
        this.stock = quantity;
    }

    public static Ticket create(Long id, Integer quantity) {
        return new Ticket(id, quantity);
    }

    public void decrease() {
        if (stock &amp;lt;= 0) {
            throw new RuntimeException(&quot;남은 수량 없음&quot;);
        }
        this.stock = this.stock - 1;
        log.info(&quot;남은 재고 : &quot; + this.stock);
    }


    public int getNumber() {
        return quantity - stock + 1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TicketReservation&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Entity
@Slf4j
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor
public class TicketReservation {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne
    @JoinColumn(name = &quot;ticket_id&quot;)
    private Ticket ticket;

    @Column
    private Integer number;

    @Column
    @CreatedDate
    private LocalDateTime createdAt;

    public TicketReservation(Ticket ticket, Integer number) {
        this.ticket = ticket;
        this.number = number;
    }

    public static TicketReservation create(Ticket ticket, int number) {
        log.info(&quot;예약 생성 : &quot; + ticket.getNumber());
        return new TicketReservation(ticket, number);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TicketService&lt;/b&gt; : 티켓 예약 비지니스로직을 호출하는 응용서비스&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TicketService {

    private final TicketReservationRepository ticketReservationRepository;
    private final TicketRepository ticketRepository;

    @Transactional
    public void reservation(final Long ticketId) {

        final Ticket ticket = ticketRepository.findById(ticketId).get();
        ticket.decrease();

        ticketReservationRepository.save(TicketReservation.create(ticket, ticket.getNumber()));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 동시성 문제의 해결 방법들&lt;/h2&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: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;100개의 전체 수량을 가지는 티켓을 만든 후, 동시에 100개의 예약을 진행했을 시 테스트 (100 - 100 =0)&lt;/li&gt;
&lt;li&gt;ExecutorService 를 이용해 100개의 작업 스레드를 만든 후, CountDownLatch 클래스를 이용해 동시에 작업이 진행되도록 sync 를 맞춰주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;@SpringBootTest
public class ThreadAccessTest {

    @Autowired
    TicketService ticketService;

    @Autowired
    TicketRepository ticketRepository;

    @Autowired
    TicketReservationRepository ticketReservationRepository;


    @Test
    @DisplayName(&quot;동시다발적으로 요청이 들어올때 실패함&quot;)
    void concurrencyFailTest() throws InterruptedException {

        //give
        Ticket ticket = ticketRepository.save(Ticket.create(1L, 100));

        //when
        int threadCount = 100;

        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i &amp;lt; threadCount; i++) {
            executorService.submit(() -&amp;gt; {
                    try {
                        ticketService.reservation(1L);
                    } finally {
                        latch.countDown();
                    }
                }
            );
        }

        latch.await();

        Ticket result = ticketRepository.findById(1L).get();

        //then
        assertAll(
            () -&amp;gt; assertThat(result.getStock()).isZero(),
            () -&amp;gt; assertThat(ticketReservationRepository.findAll().size()).isEqualTo(100)
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2738&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KPXXO/btsHW3G07Ea/jMx3el89qX75W9QrcRK7X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KPXXO/btsHW3G07Ea/jMx3el89qX75W9QrcRK7X1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KPXXO/btsHW3G07Ea/jMx3el89qX75W9QrcRK7X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKPXXO%2FbtsHW3G07Ea%2FjMx3el89qX75W9QrcRK7X1%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;2738&quot; height=&quot;220&quot; data-origin-width=&quot;2738&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2700&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bik29K/btsHWxBNGBq/a4HAGnUHtMDe6k0pMojTHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bik29K/btsHWxBNGBq/a4HAGnUHtMDe6k0pMojTHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bik29K/btsHWxBNGBq/a4HAGnUHtMDe6k0pMojTHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbik29K%2FbtsHWxBNGBq%2Fa4HAGnUHtMDe6k0pMojTHk%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;2700&quot; height=&quot;562&quot; data-origin-width=&quot;2700&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 테스트는 결과는 실패이고, 각각의 Thread 들이 공유자원에 동시에 접근한다는 것을 알 수 있었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 작업 단위 고립&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) Thread Access Lock&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;b&gt;@Synchronized&lt;/b&gt; 어노테이션이나 메소드에 &lt;b&gt;synchronized&lt;/b&gt; 키워드를 붙혀 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;//    @Transactional
//    @Synchronized
    public synchronized void reservationWithoutTransactional(final Long ticketId) {

        final Ticket ticket = ticketRepository.findById(ticketId).get();
        ticket.decrease();

        ticketRepository.save(ticket);
        ticketReservationRepository.save(TicketReservation.create(ticket, ticket.getNumber()));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 테스트 결과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2758&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qaFqS/btsHW2I2fnA/xkwAEmQ99yEEUhpM4hFYF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qaFqS/btsHW2I2fnA/xkwAEmQ99yEEUhpM4hFYF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qaFqS/btsHW2I2fnA/xkwAEmQ99yEEUhpM4hFYF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqaFqS%2FbtsHW2I2fnA%2FxkwAEmQ99yEEUhpM4hFYF0%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;2758&quot; height=&quot;522&quot; data-origin-width=&quot;2758&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  synchronized (동기화)란&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JAVA 에서 제공하는 동기화 기법이고, 여기서 동기화란 프로세스 또는 스레드들이 수행되는 시점을 조절하여 서로가 알고 있는 정보가 일치하는 것을 의미합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;  synchronized 동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JAVA 에서는 모든 객체가 monitor 를 가집니다.&lt;/li&gt;
&lt;li&gt;monitor 란 동기화 기법 중 하나인 세모포어기법을 사용자가 직접 제어하지 않도록 제공해주는 인터페이스 입니다.&lt;/li&gt;
&lt;li&gt;synchronized keyword가 있는 객체는 모니터 락을 가진 객체 인스턴스가 생성되며, 이를 통해 2개 이상의 스레드가 임계 구역(공유 자원)에 접근할 때 락(key)을 가지지 못한 스레드는 대기하여, 순차적으로 임계구역에 접근할 수 있도록 합니다.&lt;br /&gt;( &amp;rarr; JVM 에서는 FIFO 알고리즘을 사용하여 스레드의 스케쥴링 관리합니다.)&lt;/li&gt;
&lt;li&gt;Java 에서는 synchronized 키워드를 사용했을 때 &amp;rarr; 이러한 과정을 통해 동시성을 제어합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  synchronized 단점&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;synchronized 키워드로 이루어지는 모니터 락 방식의 동기화 기법은 JVM 단위에서 일어나기 때문에 단일 어플리케이션에서만 적용됩니다.&lt;/li&gt;
&lt;li&gt;그렇기 때문에 Docker 를 사용한다거나 &lt;b&gt;분산 시스템 환경이라면 공유자원에 대한 접근을 막을 수 없습니다.&lt;br /&gt;&lt;/b&gt;&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;b&gt;  synchronized + @Transactional&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;synchronized 의 다른 제약사항은 &lt;span style=&quot;color: #ee2323;&quot;&gt;@Transactional 어노테이션 함께 사용할 수 없다&lt;/span&gt;는 점 입니다.&lt;/li&gt;
&lt;li&gt;@Transactional 은 Spring AOP 로 이루어져 Proxy 객체가 실행되고 @Synchrozied 어노테이션은 상속되지 않기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2638&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dA1kJw/btsHXL7VDru/htlFmALIhLSyFHXM6R8YmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dA1kJw/btsHXL7VDru/htlFmALIhLSyFHXM6R8YmK/img.png&quot; data-alt=&quot;synchronized + @transactinonal 일 때 테스트 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dA1kJw/btsHXL7VDru/htlFmALIhLSyFHXM6R8YmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdA1kJw%2FbtsHXL7VDru%2FhtlFmALIhLSyFHXM6R8YmK%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;2638&quot; height=&quot;1058&quot; data-origin-width=&quot;2638&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;synchronized + @transactinonal 일 때 테스트 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ synchronized + @Transactional 안되는 이유&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;런타임시 Target 객체를 호출할 때 @Transactional 이 적용된 Proxy 객체가 대신 생성되어 호출 됩니다.&lt;/li&gt;
&lt;li&gt;@Synchronized 어노테이션은 상속되지않고, synchronized 도 메소드 시그니처가 아니기 때문에 마찬가지로 Proxy 객체에 상속되지 않습니다.&lt;/li&gt;
&lt;li&gt;따라서 &quot;클라이언트가 호출하면 &amp;rarr; proxy 객체에서의 트랜잭션 처리 로직 &amp;rarr; 비지니스 로직(synchronized 가 적용된) &amp;rarr; 성공 시 커밋&quot; 이러한 흐름을 가지는데, 트랜잭션이 성공 후 DB 에 commit 되기 전에 다른 스레드에서 해당 공유 데이터에 접근하여 데이터를 호출하면 커밋 되기 전의 결과를 가져가기 때문에 데이터 일관성이 깨지게 됩니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://thalals.tistory.com/478&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@Transactional 동작 과정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thalals.tistory.com/370&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;에러가 나는 이유 댓글 참고&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2154&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/maQmY/btsH0irgerH/7dnB30IEdfl5fqXh1bSuW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/maQmY/btsH0irgerH/7dnB30IEdfl5fqXh1bSuW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/maQmY/btsH0irgerH/7dnB30IEdfl5fqXh1bSuW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmaQmY%2FbtsH0irgerH%2F7dnB30IEdfl5fqXh1bSuW1%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;2154&quot; height=&quot;1136&quot; data-origin-width=&quot;2154&quot; data-origin-height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 반례로 Spring 에서 제공하는 @Transactional 을 적용하지 않고 직접 커넥셕을 가져와서 트랜잭션을 구현한다면 @Synchronized 가 정상적으로 적용되는걸 볼 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2492&quot; data-origin-height=&quot;1490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UtKed/btsH0xvsfbt/RIjxBAK3iJYnxn1TdiVcm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UtKed/btsH0xvsfbt/RIjxBAK3iJYnxn1TdiVcm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UtKed/btsH0xvsfbt/RIjxBAK3iJYnxn1TdiVcm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUtKed%2FbtsH0xvsfbt%2FRIjxBAK3iJYnxn1TdiVcm1%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;2492&quot; height=&quot;1490&quot; data-origin-width=&quot;2492&quot; data-origin-height=&quot;1490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(2) Redis + Kafka&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ @synchronized 가 가진 분산환경에서 사용하지 못하는 단점을 Redis를 사용하여 커버할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Redis 는 기본적으로 싱글스레드로 동작&lt;/b&gt;합니다. &lt;br /&gt;(내부 동작은 Event-driven 에 부분적으로 멀티스레드로 처리하는 부분이 있지만,, 클라이언트 입장에서는 싱글 스레드로 인식하고 사용하면 된다고 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Redis 의 이러한 특징을 이용하여 선착순 처리를 &lt;b&gt;INCR&amp;nbsp;&lt;/b&gt;명령어를 이용한 테스트를 진행해보았습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class TicketCountRepository {

    private final RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate;

    public Long increment(String key) {
        return redisTemplate
            .opsForValue()
            .increment(key, 1L);
    }

    public void clear() {
        redisTemplate.
            getConnectionFactory()
            .getConnection()
            .serverCommands()
            .flushAll();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1723551236219&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class service(){
	
    //생략
    @Transactional
    public void reservationToRedis(final Long ticketId) {

        //redis 로 동시성 제어
        final String key = &quot;ticket&quot; + ticketId;

        Long increment = ticketCountRepository.increment(key);
        log.info(&quot;increment : &quot; + increment);

        if (increment &amp;lt; 100) {
            // 100명 컷은 되는데, 레디스가 처리가 빨라서 db 쪽 데이터 처리량이 못따라감, 데드락 걸림
            // 이래서 카프카 써야하나봄

            final Ticket ticket = ticketRepository.findById(ticketId).get();
            reservationSuccess(ticket);
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG4ImF/btsI3LL7I2F/XvtYsxwGNNqZxlEW8JQh5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG4ImF/btsI3LL7I2F/XvtYsxwGNNqZxlEW8JQh5k/img.png&quot; data-alt=&quot;Redis&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG4ImF/btsI3LL7I2F/XvtYsxwGNNqZxlEW8JQh5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG4ImF%2FbtsI3LL7I2F%2FXvtYsxwGNNqZxlEW8JQh5k%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;193&quot; height=&quot;211&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redis&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzBYR/btsIo90eF15/SyeS5DVxfu036a2PM1gSx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzBYR/btsIo90eF15/SyeS5DVxfu036a2PM1gSx1/img.png&quot; data-alt=&quot;WAS 에서의 공유자원 접근 속도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzBYR/btsIo90eF15/SyeS5DVxfu036a2PM1gSx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzBYR%2FbtsIo90eF15%2FSyeS5DVxfu036a2PM1gSx1%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;1116&quot; height=&quot;148&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;WAS 에서의 공유자원 접근 속도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 결과는 Redis 에서 100개의 카운팅은 정상적으로 처리하지만, 공유자원 (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;b&gt;Kafka (메세징 시스템) 적용&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;  Kafka 를 사용해 Consumer 에게 데이터 처리를 로직을 맡긴다면, Consumer 가 Topic 에 쌓인 메세지를 순차적으로 불러와, 동시성문제를 제어할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kafka Config&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Configuration
public class KafkaConsumerConfig {

    @Bean
    public ConsumerFactory&amp;lt;String, Long&amp;gt; consumerFactory() {
        Map&amp;lt;String, Object&amp;gt; config = new HashMap&amp;lt;&amp;gt;();
        config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;localhost:9092&quot;);
        config.put(ConsumerConfig.GROUP_ID_CONFIG, &quot;group_1&quot;);
        config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class);

        return new DefaultKafkaConsumerFactory&amp;lt;&amp;gt;(config);
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory&amp;lt;String, Long&amp;gt; kafkaListenerContainerFactory() {

        ConcurrentKafkaListenerContainerFactory&amp;lt;String, Long&amp;gt; factory = new ConcurrentKafkaListenerContainerFactory&amp;lt;&amp;gt;();
        factory.setConsumerFactory(consumerFactory());
        factory.setConcurrency(10);

        return factory;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Configuration
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory&amp;lt;String, Long&amp;gt; producerFactory() {
        Map&amp;lt;String, Object&amp;gt; config = new HashMap&amp;lt;&amp;gt;();

        config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;localhost:9092&quot;);
        config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, LongSerializer.class);

        return new DefaultKafkaProducerFactory&amp;lt;&amp;gt;(config);
    }

    @Bean
    public KafkaTemplate&amp;lt;String, Long&amp;gt; kafkaTemplate() {
        return new KafkaTemplate&amp;lt;&amp;gt;(producerFactory());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kafka Producer &amp;amp; Conusmer&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class TicketReservationProducer {

    private static final Logger log = LoggerFactory.getLogger(TicketReservationProducer.class);
    private final KafkaTemplate&amp;lt;String, Long&amp;gt; kafkaTemplate;

    public void reserve(Long ticketId, Long count) {
        log.info(&quot;Produce Reserving ticket id {}, count {}&quot;, ticketId, count);
        CompletableFuture&amp;lt;SendResult&amp;lt;String, Long&amp;gt;&amp;gt; future = kafkaTemplate.send(&quot;ticket-reservation&quot;, ticketId);

        future.whenComplete((result, ex) -&amp;gt; {
            if (ex == null) {
                log.info(&quot;[SUCCESS] Produce Reserving ticket id {}, count {}&quot;, ticketId, count);
            } else {
                log.error(&quot;[ERROR] Reserving ticket id {}, count {}&quot;, ticketId, count);
            }
        });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class TicketReservationConsumer {

    private static final Logger log = LoggerFactory.getLogger(TicketReservationConsumer.class);
    private final TicketRepository ticketRepository;
    private final TicketReservationRepository ticketReservationRepository;

    @KafkaListener(topics = &quot;ticket-reservation&quot;, groupId = &quot;group_1&quot;)
    public void listener(Long ticketId) {

        log.info(&quot;[CONSUMER] received ticket id {}&quot;, ticketId);

        final Ticket ticket = ticketRepository.findById(ticketId).get();

        ticket.decrease();
        ticketRepository.save(ticket);
        ticketReservationRepository.save(TicketReservation.create(ticket, ticket.getNumber()));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2564&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjXGrE/btsJfUhTtxS/3NbomsxYKeIvKCK1k1kL40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjXGrE/btsJfUhTtxS/3NbomsxYKeIvKCK1k1kL40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjXGrE/btsJfUhTtxS/3NbomsxYKeIvKCK1k1kL40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjXGrE%2FbtsJfUhTtxS%2F3NbomsxYKeIvKCK1k1kL40%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;2564&quot; height=&quot;382&quot; data-origin-width=&quot;2564&quot; data-origin-height=&quot;382&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;같은 관점으로 비동기적 실시간 처리가 필요하지 않다면, Spring Batch 를 사용해도 동시성 문제를 제어할 수 있지 않을까..?&lt;br /&gt;라는 생각만 해보며, 다음으로 넘어가겠습니다 ㅎ,,ㅎ&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) 공유 자원의 고립&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;(1)  DB Lock&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업단위에서 순차적인 접근을 보장하지 않고, 공유자원(Data)의 접근에 순차석을 보장하는 방법이 있습니다.&lt;/li&gt;
&lt;li&gt;DB 의 경우 Lock 을 이용해 순차적인 접근을 보장할 수 있습니다.&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;1. 비관적 락 (Pessimistic Lock)&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;비관적 락은 Repeatable Read 또는 Serializable 정도의 트랜잭션 격리 수준을 제공합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&amp;nbsp;비관적 락이란&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비관적 락이란, &lt;b&gt;데이터 충돌 가능성&lt;/b&gt;을 높게 보고 (비관적으로) 스레드가 &lt;b&gt;데이터에 접근 시&lt;/b&gt;&amp;nbsp;락을 걸어 &lt;b&gt;다른 스레드가 공유자원에 접근하지 못하도록 점유&lt;/b&gt;하고 &amp;rarr; 성공적으로 커밋이 완료되면 점유하고있던 락을 풀어 다른 곳에서도 데이터에 접근할 수 있도록 하는 방식입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;비관적&amp;nbsp; 락은 &lt;u&gt;데이터에 대한 변경이 자주 발생하고, 충돌 가능성이 높은 환경에서 선호&lt;/u&gt;됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;혹은, 데이터가 명확하게 지켜야하는 조건이 있을때도 선호되어지는 방법이라고 생각됩니다. (ex - 재고가 0 이하가 되지 않도록 한다.)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;비관적락은 데이터 수정 즉시, 충돌을 감지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;비관적 락에는 공유락과 베타락이 존재합니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;공유락 (Shared Lock)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Read Lock이라고도 불리는 공유락은 트랜잭션이 읽기를 할 때 사용하는 락이며, 데이터를 읽기만하기 때문에 같은 공유락끼리는 동시에 접근이 가능하지만, write 작업은 막는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배타락 (Exclusive Lock)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Write Lock이라고도 불리며, 데이터를 변경할 때 사용하는 락이다. 트랜잭션이 완료될 때까지 유지되며, 배타락이 끝나기 전까지 read/write를 모두 막는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FLBf2/btsJfHDlSuW/uPMwD0HS8zSuvTY4nhRr6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FLBf2/btsJfHDlSuW/uPMwD0HS8zSuvTY4nhRr6k/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=SoQ4ExWnetg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FLBf2/btsJfHDlSuW/uPMwD0HS8zSuvTY4nhRr6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFLBf2%2FbtsJfHDlSuW%2FuPMwD0HS8zSuvTY4nhRr6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;377&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=SoQ4ExWnetg&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;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&amp;nbsp;비관적 락 사용하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;✔️ MySQL&lt;/b&gt; 의 경우 &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;SELECT FOR UPDATE와 같은 쿼리를 사용하여 데이터에 대한 락을 걸 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;추가적으로, 락을 얻어오는 where 절의 조건문이 index 가 걸려있는 컬럼이냐 아니냐에 따라 Table 에 락을 걸지 Column 에 락을 걸지가 결정됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;index 가 걸려있는 컬럼으로 조회해야만, index 범위 내의 컬럼에 대해 락을 겁니다. (아니면 테이블 락)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;SELECT * FROM ticket WHERE id=1 FOR UPDATE; //or FOR SHARE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp; 또한, 트랜잭션 대기시 &lt;b&gt;타임아웃 시간&lt;/b&gt;을 지정해야 데드락을 방지할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;✔️ &lt;u&gt;Spring Data JPA 로 비관적 락&lt;/u&gt; 사용하기&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA 를 사용한다면 @Lock 어노테이션을 이용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;지원하는 LockModeType 매개변수 타입을 통해 트랜잭션이 어떤 Lock 방식을 사용할지 지정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Pessmistic Lock 을 지원하는 변수는 아래 3가지 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 54px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 28.3721%; height: 18px; text-align: left;&quot;&gt;PESSMISTIC_READ&lt;/td&gt;
&lt;td style=&quot;width: 71.6279%; height: 18px;&quot;&gt;- Pessmistic read lock 입니다.&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;- Dirty read가 발생하지 않을 때마다 Shared Lock (공유 락)을 획득하고 &lt;br /&gt;&amp;nbsp; &amp;nbsp;데이터가 &lt;span style=&quot;color: #409d00;&quot;&gt;UPDATE, DELETE&lt;/span&gt; 되는 것을 방지 할 수 있습니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 28.3721%; height: 18px; text-align: left;&quot;&gt;PESSMISTIC_WRITE&lt;/td&gt;
&lt;td style=&quot;width: 71.6279%; height: 18px;&quot;&gt;- Pessmistic write lock 입니다.&lt;br /&gt;- &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;배타적 잠금(Exclusive Lock)을 획득하고 &lt;br /&gt;&amp;nbsp; &amp;nbsp;데이터를 다른 트랜잭션에서 &lt;span style=&quot;color: #409d00;&quot;&gt;READ, UPDATE, DELET&lt;/span&gt;E 하는것을 방지 할 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 28.3721%; height: 18px; text-align: left;&quot;&gt;PESSMISTIC_FORCE_INCREMET&lt;/td&gt;
&lt;td style=&quot;width: 71.6279%; height: 18px;&quot;&gt;- write lock 에 version 업데이트 방식을 사용합니다.&lt;br /&gt;- @Version&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;이 지정된 Entity와 협력하기 위해 도입되었습니다.&lt;br /&gt;- PESSIMISTIC_FORCE_INCREMENT 잠금을 획득할 시 버전이 업데이트 됩니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;⚙️ Lock 을 지정한 JpaRepository&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface TicketRepositoryForLock extends JpaRepository&amp;lt;Ticket, Long&amp;gt; {

    @Lock(value = LockModeType.PESSIMISTIC_READ)
    @Override
    Optional&amp;lt;Ticket&amp;gt; findById(Long aLong);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;⚙️ Service&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Transactional
public void reservationToPessimisticLock(final Long ticketId) {
    
    final Ticket ticket = ticketRepositoryForLock.findById(ticketId).get();
    reservationSuccess(ticket);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qllCj/btsJgc30I1t/0JTqB4nZxCKgJ0DtOvP4ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qllCj/btsJgc30I1t/0JTqB4nZxCKgJ0DtOvP4ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qllCj/btsJgc30I1t/0JTqB4nZxCKgJ0DtOvP4ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqllCj%2FbtsJgc30I1t%2F0JTqB4nZxCKgJ0DtOvP4ck%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;300&quot; height=&quot;208&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하이버네이트에서 날라가는 쿼리를 보면 &quot;for update&quot;를 사용함을 확인할 수 있습니다. (h2 일경우)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn4KKx/btsJk03ZGuB/pQrUIbKPKGKQHK4KFhsND0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn4KKx/btsJk03ZGuB/pQrUIbKPKGKQHK4KFhsND0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn4KKx/btsJk03ZGuB/pQrUIbKPKGKQHK4KFhsND0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn4KKx%2FbtsJk03ZGuB%2FpQrUIbKPKGKQHK4KFhsND0%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;300&quot; height=&quot;265&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(pessmistic_read 로 공유락을 걸었다면, mysql 에서는 for share 를 사용합니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2194&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rbH6C/btsJg87vWnk/qcTtGe7YpYj16ig4kguXHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rbH6C/btsJg87vWnk/qcTtGe7YpYj16ig4kguXHK/img.png&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rbH6C/btsJg87vWnk/qcTtGe7YpYj16ig4kguXHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrbH6C%2FbtsJg87vWnk%2FqcTtGe7YpYj16ig4kguXHK%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;2194&quot; height=&quot;264&quot; data-origin-width=&quot;2194&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&amp;nbsp;비관적 락 장, 단점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;장점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터를 읽을 때부터 해당 데이터에 대한 락을 걸어 다른 트랜잭션이 해당 데이터를 변경할 수 없게 하기에 데이터의 일관성을 유지하는 데 적합합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #505860; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;단점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다른 트랜잭션이 해당 데이터에 접근할 수 없기 때문에, 동시성이 떨어져 성능 저하&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;서로 자원이 필요한 경우&lt;/span&gt;&amp;nbsp;자원데드락 발생 가능성이 높다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;단일 DB 환경에서만 사용이 가능&lt;/span&gt;하다 &lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #505860; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&amp;nbsp;비관적 락이 적절한 경우&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터의 무결성이 중요하다.&lt;/li&gt;
&lt;li&gt;데이터 충돌이 많이 발생할 것으로 예상된다. (낙관적 락에 비해 충돌 처리 비용이 저렴하다)&lt;/li&gt;
&lt;li&gt;비관적 락은 데이터의 일관성이 매우 중요한 금융 시스템에서 주로 사용됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 낙관적 락 ( Optimistic Lock)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;  낙관적 락이란&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음부터 트랜잭션이 자원에 접근할 때 락을 거는게 아닌, 동시성 문제가 발생하면 그때 처리&amp;nbsp;하는 방법입니다.&lt;/li&gt;
&lt;li&gt;실제 공유 자원을 선점하여 락을 걸지않고, version과 같은&amp;nbsp;&lt;b&gt;별도의 컬럼을&lt;/b&gt;을 사용하여 커밋 시 트랜잭션 충돌을 감지하여 롤백하는 논리적인 락 입니다. (hashcode/timestamp를 사용할 수도 있습니다.)&lt;/li&gt;
&lt;li&gt;충돌이 발생했을때,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;DB가 아닌 애플리케이션 단에서 처리&lt;/b&gt;&lt;span&gt;&amp;nbsp;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;낙관적 락은 어플리케이션에 충돌을 처리하는 만큼, UPDATE에 실패해도 자동으로 예외를 던지지 않으며 단순히 0개의 row를 업데이트 합니다.&lt;br /&gt;따라서 이때 여러 작업이 묶인 트랜잭션 요청이 실패할 경우,&lt;span&gt;&amp;nbsp;&lt;/span&gt;개발자가 직접 롤백 처리를 해줘야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &amp;nbsp;낙관적 락 사용하기&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;jpa에서의-낙관적-락&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔️ JPA에서의 낙관적 락&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;@Version&lt;/u&gt;&amp;nbsp;을 통해 낙관적 락을 사용할 수 있으며, 낙관적 락이 발생하는 경우&amp;nbsp;ObjectOptimisticLockingFailureException&amp;nbsp;예외가 발생하고 이를 애플리케이션 단에서 처리해줘야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 148px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 112px;&quot;&gt;
&lt;td style=&quot;width: 36.2791%; height: 112px;&quot;&gt;NONE&lt;/td&gt;
&lt;td style=&quot;width: 63.7209%; height: 112px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;- 별도의 옵션을 사용하지 않아도 Entity에 @Version이 적용된 필드만 있으면 &lt;br /&gt;&amp;nbsp; &amp;nbsp;낙관적 잠금이 적용됩니다.&lt;br /&gt;&lt;br /&gt;- 암시적 잠금 (Implicit Lock)&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp;: J&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;PA에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;@Version&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;이 붙은 필드가 존재하거나&lt;span&gt; &lt;/span&gt;&lt;/span&gt;@OptimisticLocking&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;어노테이션이 설정되어 있을 경우 자동적으로 충돌감지를 위한 잠금이 실행&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 36.2791%; height: 18px;&quot;&gt;OPTIMISTIC (READ)&lt;/td&gt;
&lt;td style=&quot;width: 63.7209%; height: 18px;&quot;&gt;- &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Entity 수정시에만 발생하는 낙관적 잠금이 &lt;b&gt;읽기 시에도 발생&lt;/b&gt;하도록 설정합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;- 읽기시에도 버전을 체크하고 트랜잭션이 종료될 때까지 다른 트랜잭션에서 &lt;br /&gt;&amp;nbsp; &amp;nbsp;변경하지 않음을 보장합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;- 이를 통해 dirty read와 non-repeatable read를 방지합니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 36.2791%; height: 18px;&quot;&gt;OPTIMISTIC_FORCE_INCREMET (WRITE)&lt;/td&gt;
&lt;td style=&quot;width: 63.7209%; height: 18px;&quot;&gt;- &lt;span&gt;낙관적 잠금을 사용하면서 버전 정보를 강제로 증가시키는 옵션입니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️&lt;i&gt;Entity&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.data.annotation.Version;

@Entity
public class Ticket {

    @Id
    private Long id;
    @Column
    private Integer quantity;
    @Column
    private Integer stock;
    @Version
    private Integer version;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️&lt;i&gt; Repository&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface TicketRepositoryForLock extends JpaRepository&amp;lt;Ticket, Long&amp;gt; {

    @Lock(value = LockModeType.OPTIMISTIC)
    @Query(&quot;select t from Ticket t where t.id = :id&quot;)
    Optional&amp;lt;Ticket&amp;gt; findByWithOptimisticLock(Long id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;⚙️Service&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Transactional
public void reservationToOptimisticLock(final Long ticketId) {

    final Ticket ticket = ticketRepositoryForLock.findByWithOptimisticLock(ticketId).get();
    reservationSuccess(ticket);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pbkjC/btsJguc5Mg0/mmacrqWVxs4sKD4H5gIpzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pbkjC/btsJguc5Mg0/mmacrqWVxs4sKD4H5gIpzk/img.png&quot; data-filename=&quot;blob&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;267&quot; data-origin-width=&quot;300&quot; style=&quot;width: 28.6896%; margin-right: 10px;&quot; data-widthpercent=&quot;29.03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pbkjC/btsJguc5Mg0/mmacrqWVxs4sKD4H5gIpzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpbkjC%2FbtsJguc5Mg0%2FmmacrqWVxs4sKD4H5gIpzk%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;300&quot; height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EkbZo/btsJhgSDef9/6Suwk8j3k6BZdS1JjpXXpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EkbZo/btsJhgSDef9/6Suwk8j3k6BZdS1JjpXXpk/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;182&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;70.97&quot; style=&quot;width: 70.1476%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EkbZo/btsJhgSDef9/6Suwk8j3k6BZdS1JjpXXpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEkbZo%2FbtsJhgSDef9%2F6Suwk8j3k6BZdS1JjpXXpk%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;500&quot; height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; UPDATE 쿼리가 날라갈때, Select 시 받아온 Version 값과 맞는지 확인하는걸 볼 수 있었고, 롤백 처리가 이루어지지 않아 테스트가 실패함을 확인하였습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;⚙️Service 롤백 처리&lt;/i&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;처음에는 간단하게 생각하여 Transaction 안에 실패하면 요청이 반복되도록 해주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Transactional
public void reservationToOptimisticLock(final Long ticketId) throws InterruptedException {

    while (true) {
        try {
            final Ticket ticket = ticketRepositoryForLock.findByWithOptimisticLock(ticketId).get();
            reservationSuccess(ticket);
            break;

        } catch (Exception e) {
            log.error(&quot;error : {}&quot;, e.getMessage());
            Thread.sleep(50);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;❗️하지만 여기에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;트랜잭션끼리 엔티티 매니저를 공유하지 않는다.&quot;&lt;/span&gt; 는 문제점이 발생하였습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메소드가 실행될 때, 트랜잭션이 AOP 로 해당 process 를 인터셉터 하여 트랜잭션 안에서 target 메소드를 실행하게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;트랜잭션이 시작될 때, 1개의 작업 당 EnitytManageFactory 에서 1개의 Entity Manager 가 할당&lt;/b&gt;&lt;/span&gt;됩니다.&lt;/li&gt;
&lt;li&gt;@Entity 가 붙은 객체는 영속화 되어 엔티티매니저에 1차 캐싱 되어 조회 됩니다.&lt;/li&gt;
&lt;li&gt;트랜잭션이 시작될 때, 엔티티 매니저가 할당되어 1차 캐싱된 데이터를 조회합니다.&lt;/li&gt;
&lt;li&gt;낙관적 락이 실패하며 롤백하는 로직을 수행하지만, 트랜잭션 내부에서 실행하여 트랜잭셔이 종료되지 않았으므로 할당된 Entity Manager 에서 다시 조회합니다.&lt;/li&gt;
&lt;li&gt;결국 &lt;b&gt;UPDATE 되지 않은 캐싱 데이터를 계속 조회&lt;/b&gt;하기 때문 @Version 컬럼에 대해 이전 데이터 값을 얻게되어, 롤백에 계속 실패합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;  낙관적 락 트랜잭션 문제를 해결하는 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;롤백을 담당하는 로직과, @Transaction 이 적용된 비지니스 로직을 분리한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Facade Pattern 적용 : 클래스 분리&lt;/li&gt;
&lt;li&gt;AOP 적용 : Transaction의 Proxy 객체가 생성되기 전에, 또 다른 AOP 에서 먼저 인터셉터하여 롤백 처리를 담당하도록 분리&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1번은 매우 간단하지만, 불필요한 클래스가 생성됩니다.&lt;br /&gt;저는 @Retry 어노테이션을 사용하여 풀어보고자 합니다. (&lt;a href=&quot;https://github.com/spring-projects/spring-retry&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/spring-projects/spring-retry&lt;/a&gt;)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;⚙️ &lt;i&gt;의존성 추가&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;//retry
implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework:spring-aspects'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚙️ &lt;i&gt;Config 추가&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableJpaAuditing
@EnableRetry
public class ConcurrencyApplication {

    public static void main(String[] args) {
       SpringApplication.run(ConcurrencyApplication.class, args);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;⚙️ Service 에 Retry 적용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;retryFor : 낙관적락 충돌 시 발생하는 Exception 터지면 재시도 하도록 지정해줍니다.&lt;/li&gt;
&lt;li&gt;maxAttempts : 최대 시도 횟수 입니다. (default 3)&lt;/li&gt;
&lt;li&gt;backoff : delay 초를 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Transactional
@Retryable(
    retryFor = {ObjectOptimisticLockingFailureException.class},
    maxAttempts = 10,
    backoff = @Backoff(delay = 100)
)
public void reservationToOptimisticLock(final Long ticketId) throws InterruptedException {

            final Ticket ticket = ticketRepositoryForLock.findByWithOptimisticLock(ticketId).get();
            reservationSuccess(ticket);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m1b3g/btsJg9GfQ4s/Y5cTEySCURj2nCm3aOA2dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m1b3g/btsJg9GfQ4s/Y5cTEySCURj2nCm3aOA2dk/img.png&quot; data-alt=&quot;@RetryAble Order 값&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m1b3g/btsJg9GfQ4s/Y5cTEySCURj2nCm3aOA2dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm1b3g%2FbtsJg9GfQ4s%2FY5cTEySCURj2nCm3aOA2dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;120&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;@RetryAble Order 값&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV2N7p/btsJib38RCC/M7zukFKzXGvjMRACCQCdjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV2N7p/btsJib38RCC/M7zukFKzXGvjMRACCQCdjK/img.png&quot; data-alt=&quot;@Transactional global order 값&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV2N7p/btsJib38RCC/M7zukFKzXGvjMRACCQCdjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV2N7p%2FbtsJib38RCC%2FM7zukFKzXGvjMRACCQCdjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;311&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;@Transactional global order 값&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  사진과 같이, @Transaction 보다 @Retry Order 값이 더 작으므로 테스트를 성공적으로 통과할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGQnSe/btsJioPIuBU/j095XnEATeVQVxWLhhIwPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGQnSe/btsJioPIuBU/j095XnEATeVQVxWLhhIwPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGQnSe/btsJioPIuBU/j095XnEATeVQVxWLhhIwPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGQnSe%2FbtsJioPIuBU%2Fj095XnEATeVQVxWLhhIwPk%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;500&quot; height=&quot;150&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt; &amp;nbsp;낙관적 락 장, 단점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DB 자원에 락을 걸지 않아데이터베이스의 &lt;b&gt;성능 저하를 최소화하고, 동시성을 높이는 데 유리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 변경할 수 있기 때문에, &lt;b&gt;데드락(Deadlock) 발생 가능성&lt;/b&gt;이 낮다&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #505860; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;롤백 처리를 해줘야한다.&lt;/li&gt;
&lt;li&gt;충돌이 많은 환경에서는 오히려 서버 리소스를 잡아먹는다 (성능 저하)&lt;/li&gt;
&lt;li&gt;낙관적 락 또한 마찬가지로 파티션된 다중 DB 환경에서는 사용하기 어렵습니다.&lt;br /&gt;각 파티션이 독립적인 트랜잭션을 처리하고 낙관적 락은 각 파티션 내에서만 충돌을 감지하므로, 파티션 간의 트랜잭션 일관성을 유지하기 어렵습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt; &amp;nbsp;낙관적 락이 적절한 경우&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&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 id=&quot;비관적-락--낙관적-락&quot; style=&quot;color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;  비관적 락 vs 낙관적 락&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낙관적 락은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;트랜잭션을 필요로하지 않기 때문에&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;성능적으로 비관적 락보다 더 좋습니다.&lt;/li&gt;
&lt;li&gt;비관적 락은 데이터 자체에 락을 걸기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;동시성이 떨어져&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;성능이 많이 저하되며, 서로의 자원이 필요한 경우에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;데드락&lt;/b&gt;이 일어날 가능성도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  하지만 충돌이 많이 발생하는 환경에서는 반대가 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;충돌이 발생했을 때,&amp;nbsp;&lt;b&gt;비관적 락은&amp;nbsp;트랜잭션&lt;/b&gt;을&amp;nbsp;&lt;b&gt;롤백하면 끝&lt;/b&gt;이지만,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;낙관적 락&lt;/b&gt;은&amp;nbsp;까다로운 &lt;b&gt;수동 롤백 처리&lt;/b&gt;는 둘째 치고, 성능 면에서도 &lt;b&gt;(version)update를 한번씩 더&lt;/b&gt; 해줘야 하기&amp;nbsp;때문에, 비관적 락 보다 좋지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;위의 결과사진을 보았을 때도, &lt;u&gt;충돌이 많은 환경&lt;/u&gt;이기 때문에 &lt;u&gt;오히려 비관적락의 성능이 500ms (낙관적 락은 1252ms) 로 더 좋습니다.&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 네임드 락&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;MySQL 환경이라면 Named Lock 을 이용할 수 있습니다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네임드 락(Named Lock)은 특정 이름을 가진 락을 획득하거나 해제하는 방식으로 작동합니다.&lt;/li&gt;
&lt;li&gt;네임드 락은 MySQL 서버 내의 세션 간에 공유되며, 이름을 통해 관리됩니다.&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;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;  네임드 동작 과정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;락 획득&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;GET_LOCK('lock_name', timeout)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;GET_LOCK 함수는 지정된 lock_name에 대해 락을 획득하려고 시도합니다.&lt;/li&gt;
&lt;li&gt;timeout은 락을 기다릴 시간(초)을 지정하며, 0으로 설정하면 즉시 시도만 하고 실패하면 반환합니다.&lt;/li&gt;
&lt;li&gt;성공적으로 락을 획득하면 1을 반환하고, 이미 다른 세션에서 락을 가지고 있거나 시간이 초과된 경우 0을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;락 사용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;락이 획득된 동안에는 다른 세션에서 동일한 이름의 락을 획득할 수 없습니다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이 상태에서 해당 세션은 데이터를 보호하거나 특정 작업을 수행할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;락 해제&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px; color: #006dd7;&quot;&gt;RELEASE_LOCK('lock_name')&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;RELEASE_LOCK&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 함수는 지정된 이름의 락을 해제합니다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;락을 성공적으로 해제하면 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;을 반환하며, 만약 해당 락을 가지고 있지 않거나 존재하지 않는 락을 해제하려고 시도하면 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;을 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1724752533379&quot; class=&quot;sql&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 락을 획득하고 작업 수행
SELECT GET_LOCK('payment_lock', 10);

-- 결제 관련 작업 수행
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;

-- 락 해제
SELECT RELEASE_LOCK('payment_lock');&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;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;  JPA 환경에서 Named Lock 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;⚙️ Repository&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;public interface TicketRepositoryForLock extends JpaRepository&amp;lt;Ticket, Long&amp;gt; {

    //생략

    @Query(value = &quot;select get_lock(:key, 3000)&quot;, nativeQuery = true)
    String getLock(String key);

    @Query(value = &quot;select release_lock(:key)&quot;, nativeQuery = true)
    String releaseLock(String key);

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;⚙️ Facade&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class NamedLockFacade {

    private final TicketService ticketService;
    private final TicketRepositoryForLock ticketRepositoryForLock;

    @Transactional
    public void setNamedLock(Long ticketId) {

        try {
            ticketRepositoryForLock.getLock(&quot;ticket&quot;);
            ticketService.reservationToNamedLock(ticketId);
        } finally {
            ticketRepositoryForLock.releaseLock(&quot;ticket&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;⚙️ Service&lt;br /&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 여기서 Facade 로 분리하고 Transactional 타입을 REQUIRES_NEW 로 지정한 이유는 &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;Facade 없이 1개의 Transaction으로 묶였을 때
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Transaction 실행&lt;/li&gt;
&lt;li&gt;lock 을 얻음&lt;/li&gt;
&lt;li&gt;데이터 수정&lt;/li&gt;
&lt;li&gt;lock 을 반환&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;(트랜잰션 커밋 전 다른 트랜잭션에서 공유자원에 접근 가능)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Transaction 커밋&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Facade 로 Named Lock 을 분리했지만, Facade 에도 @Transactional이 붙는 이유
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;lock 을 획득과 반환은, 하나의 세션에서 이루어져야하기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Service 의 @Transactional 의 옵션을 REQUIRES_NEW 로 두어, 부모 트랜잭션과 분리하는 이유
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Facade 로 분리하는 이유와 같습니다.&lt;/li&gt;
&lt;li&gt;트랜잭션은 전파되기에, 결국 하위 트랜잭션은 상위 트랜잭션에 묶입니다.&lt;/li&gt;
&lt;li&gt;lock 의 반환되기 전에 데이터 변경을 커밋하기 위해 트랜잭션을 분리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Transactional(Transactional.TxType.REQUIRES_NEW)
public void reservationToNamedLock(final Long ticketId) {

    final Ticket ticket = ticketRepository.findById(ticketId).get();
    reservationSuccess(ticket);

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗️Named Lock 은 Lock 을 거는 별도의 커넥션이 필요합니다.&amp;nbsp;&lt;br /&gt;따라서 100개의 요청을 원할하게 처리하기 위해서는 MySQL 의 커넥션 수를 늘려주어야 했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//MySQL 의 MAX 커넥션 수 확인
show variables like 'max_connections';

//MAX 커넥션 수 지정
set global max_connections=201;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defaul 151 개를 201 로 변경 (스레드 커넥션 100개, 락 커넥션 100개 + mysql 에서 잡고있는 default 커넥션 1개)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYRE7O/btsJiL5dkUu/GkRncDLFFxEPaykSTbUkV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYRE7O/btsJiL5dkUu/GkRncDLFFxEPaykSTbUkV0/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;175&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 60.8229%; margin-right: 10px;&quot; data-widthpercent=&quot;61.54&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYRE7O/btsJiL5dkUu/GkRncDLFFxEPaykSTbUkV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYRE7O%2FbtsJiL5dkUu%2FGkRncDLFFxEPaykSTbUkV0%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;500&quot; height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ORnVB/btsJhO2Vo03/kubaXGQqeW3mDMoZrbmZ41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ORnVB/btsJhO2Vo03/kubaXGQqeW3mDMoZrbmZ41/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;280&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;38.46&quot; style=&quot;width: 38.0143%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ORnVB/btsJhO2Vo03/kubaXGQqeW3mDMoZrbmZ41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FORnVB%2FbtsJhO2Vo03%2FkubaXGQqeW3mDMoZrbmZ41%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;500&quot; height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b33eHr/btsJh0PA4iZ/ZV38MDkT7abVUN7uWZWr5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b33eHr/btsJh0PA4iZ/ZV38MDkT7abVUN7uWZWr5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b33eHr/btsJh0PA4iZ/ZV38MDkT7abVUN7uWZWr5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb33eHr%2FbtsJh0PA4iZ%2FZV38MDkT7abVUN7uWZWr5k%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;300&quot; height=&quot;81&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt; &amp;nbsp;네임드 락 장, 단점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;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;MySQL을 사용하고 있다면 추가 인프라 설정없이 사용이 가능합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #505860; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; 추가적인 커넥션이 필요&lt;/span&gt;합니다. (너무 큰 단점으로 느껴짐)&lt;/li&gt;
&lt;li&gt;lock 을 명시적으로 해제해주어야 합니다.&lt;/li&gt;
&lt;li&gt;애플리케이션 로직이 MySQL에 종속됩니다.&lt;/li&gt;
&lt;li&gt;완벽한 분산락으로 사용하고 락을 획득하는 커넥션이 애플리케이션 커넥션을 점유하지 않도록 하기 위해서는&lt;br /&gt;별도의 디비환경을 구축해야합니다...&lt;/li&gt;
&lt;li&gt;직접 구현을 해보니 모든 스레드가 먼저 락의 선점을 시도하기 때문에, 속도가 느려지는 것 같습니다. (100개 동시요청 10s)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Redis로 분산 락 구현하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 를 사용하여 분산 락 환경을 구성할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #000000; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;Lettuce&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;Redisson&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;RedLock&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ Redis  Lettuce&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lettuce 는 Named Lock 매커니즘이 비슷한 Sping Lock 방식입니다. Redis 의 Key 값을 점유하고, 반환하는 방식입니다.&lt;br /&gt;스레드에서 Lock 을 얻지 못하면&lt;b&gt; 계속 락 점유 시도를 하게 되고,&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;레디스의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;부하를 일으키게 됩니다.&lt;br /&gt;&lt;/b&gt;Named Lock 에 비해서는, RDS(MySQL) 의 부하를 나눌 수 있기에 좋은 선택지라고 생각됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Named Lock 과 비슷하기도 하고, 비교적 구현이 간단하기 때문에 &lt;a href=&quot;https://thalals.tistory.com/370&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글 링크&lt;/a&gt;를 남기고 넘어가겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ Redis Redisson&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pub-sub 기반으로 Lock 구현 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락이 해제되면, 락을 관리하는 Channel 을 subscribe 하는 클라이언트들에게 락이 해제되었다는 신호를 보냅니다.&lt;/li&gt;
&lt;li&gt;따라서, Spin Lock 방식과 다르게 Lock 점유를 시도하는 요청을 Redis 에게 계속 요청하지 않습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;대기 중인 스레드에 Lock 해제를 알려주면, 그때 락 점유를 시도합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 방식은, Lettuce와 다르게 대부분 별도의 Retry 방식을 작성하지 않아도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A9Nfc/btsJhYyGxh2/h7NjkdO6EKjydehKYT9Ah1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A9Nfc/btsJhYyGxh2/h7NjkdO6EKjydehKYT9Ah1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A9Nfc/btsJhYyGxh2/h7NjkdO6EKjydehKYT9Ah1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA9Nfc%2FbtsJhYyGxh2%2Fh7NjkdO6EKjydehKYT9Ah1%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;1562&quot; height=&quot;340&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;340&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;  Redisson 동작과정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redisson 은 pub/sub 구조와 세마포어를 이용해 스레드가 리소스가 접근하는 걸 제어합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;RLock 의 tryLock &amp;rarr; Redis 의 Hash 값의 키 중에 lockKey(RLock 의 name = lockKey) 가 없으면 생성하여 락을 점유하거나, value 중 threadId (재접근 가능) 가 있으면 count + 1 을 하여 재접근 허용해줍니다.&lt;/li&gt;
&lt;li&gt;접근하려는 lockKey 가 이미 존재한다면, TTL (유효기간) 을 반환합니다.&lt;/li&gt;
&lt;li&gt;TTL 이 존재하면, threadID 를 redis pub/sub channel(name: &quot;redisson_lock__channel&quot; + lockKey)에 구독을 요청합니다.&lt;/li&gt;
&lt;li&gt;channel 에 구독후, 지정된 시간안에 세마포어에 사용 가능한지 허가 메세지를 받으면, 락을 얻은 후 구독을 취소합니다.&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;세마포어에 접근이 불가하면, 세마포어 내부 CompletableFuture Queue 에 담아 대기합니다.&lt;br /&gt;(세마포어에 접근가능한 counter 수는 디버깅 시 유동적으로 바뀌던데 기준이 뭘까요,, 흠,, )&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;락을 얻으면, 클라이언트가 작성한 코드대로 비지니스로직을 수행하고 락을 해제하면 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  내부 코드를 자세히 분석해보고 싶으신 분은 다음 블로그를 참고해주세요 (&lt;a href=&quot;https://incheol-jung.gitbook.io/docs/q-and-a/spring/redisson-trylock&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;redisson trylock 내부로직 살펴보기&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;b&gt;  Spring Redisson 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 추가&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//redis
implementation 'org.springframework.data:spring-data-redis'
implementation 'org.redisson:redisson-spring-boot-starter:3.27.0'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RedissonConfig&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {

        Config config = new Config();
        config.useSingleServer().setAddress(&quot;redis://localhost:6379&quot;);

        return Redisson.create(config);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Facade &amp;amp; Service&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서도 앞단 Facade 클래스를 둔 이유가, 데이터가 DB에 커밋하기 전에 Redis Lock 을 풀어 다른 트랜잭션이 공유자원에&amp;nbsp; 접근하는 걸 방지하기 위함입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Slf4j
@Service
@RequiredArgsConstructor
public class RedissonLockFacade {

    private final TicketService ticketService;
    private final RedissonClient redissonClient;

    public void tryRedissonLock(Long ticketId) {

        //key 로 Lock 객체 가져옴
        String lockKey = &quot;reservation-redisson-lock-&quot; + ticketId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            //획득시도 시간, 락 점유 시간
            boolean lockable = lock.tryLock(5, 1, TimeUnit.SECONDS);
            if (!lockable) {
                log.error(&quot;Lock 획득 실패={}&quot;, lockKey);
                return;
            }
            log.info(&quot;Redisson Lock 획득&quot;);
            ticketService.reservationToRedisson(ticketId);

        } catch (InterruptedException e) {
            log.error(&quot;Redisson 락 점유 에러&quot;);
            throw new RuntimeException(e);
        } finally {
            log.info(&quot;락 해제&quot;);
            lock.unlock();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;79&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beEICA/btsJiNpKNEO/rkXKtNNUyvedwfRbrcF6M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beEICA/btsJiNpKNEO/rkXKtNNUyvedwfRbrcF6M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beEICA/btsJiNpKNEO/rkXKtNNUyvedwfRbrcF6M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeEICA%2FbtsJiNpKNEO%2FrkXKtNNUyvedwfRbrcF6M1%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;300&quot; height=&quot;79&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;79&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Facade 지겹기도하고 매우 불편하니, AOP 로 빼볼까 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  Spring Redisson Lock AOP 로 분리하기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;RedissonLock Annotaion&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissonLock {

    String lockKey();
    long waitTime() default 5000L; // Lock획득을 시도하는 최대 시간 (ms)
    long leaseTime() default 2000L; // 락을 획득한 후, 점유하는 최대 시간 (ms)

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RedssionLock AOP&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Slf4j
@Order(1)   //@Transactional 보다 먼저 하기 위해
@Aspect
@Component
@RequiredArgsConstructor
public class RedissonLockAspect {

    private final RedissonClient redissonClient;

    @Around(&quot;@annotation(com.study.concurrency.config.redisson.RedissonLock)&quot;)
    public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RedissonLock annotation = method.getAnnotation(RedissonLock.class);

        String lockKey = method.getName() + getDynamicValue(signature.getParameterNames(), joinPoint.getArgs());

        //key 로 Lock 객체 가져옴
        RLock lock = redissonClient.getLock(lockKey);

        try {
            //획득시도 시간, 락 점유 시간
            boolean lockable = lock.tryLock(annotation.waitTime(), annotation.leaseTime(), TimeUnit.MILLISECONDS);
            if (!lockable) {
                log.error(&quot;Lock 획득 실패={}&quot;, lockKey);
                return false;
            }
            log.info(&quot;Redisson Lock 획득&quot;);
            joinPoint.proceed();
        } catch (InterruptedException e) {
            log.error(&quot;Redisson 락 점유 에러&quot;);
            throw e;
        } finally {
            log.info(&quot;락 해제&quot;);
            lock.unlock();
        }

        return true;
    }

    private String getDynamicValue(String[] parameterNames, Object[] args) {

        StringBuilder stringBuilder = new StringBuilder();

        for (int i = 0; i &amp;lt; parameterNames.length; i++) {
            stringBuilder.append(&quot;-&quot;).append(parameterNames[i]).append(&quot;:&quot;).append(args[i]);
        }

        return stringBuilder.toString();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service 비지니스 로직&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Transactional
@RedissonLock(lockKey = &quot;reservation&quot;)
public void reservationToRedisson(final Long ticketId) {

    final Ticket ticket = ticketRepository.findById(ticketId).get();
    reservationSuccess(ticket);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yYpGm/btsJh7PTcWg/lzQmKQLuZNPOlGkgr2XmV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yYpGm/btsJh7PTcWg/lzQmKQLuZNPOlGkgr2XmV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yYpGm/btsJh7PTcWg/lzQmKQLuZNPOlGkgr2XmV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyYpGm%2FbtsJh7PTcWg%2FlzQmKQLuZNPOlGkgr2XmV1%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;300&quot; height=&quot;78&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;78&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;  Spring Redisson Lock AOP, 부모 @Transacnal 전파에서 구출하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  하지만 만약, @RedissonLock 어노테이션을 사용하는 클래스의 앞단에서 @Transactional 이 전파된다면 커밋이 되기전 락을 반환해 테스트에 실패합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/stJIx/btsJiV83QzF/KgPZ9V7h4ky1jRNldIeLX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/stJIx/btsJiV83QzF/KgPZ9V7h4ky1jRNldIeLX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/stJIx/btsJiV83QzF/KgPZ9V7h4ky1jRNldIeLX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FstJIx%2FbtsJiV83QzF%2FKgPZ9V7h4ky1jRNldIeLX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;256&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;256&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;@RedissonLock 어노테이션이 적용된 AOP 에서 내부적으로 Target Proceed 전에 트랜잭션을 분리하여 해결하고자 했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Component
public class AopForTransaction {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
@Order(1)   //@Transactional 보다 먼저 하기 위해
@Aspect
@Component
@RequiredArgsConstructor
public class RedissonLockAspect {

    private final RedissonClient redissonClient;
    private final AopForTransaction aopForTransaction;

    @Around(&quot;@annotation(com.study.concurrency.config.redisson.RedissonLock)&quot;)
    public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable {
       
        //...
        
        try {
            //획득시도 시간, 락 점유 시간
            boolean lockable = lock.tryLock(annotation.waitTime(), annotation.leaseTime(), TimeUnit.MILLISECONDS);
            if (!lockable) {
                log.error(&quot;Lock 획득 실패={}&quot;, lockKey);
                return false;
            }
            log.info(&quot;Redisson Lock 획득&quot;);

			// AOP 내부에서 transaction 분리
            return aopForTransaction.proceed(joinPoint);

        } catch (InterruptedException e) {
            log.error(&quot;Redisson 락 점유 에러&quot;);
            throw e;
        } finally {
            log.info(&quot;락 해제&quot;);
            lock.unlock();
        }
    }

	//...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모의 트랜잭션을 전파받지 않고, 새로운 트랜잭션으로 분리하다보니 추가적인 Connection Pool Size 확보가 필요했습니다 .. &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVfoSA/btsJk3d0lxq/A5dkGPzpw61byNKzBkDTJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVfoSA/btsJk3d0lxq/A5dkGPzpw61byNKzBkDTJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVfoSA/btsJk3d0lxq/A5dkGPzpw61byNKzBkDTJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVfoSA%2FbtsJk3d0lxq%2FA5dkGPzpw61byNKzBkDTJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;265&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ Redis RedLock&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 노드로 구축된 Redis 는 단일 장애 지점(SPOF, Single Point Of Failure)을 가집니다.&lt;br /&gt;이러한 단점을 극복하기 위해 Redis 에서 제안한 분산락 알고리즘이 레드락(Redlock) 알고리즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 잘 설명된 블로그를 참고하고 넘어가겠습니다,,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUT68H/btsJlf7afjf/XiWR3c7geCscNbqRvoBWV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUT68H/btsJlf7afjf/XiWR3c7geCscNbqRvoBWV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUT68H/btsJlf7afjf/XiWR3c7geCscNbqRvoBWV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUT68H%2FbtsJlf7afjf%2FXiWR3c7geCscNbqRvoBWV0%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;1952&quot; height=&quot;910&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  자세한 내용은 여기로! &amp;rarr; 출처: &lt;a href=&quot;https://mangkyu.tistory.com/311&quot;&gt;https://mangkyu.tistory.com/311&lt;/a&gt; [MangKyu's Diary:티스토리]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 그 외의 방법들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부를 하면서 찾아보니 MSA와 같은 분산 환경에서 동시성을 제어하며, 데이터의 일관성을 보장하는 몆가지 패턴들에 알게되어&lt;br /&gt;간단하게 참고용으로 남깁니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MVCC (다중버전 동시성 제어, Multi-Version Concurrency Control)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 스냅샵 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Saga 패턴
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션을 이벤트로 관리 (롤백 이벤트 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;TCC (Try-Confim-Cancel)&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Saga 패턴과 마찬가지로 트랜잭션을 이벤트로 관리 (롤백 이벤트 필요)&lt;/li&gt;
&lt;li&gt;최종적 일관성만을 보장&lt;/li&gt;
&lt;li&gt;Sagas 와의 차이점은 롤백의 비동기적 처리...?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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 id=&quot;monitor&quot;&gt;&lt;a href=&quot;https://jayhyun-hwang.github.io/2021/08/23/Monitor/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;동시성 프로그래밍에서의 모니터(Monitor)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%8B%A4%EC%8A%B5/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;인프런 실습으로 배우는 선착순 시스템&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=SoQ4ExWnetg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[youtube] 낙관적 락 vs 비관적 락&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://www.youtube.com/watch?v=LDi5muN2kgI&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[youtube] &lt;span style=&quot;background-color: #ffffff;&quot;&gt;[10분 테코톡] 우르의 Lock &amp;amp; JPA Lock&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://colevelup.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;[JPA] Persistence context(영속성 컨텍스트)와 EntityManager&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://velog.io/@e1psycongr00/Spring-Transactional%EA%B3%BC-entityManager-%EA%B4%80%EA%B3%84-%EB%B6%84%EC%84%9D%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;[Spring] @Transactional과 entityManager 관계 분석하기&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://xxeol.tistory.com/57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AOP&lt;span style=&quot;letter-spacing: -1px; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; letter-spacing: -1px; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;및 @Retryable를 활용한 낙관적 락 재시도&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://redisson.org/glossary/java-semaphore.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://redisson.org/glossary/java-semaphore.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devoong2.tistory.com/entry/Spring-Redisson-TryLock-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Spring] Redisson tryLock 동작 과정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://devoong2.tistory.com/entry/Spring-Redisson-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-Distribute-Lock-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%B2%98%EB%A6%AC-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; letter-spacing: -1px; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;[Spring] Redisson 라이브러리를 이용한 Distribute Lock 동시성 처리 (1/2)&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://mangkyu.tistory.com/311&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; letter-spacing: -1px; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;[Redis]&amp;nbsp;레디스가&amp;nbsp;제공하는&amp;nbsp;분산락(RedLock)의&amp;nbsp;특징과&amp;nbsp;한계&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>java</category>
      <category>Redis Lock</category>
      <category>spring</category>
      <category>spring 대용량</category>
      <category>동시성 제어 문제 방법</category>
      <category>동시성 처리</category>
      <category>동시성제어</category>
      <category>백엔드</category>
      <category>분산환경 대용량 처리</category>
      <category>서버</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/485</guid>
      <comments>https://thalals.tistory.com/485#entry485comment</comments>
      <pubDate>Fri, 30 Aug 2024 13:46:59 +0900</pubDate>
    </item>
    <item>
      <title>[한빛앤 MSA 세미나] 모니터링 | 강동호</title>
      <link>https://thalals.tistory.com/484</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;한빛앤 MSA 세미나 강동호 연사님의 &quot;서버 모니터링&quot; 세미나를 듣고 정리한 글 입니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQbc17/btsI9N31O8r/ORbfrqQP2rkhibKxWSbyc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQbc17/btsI9N31O8r/ORbfrqQP2rkhibKxWSbyc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQbc17/btsI9N31O8r/ORbfrqQP2rkhibKxWSbyc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQbc17%2FbtsI9N31O8r%2FORbfrqQP2rkhibKxWSbyc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;348&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;348&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 모니터링 도입이 어려운 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 개발하는데도 시간이 오래걸림&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요구사항 분석&lt;/li&gt;
&lt;li&gt;시스템 설계&lt;/li&gt;
&lt;li&gt;개발&lt;/li&gt;
&lt;li&gt;테스트 및 버그 수정&lt;/li&gt;
&lt;li&gt;코드 리뷰&lt;/li&gt;
&lt;li&gt;배포&lt;/li&gt;
&lt;li&gt;유지보수&lt;/li&gt;
&lt;li&gt;버그 수정&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbvROx/btsI8XGmyM4/tmWabNhlpsKF2Xrd9UKDO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbvROx/btsI8XGmyM4/tmWabNhlpsKF2Xrd9UKDO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbvROx/btsI8XGmyM4/tmWabNhlpsKF2Xrd9UKDO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbvROx%2FbtsI8XGmyM4%2FtmWabNhlpsKF2Xrd9UKDO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;287&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 대부분은 재시작으로 해결할 수 있어서&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통의 운영 서버의 부하 버그인 경우 재시작으로 해결이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 아직은 문제가 발생하지 않아서&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확하지 않은 가용성 체크
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 저번에 배포한 서비스도 Spring 인데, 동일하게 EC2 서버크기 세팅할게요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;잠재적 문제의 누적
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 알파환경 에서는 선착순 테스트 진행했는데 정상적이었어요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;팀 리더의 반대
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;아직 사용자도 적은데 나중에 붙여도 늦지 않다&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 모니터링의 중요성&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;지속적인 감시, 관찰을 통해 상태나 가용성, 변화 등을 확인하고 대비&lt;/b&gt;하는 것을 의미합니다.&lt;/li&gt;
&lt;li&gt;해당 세미나에서 이야기하는 &lt;b&gt;&amp;ldquo;모니터링&amp;rdquo;&lt;/b&gt;은&lt;b&gt; &amp;ldquo;서버라는 매개체에서 진행하는 모니터링 (어플리케이션, 인프라, 로그, DB 등)&lt;/b&gt;&amp;rdquo; 을 포괄해서 진행합니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;모니터링의 중요성&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;ex) 서비스를 배포하고 난 이후 특정 시점 이후 CPU 가 100%&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgQ99N/btsI9ujnVKE/2ebwEGlwfCuKCVESS96bI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgQ99N/btsI9ujnVKE/2ebwEGlwfCuKCVESS96bI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgQ99N/btsI9ujnVKE/2ebwEGlwfCuKCVESS96bI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgQ99N%2FbtsI9ujnVKE%2F2ebwEGlwfCuKCVESS96bI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;260&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 만약 모니터링 시스템이 구축되어 있다면?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CPU 가 갑자기 치솟은 부분을 확인&lt;/li&gt;
&lt;li&gt;직전 시간대의 API 병목점 체크&lt;/li&gt;
&lt;li&gt;기존 API 정상 동작을 위한 롤백&lt;/li&gt;
&lt;li&gt;로직 보완 후 재배포&lt;/li&gt;
&lt;li&gt;&amp;rarr; 빠른 버그 추출 및 재가동 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 모니터링 시스템의 이점&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;성능 최적화&lt;/b&gt; - 모니터링을 통해 부하가 발생한 위치를 정확하기 파악하여 성능 최적화 지점을 확인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가용성 보장&lt;/b&gt; - 지표들을 미리 체크하여 고객 문의 이전에 문제 상황을 먼저 확인하여 비지니스 연속성을 보장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;안정성 유지&lt;/b&gt; - 오류 상황을 빠르게 파악하여 시스템의 안정성을 유지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 강화&lt;/b&gt; - 모니터링 과정에서 악성 사용자의 비정상적인 동작을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 서버 모니터링은 IT 인프라의 핵심적인 부분으로, 서버의 건강 상태를 지속적으로 점검하고 시스템의 효율성과 안정성을 유지하기 위해 필수이다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기본 지표 이해하기&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;모니터링에서 기본적으로 사용하는 지표 정리&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ CPU 지표&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 사용률
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 가 얼마나 바쁜지, 얼마나 많은 작업을 처리하고 있는지 나타냅니다.&lt;/li&gt;
&lt;li&gt;지속적으로 높은 사용률은 시스템의 오버로드를 의미합니다.&lt;/li&gt;
&lt;li&gt;일반적으로 백분율 (%) 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CPU 평균 로드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정시간 동안 시스템이 경험한 평균적인 부하를 의미&lt;/li&gt;
&lt;li&gt;보통 1분, 5분, 15분 간격으로 측정됩니다.&lt;/li&gt;
&lt;li&gt;평균 로드가 높으면 &amp;ldquo;지연&amp;rdquo;이 발생할 수 있어 높지 않게 유지해야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CPU Idle 시간
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU가 작업을 수행하지 않고 쉬고 있는 시간을 의미&lt;/li&gt;
&lt;li&gt;적정한 양의 아이들 타임은 중요하지만 너무 많으면 리소스 낭비를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Context Switch
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU가 한 작업에서 다른 작업으로 전환되는 빈도&lt;/li&gt;
&lt;li&gt;이 지표가 높다면 비효율적인 멀티태스킹을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CPU 인터럽트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하드웨어 문제나 악성 프로그램으로 인해 발생할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; CPU 사용량 지표가 높은 상태에서 배포를 진행한다면 &amp;ldquo;배포 시간이 늘어나고 서버 리소스가 추가적으로 소모&amp;rdquo; 될 수 있습니다.&lt;br /&gt;&amp;rarr; 되도록이면 CPU 가용룔을 50% 이하로 유지하는게 좋다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&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;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;메모리 사용량이 높은 경우 &amp;rarr; 스왑 공간을 사용할 수 있어, 성능 저하를 야기시킬 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가용 메모리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 사용 가능한 메모리 양&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메모리 캐시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템이 자주 접근한느 데이터를 빠르게 액세스하기위해 사용하는 영역&lt;/li&gt;
&lt;li&gt;캐시 메모리의 사용량은 시스템 성능에 큰 영향을 줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스왑 사용량
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디스크 공간을 임시 메모리 저장소로 사용하는 것&lt;/li&gt;
&lt;li&gt;지속적으로 높은 스왑 사용량은 메모리 자원이 부족하다는 신호&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Page Faults
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스가 접근하려는 메모리가 현재 메모리에 없을 때 발생&lt;/li&gt;
&lt;li&gt;이 지표가 높은 빈도라면, 스왑이 많이 일어나거나 메모리 최적화가 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 디스크 지표&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;파일처리&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;사용률이 매우 높으면 &amp;rarr; 성능 저하의 원인이 될 수 있음 (데이터 누락, 비정상적인 동작 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;디스크 I/O
&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;높은 I/O 는 디스크 성능에 영향을 미칠 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;디스크 대기 시간
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디스크 I/O 요청이 처리되기까지 걸리는 시간&lt;/li&gt;
&lt;li&gt;높은 디스크 대기 시간은 디스크 성능 문제를 나타낼 수 있음, 시스템 응답 시간에 부정적인 영향&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;IOPS
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초당 디스크 I/O 작업의 수&lt;/li&gt;
&lt;li&gt;IOPS는 디스크 성능과 처리 능력을 나타냄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스루풋
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간 당 디스크를 통해 전송되는 데이터 양&lt;/li&gt;
&lt;li&gt;높은 스르풋은 디스크가 많은 데이터를 빠르게 처리하고 있다는 것을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;에러율
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디스크 읽기/쓰기 작업 중 발생하는 에러의 비율&lt;/li&gt;
&lt;li&gt;높은 에러율은 디스크의 물리적 손상이나 다른 기술적 문제를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 디스크 지표는 파일 처리 성능을 위해 필수적으로 관리해야합니다.&lt;br /&gt;&amp;rarr; 빠른 액세스를 보장하기 위해 중요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크의 용량 대비 실제 사용량&lt;/li&gt;
&lt;li&gt;사용률이 높으면 네트워크 오버러도의 위험을 나타낼 수 있으며, 트래픽 관리 및 용량 계획에 중요한 정보를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트래픽 흐름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크를 통해 이동하는 데이터의 양과 방향&lt;/li&gt;
&lt;li&gt;네트워크의 성능과 사용 패턴을 이해는 데 도움이 되며, 네트워크 최적화와 라우팅 계획에 중요한 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;지연 시간
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 패킷이 한 지점에서 다른 지점까지 도달하는 데 걸리는 시간&lt;/li&gt;
&lt;li&gt;높은 지연 시간은 네트워크 성능 문제를 나타내며, 특히 실시간 데이터 처리에 중요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;패킷 손실
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크를 통해 전송되는 데이어 퍀시 중 손실 비율&lt;/li&gt;
&lt;li&gt;패킷 손실이 발생하면, 네트워크의 문제 또는 혼잡을 의미 (데이터 전송의 신뢰성 감소)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;연결 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 장애 또는 다운 타임을 신속하게 감지하는 데 중요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 모니터링 도구 소개&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 상용 모니터링 도구 (유료)&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Data Dog
&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;각 지표간 연계 가&lt;/li&gt;
&lt;li&gt;APM 기능 특화&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Whatap
&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;스타트업 패키지가 있음&lt;/li&gt;
&lt;li&gt;서버 모니터링 5대 무료&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;New Relic
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주로 APM 툴 위주로 사용자 경험 분석 특화&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8d3hq/btsI9FkGBJ4/BJXtyKut3Zr6K4PLimVW9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8d3hq/btsI9FkGBJ4/BJXtyKut3Zr6K4PLimVW9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8d3hq/btsI9FkGBJ4/BJXtyKut3Zr6K4PLimVW9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8d3hq%2FbtsI9FkGBJ4%2FBJXtyKut3Zr6K4PLimVW9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;285&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 오픈소스 모니터링 도구 (무료)&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;Zabbix
&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;네트워크, 가용성 모니터링에 특화&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Prometheus
&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;시계열 데이터 수집이 용이&lt;/li&gt;
&lt;li&gt;지표 수집 및 쿼리 기반 대시보드 지원&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;ELK Stack
&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;쿼리 기반 조회를 통한 시각화&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsOBDb/btsJabi6qqt/KK5xbra0BdZ0KMXALkx2vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsOBDb/btsJabi6qqt/KK5xbra0BdZ0KMXALkx2vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsOBDb/btsJabi6qqt/KK5xbra0BdZ0KMXALkx2vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsOBDb%2FbtsJabi6qqt%2FKK5xbra0BdZ0KMXALkx2vK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;283&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;283&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&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;li&gt;❗️ 오픈소스 모니터링 툴(무료) 와 상용(유료)를 고민 중 이라면, 개발자의 인건비또한 고려해보는게 좋은 선택이다.&lt;br /&gt;(오픈소스 모니터링 툴을 공부하고 적용하는, 시간또한 비용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctdPnx/btsI99esZD5/IyDSqKtFgtkkxjg47brK7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctdPnx/btsI99esZD5/IyDSqKtFgtkkxjg47brK7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctdPnx/btsI99esZD5/IyDSqKtFgtkkxjg47brK7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctdPnx%2FbtsI99esZD5%2FIyDSqKtFgtkkxjg47brK7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;302&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 실시간 모니터링&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 주간 모니터링 (평일 오전에 진행)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어제와 다른 특이점을 비교 (경고 알림은 오지 않았지만 &amp;rarr; 평소와는 다른 서버 가용률을 모니터링 &amp;rarr; 이슈가 터지기 전에 버그를 잡을 수 있음)&lt;/li&gt;
&lt;li&gt;서비스, 서버, 연결된 DB 등을 위주로 파악&lt;/li&gt;
&lt;li&gt;차이가 있다면 연관된 로직을 파악 &amp;rarr; 문제 상황 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&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;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 전 인스턴스 사요량을 체크 &amp;rarr; 부하가 없을 때 배포 진행&lt;/li&gt;
&lt;li&gt;배포 이후 CPU, 메모리, DB 등에 부하가 발생하지 않았는지 확인&lt;/li&gt;
&lt;li&gt;문제가 확인되면, 이전 배포 버전으로 롤백&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.&amp;nbsp; 알림 시스템의 구축 및 관리&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 임계값 설정과 조정&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;언제 알림을 받아야 할까?&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평소 상황의 +10~20% 정도를 더한값 (통상 70%) 이하 사이로 임계값을 지정&lt;/li&gt;
&lt;li&gt;임계값은 어떤 서비스를 운영하는지에 따라 다르게 적용되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 로그 데이터&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 로그 데이터의 이해&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터 시스템, 네트워크, 또는 응용 프로그램의 동작에 따라 기록되는 데이터&lt;/li&gt;
&lt;li&gt;Log Level
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;trace : debug 보다 세분화된 정보&lt;/li&gt;
&lt;li&gt;debug : 디버깅하는데 유용한 정보&lt;/li&gt;
&lt;li&gt;info : 진행상황이나 요청, 응답값 등의 정보 전달에 사용&lt;/li&gt;
&lt;li&gt;warn : 에러는 아니지만, 잠재적인 오류 원인이 될 수 있는 경고성 정보&lt;/li&gt;
&lt;li&gt;error : 비지니스 로직의 에러&lt;/li&gt;
&lt;li&gt;fatal : 종료가 발생할 정도로 치명적인 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주의 사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그가 저장되는 저장소의 용량관리가 필요 (너무 많이 쌓으면 안됨)&lt;/li&gt;
&lt;li&gt;개인정보, 시스템 주요 정보가 노출되지 않도록 유의 (마스킹)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;2) 로그 관리 전략&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간, 로그레벨을 포함하여 기록&lt;/li&gt;
&lt;li&gt;로그는 특정 기간 이후 자동 삭제되도록 지정&lt;/li&gt;
&lt;li&gt;전사에서 사용하는 에러 코드와 메시지 공통화&lt;/li&gt;
&lt;li&gt;항상 모든 로그 기록 x &amp;rarr; 에러나 비지니스 관련 히스토리 위주로 기록&lt;/li&gt;
&lt;li&gt;프로덕션 데이터 디버깅
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 유저 아이디인 경우에 모든 debug level 이 기록되도록 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀원, QA 멤버, VoC 인입 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로그 총량은 최소화하고 추가적으로 배포하지 않아도 자세한 디버깅 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Exception 로그
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 발생하느느 에러는 기록 x
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 이메일 Validation - 비지니스 관점에서 정상적인 처리 로직&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비지니스 레벨의 에러는 슬랙 알림 지정&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;h3 data-ke-size=&quot;size23&quot;&gt;3) 로그 세팅하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Logback 설정&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;SpringBoot 에 내장된 로깅 프레임워크
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그 파일 사이즈 설정 가능&lt;/li&gt;
&lt;li&gt;기간 설정 가능&lt;/li&gt;
&lt;li&gt;로그 파일명, 기록되는 형태를 패턴으로 지정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 모니터링과 보안&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;고급 옵션 살펴보기&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모니터링을 통한 보안 위협 감지 - ex) 상품 크롤링 감지 (외부 아이피의 반족적인 호출)
&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;아이피 차단 및 사용자에게 경고성 이메일 전송 등의 대응이 가능해짐&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;서비스 간의 호출관계 파악 (MSA)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 서비스간의 DB 내용이 바뀌었는데 누가 접근했는지 모니터링을 통해 파악이 가능&lt;/li&gt;
&lt;li&gt;각 마이크로 서비스의 Dependency Trace 가능 (추적)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&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;서비스 내의 모든 동작에 대한 CCTV와 같은 역할&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정리 끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Spring Actuator 를 고민하고 있었는데&lt;br /&gt;상용 서비스인 Whatap 또한 스타트업 패키지(5개 서버 무료) 를 제공하고 있어서 &lt;br /&gt;좋은 선택지가 될 것 같다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  공대생은 성장 중/세미나</category>
      <category>모니터링 구축</category>
      <category>모니터링 도구</category>
      <category>백엔드</category>
      <category>서버</category>
      <category>서버개발자</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/484</guid>
      <comments>https://thalals.tistory.com/484#entry484comment</comments>
      <pubDate>Tue, 20 Aug 2024 17:20:13 +0900</pubDate>
    </item>
    <item>
      <title>Repository는 어느 모듈에 위치해야할까? (DIP. 고수준모듈, 저수준 모듈)</title>
      <link>https://thalals.tistory.com/483</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;레이어드 아키텍쳐를 사용하는 단일 모듈 서비스를 멀티모듈로 나누는 과정에서,,, 시작된&amp;nbsp; Repository 는 과연 어디 모듈에 위치해야하는 가에 대한 주저리주저리 고민 정리 글입니다.&lt;br /&gt;&lt;br /&gt;코드 예시는 &lt;a href=&quot;https://github.com/thalals/Multi-Module-lab&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에 있습니다.&lt;/blockquote&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;br /&gt;이런 설계에서 &lt;b&gt;Repository 는 core:domain 모듈에 들어가야하는가 core:Infra 에 들어가야하는가&lt;/b&gt;에 대한 매우 심도깊은 고민을 하고있습니다..ㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;617&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VT9te/btsIr8ujhzA/S9t4xkSu1AikLHhEqiHy0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VT9te/btsIr8ujhzA/S9t4xkSu1AikLHhEqiHy0K/img.png&quot; data-alt=&quot;저의 가소로운 고민입니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VT9te/btsIr8ujhzA/S9t4xkSu1AikLHhEqiHy0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVT9te%2FbtsIr8ujhzA%2FS9t4xkSu1AikLHhEqiHy0K%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;1050&quot; height=&quot;617&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;617&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;저의 가소로운 고민입니다&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[궁금한 것]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;고수준 모듈은 뭐고 저수준모듈은 무엇인가.&lt;/li&gt;
&lt;li&gt;왜 고수준 모듈이 저수준 모듈의 의존성을 가지면 안되는가&lt;/li&gt;
&lt;li&gt;그래서 Repository 는 어떤 모듈에 위치해야하는가&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~~ 에 대해 정리해봅니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 고수준 모듈과 저수준 모듈의 정의는 무엇일까??&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;멀티모듈에서 서로 다른 수준의 공통 코드 분리하기&amp;nbsp;&lt;/blockquote&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;✔️서로 수준(Level)이 다르다&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 변경의 속도를 가진다.&lt;/li&gt;
&lt;li&gt;즉, 수준이 다른 모듈의 의존성을 분리해야 변경에 유연해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️그럼 수준(Level) 이란 도대체 무엇일까요&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;입력 : HTTP, 웹소켓&lt;/li&gt;
&lt;li&gt;출력 : 캐시. 데이터베이스 등&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&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;고수준 - Domain 영역 (비지니스 요구사항에 의해 수시로 변경됨)&lt;/li&gt;
&lt;li&gt;저수준 - 운영중인 데이터베이스의 변경 등,,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8ZLq0/btsIqDI3WCm/IwvlkuKkbtKaHGPRekTKfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8ZLq0/btsIqDI3WCm/IwvlkuKkbtKaHGPRekTKfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8ZLq0/btsIqDI3WCm/IwvlkuKkbtKaHGPRekTKfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8ZLq0%2FbtsIqDI3WCm%2FIwvlkuKkbtKaHGPRekTKfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;225&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ 모듈관점에서의 고수준과 저수준&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고수준 모듈&lt;/b&gt;&lt;/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;ex - Service 단의 응용 계층, Domain 비지니스 로직&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저수준 모듈&lt;/b&gt;&lt;/p&gt;
&lt;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;ex - JPA 로 데이터를 불러오는 것, 특정 부가 기능을 수행하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;160&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dExndI/btsIqV3NnNI/rlWXe7gaoOEXrmmLZhMvc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dExndI/btsIqV3NnNI/rlWXe7gaoOEXrmmLZhMvc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dExndI/btsIqV3NnNI/rlWXe7gaoOEXrmmLZhMvc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdExndI%2FbtsIqV3NnNI%2FrlWXe7gaoOEXrmmLZhMvc1%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;160&quot; height=&quot;72&quot; data-origin-width=&quot;160&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저의 구조에서는&lt;br /&gt;&lt;u&gt;&quot;Domain 이 고수준 모듈&quot;&lt;/u&gt;&lt;br /&gt;&lt;u&gt;&quot;Infra 가 저수준 모듈&quot;&lt;br /&gt;&lt;/u&gt;이 되겠네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 고수준 모듈이 저수준 모듈의 의존성을 가지면 안되는가&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 고수준 모듈 : 의미 있는 단일 기능을 제공하는 모듈&lt;br /&gt;✔️ 저수준 모듈 : 고수준 모듈의 기능을 구현하는데 필요한 하위 기능을 실제로 구현한 것&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 고수준 모듈이 제대로 동작하려면, 저수준 모듈을 사용해야 한다.&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;회원가입 기능을 제공하는 고수준 모듈 JoinService 가 있다가 했을 때 다음과 같은 하위 기능이 필요합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가입하려는 회원의 정보가 기존에 존재하는지 확인하는 UserRepository 가 필요&lt;/li&gt;
&lt;li&gt;회원가입 완료 메세지를 전달하는 외부 써드파티 API 를 사용하는 MessageUtls&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1720450088361&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class JoinService{

    private final UserRepository userRepository;
    private final MessageUtils messageUtils;
    
    //생성자 생략
    public String join(Object 어떤정보){
    
    	//중복 가입 DB 확인
       	boolean isUser = userRepository.중복인가요?();
        
        //회원가입하면 메세지 전달
        if(!isUser){
            userRepository.회원가입!(유저정보);
            messageUtils.가입축하메세지전달(유저정보);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;rarr; 이런 경우 저수준 모듈에 해당하는 UserRepository 나 MessageUtils 등의 기능을 직접 불러와서 생성자로 객체를 생성하고, 고수준 모듈에서 바로 사용한다면.. &lt;b&gt;추후 DB가 변경되거나, 써드파티 API 를 다른 서비스로 변경할 때&lt;/b&gt;&amp;nbsp;&lt;b&gt;변경포인트가 매우 많아지겠죠&lt;/b&gt;?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;고수준 모듈이 저수준의 의존성을 가지면 안된다&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;b&gt;DIP(Dependency Inversion Principle)&lt;/b&gt; 을 사용하여 &quot;&lt;b&gt;저수준 모듈이 고수준 모듈에 의존&lt;/b&gt;&quot; 하도록 바꿔야합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;저수준 모듈이 고수준 모듈에 의존하게 하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;  모두가 다 알듯이 인터페이스를 사용합니다.&lt;/p&gt;
&lt;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;br /&gt;&amp;rarr;&lt;b&gt; &quot;고수준 모듈은, 저수준 모듈에 의존하지 않고 구현을 추상화한 인터페이스에 의존&quot;&lt;/b&gt;하게 됩니다.&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;이렇게되면, DB를 변경하거나 외부 API 를 더 저렴하고 좋은 다른 서비스로 변경하더라도 구현체만 새로 만들어 갈아끼면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;！즉, 고수준 모듈은 저수준의 변경이 일어나도 영향을 받지않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 모듈 관점에서의 DIP&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그래서 Repository 는 어떤 모듈에 위치해야하는가&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제를 모듈구조로 분리해보고자 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;비지니스로직을 가지고 요구사항과 밀접한 관계가있는 JoinService 는 Domain 영역에 가야한다고 생각합니다.&lt;/li&gt;
&lt;li&gt;단순하게 생각했을때 DB 에 접속하고, 외부 API 를 연동하는 부분은 Infra로 가야하겠죠&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9u69I/btsIryG4Jes/dldgSL3IfquqkjQtTg1xw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9u69I/btsIryG4Jes/dldgSL3IfquqkjQtTg1xw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9u69I/btsIryG4Jes/dldgSL3IfquqkjQtTg1xw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9u69I%2FbtsIryG4Jes%2FdldgSL3IfquqkjQtTg1xw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;281&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  하지만, 이렇게 되면 &quot;고수준 모듈인 Domain 이 저수준 모듈인 Infra 를 의존하게 됩니다.&quot;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한 구조는, UserRepository 인터페이스를 고수준 모듈인 도메인 모듈 관점이 아닌 저수준 모듈 관점에서 도출한 것 입니다.&lt;/li&gt;
&lt;li&gt;DIP 를 적용할 때, 하위 기능을 &lt;b&gt;추상화한 인터페이스는 고수준 모듈 관점에서 도출&lt;/b&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9G5UV/btsIsw9z79T/pr8cazZLHIifhOrmhII0JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9G5UV/btsIsw9z79T/pr8cazZLHIifhOrmhII0JK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9G5UV/btsIsw9z79T/pr8cazZLHIifhOrmhII0JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9G5UV%2FbtsIsw9z79T%2Fpr8cazZLHIifhOrmhII0JK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;238&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;238&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;br /&gt;하위 기능을 추상화한 Repository 인터페이스는 도메인 모델 영역에(고수준)에 &lt;br /&gt;실제 구현 클래스는 인프라스트럭쳐 영역에(저수준) 에 속하는것이 좋다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FQ05F/btsIsyGjUuG/mVPrAQWoEEaiRwqASHjlOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FQ05F/btsIsyGjUuG/mVPrAQWoEEaiRwqASHjlOk/img.png&quot; data-alt=&quot;더 복잡한 모듈 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FQ05F/btsIsyGjUuG/mVPrAQWoEEaiRwqASHjlOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFQ05F%2FbtsIsyGjUuG%2FmVPrAQWoEEaiRwqASHjlOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;427&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;더 복잡한 모듈 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4. 코드 예시&amp;nbsp;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;도메인 모듈을 분리한 멀티모듈 구조&lt;/blockquote&gt;
&lt;pre id=&quot;code_1724150716112&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;프로젝트 폴더
├── admin
│   └── {ui}
│   └── {usecase}
├── api
│   └── {ui}
│   └── {usecase}
├── core
│   ├──  domain
│   │     └── {domain}
│   │     └── {service}
│   │     └── {repository}
│   ├──  infra
│   │     └── {entity}
│   │     └── {repository}
│   │     └── {repositoryImpl}
│   │     └── {config}
├── common-utils
│   └── {utils}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ API 모듈&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;package com.example.api.presentation;

import com.example.infra.MemberDto;
import com.example.infra.MemberRepository;
import com.example.api.presentation.response.MemberResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MemberUsecase {

    private final MemberRepository memberRepository;

    public MemberResponse findMemberById(Long id) {
        MemberDto memberDto = memberRepository.find(id);

        return MemberResponse.ofDto(memberDto);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;✔️&lt;span&gt; Infra 모듈&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package com.example.infra;

public interface MemberRepository {

    MemberDto find(Long memberId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package com.example.infra;

import com.example.domain.member.Member;
import com.example.domain.member.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class MemberRepositoryConnector implements MemberRepository {

    private final MemberService memberService;

    @Override
    public MemberDto find(Long memberId) {
        Member member = memberService.findMemberById(memberId);
        return MemberDto.ofEntity(member);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;✔️&lt;span&gt; Domain 모듈&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;예시에서는 Entity 를 Domain 으로 보고 있습니다.&lt;/blockquote&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;package com.example.domain.member;

import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberJpaRepository extends JpaRepository&amp;lt;Member, Long&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;✔️&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;모듈간 의존관계&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xC8Ko/btsI9hLrHwG/PKih5NKN9iF1BHXwCmzadk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xC8Ko/btsI9hLrHwG/PKih5NKN9iF1BHXwCmzadk/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=p5ZMF2bpE6A&amp;amp;amp;t=572s&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xC8Ko/btsI9hLrHwG/PKih5NKN9iF1BHXwCmzadk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxC8Ko%2FbtsI9hLrHwG%2FPKih5NKN9iF1BHXwCmzadk%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;400&quot; height=&quot;299&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=p5ZMF2bpE6A&amp;amp;t=572s&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝,,이지만&lt;br /&gt;잘못된 내용이나 더 올바른 방향성을 댓글로 알려주신다면 정말 큰 도움이 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;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;a href=&quot;https://www.youtube.com/watch?v=uvG-amw2u2s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #000000; letter-spacing: -1px; background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;우리는 이렇게 모듈을 나눴어요: 멀티 모듈을 설계하는 또 다른 관점 | 인프콘2023&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&quot;top-row&quot; style=&quot;background-color: #ffffff; color: #0f0f0f; text-align: start;&quot;&gt;
&lt;div id=&quot;owner&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>DDD</category>
      <category>dip</category>
      <category>java</category>
      <category>module</category>
      <category>multimodule</category>
      <category>spring</category>
      <category>멀티모듈</category>
      <category>모듈 나누기</category>
      <category>모듈 설계</category>
      <category>스프링</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/483</guid>
      <comments>https://thalals.tistory.com/483#entry483comment</comments>
      <pubDate>Tue, 9 Jul 2024 01:20:52 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot 에서 Redis Cache 사용하기</title>
      <link>https://thalals.tistory.com/482</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 이전 포스팅에서 못 다한 Spring Redis Cache 를 사용해보고자 합니다.&lt;br /&gt;코드는 &lt;a href=&quot;https://github.com/thalals/SpringStudy/tree/main/redis-study/redis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github&lt;/a&gt; 에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Redis 묶음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/479&quot;&gt;Redis 란 (특징, 주의점, 동작 구조)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/481&quot;&gt;Spring 에서 Redis 사용하기 (설정, In-memory DB, Transaction)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/482&quot;&gt;✔️ Spring Boot 에서 Redis Cache 사용하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[목차]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Cache Manager&lt;/li&gt;
&lt;li&gt;Redis Cache
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;config&lt;/li&gt;
&lt;li&gt;@Cachable&lt;/li&gt;
&lt;li&gt;@CachEvict&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Cache hits 모니터링 종류&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;page-title&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Spring Cache Abstraction&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 에서는 캐시 기능 자체에 대해서 특정 기술에 종속되지 않게 추상화를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP 를 이용한 어노테이션을 활용하여 특정 기술에 종속되지 않고, 애플리케이션에 캐싱 기능을 부여할 수 있고&lt;br /&gt;이를 통해 프로덕션 코드가 외부 서비스의 변경에 의존하지 않도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 직접적으로 캐싱기능을 제공하는 외부 서비스를 Cache Manager 로 Bean 으로 등록하여 사용할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Spring Cache Manager&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 추상화 에서는 캐시 기술을 지원하는 캐시 매니저를 빈으로 등록해야합니다.&lt;br /&gt;캐시 매니저의 종류는 상당히 여러가지인데, 저는 Redis 를 캐시매니저로 사용해보고자 합니다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;ConcurrentMapCacheManager&lt;/li&gt;
&lt;li&gt;SimpleCacheManager&lt;/li&gt;
&lt;li&gt;EhCacheCacheManager&lt;/li&gt;
&lt;li&gt;CaffeineCacheManager .. 등등&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 요즘은 CaffeineCacheManager 가 EnCache 보다 성능이 좋다고하여 잘 쓰인다고 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Redis Cache&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레디스는 Cache Manager 로 굉장히 많이 사용됩니다.&lt;/li&gt;
&lt;li&gt;Cache 란 데이터를 더 빠르게 조회하기 위해, 한번 조회된 데이터를 임시 저장소에 저장한 후 &amp;rarr; 반복된 동일 조회 시 임시 저장소에서 해당 데이터를 빠르게 호출해오는 것을 의미합니다.&lt;/li&gt;
&lt;li&gt;레디스는 In-memory를 사용하는 서버이기 때문에, Cache 저장소로 충분히 사용할만큼 빠른 작업 속도를 가집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;0) Yaml&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 에서 Cache 용도로 Redis 를 쓸거라고 명시적으로 작성해줍니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;spring:
  cache:
    type: redis&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;1) Config&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;그 다음 RedisCacheManager 를 설정해줍니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {

        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(&quot;localhost&quot;, 6379));
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.create(connectionFactory);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RedisCacheManagerBuilder&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;혹은 RedisCacheManagerBuilder로 구성할 수 있으며, 이를 통해 기본 RedisCacheConfiguration, 트랜잭션 동작 및 미리 정의된 캐시를 설정할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {

        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(&quot;localhost&quot;, 6379));
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {

        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultCacheConfig())
            .withInitialCacheConfigurations(singletonMap(&quot;predefined&quot;, defaultCacheConfig().disableCachingNullValues()))
            .transactionAware()
            .build();

//        return RedisCacheManager.create(connectionFactory);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RedisCacheConfiguration&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RedisCacheManager로 생성된 RedisCache의 동작은 RedisCacheConfiguration으로 정의됩니다.&lt;/li&gt;
&lt;li&gt;이 구성을 사용하면 다음 예제와 같이 바이너리 스토리지 형식으로 변환하기 위한 키 만료 시간, 접두사 및 RedisSerializer 구현을 설정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이 외에 읽기 쓰기 잠금, 배치 전략, 만료시간 설정, 통계 등의 설정은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/#redis:support:cache-abstraction&quot;&gt;공식문서&lt;/a&gt;를 참고해주세요&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {

        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(&quot;localhost&quot;, 6379));
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            .disableCachingNullValues()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .entryTtl(Duration.ofMinutes(3L));


        return RedisCacheManager
            .RedisCacheManagerBuilder
            .fromConnectionFactory(connectionFactory)
            .cacheDefaults(redisCacheConfiguration)
            .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) 캐싱 사용하기&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spring 에서 제공하는 3가지 어노테이션으로 캐싱 작업을 설정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;@Cacheable&lt;/span&gt; : 메서드 호출 시, 메서드가 호출된 적 있는지를 판단하고 있으면 캐싱된 데이터를 리턴, 없으면 리턴값을 캐싱&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;@CachePut&lt;/span&gt; : 데이터를 캐시에 저장&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;@CacheEvict&lt;/span&gt; : 캐시 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style4&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;@Cacheable&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;호출 결과(리턴 값)을 캐시할 수 있음을 의미하는 주석입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;cacheName(value) 값을 기준으로 캐시를 저장합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;해당 메서드가 호출될 때 마다 캐싱 동작이 수행되며 해당 메서드가 이미 호출되어 있는지 여부를 확인하고 캐시 데이터를 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Cacheable(cacheNames = &quot;findTicketAll&quot;, key = &quot;#root.methodName&quot;, cacheManager = &quot;cacheManager&quot;)
public List&amp;lt;Ticket&amp;gt; getTickets() {
    return ticketRepository.findAll();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vUrPg/btsIinFEe06/Lc6Te7yszkTKsBxyHny3c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vUrPg/btsIinFEe06/Lc6Te7yszkTKsBxyHny3c1/img.png&quot; data-alt=&quot;5만개 조회 229ms&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vUrPg/btsIinFEe06/Lc6Te7yszkTKsBxyHny3c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvUrPg%2FbtsIinFEe06%2FLc6Te7yszkTKsBxyHny3c1%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;1684&quot; height=&quot;194&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5만개 조회 229ms&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi48KK/btsIhy2aMmY/Vtq62pKtXFIPU7JzRcOPkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi48KK/btsIhy2aMmY/Vtq62pKtXFIPU7JzRcOPkk/img.png&quot; data-alt=&quot;캐싱후 8ms&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi48KK/btsIhy2aMmY/Vtq62pKtXFIPU7JzRcOPkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi48KK%2FbtsIhy2aMmY%2FVtq62pKtXFIPU7JzRcOPkk%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;1694&quot; height=&quot;220&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;캐싱후 8ms&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1265&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tiigh/btsIksTsC0l/G8VXw9pnm04ih9cuok3xr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tiigh/btsIksTsC0l/G8VXw9pnm04ih9cuok3xr1/img.png&quot; data-alt=&quot;Redis 에 생성된 Key 와 Value&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tiigh/btsIksTsC0l/G8VXw9pnm04ih9cuok3xr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftiigh%2FbtsIksTsC0l%2FG8VXw9pnm04ih9cuok3xr1%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;102&quot; data-origin-width=&quot;1265&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redis 에 생성된 Key 와 Value&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;✔️&amp;nbsp; &lt;/b&gt;Optional Element&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;cacheName&lt;/b&gt; : 캐시 이름&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;value&lt;/b&gt; : cacheName의 alias (cacheName 을 쓰나, value 를 쓰나 저장되는 key 값은 같았습니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;key&lt;/b&gt; : 동적인 키 값을 사용하는 SpEL 표현식 (동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;condition&lt;/b&gt; : SpEL 표현식이 &lt;b&gt;참일 경우에만 캐싱&lt;/b&gt; 적용 (or, and 등 조건식, 논리연산 가능)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;unless&lt;/b&gt; : &lt;b&gt;캐싱을 막기 위해 사용&lt;/b&gt;되는 SpEL 표현식 (condition과 반대로 참일 경우에만 캐싱이 적용되지 않음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;cacheManager&lt;/b&gt; : 사용&amp;nbsp;할&amp;nbsp;CacheManager&amp;nbsp;지정&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sync&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;즉,&amp;nbsp;캐시&amp;nbsp;구현체가&amp;nbsp;Thread&amp;nbsp;safe&amp;nbsp;하지&amp;nbsp;않는&amp;nbsp;경우,&amp;nbsp;캐시에&amp;nbsp;동기화를&amp;nbsp;걸&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;속성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719840647235&quot; class=&quot;less&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Cacheable(cacheNames = &quot;platformTeamBooks&quot;, key = &quot;#root.target + #root.methodName + '_'+ #p0&quot;)
public Book getPlatformTeamBook(int bookId) { /* ... */}

@Cacheable(cacheNames = &quot;users&quot;, condition = &quot;#user.type == 'ADMIN'&quot;)
public Book selectUser(User user) { /* ... */}

@Cacheable(value=&quot;addresses&quot;, unless=&quot;#result.length()&amp;lt;64&quot;)
public String getAddress(Customer customer) {...}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://gngsn.tistory.com/157&quot;&gt;https://gngsn.tistory.com/157&lt;/a&gt; [ENFJ.dev:티스토리]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;@CacheEvict&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;메서드가 호출될 때 캐시에 있는 데이터가 삭제합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;✔️ 데이터가 변경됬을 시, 캐싱 된 데이터가 함께 변경되지 않기 때문에 보통 캐싱되었던 데이터를 삭제할 때 사용합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✔️&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;Optional Element&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;allEntries&lt;/b&gt; : 캐시 내의 모든 리소스를 삭제할지의 여부 (defalut : false)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;condition&lt;/b&gt; : SpEL 표현식이 참일 경우에만 삭제 진행 ( or, and 등 조건식, 논리연산 가능 )&lt;/li&gt;
&lt;li&gt;&lt;b&gt;beforeInvocation&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;true - 메서드 수행 이전 캐시 리소스 삭제&amp;nbsp;&lt;/li&gt;
&lt;li&gt;false&amp;nbsp;-&amp;nbsp;메서드&amp;nbsp;수행&amp;nbsp;후&amp;nbsp;캐시&amp;nbsp;리소스&amp;nbsp;삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@CacheEvict(cacheNames = &quot;findTicketAll&quot;, allEntries = true, cacheManager = &quot;cacheManager&quot;)
public void updateTicketQuantity(long ticketId) {

    Ticket ticket = ticketRepository.findById(ticketId).get();
    ticket.changeQuantity(-1);

    ticketRepository.save(ticket);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;@CachePut&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;캐시에 값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;저장하는 용도로만&lt;/b&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용합니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;@Cacheabler 과 다르게 데이터를 무조건 저장하며&lt;/span&gt;&amp;nbsp;항상 메소드의 로직을 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3) Redis Cache Hits 모니터링&lt;/h3&gt;
&lt;p style=&quot;color: #313131; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;  캐시의 성능을 측정하는 데 사용되는 몇 가지 용어가 있습니다. 이 용어들은 캐시의 효율성과 성능을 평가하고 측정하는 데에 도움이 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #313131; text-align: justify;&quot;&gt;Cache Hit (캐시 적중): 캐시에 데이터가 이미 존재하여 캐시를 통해 데이터 제공을 수행한 경우.&lt;/li&gt;
&lt;li style=&quot;color: #313131; text-align: justify;&quot;&gt;Cache Miss (캐시 누락): 접근하려는 데이터에 캐싱되어 있지 않은 상태. 이런 경우 원본 서버/스토리지를 통해 데이터를 조회가 이뤄진다.&lt;/li&gt;
&lt;li style=&quot;color: #313131; text-align: justify;&quot;&gt;Cache Hit Rate(캐시 적중률): 데이터를 캐시로 제공을 수행한 비율.&lt;/li&gt;
&lt;li style=&quot;color: #313131; text-align: justify;&quot;&gt;Cache Hit Ratio = Cache Hits Count / (Cache Hits Count + Cache Misses Count)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #313131; text-align: justify;&quot;&gt;캐싱 성능이 잘나온다는 건 &lt;b&gt;높은 Hit Ratio &amp;uarr;, 낮은 Miss Ratio &lt;span style=&quot;color: #313131; text-align: justify;&quot;&gt;&amp;darr;&lt;/span&gt;&lt;/b&gt; 를 말합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 모니터링 도구&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Redis stat&lt;/li&gt;
&lt;li&gt;grafana&lt;/li&gt;
&lt;li&gt;spring actuator&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 3가지 정도가 Redis Hit Ratio 를 모니터링하기 좋은 도구인 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 간단하게 Redis Server 의 자원 사용량에 대해 모니터링하기하는 방법으로는 '&lt;b&gt;INFO ALL&lt;/b&gt;' 명령어도 존재합니다&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;info all&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Redis Cache 에 대하여&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝..!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/cache.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/integration/cache.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>Cache</category>
      <category>cache manager</category>
      <category>java</category>
      <category>redis</category>
      <category>redis monitoring</category>
      <category>spring</category>
      <category>Spring Redis</category>
      <category>SpringBoot redis</category>
      <category>레디스 hit</category>
      <category>레디스 모니터링</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/482</guid>
      <comments>https://thalals.tistory.com/482#entry482comment</comments>
      <pubDate>Tue, 2 Jul 2024 23:30:06 +0900</pubDate>
    </item>
    <item>
      <title>Spring 에서 Redis 사용하기 (설정, In-memory DB, Transaction)</title>
      <link>https://thalals.tistory.com/481</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;본 포스팅은 다음과 같은 환경 아래에서 진행합니다. JDK 17, Spring Boot 3.x, Gradle&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Redis 묶음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/479&quot;&gt;Redis 란 (특징, 주의점, 동작 구조)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/481&quot;&gt;✔️ Spring 에서 Redis 사용하기 (설정, In-memory DB, Transaction)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/482&quot;&gt;Spring Boot 에서 Redis Cache 사용하기&lt;/a&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번 포스팅에서는 Spring 에서 제공하는 Spring Data Redis 를 사용하여 Redis 를 이용하는 방법에 대해 기록하고자 합니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대부분의 내용이 Spring Data Redis 공식문서 내용 정리입니다&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[목차]&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;의존성추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis 연결&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis Connection&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;RedisTemplate&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;RedisTemplate&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;StringRedisTemplate&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis Repository&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis Transaction&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Spring Data Redis Transaction 동작과정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis Transaction 파이프라인 직접 구현&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 의존성 추가&lt;/span&gt;&lt;/h2&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;//redis
implementation 'org.springframework.data:spring-data-redis'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;참고 : &lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis/3.3.1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis/3.3.1&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. Redis 연결&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis와 Spring을 사용할 때 가장 먼저 해야 할 작업 중 하나는 IoC 컨테이너를 통해 스토어에 연결하는 것입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Spring 공식문서에서 말하기를 &lt;b&gt;RedisConnection&lt;/b&gt; 및 &lt;b&gt;RedisConnectionFactory&lt;/b&gt; &lt;b&gt;인터페이스&lt;/b&gt;만 사용해야한다고 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;RedisConnection 객체는 RedisConnectionFactory를 통해 생성할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;RedisConnection 클래스는 thread-safety 하지 않다고 합니다.&lt;br /&gt;&amp;rarr; 기본 네이티브 연결(예: Lettuce의 StatefulRedisConnection)은 스레드에 안전할 수 있지만, Spring Data Redis의 LettuceConnection 클래스 자체는 그렇지 않습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;  따라서 Thread Safe 한 작업을 위해 권장하는 방법은, RedisTemplate 를 사용하는 것이라고 합니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #191e1e; text-align: start;&quot;&gt;[공식문서 발췌]&lt;/span&gt;&lt;span style=&quot;color: #191e1e; text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ebf2f2; color: #191e1e; text-align: start;&quot;&gt;If you need to share (stateful) Redis resources, like connections, across multiple Threads, for performance reasons or otherwise, then you should acquire the native connection and use the Redis client library (driver) API directly. Alternatively, you can use the&lt;/span&gt;RedisTemplate&lt;span style=&quot;background-color: #ebf2f2; color: #191e1e; text-align: start;&quot;&gt;, which acquires and manages connections for operations (and Redis commands) in a Thread-safe manner. See&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #000000; text-align: start;&quot; href=&quot;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/#redis:template&quot;&gt;documentation&lt;/a&gt;&lt;span style=&quot;background-color: #ebf2f2; color: #191e1e; text-align: start;&quot;&gt;&amp;nbsp;on&amp;nbsp;&lt;/span&gt;RedisTemplate&lt;span style=&quot;background-color: #ebf2f2; color: #191e1e; text-align: start;&quot;&gt;&amp;nbsp;for more details.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) Redis Connection 얻어오기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;자바의 Redis Client 라이브러리는&amp;nbsp;&lt;b&gt;Jedis&lt;/b&gt;와&amp;nbsp;&lt;b&gt;Lettuce&lt;/b&gt;가 있습니다.&lt;br /&gt;Spring 공식문서 자체에서 Lettuce 에 대한 언급이 많으므로 저는 Lettuce 를 사용해보았습니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; &lt;a style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; href=&quot;https://github.com/lettuce-io/lettuce-core&quot;&gt;Lettuce 는 패키지를 통해 Spring Data Redis가 지원하는&amp;nbsp;&lt;/a&gt;&lt;a style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; href=&quot;https://netty.io/&quot;&gt;Netty&lt;/a&gt;&amp;nbsp;기반 오픈 소스 커넥터&amp;nbsp;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {

        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(&quot;server&quot;, 6379));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;i&gt;gradle.build&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;lettuce 의존성 추가&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;//redis
implementation 'org.springframework.data:spring-data-redis'
implementation 'io.lettuce:lettuce-core'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;+ (참고 내용) Spring Data Redis 에서는 Redis 서버의 고가용성을 위해 Redis Sentinal 을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;i&gt;예시 - 참고(&lt;a href=&quot;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/#redis:sentinel&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719469493259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Lettuce
 */
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
  .master(&quot;mymaster&quot;)
  .sentinel(&quot;127.0.0.1&quot;, 26379)
  .sentinel(&quot;127.0.0.1&quot;, 26380);
  return new LettuceConnectionFactory(sentinelConfig);
}

/**
 * Jedis
 */
@Bean
public RedisConnectionFactory jedisConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
  .master(&quot;mymaster&quot;)
  .sentinel(&quot;127.0.0.1&quot;, 26379)
  .sentinel(&quot;127.0.0.1&quot;, 26380);
  return new JedisConnectionFactory(sentinelConfig);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) RedisTemplate 사용하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;i&gt;RedisConfig.class&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;RedisTemplate&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;RedisTemplate 는 한번 구성되면 스레드에 안전합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;매겨변수 &amp;lt;k, v&amp;gt;를 가집니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;K : 템플릿이 작동하는 Redis 키 유형(일반적으로 문자열)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;V : 템플릿이 작동하는 Redis 값 유형&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  프레임워크의 관점에서,&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;Redis에 저장되는 데이터는 &lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;byte[]로 직렬화하기 때문에&lt;br /&gt;&lt;/span&gt;Redis 저장시 &quot;key&quot; 와 &quot;value&quot;의 데이터를 문자열로 지정하기위해 직렬화 도구(StringRedisSerializer)를 별도로 지정해 주어야합니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;b&gt;StringRedisTemplate&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;보통 Redis Key에 저장되는 값의 타입은 '문자열'이 대부분이기 때문에 &lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;집중적인 String 연산을 위한 편리한 원스톱 솔루션으로&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;StringRedisConnection(및 그 DefaultStringRedisConnection 구현)과 StringRedisTemplate이라는 두 가지 확장을 제공한다고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;템플릿과 연결은 String 키에 바인딩될 뿐만 아니라 그 아래에 &lt;b&gt;StringRedisSerializer&lt;/b&gt;를 사용하므로 저장된 키와 값을 사람이 읽을 수 있습니다(Redis와 코드에서 동일한 인코딩이 사용된다고 가정할 때)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class RedisConfig {
	
	//민감정보 외부 주입을 위한 host, port 변수	
    @Value(&quot;${spring.redis.host}&quot;)
    private String host;

    @Value(&quot;${spring.redis.port}&quot;)
    private int port;
    
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {

        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port));
    }

    @Bean
    RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate&amp;lt;String, String&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        template.setConnectionFactory(redisConnectionFactory);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        
        return template;
    }
    
    @Bean
    StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 결론적으로 위와같이 설정했을 때 &lt;b&gt;RedisTemplate&lt;/b&gt; 와 &lt;b&gt;StringRedisTemplate&lt;/b&gt;는 동일한 인코딩 결과를 가져옵니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 선호에 따라 선택해서 사용하면.. 되겠져?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;✔️ RedisTemplate 예시&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;1. 레디스 템플릿은 사용하는 자료구조마다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&amp;nbsp;제공하는 메서드가 다르기 때문에 사용하고자 하는 데이터 타입 객체를 만들어 레디스의 자료구조 타입에 맞는 메소드를 사용할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #212529; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메서드 명&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;레디스 타입&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;opsForValue&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;String&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;opsForList&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;List&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;opsForSet&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Set&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;opsForZSet&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Sorted Set&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;opsForHash&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Hash&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class RedisRepository {

    private final RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate;

    public void forList() {
        redisTemplate.opsForList().leftPush(&quot;forList&quot;, &quot;1&quot;);
    }

    public void forSet() {
        redisTemplate.opsForSet().add(&quot;forSet&quot;, &quot;1&quot;);
    }

    public void forString() {
        redisTemplate.opsForValue().increment(&quot;forString&quot;, 1);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 혹은&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;특정 템플릿을 종속성으로 선언하고 템플릿을 삽입하여 사용하여 다음 예제와 같이 컨테이너가 자동으로 변환을 수행하여 opsFor[X] 호출을 제거할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class RedisRepository {

    private final RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate;
    
    // inject the template as ListOperations
    @Resource(name=&quot;redisTemplate&quot;)
    private ListOperations&amp;lt;String, String&amp;gt; listOps;

    public void forList() {
        listOps.leftPush(&quot;forList&quot;, &quot;1&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;✨ 레디스 명령어 모음 &amp;rarr; &lt;/b&gt;&lt;a href=&quot;https://redis.io/docs/latest/commands/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://redis.io/docs/latest/commands/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3) Redis Repository&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #191e1e; text-align: start;&quot;&gt;Redis Repository를 사용하여 &lt;b&gt;도메인 객체&lt;/b&gt;를 Redis Key에 매핑하는 방법도 존재합니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #191e1e; text-align: start;&quot;&gt;Redis &lt;span style=&quot;background-color: #ffffff; color: #191e1e; text-align: start;&quot;&gt;Repository는 사용하면,&amp;nbsp;&lt;/span&gt;Redis 해시에서 도메인 개체를 변환 및 저장하고, 사용자 지정 매핑 전략을 적용하고, 보조 인덱스를 사용할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #191e1e; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️ RedisRepository 는 Redis &lt;b&gt;Hash&lt;/b&gt; 자료구조를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 &lt;b&gt;@RedisHash&lt;/b&gt; 어노테이션과 &lt;b&gt;@Id&lt;/b&gt; (org.springframework.data.annotation.id) 를 함께 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@Id로 지정된 필드는 현재 값이 null인 경우 새 id를 생성하거나 이미 설정된 id 값을 재사용하고 &lt;b&gt;키스페이스:id&lt;/b&gt; 패턴의 키로 Redis 해시 내에 Domain 객체 유형의 프로퍼티를 저장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[✨ Redis CRUD Domain Object Example]&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;@RedisHash(value, timeToLive)&lt;/b&gt; &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- Domain Object를 Redis Hash 자료구조로 변환하는 방식, &lt;b&gt;value&lt;/b&gt;에 특정한 값을 넣어줌으로써, 데이터가 저장될 때 key의 prefix를 지정합니다. 또한&amp;nbsp;&lt;b&gt;timeToLive&lt;/b&gt; : 입력한 숫자만큼 &quot;초&quot; 단위로 유효기간을 지정합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;@Id&amp;nbsp;&lt;/b&gt;: Redis의 해시 키로 지정되는 값입니다. 실제 객체가 Redis 에 저장될 때 [키스페이스(위의 value값):id] 로 HSET, HHASH 의 Key 값으로 저장됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;@Indexed&amp;nbsp;&lt;/b&gt;: Secondary Indexes 의 역할을 하도록 지정합니다. hset 해당 값도 저장되어 빠른 탐색에 용이하게 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;@TimeToLive &lt;/b&gt;: 위의 timeToLive 옵션과 동일한 기능을 수행합니다. 단 &lt;b&gt;유닉스타임스템프&lt;/b&gt;로 지정해야합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

@RedisHash(value = &quot;ticket_count&quot;, timeToLive = 30)
@Getter
@ToString
public class TicketCount {

    @Id
    @Indexed
    private String id;
    @Indexed
    private String ticketName;
    private int count;
//    @TimeToLive
//    private long expire;

    public TicketCount(String ticketName, int count) {
        this.ticketName = ticketName;
        this.count = count;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[✨ Redis Repository Example]&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface RedisCrudRepository extends CrudRepository&amp;lt;TicketCount, String&amp;gt; {

    Optional&amp;lt;TicketCount&amp;gt; findByTicketName(String ticketName);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[✨ Test]&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@SpringBootTest
public class RedisTest {

    @Autowired
    RedisCrudRepository redisCrudRepository;

    @Test
    void test() {
        TicketCount ticketCount = new TicketCount(
            &quot;흠뻑쑈티켓&quot;,
            0
        );

        //create
        redisCrudRepository.save(ticketCount);

        //read
        String id = ticketCount.getId();
        System.out.println(id);
        System.out.println(redisCrudRepository.findByTicketName(ticketCount.getTicketName()));
        System.out.println(redisCrudRepository.findById(id));

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KRvVU/btsIgn6ZaKD/ZjDtyp6J2DxaVCshRijzK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KRvVU/btsIgn6ZaKD/ZjDtyp6J2DxaVCshRijzK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KRvVU/btsIgn6ZaKD/ZjDtyp6J2DxaVCshRijzK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKRvVU%2FbtsIgn6ZaKD%2FZjDtyp6J2DxaVCshRijzK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1924&quot; height=&quot;204&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o9vvB/btsIie8nrXT/2MD2VyrC5SoXqSBqT2S0aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o9vvB/btsIie8nrXT/2MD2VyrC5SoXqSBqT2S0aK/img.png&quot; data-alt=&quot;생성된 Redis Hash 구조 살펴보기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o9vvB/btsIie8nrXT/2MD2VyrC5SoXqSBqT2S0aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo9vvB%2FbtsIie8nrXT%2F2MD2VyrC5SoXqSBqT2S0aK%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;1770&quot; height=&quot;570&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생성된 Redis Hash 구조 살펴보기&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  단, &lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;Redis 리포지토리는 Redis Server 버전 2.8.0 이상이 필요하며 &lt;span style=&quot;color: #ee2323;&quot;&gt;트랜잭션과 함께 작동하지 않는다&lt;/span&gt;고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Sprign Transaction 동작에 Redis를 포함시키고 싶다면 RedisTemplate을 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. Redis Transaction&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기본적으로 RedisTemplate 또한 Spring Transaction 에 포함되지는 않는다고 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis 트랜잭션을 사용하려면 설정 시 &lt;b&gt;트랜잭션 지원을 명시적으로 활성화&lt;/b&gt;해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719567203793&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
	
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);              
    return template;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Spring Redis Transaction 동작과정&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트랜잭션 지원을 활성화하면 현재 스레드 로컬이 지원하는 트랜잭션에 RedisConnection이 바인딩됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트랜잭션이 오류 없이 완료되면 Redis 트랜잭션은 EXEC로 커밋되고, 그렇지 않으면 DISCARD로 롤백됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis 트랜잭션은 배치 지향이며 진행 중인 트랜잭션 중에 실행된 명령은 큐에 대기하고 있다가 트랜잭션을 커밋할 때만 적용됩니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 Spring Data Redis는 진행 중인 트랜잭션에서 읽기 전용 명령과 쓰기 명령을 구분합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;KEYS와 같은 읽기 전용 명령은 읽기를 허용하기 위해 새로운(스레드에 바인딩되지 않은) RedisConnection으로 파이프됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;쓰기 명령은 RedisTemplate에 의해 큐에 대기하고 커밋 시 적용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #191e1e; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Redis Transaction 직접 구현&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;혹은 아래와 같이 롤백 파이프라인을 직접 구현할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719567399772&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//execute a transaction
List&amp;lt;Object&amp;gt; txResults = redisTemplate.execute(new SessionCallback&amp;lt;List&amp;lt;Object&amp;gt;&amp;gt;() {
  public List&amp;lt;Object&amp;gt; execute(RedisOperations operations) throws DataAccessException {
    operations.multi();
    operations.opsForSet().add(&quot;key&quot;, &quot;value1&quot;);

    // This will contain the results of all operations in the transaction
    return operations.exec();
  }
});
System.out.println(&quot;Number of items added to set: &quot; + txResults.get(0));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다음 포스팅에서는 Redis Cache 를 사용해보겠습니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-07-01 오후 3.28.04.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn36my/btsIklNkok4/DvdQAG6RDWFv4OjkOGH931/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn36my/btsIklNkok4/DvdQAG6RDWFv4OjkOGH931/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn36my/btsIklNkok4/DvdQAG6RDWFv4OjkOGH931/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn36my%2FbtsIklNkok4%2FDvdQAG6RDWFv4OjkOGH931%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;300&quot; height=&quot;198&quot; data-filename=&quot;edited_스크린샷 2024-07-01 오후 3.28.04.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;* 참고&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;추가 살펴보면 좋을 내용&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;redis messaging pub/sub :&amp;nbsp;&lt;a href=&quot;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/#pubsub&quot;&gt;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/#pubsub&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Reactive Redis Config (non-blocking 연산) :&amp;nbsp;&lt;a href=&quot;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/#redis:support:cache-abstraction&quot;&gt;https://docs.spring.io/spring-data/redis/docs/3.1.6/reference/html/#redis:support:cache-abstraction&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>java</category>
      <category>redis</category>
      <category>spring</category>
      <category>Spring Redis</category>
      <category>레디스</category>
      <category>레디스 설정</category>
      <category>레디스연동</category>
      <category>스프링</category>
      <category>스프링 레디스</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/481</guid>
      <comments>https://thalals.tistory.com/481#entry481comment</comments>
      <pubDate>Mon, 1 Jul 2024 15:28:44 +0900</pubDate>
    </item>
    <item>
      <title>[한빛앤 MSA 세미나] 서비스 장애 잘 이해하고 대비하기 | 박순영</title>
      <link>https://thalals.tistory.com/480</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;한빌앤 MSA 세미나 2-7 : 볼트업 CTO 박순영 연사님의 &quot;서비스 장애 잘 이해하고 대비하기&quot; 오프라인 세미나를 듣고 정리한 글 입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 세미나 Keyword : Reliability&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;/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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&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;장애는 어떻게 잘 대응할 수 있을까?&lt;/li&gt;
&lt;li&gt;장애를 예방할 수 있을까?&lt;/li&gt;
&lt;li&gt;부록: 장애 대응의 2가지 사례&lt;/li&gt;
&lt;/ol&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCrzeg/btsH8UhZJUE/MQ7p1Ak8cA7xkhkUojpRR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCrzeg/btsH8UhZJUE/MQ7p1Ak8cA7xkhkUojpRR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCrzeg/btsH8UhZJUE/MQ7p1Ak8cA7xkhkUojpRR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCrzeg%2FbtsH8UhZJUE%2FMQ7p1Ak8cA7xkhkUojpRR0%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;300&quot; height=&quot;199&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(1) 장애의 정의 (어디까지 장애일까?)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  간단한 오류도 장애에 포함해야 할까? &amp;rarr; 장애를 나누는 기준&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 서비스 장애의 기준&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;# 민감도와 심각도&lt;/h4&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;사용자 범위
&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;&amp;rarr; 즉, 장애/오류를 경험하는 사용자 수를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;ex) 서비스는 돌아가지만 일부기능만 안된다. | 전체 기능이 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 서비스 장애의 범주&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;장애는 어느 수준까지 발생 할 수 있을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 서비스의 장애는 통제 가능 영역과 통제 불가능 영역으로 나눌 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sie5t/btsH5Y7uoRV/KcLxnykCTEOKYb3dmOc6nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sie5t/btsH5Y7uoRV/KcLxnykCTEOKYb3dmOc6nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sie5t/btsH5Y7uoRV/KcLxnykCTEOKYb3dmOc6nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsie5t%2FbtsH5Y7uoRV%2FKcLxnykCTEOKYb3dmOc6nK%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;458&quot; height=&quot;197&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 809px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 42.2093%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;통제 가능한 장애&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.3488%; height: 20px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;통제 불가능&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 42.2093%; height: 20px; text-align: center;&quot;&gt;기술적&lt;/td&gt;
&lt;td style=&quot;width: 30.3488%; height: 20px; text-align: center;&quot;&gt;인적&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%; height: 20px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 769px;&quot;&gt;
&lt;td style=&quot;width: 42.2093%; height: 769px;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;li&gt;아키텍처의 실패&lt;/li&gt;
&lt;li&gt;내/외부 시스템 오류 (결제, 메일, 외부 API)&lt;/li&gt;
&lt;li&gt;운영체제 오류&lt;/li&gt;
&lt;li&gt;최적화 실패&lt;/li&gt;
&lt;li&gt;취약점 유출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;li&gt;연결 실패&lt;/li&gt;
&lt;li&gt;Fallback 시스템 미흡&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;li&gt;서버 자원의 고장 및 손상 (디스크, 메모리 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;li&gt;공조시설 고장&lt;/li&gt;
&lt;li&gt;건물 손상 &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 30.3488%; height: 769px;&quot;&gt;&lt;br /&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;li&gt;해커의 침입&lt;/li&gt;
&lt;li&gt;바이러스 감염&lt;/li&gt;
&lt;li&gt;유출 사고 &lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 27.4419%; height: 769px;&quot;&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;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(2) 장애는 어떻게 잘 대응할 수 있을까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; 장애 대응의 원칙&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장애는 &amp;ldquo;&lt;b&gt;언제든 발생&lt;/b&gt;&amp;rdquo;할 수 있다.&lt;/li&gt;
&lt;li&gt;최대한 &amp;ldquo;&lt;b&gt;빠르게 감지&lt;/b&gt;&amp;rdquo;하고 &amp;ldquo;&lt;b&gt;빠르게 복구&lt;/b&gt;&amp;rdquo;하며 &amp;ldquo;&lt;b&gt;재발을 방지&lt;/b&gt;&amp;rdquo;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 빠르게 감지&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고객보다 먼저 감지하는 것&lt;/li&gt;
&lt;li&gt;장애의 영향도 파악 후 공유가 필요하다. &amp;rarr; 장애 징후 감지 후 심각도[장애 발생 시의 서비스 영향도] 파악 &amp;rarr; 유관부서에게 전파까지&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 빠르게 복구&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임시 대처라도 장애 영향을 줄이자 &lt;br /&gt;(&lt;i&gt;임시 대처로 인한 추가 장애 영역이 발생하지 않도록하자는 의미였던 것 같습니다.&lt;/i&gt;)&lt;/li&gt;
&lt;li&gt;잘못된 복구 대응이 되지 않도록 최소한의 장애 분석 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 재발 방지&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;ex) 트래픽 증가 &amp;rarr; 서버 다운 &amp;rarr; 로그 분석 &amp;rarr; 장애 지점 파악 &amp;rarr; 만약 redis 의 atomic 연산 부분의 비효울적인 사용이 원인이라면 &amp;rarr; redis 오픈소스 장애 분석&lt;/li&gt;
&lt;li&gt;재발방지를 위해 이런 식의 깊이있는 분석이 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가능한 완전한 솔루션 적용 (이상적인 이야기 )&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 일반적인 장애 대응 프로세스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감지 &amp;rarr; 전파 &amp;rarr; 판정 &amp;rarr; 복구 &amp;rarr; 분석 &amp;rarr; 공유 &amp;rarr; 회고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCAuXP/btsH5VQq1lZ/QBRh2cCk9AymRQNaQCzn2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCAuXP/btsH5VQq1lZ/QBRh2cCk9AymRQNaQCzn2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCAuXP/btsH5VQq1lZ/QBRh2cCk9AymRQNaQCzn2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCAuXP%2FbtsH5VQq1lZ%2FQBRh2cCk9AymRQNaQCzn2K%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;500&quot; height=&quot;230&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 시스템의 가시성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평소 &lt;span style=&quot;color: #ee2323;&quot;&gt;모니터링 시스템을 구축&lt;/span&gt;하여 원인 분석의 단서를 확보해 두는 것이 중요하다.&lt;/li&gt;
&lt;li&gt;빠른 장애대응을 위한 &lt;span style=&quot;color: #ee2323;&quot;&gt;시스템 가시성&lt;/span&gt;을 확보하자&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnPKle/btsH5v5KgUI/h83jYJQkU138P5JnotOGGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnPKle/btsH5v5KgUI/h83jYJQkU138P5JnotOGGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnPKle/btsH5v5KgUI/h83jYJQkU138P5JnotOGGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnPKle%2FbtsH5v5KgUI%2Fh83jYJQkU138P5JnotOGGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;345&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;시스템 가시성 확보의 순차적인 프로세스&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;1. 시스템

2. 모니터링 [APM, ELK, Prometheus 등]
    - 시스템 지표/로그
        - 인프라 장비 지표
        - 미들웨어 자체 로그
        - CPU/Memory 등의 사용량
        - 네트워크 지표

    -어플리케이션 지표/로그
        - 어플리케이션. 로그
        - JVM, 스레드 등의 지표
        - 처리 시간 등의 지표
        - 이슈 트래킹 : 비슷한 에러에 대한 그룹화..?

3. 전파 시스템
    - 에러 감지 시, 해당 담당개발자에게 전파가 되어야 함&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 장애 대응할 지점 파악하기 : 약한 고리부터 탐색하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ &lt;b&gt;최근 주요한 변화 찾기&lt;/b&gt; &amp;rarr; 롤백 증설?&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;구성 환경의 변화&lt;/li&gt;
&lt;li&gt;데이터(캐시 포함)의 변화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ &lt;b&gt;연관 취약점 탐색&lt;/b&gt; &amp;rarr; 복구 조치/ 회고&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;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 장애 회고와 피드백&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  장애 대응을 했다면 회고를 해야한다.&lt;br /&gt; &amp;nbsp;조치는 빠르게 해도, 분석은 low 레벨까지 해야한다.&lt;/blockquote&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;&amp;rarr; 임시 조치로 인한 복구시에는 완전 조치로까지 이루어져야 한다.&lt;/li&gt;
&lt;li&gt;&amp;rarr; 당시 모니터링 시스템에서 수집된 정보들로 최대한 하위 레벨까지 분석해야한다.&lt;br /&gt;&lt;br /&gt;&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;원인에 따라 고객이나 시스템에 대한 후속 조치가 필요하다. (CS or Data)&lt;/li&gt;
&lt;li&gt;정확한 원인 파악 후에는 시스템 개선을 진행한다.&lt;br /&gt;&lt;br /&gt;&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;장애 재발 대책과 조직 내 공유를 통한 학습과 고객 공지가 필수로 이루어져야 한다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(3) 장애를 예방할 수 있을까?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;성능 지표를 통한 모니터링환경을 구축하여 가시성 높은 시스템을 만들자&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 인프라 설치 및 관리하던 시스템 관리자로부터 출발하여 클라우드의 출현으로 서비스 안정성을 책임지는 엔지니어링의 발달&lt;br /&gt;2. 컴퓨팅 파워가 증가, 인프라와 클라우드 시스템의 발달, 다양한 에러상황의 생성 &amp;rarr; SRE 관련 측면이 중요해짐&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;darr;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SRE&lt;/b&gt;(&lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;Site Reliability Engineering, 사이트 신뢰성 엔지니어링&lt;/span&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;IT 운영에 대한 소프트웨어 엔지니어링 접근 방식입니다.&lt;/li&gt;
&lt;li&gt;SRE 팀은 소프트웨어를 툴로 활용하여 시스템을 관리하고, 문제를 해결하고, 운영 태스크를&amp;nbsp;자동화합니다.&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;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;시스템의 상태와 가용성 확인&lt;/li&gt;
&lt;li&gt;문제에 대한 긴급 대응&lt;/li&gt;
&lt;li&gt;변화의 추적과 관리&lt;/li&gt;
&lt;li&gt;서비스 트래픽의 수요 예측&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 주요 시스템 지표의 정의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가용성에 대한 지표: Usage (사용량)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Connections&lt;/li&gt;
&lt;li&gt;Usage, Utilization (Server, Pod, Pool, CPU, Memory)  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;응답 성능에 대한 지표 : Latency (지연성) - 응답 성능
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Write/Read IO&lt;/li&gt;
&lt;li&gt;Network Latency (각 구간별)&lt;/li&gt;
&lt;li&gt;Application Latency (각 시스템 별)&lt;/li&gt;
&lt;li&gt;Query &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오류에 대한 지표
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Error Rate (5xx, DeadLock, Exception Rate 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 가시성 있는 모니터링 시스템&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;오토스케일링 등을 사용하자 &lt;br /&gt;장애 전에 특정 지표를 활용하여 파악해 미리 예방할 수 있도록 하는게 중요&lt;/blockquote&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;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;특정 패턴 발생 또는 임계치 근접 시 사전 대응올 장애에 사전 조치가 가능하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장애 회고에 의한 학습으로 장애 사전 예방&lt;/li&gt;
&lt;li&gt;적절한 임계치 설정과 알람 설정으로 사전 대응으로 장애 예방&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 빠른 감지와 전파 시스템&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서도 말했지만, 가시성있는 모니터링 시스템으로 장애 징조를 파악했다면 &amp;rarr; 담당자에게 전파할 수 있는 시스템까지 구축해보자~~&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 신뢰성 있는 테스트와 품질관리&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1) 조직 내 테스트와 QA에 대한 적절한 체계가 수립되고 운영되어야 함&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 기능의 명확한 추적 및 릴리즈 노트 작성 (장애 파악에 용이)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&amp;nbsp; 단, 회사 사정과 규모에 따라서 어디까지 적용해야할지가 다르다.&lt;br /&gt;&amp;rarr; 하지만 ! &lt;span style=&quot;color: #ee2323;&quot;&gt;릴리즈 노트&lt;/span&gt;만은 작성하자~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 적절한 테스트 코드, 코드 품질을 유지하는 개발 문화&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;지나친 의존성 지양&lt;/li&gt;
&lt;li&gt;코드 리뷰를 통한 원칙 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Tolerance 가 높은 시스템 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 각 시스템 별 실패에 대한 고려로 장애 발생 시에도 서비스의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;가용성&lt;/b&gt;&lt;/span&gt; 최대화 하자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터베이스의 실패
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;rarr; &quot;데이터 베이스 이중화&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버간 통신의 실패
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;rarr; Fallback 메소드 구현 [하나의 서버에 너무 의존하지 않도록 하는 것 (이것도 서킷 브레이크(임시 응답, 트래픽 파킹)로 대응 가능)]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;급격한 트래픽의 대응 실패
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;rarr;&amp;nbsp; 파킹 서버, 서킷 브레이커&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;배포 실패
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;rarr;&amp;nbsp; 빠른 롤백, 블루그린 및 카나리 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인프라 실패
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;rarr;&amp;nbsp;각 인프라의 이중화, 임시 백업 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(04) 부록: 장애 대응의 2가지 사례&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;## 1. 티켓팅 시스템의 서비스 전면 장애 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;[문제 상황]&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유명하지 않았던 서비스의 티켓팅 이벤트를 오픈
&lt;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;li&gt;데이터베이스 CPU 점유율 상승&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;[장애 원인 파악]&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;티켓팅 해당 로직 장애 테스트 했음 &amp;rarr; 하지만 예상하지 못했던 회원가입이 몰림(유명하지 않았던 서비스였기 때문에)&lt;/li&gt;
&lt;li&gt;외부 메일 서버의 장애 였음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원 가입 시의 메일 처리 비동기 스레드 수 증가 &amp;rarr; 타임아웃이 너무 길고, 감지가 늦음&lt;/li&gt;
&lt;li&gt;&amp;nbsp;스레드 한계치 사용으로 서버 자원 부족&lt;/li&gt;
&lt;li&gt;요청 응답에 성능 저하 및 전면 장애&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;응모 등의 화면에서 데이터베이스 쓰기 증가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응모 시의 과도한 트래픽이 Write DB로 바로 접근
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Slave / Read DB만으로 대응 불가&lt;/li&gt;
&lt;li&gt;과도한 락 사용으로 점유 증가&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결국 서버를 올려도, 커넥션이 없어서 대기하다가 스레드 증가로 다시 다운 (서버 자원의 부족) &amp;rarr; 전면 장애&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;[임시 조치]&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원가입시 메일 인증을 잠시 스킵함&lt;/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 data-ke-size=&quot;size16&quot;&gt;[내가 느낀점] : 듣기만 해도 아찔하다,,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스트레스 테스트 중요하다.&lt;/li&gt;
&lt;li&gt;외부 써드 파트 API 부분에서 장애가 날 수 있다는 것을 열어 두자&lt;/li&gt;
&lt;li&gt;임시 조치라도, 그로인해 장애 다시 발생하지 않게 시간이 조금 더 걸리더라도 침착하게 대응하고 이후 완전 조치로 마무리하자&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;## 2. 클라이언트 앱의 HTTP 요청의 일부 사용자 요청 실패 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;[문제 상황]&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모바일 앱에서 서버로 데이터 요청 시 일부 &lt;b&gt;사용자들만 안됨&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;클라이언트의 요청 건수와 Web(Nginx, Ingress) 서버로 유입되는 실 커넥션 수가 다름&lt;/li&gt;
&lt;li&gt;일부 클라이언트에서는 요청 실패 오류의 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK2bfj/btsH65MhM43/B7IYcNVbJBomWeU4Sao7Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK2bfj/btsH65MhM43/B7IYcNVbJBomWeU4Sao7Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK2bfj/btsH65MhM43/B7IYcNVbJBomWeU4Sao7Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK2bfj%2FbtsH65MhM43%2FB7IYcNVbJBomWeU4Sao7Kk%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;261&quot; height=&quot;112&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;[원인 파악]&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일부 모바일 OS에서 쿠버네티스 버전 업데이트 작업에 따른 TLS 스펙 미지원&lt;/li&gt;
&lt;li&gt;인그레스에서 지원 TLS의 버전 변경이 가해짐&lt;/li&gt;
&lt;li&gt;과거 OS에서 TLS 암호화 미지원으로 오류 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAF7dg/btsH7h6Pjem/GMrsO4COiCiuAdxWIDvzb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAF7dg/btsH7h6Pjem/GMrsO4COiCiuAdxWIDvzb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAF7dg/btsH7h6Pjem/GMrsO4COiCiuAdxWIDvzb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAF7dg%2FbtsH7h6Pjem%2FGMrsO4COiCiuAdxWIDvzb1%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;331&quot; height=&quot;107&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정리 끝..!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(추가) 가볍게 정리한 Q&amp;amp;A 내용&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;1. 장애가 나면 당황하지 말자~&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 복구전에 복구한 데이터 때문에 추가적인 장애는 없는지 한번 더 확인하자&lt;/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;span style=&quot;background-color: #f6e199;&quot;&gt;2. B2c 중요 지표&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 CPU, Memory 20~30 % | 50% 넘어가면 증설 필요&lt;/li&gt;
&lt;li&gt;Db, Persitantcy 레이어 단의 지표&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;span style=&quot;background-color: #f6e199;&quot;&gt;3. 로그 잘남기는 방법&lt;/span&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;⭐️&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 요청 로그 당연 (요청 바디, 응답 로그) &amp;rarr; 단, 로그가 너무 커지기에 response 는 주로 유효기간을 두어 따로 빼서 남기는 것을 추천&lt;/li&gt;
&lt;li&gt;데이터 생성에 관련된 로그는 미들웨어 로그로 대응 가능(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;span style=&quot;background-color: #f6e199;&quot;&gt;장애 회고는 어떻게&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타임라인 : 원인&lt;/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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;한빛앤 MSA 세미나 (이전 세미나 링크) : &lt;a href=&quot;https://www.hanbitn.com/seminarwelcome/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.hanbitn.com/seminarwelcome/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;박순영 연사님 링크드인 : syparkme&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  공대생은 성장 중/세미나</category>
      <category>server 장애</category>
      <category>백엔드</category>
      <category>서버 장애 대응</category>
      <category>서버개발자</category>
      <category>장애 대응</category>
      <category>장애 대응 지표</category>
      <category>장애 시 보면 좋은 것</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/480</guid>
      <comments>https://thalals.tistory.com/480#entry480comment</comments>
      <pubDate>Fri, 21 Jun 2024 15:21:00 +0900</pubDate>
    </item>
    <item>
      <title>Redis 란 (특징, 주의점, 동작 구조)</title>
      <link>https://thalals.tistory.com/479</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt; Redis 묶음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://thalals.tistory.com/479&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;✔️ Redis 란 (특징, 주의점, 동작 구조)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thalals.tistory.com/481&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring 에서 Redis 사용하기 (설정, In-memory DB, Transaction)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thalals.tistory.com/482&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring Boot 에서 Redis Cache 사용하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[목차]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Redis 란&lt;/li&gt;
&lt;li&gt;Redis 특징&lt;/li&gt;
&lt;li&gt;Redis 사용시 주의사항&lt;/li&gt;
&lt;li&gt;Redis 동작 구조&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis 란 무엇일까&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Redis - Remote dictionary server (외부 딕셔너리[key : value] 구조의 서버)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis 는 Key, Value 의 구조를 가지는 비정형 데이터를 저장하고 관리하는 비 관계형 데이터베이스 관리 시스템(DBMS) 입니다.&lt;/li&gt;
&lt;li&gt;Redis 는 DataBase, Cache, Message Broker (Pub/Sub)으로 주로 사용됩니다.&lt;/li&gt;
&lt;li&gt;Redis 는 인 메모리 데이터 구조를 가진 저장소이기 때문에 하드디스크에 데이터를 저장하는 DBMS(Mysql 등등.) 보다 더 빠른 성능을 가집니다.&lt;/li&gt;
&lt;li&gt;Redis 는 인-메모리 데이터를 사용하기에 높은 데이터 휘발성을 가집니다. 따라서 서버 장애시 데이터 유실이 발생할 수 있기에 그에 대한 운영 플랜을 필요로 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;특징&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Remote dictionary server&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Key : Value 의 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Database, Cache, Message broker&lt;/li&gt;
&lt;li&gt;In-memory data Structure Store
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 상에 데이터를 저장하는 서버 (Main Memroy - DRAM)&lt;/li&gt;
&lt;li&gt;Database보다 &quot;더 빠른 Memory&quot;에 더 &quot;자주 접근&quot;하고 &quot;덜 자주 바뀌는&quot;&amp;nbsp; 데이터를 저장하자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Support rich data structure (&lt;a href=&quot;https://redis.io/redis-enterprise/data-structures/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Strings (key : value)&lt;/li&gt;
&lt;li&gt;Lists&lt;/li&gt;
&lt;li&gt;Set&lt;/li&gt;
&lt;li&gt;Sorted Set&lt;/li&gt;
&lt;li&gt;Hash&lt;/li&gt;
&lt;li&gt;Bitmaps, Bitfields (이진 논리와 상태를 저장, 비트맵 키 간에 AND, OR, XOR, NOT 연산 제공)&lt;/li&gt;
&lt;li&gt;HyperLogLog&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Geospatial indexes&lt;/li&gt;
&lt;li&gt;Streams&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Single Threaded
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis 는 기본적으로 Single Thread 구조를 가집니다.&lt;/li&gt;
&lt;li&gt;Redis 자료구조는 Atomic Critical Section 에 대한 동기화를 제공하기 때문에 Thread Safety 합니다.&lt;/li&gt;
&lt;li&gt;서로 다른 Transactinon 의 Read/Write 동기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis 사용 시 주의해야 할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. Redis 는 Single Thread 서버 이므로 시간 복잡도를 반드시 고려해야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업 처리의 시간 복잡도가 O(N) 인 자료구조의 사용은 지양해야한다고 합니다.&lt;/li&gt;
&lt;li&gt;ex) KEYS, Flush, GetAll&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQMIiR/btsH42noPJR/XGRU1EpHzVA6ruEDzuD3KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQMIiR/btsH42noPJR/XGRU1EpHzVA6ruEDzuD3KK/img.png&quot; data-alt=&quot;Redis 의 커멘드 처리 구조 - Single Thread&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQMIiR/btsH42noPJR/XGRU1EpHzVA6ruEDzuD3KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQMIiR%2FbtsH42noPJR%2FXGRU1EpHzVA6ruEDzuD3KK%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;561&quot; height=&quot;302&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redis 의 커멘드 처리 구조 - Single Thread&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. In-memory 특성상 메모리 단편화, 가상 메모리등의 이해가 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;단편화&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;단편화&lt;/span&gt;로인해 CPU에서는 실제 사용되는 Memory 총량보다 더 많은 메모리를 잡고있다고 생각할 수 있습니다.&lt;br /&gt;따라서 Redis 를 사용할때는 보다 여유있게 메모리 공간을 설정해야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Virtual Memory - Swap
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상 메모리를 사용한다면 하드 디스크에서 데이터를 가져올 때의 Context Swtiching 지연시간으로 인해 Single Thread 인 Redis 에 부담이 가해질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Replication - Fork
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis 는 휘발성을 가지고 있는 메모리상에 데이터를 저장하기 때문에 Replication(데이터 복사) 기능을 제공합니다.&lt;/li&gt;
&lt;li&gt;만약 메모리상의 데이터 저장공간이 가득 찼다면 Fork연산 중 Redis server 가 다운 될 수도 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFNMkg/btsH5kVIxta/rqzMDK6Pr8O41hmjrs7OsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFNMkg/btsH5kVIxta/rqzMDK6Pr8O41hmjrs7OsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFNMkg/btsH5kVIxta/rqzMDK6Pr8O41hmjrs7OsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFNMkg%2FbtsH5kVIxta%2FrqzMDK6Pr8O41hmjrs7OsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;263&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis 동작 구조&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Redis 는 정말 싱글스레드일까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 레디스는 &lt;b&gt;Event Loop(이벤트루프)&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;즉, 실제 명령에 대한 작업(Task)는 커널 I/O 레벨에서 Multiplexing(멀티플렉싱)을 통해 처리하여 동시성을 보장합니다.&lt;/li&gt;
&lt;li&gt;따라서, 유저 레벨에서는 싱글스레드로 동작하지만, 커널 I/O 레벨에서는 스레드풀을 이용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEahAI/btsH5yNdGCk/nkQKE0227Pup9ZNeQ1PYzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEahAI/btsH5yNdGCk/nkQKE0227Pup9ZNeQ1PYzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEahAI/btsH5yNdGCk/nkQKE0227Pup9ZNeQ1PYzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEahAI%2FbtsH5yNdGCk%2FnkQKE0227Pup9ZNeQ1PYzk%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;1444&quot; height=&quot;580&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;rarr; 즉, 클라이언트로 부터 전송된&amp;nbsp;&lt;/span&gt;&lt;b&gt;네트워크를 읽는 부분과 전송하는 부분&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;은&amp;nbsp;&lt;/span&gt;&lt;b&gt;Multi Thread&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;로 구현되어있으며,&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;rarr; redis에 요청한&amp;nbsp;&lt;/span&gt;&lt;b&gt;명령을 실행&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;하는 부분은&amp;nbsp;&lt;/span&gt;&lt;b&gt;Single thread&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;로 구현되어 있어 작업의 원자성(Atomic) 보장합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 멀티스레드 동작 지원은 v6 이후에 릴리즈 되었다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boN10V/btsH5C28XYg/KG4vy1aFUjy1NzjJ82nfP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boN10V/btsH5C28XYg/KG4vy1aFUjy1NzjJ82nfP1/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;405&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 34.7907%; margin-right: 10px;&quot; data-widthpercent=&quot;35.2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boN10V/btsH5C28XYg/KG4vy1aFUjy1NzjJ82nfP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboN10V%2FbtsH5C28XYg%2FKG4vy1aFUjy1NzjJ82nfP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVVlGM/btsH4Bxmw8j/GoRmComOQczHAXL5kWcLNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVVlGM/btsH4Bxmw8j/GoRmComOQczHAXL5kWcLNK/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;220&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 64.0465%;&quot; data-widthpercent=&quot;64.8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVVlGM/btsH4Bxmw8j/GoRmComOQczHAXL5kWcLNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVVlGM%2FbtsH4Bxmw8j%2FGoRmComOQczHAXL5kWcLNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(좌) Redis v4 (우) Redis v6&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;reids data structure : &lt;a href=&quot;https://redis.io/redis-enterprise/data-structures/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://redis.io/redis-enterprise/data-structures/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;redis가 싱글 스레드인 이유 : &lt;a href=&quot;https://akasai.space/redis/about_redis_2/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://akasai.space/redis/about_redis_2/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;redis 정말 싱글스레드 일가 : &lt;a href=&quot;https://velog.io/@hope1213/Redis-%EC%A0%95%EB%A7%90-%EC%8B%B1%EA%B8%80%EC%8A%A4%EB%A0%88%EB%93%9C%EC%9D%BC%EA%B9%8C&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@hope1213/Redis-%EC%A0%95%EB%A7%90-%EC%8B%B1%EA%B8%80%EC%8A%A4%EB%A0%88%EB%93%9C%EC%9D%BC%EA%B9%8C&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; 디디의 Redis : &lt;a href=&quot;https://youtu.be/Gimv7hroM8A?si=80Ml6RRa3caec4-c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/Gimv7hroM8A?si=80Ml6RRa3caec4-c&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DataBase/Redis</category>
      <category>redis</category>
      <category>redis 구조</category>
      <category>redis 동작 구조</category>
      <category>redis 원리</category>
      <category>레디스</category>
      <category>레디스 구조</category>
      <category>레디스 특징</category>
      <category>레디스란</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/479</guid>
      <comments>https://thalals.tistory.com/479#entry479comment</comments>
      <pubDate>Wed, 19 Jun 2024 15:36:34 +0900</pubDate>
    </item>
    <item>
      <title>@Transactional 동작과정 살펴보기 (with. Spring AOP)</title>
      <link>https://thalals.tistory.com/478</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이번 포스팅에서는 Spring 에서 제공해주는 @Transactional 어노테이션의 동작과정에 대한 공부한 것을 기록합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional 은 Spring 에서 제공해주는 어노테이션입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Transactional이 제공하는 기능은&amp;nbsp; 해당 어노테이션이 붙은 시점의 작업단위를 하나의 트랜잭션 단위로 묶어 관리할 수 있도록 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어노테이션은 단지 주석&lt;/b&gt;일 뿐이고, 실제로 런타임 시에 어떠한 일이 일어나 해당 어노테이션이 우리가 원하는 기능을 제공해주는지 일련의 과정을 살펴보고자 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 에서 어노테이션을 처리하는 방법이 몆가지 있다고 하는데, 이번 포스팅에서는 @Transactional 이 목표이기 때문에 해당 내용은 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;@Transactional 은 AOP 기반으로 처리됩니다.&lt;/u&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring AOP(Aspect Oriented Programming)란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ AOP : 관점 지향 프로그래밍&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 지향 프로그래밍(OOP)에서는 주요 관심사(역할, 책임,, 등) 에 따라 클래스를 분리합니다.&lt;/li&gt;
&lt;li&gt;이렇게 설계된 하나의 클래스는 SRP, 즉 하나의 책임만을 갖도록 설계되므로 프로그래밍적으로 필요한 부가기능(로그, 보안, 트랜잭션)들을 포함하는 로직을 부가기능으로 분리하고 다시 해당 관점으로 각각 모듈화하는 것을 AOP기법이라고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; Spring 에서는 Proxy 객체를 이용하여 몆가지 기능을 AOP 관점으로 지원하고 있습니다.&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 AOP에서 프록시 객체가 생성되는 방식에는 두 가지가 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;JDK 동적 프록시 (JDK Dynamic Proxy):&lt;/b&gt; 인터페이스 기반의 프록시를 생성합니다. 대상 빈이 하나 이상의 인터페이스를 구현하고 있는 경우, Spring은 JDK 동적 프록시를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CGLIB 프록시:&lt;/b&gt; 클래스 기반의 프록시를 생성합니다. 대상 빈이 인터페이스를 구현하고 있지 않거나, 특정 상황에서는 CGLIB을 사용하여 프록시를 생성합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  @Transactional 어노테이션 붙은 클래스도 스프링 컨테이너에서 빈이 초기화될 때 Proxy 객체가 생성되어 &lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;런타임 시,&lt;span&gt; 실제 호출될떄는 Proxy 객체를 호출하게 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oltv0/btsHZ9OJkR4/SjLVR0K9Krn9G9RJLS0AN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oltv0/btsHZ9OJkR4/SjLVR0K9Krn9G9RJLS0AN1/img.png&quot; data-alt=&quot;CGLIB으로 생성된 TicketService 프록시 객체&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oltv0/btsHZ9OJkR4/SjLVR0K9Krn9G9RJLS0AN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foltv0%2FbtsHZ9OJkR4%2FSjLVR0K9Krn9G9RJLS0AN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;76&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CGLIB으로 생성된 TicketService 프록시 객체&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Transactional 살펴보기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ch1Pvr/btsHYKBM9Id/xjoO2o7T8Bqv53F7xHOTA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ch1Pvr/btsHYKBM9Id/xjoO2o7T8Bqv53F7xHOTA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ch1Pvr/btsHYKBM9Id/xjoO2o7T8Bqv53F7xHOTA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fch1Pvr%2FbtsHYKBM9Id%2FxjoO2o7T8Bqv53F7xHOTA0%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;546&quot; height=&quot;144&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Traget : 해당 어노테이션을 사용할 대상 범위를 지정
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ElementType.Type : 클래스,&amp;nbsp;인터페이스(주석&amp;nbsp;인터페이스&amp;nbsp;포함),&amp;nbsp;열거형&amp;nbsp;또는&amp;nbsp;레코드&amp;nbsp;선언&lt;/li&gt;
&lt;li&gt;ElementType.MEHOD : 메소드&amp;nbsp;선언&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Retention : 해당 어노테이션의 정보 유지 범위 지정&lt;/li&gt;
&lt;li&gt;@Inherited : 부모 클래스에 해당 어노테이션이 적용되었다면 자식 클래스에게도 상속&lt;/li&gt;
&lt;li&gt;@Documented : 어노테이션에&amp;nbsp;대한&amp;nbsp;정보가&amp;nbsp;javadoc으로&amp;nbsp;작성한&amp;nbsp;문서에&amp;nbsp;포함되도록&amp;nbsp;하는&amp;nbsp;어노테이션&amp;nbsp;설정이다.&lt;/li&gt;
&lt;li&gt;@Reflective : 주석이 달린 요소에 리플렉션이 필요함을 의미합니다. 해당 주석이 달린 요소에 대한 proccessor 를 트리거하고, 기본적으로 해당 주석이달린 요소에 등록하거나 필요한 경우 검색하여 동작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;@Transactional 동작과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional 이 붙은 객체를 호출했을 때를 디버깅하면 아래와 같은 순서로 작업이 진행되는 걸 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-16 오후 5.15.12.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bglmcj/btsHZ56O06g/OROTk9LHnR5fBlqtNIkErk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bglmcj/btsHZ56O06g/OROTk9LHnR5fBlqtNIkErk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bglmcj/btsHZ56O06g/OROTk9LHnR5fBlqtNIkErk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbglmcj%2FbtsHZ56O06g%2FOROTk9LHnR5fBlqtNIkErk%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;750&quot; height=&quot;404&quot; data-filename=&quot;스크린샷 2024-06-16 오후 5.15.12.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@Transactional 이 붙은 타켓 객체(TicketService) 가 호출될 때 Target Object 를 상속한 Proxy 객체가 대신 호출된다.&lt;/li&gt;
&lt;li&gt;Cglib Proxy Interceptor 객체에서 Proxy 객체의 Target 객체의 인터셉터를 호출한다.&lt;/li&gt;
&lt;li&gt;Target 객체의 interceptor 객체 (Trasactionalnterceptor) 에서 구현된 부가기능 (Transaction) 로직을 수행한다.&lt;/li&gt;
&lt;li&gt;구현된 트랜잭션 로직안에서 타켓 객체의 비지니스로직을 수행하고, 정상적으로 처리되면 commit 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2068&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NNAxI/btsH1ccDPmK/qArBO4kzakZf0k2DJauxCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NNAxI/btsH1ccDPmK/qArBO4kzakZf0k2DJauxCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NNAxI/btsH1ccDPmK/qArBO4kzakZf0k2DJauxCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNNAxI%2FbtsH1ccDPmK%2FqArBO4kzakZf0k2DJauxCk%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;2068&quot; height=&quot;830&quot; data-origin-width=&quot;2068&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYtM09/btsHZJXjKFU/MqkLkIOKlKjHB5wPs8oHqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYtM09/btsHZJXjKFU/MqkLkIOKlKjHB5wPs8oHqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYtM09/btsHZJXjKFU/MqkLkIOKlKjHB5wPs8oHqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYtM09%2FbtsHZJXjKFU%2FMqkLkIOKlKjHB5wPs8oHqK%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;1836&quot; height=&quot;176&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@Transactional 붙은 객체는 스프링 컨체이너에서 빈이 초기화 될때 Proxy 객체가 생성된다.&lt;/li&gt;
&lt;li&gt;Proxy 객페는 target 객체가 interface 냐 class 냐에 따라 jdk or cglib 형식의 proxy 객체가 생성된다.&lt;/li&gt;
&lt;li&gt;client 가 target 객체를 호출하면, 상속(cglib) 혹은 구현(jdk proxy - DI)로 주입된 proxy 객체가 대신 호출된다.&lt;/li&gt;
&lt;li&gt;proxy 객체에서 부가기능가 주기능을 수행하고 클라이언트에게 응답한다.&lt;br /&gt;&lt;s&gt;&lt;i&gt;&lt;/i&gt;&lt;/s&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;&lt;i&gt;맞겠지..!??!&lt;/i&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NkgAY/btsH0bFP2Be/q43TPSlthBxuKuxPhNaEMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NkgAY/btsH0bFP2Be/q43TPSlthBxuKuxPhNaEMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NkgAY/btsH0bFP2Be/q43TPSlthBxuKuxPhNaEMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNkgAY%2FbtsH0bFP2Be%2Fq43TPSlthBxuKuxPhNaEMk%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;618&quot; height=&quot;396&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>@transaction 원리</category>
      <category>@transactional 동작</category>
      <category>@Transactional 원리</category>
      <category>aop 동작과정</category>
      <category>proxy 생성시점</category>
      <category>spring transaction 동작</category>
      <category>스프링AOP</category>
      <category>트랜잭션</category>
      <category>트랜잭션동작과정</category>
      <category>트랜잭션원리</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/478</guid>
      <comments>https://thalals.tistory.com/478#entry478comment</comments>
      <pubDate>Sun, 16 Jun 2024 17:40:11 +0900</pubDate>
    </item>
    <item>
      <title>OpenAPI 와 스웨거를 활용한 실전 API 설계 (feat. 요구사항으로 부터 도메인 모델링하기)</title>
      <link>https://thalals.tistory.com/477</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Designing APIs with Swagger and OpenAPI(OpenAPI 와 스웨거를 활용한 실전 API 설계) - 조시 포널랫, 루카스 로젠스톡 의 책을 읽고 특정 부분을 정리한 글입니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAPI를 더 유연하게 사용할 수 있는 방법을 기대한 책이었지만, 프로젝트를 전반적으로 진행하며 클라이언트와 서버간의 협업과정에서의 도메인 모델을 상세하게 구축해나가는 부분이 인상깊어 책의 전체 내용이 아닌 해당 부분에 대해서만 정리하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;체크포인트&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;해당 책에서는 [펫시터 구인구직 서비스] 라는 가상의 서비스를 주제로 설계해 나갑니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCsjDE/btsHrVvU2eo/2b9kZlbVqXelmEG3Z5IhOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCsjDE/btsHrVvU2eo/2b9kZlbVqXelmEG3Z5IhOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCsjDE/btsHrVvU2eo/2b9kZlbVqXelmEG3Z5IhOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCsjDE%2FbtsHrVvU2eo%2F2b9kZlbVqXelmEG3Z5IhOK%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;224&quot; height=&quot;284&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[목차]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;도메인 모델링과 API
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API 에 사용할 도메인 모델링&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&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;도메인 모델에 속성 추출하기&lt;/li&gt;
&lt;li&gt;사용자 스토리 작성하기&lt;/li&gt;
&lt;li&gt;사용자 스토리와 도메인 모델 매핑하기&lt;/li&gt;
&lt;li&gt;사용자 스토리에서 새로운 도메인 모델 추출하기&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 도메인 모델링과 API&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;모델은, 반려견 모델처럼 &lt;b&gt;하나의 개념에 대한 표현&lt;/b&gt;을 의미할 때도 있으며&lt;/li&gt;
&lt;li&gt;도메인 모델처럼 반려견 모델을 포함한 &lt;b&gt;영역 전체&lt;/b&gt;를 의미하기도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API 에 사용할 도메인 모델링&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비지니스로직에 밀접한 관계를 가지는 클래스나 관계형 데이터베이스 테이블은 대부분 내부 처리 과정에 적합한 상세 구현에 가깝습니다.&lt;/li&gt;
&lt;li&gt;하지만, API 는 애플리케이션의 뷰(View)계층에 더 가깝습니다. API는 시스템을 명확하게 구분하는 경계이고, 잠재적으로 내부 복잡성을 숨기는 추상 계층 역할을 수행하기도 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;API 설계자는 서버가 아닌 클라이언트 관점에서 API를 바라보아야 합니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 도메인 모델 추출하기&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) 모델에 사용되는 개념 추출하기 - 펫시터 도메인 모델&lt;/h4&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;사용자(User)&lt;/b&gt; 라는 모델이 필요합니다.&lt;/li&gt;
&lt;li&gt;반려동물 주인은 구인 공고를 등록하고, 돌봄 도우미는 구인 공고를 보고 지원하므로 &lt;b&gt;구인 공고(Job)&lt;/b&gt;이라는 모델이 필요합니다.&lt;/li&gt;
&lt;li&gt;구인 공고는 개를 다루는 내용이므로 &lt;b&gt;반려견(Dog)&lt;/b&gt; 모델이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcxROP/btsHmVXZjNZ/BCK1ayFAtbnYEeRc3OPnb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcxROP/btsHmVXZjNZ/BCK1ayFAtbnYEeRc3OPnb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcxROP/btsHmVXZjNZ/BCK1ayFAtbnYEeRc3OPnb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcxROP%2FbtsHmVXZjNZ%2FBCK1ayFAtbnYEeRc3OPnb1%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;500&quot; height=&quot;285&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(2) 도메인 모델에 속성 추출하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;User&amp;nbsp;&lt;/b&gt;- (id, email, password, name, role)&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;Role&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;그 외의 사용자에게 필요한 속성으로 &quot;&lt;b&gt;이메일 주소, 비밀번호, 이름&lt;/b&gt;&quot; 등의 공통적인 속성을 예상할 수 있습니다.&lt;/li&gt;
&lt;li&gt;백엔드 개발자로써, 도메인을 바라보았을 때 &quot;&lt;b&gt;변경되지 않을 식별자가 필요합니다.&lt;/b&gt;&quot; 이메일, 주소, 이름은 변경될 가능성이 있으므로 ID 속성을 추가합니다.&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;b&gt;Job 과 Dog&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; User&lt;/b&gt;는 흔한 도메인이라 비교적 쉽게 속성을 유추할 수 있었지만, 펫시터 서비스만의 도메인인 &lt;b&gt;구인공고(Job)과 반려견(Dog)&lt;/b&gt;은 서비스에 필요한 속성을 도출해 내야합니다. (현실과 다른 소프트웨어세계 안에서의 도메인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr;&lt;/b&gt; 책에서는 &lt;u&gt;브레인 스토밍&lt;/u&gt;을 이용해 &lt;b&gt;사용자 입장에서 모델을 바라보도록하여&lt;/b&gt; 공통 질문을 추출합니다.&amp;nbsp;&lt;br /&gt;(&quot;당신에게 내 개를 돌봐달라고 요청했는데, 당신은 내 개를 처음보는 상황이라면 무엇을 알고 싶을까요?&quot; &lt;b&gt;&amp;rarr;&amp;nbsp;&lt;/b&gt;서비스의 핵심 비지니스)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;돌봄은 &lt;u&gt;언제 시작&lt;/u&gt;해서 &lt;u&gt;얼마 동안&lt;/u&gt; 해야 되는가?&lt;/li&gt;
&lt;li&gt;돌봄에는 &lt;u&gt;어떤 일&lt;/u&gt;이 포함되는가? 산책, 집 안에서 돌보기, 또는 집 밖에서도 돌봐야 하는가?&lt;/li&gt;
&lt;li&gt;반려견 &lt;u&gt;이름&lt;/u&gt;과 &lt;u&gt;품종&lt;/u&gt; 등 &lt;u&gt;기본 정보&lt;/u&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; 속성 추출&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Job (구인 공고)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Dog (반려견)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&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;종료 시간&lt;/li&gt;
&lt;li&gt;활동&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&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;나이&lt;/li&gt;
&lt;li&gt;품종&lt;/li&gt;
&lt;li&gt;크기&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diL3A4/btsHnsVxe78/o0kJi4644x16DmI4JfT2ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diL3A4/btsHnsVxe78/o0kJi4644x16DmI4JfT2ZK/img.png&quot; data-alt=&quot;도메인 모델에 속성 추출&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diL3A4/btsHnsVxe78/o0kJi4644x16DmI4JfT2ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiL3A4%2FbtsHnsVxe78%2Fo0kJi4644x16DmI4JfT2ZK%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;500&quot; height=&quot;224&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도메인 모델에 속성 추출&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(3) 사용자 스토리 작성하기&amp;nbsp;&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;모델과 모델이 취할 수 있는 행위&lt;/b&gt;, 또는 &lt;b&gt;모델에 가해지는 행위 사이의 연결&lt;/b&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 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;사용자 스토리는 소프트웨어 제품의 사용자 관점에서 작성하며, 소프트웨어 안에서 무언가를 달성하기 위해 해야하는 활동을 기술하는 &amp;rarr; 요구사항을 분석하는 데 사용하는 비형식적인 프로젝트 관리 방법&lt;/li&gt;
&lt;/ul&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;나는 &lt;b&gt;&amp;lt;역할&amp;gt;&lt;/b&gt; 로서 &lt;b&gt;&amp;lt;기능&amp;gt;&lt;/b&gt;을 할 수 있고, 그 결과 &lt;b&gt;&amp;lt;보상&amp;gt;&lt;/b&gt;을 받는다. (&quot;그 결과 ~ 보상은&quot; 필수는 아닙니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;lt;사전 조건&amp;gt;&lt;/b&gt;이 주어지면 나는 &lt;b&gt;&amp;lt;기능&amp;gt;&lt;/b&gt;을 할 수 있다.&lt;/li&gt;
&lt;li&gt;ex)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;반려동물 주인&lt;/b&gt;으로서 나는 &lt;b&gt;구인 공고를 올릴 수 있고&lt;/b&gt;, 그 결과 휴일에 자유로운 시간을 보 낼 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구인 공고가 올려졌으므로&lt;/b&gt; 구인 공고 &lt;b&gt;상태를 확인&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가. 반려동물 주인(사용자 스토리)&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;나는 역할을 선택해서 새 사용자를 등록할 수 있고, 그 결과 로그인할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 로그인할 수 있고, 그 결과 마켓플레이스를 이용할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 펫시터에 내 반려견에 대한 설명을 포함하는 구인 공고를 올릴 수 있고, 그 결과 반려동물 돌봄 도우미가 구인 공고를 보고 지원할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 내가 등록한 구인 공고 목록을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;구인 공고를 등록하면 상세 내용을 조회하고 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;구인 공고를 등록하면 등록한 구인 공고를 삭제할 수 있다.&lt;/li&gt;
&lt;li&gt;구인 공고를 등록하면 구인 공고에 지원한 돌봄 도우미를 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;적절한 지원자가 있다면 승인할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 내 계정 정보를 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 내 계정을 삭제할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;나. 돌봄 도우미 (사용자 스토리)&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;나는 역할을 선택해서 새 사용자를 등록할 수 있고, 그 결과 로그인할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 로그인을 할 수 있고, 그 결과 마켓플레이스를(펫시터 서비스) 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 돌봄이 필요한 반려동물 목록을 조회할 수 있다.&lt;/li&gt;
&lt;li&gt;마음에 드는 구인 공고가 있으면 지원할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 내 계정 상세 정보를 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 내 계정을 삭제할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다. 관리자 (사용자 스토리)&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;나는 로그인을 할 수 있고, 그 결과 어드민 기능을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 내 계정 상세 정보를 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 다른 사용자의 계정 상세 정보를 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 다른 사용자가 등록한 구인 공고를 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;나는 내 계정을 삭제할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;(4) 사용자 스토리와 도메인 모델 매핑하기&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;✔️ 이제 위에서 추출한 유저, 구인 공고, 반려견 - &lt;b&gt;3개의 모델과 추출된 사용자 스토리의 행위와 관계를 매핑&lt;/b&gt;해봅니다.&lt;br /&gt;✔️ 먼저 여러 모델에 걸쳐 동일하거나 비슷한 내용을 담고 있는 사용자 스토리를 점검합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;나는 역할을 선택해서 새 사용자를 등록할 수 있다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반려동물 주인, 돌봄 도우미에게 공통적으로 해당 됩니다.&lt;/li&gt;
&lt;li&gt;사용자는 역할과 무관하게 사용자 등록을 해야합니다.&lt;/li&gt;
&lt;li&gt;따라서 &lt;b&gt;사용자 모델&lt;/b&gt;에 필요한 &lt;b&gt;Register(사용자 등록) 행위&lt;/b&gt;를 도출할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;나는 내 계정으로 로그인할 수 있다.&lt;/blockquote&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;b&gt;사용자 모델&lt;/b&gt;에 &lt;b&gt;Login(로그인) 행위&lt;/b&gt;를 추가합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;나는 내 계정을 삭제할 수 있다, 나는 내 계정을 삭제할 수 있다.&lt;/blockquote&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;b&gt;Modify(수정) 행위&lt;/b&gt;를 추가합니다.&lt;/li&gt;
&lt;li&gt;또한, 상세정보를 수정하기위해서는 조회를 해야하므로 &lt;b&gt;View(조회) 행위&lt;/b&gt;도 추가합니다.&lt;/li&gt;
&lt;li&gt;마찬가지로 &lt;b&gt;Delete(삭제) 행위&lt;/b&gt;를 추가해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반려동물 주인 (구인 공고)&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;나는 펫시터에 내 반려견에 대한 설명을 포함하는 구인 공고를 올릴 수 있다.&lt;/blockquote&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;사용자 스토리에 따르면, &quot;반려견 정보를 포함하는 구인 공고를 게시하는 일&quot; 은 하나의 단계로 처리됩니다.&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;구인 공고는 오직 한 마리의 반려견에 대한 정보만 포함하고, 모든 반려견은 오직 하나의 구인 공고에만 포하모디므로 둘은 일대일 매핑 관계입니다. (현실과는 다른 소프트웨어 상에서, 단순화를 위해 1:1 매핑으로 가정)&lt;/li&gt;
&lt;li&gt;위에서 도출된 약속(요구사항)을 기반으로 &amp;rarr; 반려견 설명이 구인 공고에 포함되므로, 실제로는 동일한 반려견에 대한 정보라고 할지라도 서로 다른 구인 공고에 포함된 반려견 정보는 다른 반려견 정보로 인식됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWZa9T/btsHmG70IiC/ng0qDKXie3D53jNbmTl1a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWZa9T/btsHmG70IiC/ng0qDKXie3D53jNbmTl1a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWZa9T/btsHmG70IiC/ng0qDKXie3D53jNbmTl1a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWZa9T%2FbtsHmG70IiC%2Fng0qDKXie3D53jNbmTl1a0%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;158&quot; height=&quot;441&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;나는 내가 등록한 구인 공고 목록을 확인할 수 있다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반려동물 주인이 등록한 구인 공고 목록을 조회할 수 있어야 하므로 구인 공고 모델에 List my own(내 공고 목록 조회) 행위를 추가합니다.&lt;/li&gt;
&lt;li&gt;이를 위해서, 구인공고를 생성한 반려동물 주인이 어떤 사용자인지 알아야 합니다. (User 와 Job의 관계)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yYwcX/btsHo2PfdnV/tEkkHfdvsxHCUDKkQ9KnKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yYwcX/btsHo2PfdnV/tEkkHfdvsxHCUDKkQ9KnKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yYwcX/btsHo2PfdnV/tEkkHfdvsxHCUDKkQ9KnKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyYwcX%2FbtsHo2PfdnV%2FtEkkHfdvsxHCUDKkQ9KnKK%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;300&quot; height=&quot;482&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;(5) 사용자 스토리에서 새로운 도메인 모델 추출하기&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;구인 공고를 등록하면 상세 내용을 조회, 수정, 삭제, 지원한 돌봄 도우미를 확인할 수 있다.&lt;/blockquote&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;  하지만, '지원 목록 조회' 에 대한 행위는 Job 과 User 둘 모두에게 관련이 있을 수도 있습니다.&lt;/li&gt;
&lt;li&gt;  또한 '지원' 이라는 &lt;span style=&quot;color: #ee2323;&quot;&gt;새로운 행위가 등장&lt;/span&gt;했고, 이 &lt;span style=&quot;color: #ee2323;&quot;&gt;행위에 새로운 이름을 붙여야 한다면 도메인 모델에 새로운 개념을 도입할 필요&lt;/span&gt;가 있는 징조라고 볼 수도 있습니다.&lt;/li&gt;
&lt;li&gt;&amp;rarr; 책에서는,&lt;b&gt; &quot;구인공고 지원&quot;&lt;/b&gt; 이라는 모델을 새롭게 생성하여 두 도메인 모델과 연결합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/td3x7/btsHpxujKLZ/tnUnrP65ai9Skzt0jfw3rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/td3x7/btsHpxujKLZ/tnUnrP65ai9Skzt0jfw3rK/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;506&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 42.5895%; margin-right: 10px;&quot; data-widthpercent=&quot;43.09&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/td3x7/btsHpxujKLZ/tnUnrP65ai9Skzt0jfw3rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftd3x7%2FbtsHpxujKLZ%2FtnUnrP65ai9Skzt0jfw3rK%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;300&quot; height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU44ea/btsHn5lodJo/hbr8ILf3fV3EAsu3daFup1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU44ea/btsHn5lodJo/hbr8ILf3fV3EAsu3daFup1/img.png&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;1484&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.2477%;&quot; data-widthpercent=&quot;56.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU44ea/btsHn5lodJo/hbr8ILf3fV3EAsu3daFup1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU44ea%2FbtsHn5lodJo%2Fhbr8ILf3fV3EAsu3daFup1%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;1162&quot; height=&quot;1484&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;새로운 도메인 모델 추출&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구인공고 지원&lt;/b&gt;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;적절한 지원자가 있다면 승인할 수 있다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구인공고 지원에 대하여 &lt;b&gt;승인하다(Approve)&lt;/b&gt;라는 행위를 추가해줍니다.&lt;/li&gt;
&lt;li&gt;처음에 존재하지 않았던 구인공고 지원 모델이 생겼고&lt;b&gt;, 행위에 대한 결과&lt;/b&gt;를 알 수 있어야 하므로 &lt;b&gt;상태 속성과 ID 속성&lt;/b&gt;을 추가해줍니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;나는 마음에 드는 구인 공고가 있으면 지원할 수 있다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구인공고 지원이 하나의 모델이 되었으므로, 사용자가 구인공고지원을 했을때, 구인공고 지원이 생성되어야합니다. (Create)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq6WXY/btsHnuskKQ8/nUuV0OoKj7vXCqNd3tfNe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq6WXY/btsHnuskKQ8/nUuV0OoKj7vXCqNd3tfNe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq6WXY/btsHnuskKQ8/nUuV0OoKj7vXCqNd3tfNe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq6WXY%2FbtsHnuskKQ8%2FnUuV0OoKj7vXCqNd3tfNe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;663&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 책에서는 변경되는 요구사항에 맞춰, 반려견 &amp;rarr; 반려동물로 변경하기, 그에따른 사용자 스토리 검토, 서브타이핑(다형성)으로 도메인 모델 구축하기 등의 과정이 이루어지지만, 생략하겠습니다 ㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 끝!&lt;/p&gt;</description>
      <category>  개발자 책 읽기/독후감</category>
      <category>개발자 책</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/477</guid>
      <comments>https://thalals.tistory.com/477#entry477comment</comments>
      <pubDate>Thu, 16 May 2024 20:58:39 +0900</pubDate>
    </item>
    <item>
      <title>클래스 다이어그램 정리</title>
      <link>https://thalals.tistory.com/476</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최범균님의 - UML 기초 01 - 클래스 다이어그램 유튜브 강의를 정리한 글입니다.&lt;br /&gt;link : &lt;a href=&quot;https://www.youtube.com/watch?v=HG0dwNnTsII&amp;amp;list=PLwouWTPuIjUgd-1167R5%5C_6gTBsp%5C_9cfu3&quot;&gt;https://www.youtube.com/watch?v=HG0dwNnTsII&amp;amp;list=PLwouWTPuIjUgd-1167R5\_6gTBsp\_9cfu3&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 클래스 다이어그램이란&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 타입을 표현&lt;/li&gt;
&lt;li&gt;타입 간의 정적 관계 기술&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;전달하고자 하는 내용 위주로 표시&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반드시 모든 것을 다 표시하는 것이 아님&lt;/li&gt;
&lt;li&gt;UML로 완벽하게 표현할 수도 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0cHJF/btsG5DaHjN8/PkZyauCS3FLzjlqN9WAlsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0cHJF/btsG5DaHjN8/PkZyauCS3FLzjlqN9WAlsk/img.png&quot; data-alt=&quot;클래스 다이어그램 구성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0cHJF/btsG5DaHjN8/PkZyauCS3FLzjlqN9WAlsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0cHJF%2FbtsG5DaHjN8%2FPkZyauCS3FLzjlqN9WAlsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;349&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;클래스 다이어그램 구성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 클래스 다이어그램 구조&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 이름&lt;/li&gt;
&lt;li&gt;속성&lt;/li&gt;
&lt;li&gt;오퍼레이션&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 속성 표기법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가시성 이름 : 타입 = 기본값&lt;/li&gt;
&lt;li&gt;가시성 : &lt;b&gt;+&lt;/b&gt;(public), &lt;b&gt;-&lt;/b&gt;(private), &lt;b&gt;#&lt;/b&gt;(protected), &lt;b&gt;~&lt;/b&gt;(package)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baWyS6/btsG2pZxZtD/vdVsgCYnAVU0z8tZnOkLL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baWyS6/btsG2pZxZtD/vdVsgCYnAVU0z8tZnOkLL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baWyS6/btsG2pZxZtD/vdVsgCYnAVU0z8tZnOkLL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaWyS6%2FbtsG2pZxZtD%2FvdVsgCYnAVU0z8tZnOkLL0%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;300&quot; height=&quot;156&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 오퍼레이션 표기법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가시성 이름 (매개변수 목록) : 리턴타입&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvTFMw/btsG2ngq6sp/05URAOxcpaiA2SvfQyk0CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvTFMw/btsG2ngq6sp/05URAOxcpaiA2SvfQyk0CK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvTFMw/btsG2ngq6sp/05URAOxcpaiA2SvfQyk0CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvTFMw%2FbtsG2ngq6sp%2F05URAOxcpaiA2SvfQyk0CK%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;300&quot; height=&quot;81&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 연관관계 (association)&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;실선 : 연관을 가짐&lt;/li&gt;
&lt;li&gt;방향 : 연관 방향&lt;/li&gt;
&lt;li&gt;이름 : 연관 이름&lt;/li&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbncy0/btsG1wkiwVc/kxhzXYQTFgi3DdA5t38st0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbncy0/btsG1wkiwVc/kxhzXYQTFgi3DdA5t38st0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbncy0/btsG1wkiwVc/kxhzXYQTFgi3DdA5t38st0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbncy0%2FbtsG1wkiwVc%2FkxhzXYQTFgi3DdA5t38st0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;287&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 연관 탐색 (Navigation)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실선에 화살표를 이용해서 탐색 가능 방향 지정&lt;/li&gt;
&lt;li&gt;양쪽에 모두 지정하지 않으면 양방향 탐색 의미 (보통은 둘다 표시하지 않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oR25Y/btsG3BZry5l/t3Z59nYKEHfOeVBie8LW7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oR25Y/btsG3BZry5l/t3Z59nYKEHfOeVBie8LW7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oR25Y/btsG3BZry5l/t3Z59nYKEHfOeVBie8LW7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoR25Y%2FbtsG3BZry5l%2Ft3Z59nYKEHfOeVBie8LW7k%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;500&quot; height=&quot;81&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ex)&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;order 객체가 orderLine 객체를 접근할 수 있다.&lt;/li&gt;
&lt;li&gt;orderLine은 order 객체에 접근할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 의존 (Dependency)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 사용 관계를 표현&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A가 변할 때 A를 사용하는 B도 변한다면 &amp;rarr; B가 A에 의존&lt;/li&gt;
&lt;li&gt;점선으로 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDNHhk/btsG2WCZ9si/2tKqS2WDmS1pQZymmnsksK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDNHhk/btsG2WCZ9si/2tKqS2WDmS1pQZymmnsksK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDNHhk/btsG2WCZ9si/2tKqS2WDmS1pQZymmnsksK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDNHhk%2FbtsG2WCZ9si%2F2tKqS2WDmS1pQZymmnsksK%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;300&quot; height=&quot;133&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ex)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Controller 클래스가 Handler 클래스에 의존한다.&lt;/li&gt;
&lt;li&gt;Controller 클래스의 render( ) 메소드는 Template 클래스에 의존한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 인터페이스, 추상 클래스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스 : 꺽쇠 2개(스테레오 타입)로 인테페이스임을 알림&amp;nbsp;&lt;/li&gt;
&lt;li&gt;추상 클래스 : 클래스 이름이 Italic 채 (기울기) 인 것으로 표현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현 : 점선&lt;/li&gt;
&lt;li&gt;일반화,상속 : 실선&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOmVbx/btsG3ATLRux/7ArKLQN2LJR1jBVRMqbEj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOmVbx/btsG3ATLRux/7ArKLQN2LJR1jBVRMqbEj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOmVbx/btsG3ATLRux/7ArKLQN2LJR1jBVRMqbEj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOmVbx%2FbtsG3ATLRux%2F7ArKLQN2LJR1jBVRMqbEj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;333&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) aggreagtion (소유), compostion (구성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ composition : 조금더 강력한 소유를 뜻함 (검은 마름모)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OrderLine 의 여러 객체는 Order 가 강력한 소유 (compostion)&lt;/li&gt;
&lt;li&gt;Order 가 삭제될 떄 모든 OrderLine이 삭제됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;✔️&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;aggreagtion : 조금 더 약한관계&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Player 는 여러 Team 에 공유될 수 있음 (약한 관계)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsMU2S/btsG3m2xRFY/oZaYY8ISaY0oLxmkRPkOn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsMU2S/btsG3m2xRFY/oZaYY8ISaY0oLxmkRPkOn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsMU2S/btsG3m2xRFY/oZaYY8ISaY0oLxmkRPkOn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsMU2S%2FbtsG3m2xRFY%2FoZaYY8ISaY0oLxmkRPkOn1%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;500&quot; height=&quot;260&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습..!&lt;br /&gt;혹여 틀린점이 있다면 댓글로 알려주시면 감사드리겠습니다 :)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2166&quot; data-origin-height=&quot;1578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9Sgna/btsG3LOn2Mc/yIMqPbPxkVKrhrakdz0mAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9Sgna/btsG3LOn2Mc/yIMqPbPxkVKrhrakdz0mAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9Sgna/btsG3LOn2Mc/yIMqPbPxkVKrhrakdz0mAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9Sgna%2FbtsG3LOn2Mc%2FyIMqPbPxkVKrhrakdz0mAk%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;2166&quot; height=&quot;1578&quot; data-origin-width=&quot;2166&quot; data-origin-height=&quot;1578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  공대생은 성장 중/일잘하기</category>
      <category>UML</category>
      <category>백엔드</category>
      <category>클래스 다이어그램</category>
      <category>클래스 다이어그램 사용법</category>
      <category>클래스다이어그램</category>
      <category>클래스다이어그램정리</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/476</guid>
      <comments>https://thalals.tistory.com/476#entry476comment</comments>
      <pubDate>Tue, 30 Apr 2024 17:16:25 +0900</pubDate>
    </item>
    <item>
      <title>객체지향의 사실과 오해 - 역할, 책임, 협력 관점에서 본 객체지향 | 조영호</title>
      <link>https://thalals.tistory.com/475</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2년 전에 읽었던 객사오를 다시 읽고, 책의 내용을 정리한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FHq90/btsGrRgxVDu/RN4bX0sCAgk2fdVcfkRnA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FHq90/btsGrRgxVDu/RN4bX0sCAgk2fdVcfkRnA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FHq90/btsGrRgxVDu/RN4bX0sCAgk2fdVcfkRnA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFHq90%2FbtsGrRgxVDu%2FRN4bX0sCAgk2fdVcfkRnA1%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;227&quot; height=&quot;277&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[목차]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;협력하는 객체들의 공동체&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;객체란&amp;nbsp;무엇일까&lt;/li&gt;
&lt;li&gt;협력하는&amp;nbsp;사람들&lt;/li&gt;
&lt;li&gt;협력&amp;nbsp;속에&amp;nbsp;사는&amp;nbsp;객체&lt;/li&gt;
&lt;li&gt;메세지와&amp;nbsp;메서드&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;이상한 나라의 객체&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;객체와&amp;nbsp;소프트웨어&amp;nbsp;나라&lt;/li&gt;
&lt;li&gt;객체와&amp;nbsp;상태&lt;/li&gt;
&lt;li&gt;상태&amp;nbsp;캡술화&lt;/li&gt;
&lt;li&gt;행동이&amp;nbsp;상태를&amp;nbsp;결정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;타입과 추상화&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;추상화를&amp;nbsp;통한&amp;nbsp;복잡성&amp;nbsp;극복&lt;/li&gt;
&lt;li&gt;객체지향과&amp;nbsp;추상화&lt;/li&gt;
&lt;li&gt;객체의&amp;nbsp;일반화와&amp;nbsp;특수화&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;역할, 책임, 협력&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;책임의&amp;nbsp;분류&lt;/li&gt;
&lt;li&gt;대체&amp;nbsp;가능성&lt;/li&gt;
&lt;li&gt;책임&amp;nbsp;주도&amp;nbsp;개발&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;책임과 메세지&lt;br /&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;유연하고&amp;nbsp;확장가능하고&amp;nbsp;재사용성이&amp;nbsp;높은&amp;nbsp;협력의&amp;nbsp;의미&lt;/li&gt;
&lt;li&gt;객체 인터페이스&lt;/li&gt;
&lt;li&gt;인터페이스와&amp;nbsp;구현의&amp;nbsp;분리&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;도메인 모델&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 협력하는 객체들의 공동체&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 객체란 무엇일까&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 객체란 현실 세계에 존재하는 &lt;u&gt;사물에 대한 추상화&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 객체 지향의 목표는 실세계를 모방하는 것이 아닌, &lt;u&gt;오히려 새로운 세계를 창조&lt;/u&gt;하는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;상태&lt;/b&gt;&lt;/span&gt;&quot; 와 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;행위&lt;/b&gt;&lt;/span&gt;&quot;를 &quot;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;캡슐화&lt;/b&gt;&lt;/span&gt;&quot;하는 소프트웨어 객체의 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;자율성&lt;/b&gt;&lt;/span&gt;&quot; 을 설명하기에는 &lt;br /&gt;&amp;rarr; 객체를 스스로 생각하고 스스로 결정하는 현실 세계의 생명체에 비유해보는 것이 휴과적이다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체의 협력&lt;/span&gt; &lt;br /&gt;&amp;rarr; [협력하는 사람들]은 현실 세계에서 암묵적인 [약속]과 [명시적인 제약]을 기반으로 [협력] 하며 목표를 달성해 나가는 과정을 &lt;br /&gt;[메세지]를 주고받으며 공동의 목표를 달성한다.&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;  소프트웨어 세계의 객체란, &quot;상태&quot;를 가지며 스스로의 상태를 변화시킬 &quot;행위&quot;를 가지고 있다&lt;br /&gt;  소프트웨어 세계의 객체는, &quot;상태&quot; 와 &quot;행위&quot;를 보호하기 위해 캡슐화 되어있고, 외부 객체와의 협력을 위해 &quot;메세지&quot;를 사용할 수 있다.&lt;br /&gt;  이런 관점에서 객체를 설계한다면, 객체지향적일 것이다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 협력하는 사람들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 현실세계의 직업 처럼 (바리스타, 손님, 캐시어) 소프트웨어상에서의 객체 또한 직업(역할과 책임) 을 엄격하게 분리하는 것이 좋다.&lt;br /&gt;✔ 객체는 역할과 책임을 가지고, 목표를 위해 다른 객체와 협력해야한다.&lt;br /&gt;✔ 특정한 역할은 특정한 책임을 암시한다. (역할 == 책임)?&lt;br /&gt;✔ 사람들이 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;협력&lt;/b&gt;&lt;/span&gt;을 위해 특정한 역할을 맡고 책임을 수행한다는 것은 몆가지 중요한 사실을 암시한다! (객체 관계간의)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;여러 사람이 동일한 역할을 수행&lt;/span&gt;할 수 있다. &amp;rarr; 객체가 주어진 책임을 다한다면, 협력하는 객체는 누구든지 상관없다는 뜻
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;손님 : 손님은 커피를 마실수만 있다면 어떤 캐시어가 주문을 받는지 상관없다. (바리스타가 받더라도!)&lt;/li&gt;
&lt;li&gt;캐시어 : 캐시어는 손님에게 커피를 전달할 수 있다면 어떤 바리스타가 커피를 만들든 상관이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;역할은 대체 가능성&lt;/span&gt;을 의미한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위와같은 상황에서, 손님 입장에서 캐시어는 대체 가능하다.(substitutable)&lt;/li&gt;
&lt;li&gt;2 명의 동일한 역할을 수행할 수 있다면 요청자 입장에서 둘 중 어떤 사람이 역할을 수행하더라도 문제가 되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;책임을 수행하는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;방법은 자율적으로 선택 가능&lt;/span&gt;하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;요청을 받은 사람은 요청을 처리하는 방식을 자유롭게 선택할 수 있다.&amp;nbsp;&lt;/u&gt; (&lt;span style=&quot;color: #ee2323;&quot;&gt;자율성&lt;/span&gt;)&lt;br /&gt;ex) 바리스타 : 커피제작 요청을 받은 바리스타는, 핸드드립 or 머신 추출 or 믹스 커피 등 다양한 행위로 요청에 응답할 수 있다.&lt;/li&gt;
&lt;li&gt;즉, 동일한 요청을 받더라도 바리스타의 역할을 수행하는 사람들마다 서로&lt;b&gt; 다른 &quot;방식&quot;&lt;/b&gt; &lt;b&gt;으로 요청을 처리할 수 있다.&lt;/b&gt; = [&lt;span style=&quot;color: #ee2323;&quot;&gt;다형성&lt;/span&gt;]&lt;/li&gt;
&lt;li&gt;한 사람이 동시에 여러 역할을 수행할 수 있다.&lt;br /&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;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  설계하고자하는 서비스에서, &lt;span style=&quot;color: #ee2323;&quot;&gt;객체의 관계를 묘사&lt;/span&gt;할때 가장 중요한 점은 &lt;span style=&quot;color: #ee2323;&quot;&gt;어디까지가 해당 객체의 책임인가&lt;/span&gt;인 것 같습니다.&lt;br /&gt;  (2번) 문항은 저에게, &lt;span style=&quot;color: #ee2323;&quot;&gt;객체지향적인 설계는 변경을 용이하게&lt;/span&gt; 만들어준다고 들려집니다.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr; 객체들에게 명확한 책임과 역할을 부여하고, &lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;설계한 객체들의 협력을 명확한 요청과 응답이라는 명확한 메세지로 상태를 변경할 수 있다면 &lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;[유연하게 객체의 행위나 협력관계를 변경]할 수 있을것만 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  객체의 명확한 책임과 역할이 캡슐화 되어있다면, 객체는 외부 상황을 전혀 신경 쓰지 않아도 되기 때문입니다. (1번)&lt;br /&gt;  대체 가능한 역할과 책임은 객체지향의 &quot;다형성&quot;과 깊이 연관되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 다형성이란? (밑에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 협력 속에 사는 객체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ &lt;b&gt;객체는 스스로 자율성을 가져야 한다.&lt;/b&gt; &amp;rarr; 어떤 방식으로 요청에 응답할지, 요청에 응답할지에 대한 여부까지 객체 내부에서 스스로 판단할 수 있게끔 역할과 책임을 주어야 객체지향적인 패러다임 설계이다.&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;객체의 사적인 부분(상태 변경) 은 외부에서 일체 간섭할 수 없도록 차단해야 한다!&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;[접근이 허락된 수단]을 통해서만 객체와 [의사소통] 해야한다..! (중요!!)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;외부의 협력객체는 해당 객체가 &quot;무엇&quot;을 수행하는지는 알수 있지만, &quot;어떻게&quot; 수행하는지는 알 수 없어야한다.&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;br /&gt;  직접적으로 의존하지 않고, 메세지를 통해 협력하는 관계를 맺는다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 메세지와 메서드&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;span style=&quot;color: #ee2323;&quot;&gt;메세지와 메서드를 명확하게 분리&lt;/span&gt;하면 객체의 협력에 참여하는 &lt;b&gt;객체들 간의 자율성을 증진&lt;/b&gt;시킨다. &lt;br /&gt;&amp;nbsp; &amp;nbsp;(객체 안의 메서드끼리도 역할과 책임을 분리하는 의미 같습니다. 메세지를 받는 메서드, 메세제의 행위를 처리하는 메서드)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 이상한 나라의 객체&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 객체와 소프트웨어 나라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 객체지향 패러다임의 목적은 현실 세계를 모방하는 것이 아닌, 현실을 기반으로 새로운 세계를 창조하는 것이다.&lt;br /&gt;✔ &lt;u&gt;객체의 상태를 변화(결정) 시키는 것&lt;/u&gt;은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;행동&lt;/b&gt;&lt;/span&gt;이지만, &lt;u&gt;행동의 결과를 변화시키는 것(결정)&lt;/u&gt;하는 것 또한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;상태&lt;/b&gt;&lt;/span&gt;이다.&lt;br /&gt;✔ 즉, 어떤 행동의 성공 여부는 이전 행동에 영향을 받을 수도 있다, (이전 행동으로 상태가 변해서) &amp;rarr; 이것은 행동 간의 순서가 중요하다는 것을 의미한다.&lt;br /&gt;✔ 객체는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;상태(state), 행동(behavior), 식별자(identity)&lt;/span&gt;를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;지닌 실제&lt;/span&gt;로 보는 것이 가장 효과적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 객체와 상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ &lt;b&gt;객체에 상태가 존재&lt;/b&gt;하고, &lt;b&gt;행위는 상태에 의존(과거의 행위)에 의존&lt;/b&gt;할 수 있기 때문에 과거의 클래스 패러다임과 다르게 &lt;b&gt;소프트웨어의 설계에서 과거에 얽메이지 않고 현재를 기반으로 객체의 행동 방식을 이해&lt;/b&gt;할 수 있게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;✔ 때로는 객체의 상태를 다른 객체를 이용해 표현할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 상태 캡술화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 위에서 정의한대로와 같이, 객체의 상태는 객체 자신만이 변경가능해야 합니다.&lt;br /&gt;✔ 객체는 상태를 캡슐안에 감춰둔 채 외부로 노출하지 않습니다.&lt;br /&gt;✔ 객체가 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;외부에 노출하는 &quot;행동&quot;뿐&lt;/span&gt;이며, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;외부에서 접근할 수 있는 유일한 벙법 역시 &quot;행동&quot;&lt;/span&gt;뿐이다.&lt;br /&gt;✔ 객체의 행동을 유발하는 것은 외부로부터 전달된 &quot;&lt;b&gt;메세지&lt;/b&gt;&quot;이지만, &lt;b&gt;상태를 변경할 지는 객체 &quot;스스로&quot; 결정&lt;/b&gt;한다!&lt;br /&gt;✔ 상태를 노출시키지 않고 [&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;행동&lt;/span&gt;을 경계로 캡슐화&lt;/b&gt;] 하는 것은 객체의 자율성을 높이고, 자율적인 객체는 [&lt;b&gt;스스로 판단하고 스스로 결정&lt;/b&gt;] 하기 때문에 객체들간의 [&lt;b&gt;협력은 유연해지고 간결&lt;/b&gt;] 해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이것이 상태를 캡슐화 해야하는 이유! &amp;rarr; 유연하고 간결한 협력을 위해 객체의 자율성을 높이자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 행동이 상태를 결정한다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;상태를 중심으로 객체를 바라보면 안된다.&lt;/span&gt;&lt;/b&gt; &amp;rarr; 상태를 먼저 보고 행동을 나중에 결정하는 것은 설계에 나쁜 영향을 준다.&lt;/p&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;객체를 협력자가 아닌 고립된 섬으로 만든다. - 객체는 협력을 위해 존재한다. 상태에 초점을 맞추면 협력하지 못할 가능성이 높다.&lt;/li&gt;
&lt;li&gt;객체의 재사용성이 저하된다. - 마찬가지로 다양한 협력에 참여하는 능력이 저하되기에 재사용성이 저하된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 결과적으로 &quot;우리가 어플리케이션 안에서 어떤 행동을 원하느냐&quot; 가 어떤 객체가 적합한지를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 객체의 적합성을 결정하는 것은 상태가 아닌 [객체의 행동]이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ ! 행동을 결정한 후에야, 행동에 필요한 정보를 고려할 수 있고 그것이 상태가되어야 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 협력 안에서 &quot;객체의 행동&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;  책임주도설계 - RDD(Responsibility Driven Design)&lt;br /&gt;  협력안에서 객체의 행동을 생각하도록 유도함으로써 &amp;rarr; [응집도 높고 재사용 가능한 객체]를 만들 수 있다.&lt;br /&gt;  &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&quot;행동이 상태를 결정한다.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 타입과 추상화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 추상화를 통한 복잡성 극복&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 추상화 : 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법&lt;br /&gt;✔️ &lt;b&gt;진정한 의미에서 추상화란&lt;/b&gt;, &lt;u&gt;현실에서 출발하되 불필요한 부분을 도려내가면서 사물의 놀라운 본질을 드러나게 하는 과정&lt;/u&gt;&lt;br /&gt;✔ 전철 노선도 : 전철노선도는 하나의 유연한 직선 혹은, 타원이지만 실상황에서의 노선도는 복잡하기 그지없다. (꼬불꼬불)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 객체지향과 추상화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 객체가 아무리 다양한 특징을 가진다고 해도, 공통점만을 취해 단순화시킬 수 있다. (추상화)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRpY6u/btsGqPKrri8/6f1p2wvQK08m77zc8zzj60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRpY6u/btsGqPKrri8/6f1p2wvQK08m77zc8zzj60/img.png&quot; data-alt=&quot;개념으로 분리해도 그 안에 다양한 특징으로 다시 분리될 수 도 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRpY6u/btsGqPKrri8/6f1p2wvQK08m77zc8zzj60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRpY6u%2FbtsGqPKrri8%2F6f1p2wvQK08m77zc8zzj60%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;346&quot; height=&quot;257&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개념으로 분리해도 그 안에 다양한 특징으로 다시 분리될 수 도 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ &lt;span style=&quot;background-color: #f6e199;&quot;&gt;분류는 추상화를 위한 도구&lt;/span&gt;이다.&lt;br /&gt;✔ 분류란, 객체에 특정한 개념을 적용하는 작업. 즉, 객체에 특정한 개념을 적용하기로 결심했을 때 우리는 그 객체를 특정한 집합의 멤버로 분류하고 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 객체의 일반화와 특수화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 특정 개념을 가진 객체를 분류했을때, 해당 집합의 일부 객체만 특정한 행동을 할 수 있다고 가정하자. (트럼프 인간 &amp;lt; 트럼프 병사 &amp;lt; 트럼프 여왕)&lt;br /&gt;✔ 이러한 관계를 &lt;b&gt;일반화와 특수화&lt;/b&gt;라고 한다.&lt;br /&gt;✔ 객체지향에서 &lt;b&gt;일반화/특수화를 관계를 결정하는 것&lt;/b&gt;은 [상태]가 아닌 [&lt;span style=&quot;color: #ee2323;&quot;&gt;행동&lt;/span&gt;]이다.&lt;br /&gt;✔ 특수 타입은 일반 타입보다 더 많은 행동을 가진다.&lt;br /&gt;✔ 단, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;특수 타입은 일반적인 타입이 할 수 있는 모든 행동을 동일하게 수행할 수 있어야 추상화가 가능&lt;/span&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;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 역할, 책임, 협력&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 책임의 분류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ &lt;b&gt;객체의 책임&lt;/b&gt;은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;'객체가 무엇을 알고있는가(knowing)'&lt;/span&gt; 와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;'무엇을 할 수 있는가(doing)'&lt;/span&gt;으로 구성된다.&lt;br /&gt;✔ 이러한 분류에서 &lt;b&gt;객체의 책임&lt;/b&gt;을 크게 [&lt;b&gt;하는 것&lt;/b&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;하는 것(doing)&lt;br /&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;다른 객체의 행동을 시작시키는 것&lt;/li&gt;
&lt;li&gt;다른 객체의 활동을 제어하고 조절하는 것&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&amp;nbsp;아는 것(knowing)&lt;br /&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;관련된 객체에 관해 아는 것&lt;/li&gt;
&lt;li&gt;자신이 유도하거나 계산할 수 있는 것에 관해 아는 것&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 즉, 책임은 &lt;span style=&quot;color: #ee2323;&quot;&gt;외부&lt;/span&gt;에 제공할 정보(아는것) 과 외부에 제공할 서비스(하는것)의 목록이기에 [객체의 공용 인터페이스]를 구성할 수 잇다, (public interface)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 대체 가능성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 역할은 협력 안에서 구체적인 객체로 대체될 수 있는 추상적인 협력자이다. (객체가 적합한 역할과 책임을 수행하기에)&lt;br /&gt;✔ 따라서 역할은 다른 객체에 의해 대체 가능함을 의미한다.&lt;br /&gt;✔ 객체가 &lt;b&gt;역할을 대체하기 위해서는&lt;/b&gt; &amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;역할이 수행하는 모든 책임을 동일하게 수행할 수 있어야한다.&lt;/span&gt;&lt;br /&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;rarr; 행위 호환성은 동일한 책임의 수행을 의미한다.&lt;br /&gt;  특정한 기능을 위해 객체간의 협력이 필요할때, 협력은 객체들의 역할로 이루어져 있고, &lt;b&gt;역할은 대체 가능&lt;/b&gt;하다.&lt;br /&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;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 책임 주도 개발&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 만약 책임을 여러 종류의 객체가 수행할 수 있다면, &amp;rarr; 협력자는 객체가 아닌 추상적인 역할로 대체가능하다. (추상화, 다형성)&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 책임과 메세지&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 다형성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 다형성이란, 서로 다른 유형의 객체가 동일한 메시지에 대해 서로 다르게 반응하는 것을 의미한다.&lt;br /&gt;✔ 다형성의 가능성 : 메시지는 &quot;무엇&quot;이 실행될지 명시하지만 &quot;어떻게&quot; 실행될 것인지는 수신자만이 알 수 있다.&lt;br /&gt;✔ 다형성은 객체들의 대체 가능성을 이용해 설계를 유연하고 재사용 가능하게 만든다.&lt;br /&gt;✔ 다형성을 사용하면 송신자가 수신자의 종류를 모르더라도 메시지를 전송할 수 있다, (역할을 추상화하였기 때문에)&lt;br /&gt;✔ 즉, 다형성은 수신자의 종류를 캡슐화 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 유연하고 확장가능하고 재사용성이 높은 협력의 의미&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  객체지향 패러다임의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&quot;다형성&quot; 이 가지는 장점&lt;/span&gt;에 대한 정리!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 송신자(요청자)가 수신자(응답자)에 대해 매우 적은 정보만 알고있더라도 상호 협력이 가능하다는 사실이 설계 품질에 큰 영향을 미친다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;협력이 유연해진다.&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;송신자는 수신자에 대해 어떤 가정도 하지 않고 &lt;u&gt;수신자를 다른 타입의 객체로 대체&lt;/u&gt;하더라도(다형성) &lt;span style=&quot;color: #ee2323;&quot;&gt;송신자는 전혀 알지 못한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;따라서 [&lt;u&gt;송신자는, 수신자의 변경에 의한 파급효과(side-effect) 가 없다.&lt;/u&gt;]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;협력이 수행되는 방식을 확장할 수 있다.&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;송신자에 아무런 영향을 끼치지 않고 수신자를 교체할 수 있기 때문에 새로운 유형의 객체를 협력에 끼워 맞출 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;협력이 수행되는 방식을 재사용할 수 있다.&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;협력에 영향을 미치지 않고서도 다양한 객체들이 수신자(응답자)의 자리를 대체할 수 있기 때문에 다양한 문낵에서 협력을 재사용할 수 있다.&lt;/li&gt;
&lt;li&gt;협력자들이 추상적인 역할로 추상화 되었기 때문에!!!!!!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;다형성은,&lt;/b&gt; &lt;b&gt;개별 객체가 아니라&lt;/b&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체들이 주고받는 메시지에 초점을 맞출때 가장 강력하고 적합&lt;/span&gt;하다.&lt;br /&gt; &lt;b&gt; 메시지를 중심으로 설계된 구조&lt;/b&gt;는 유연하고 확장 가능하며 재사용 가능하다.&lt;br /&gt;  메시지를 중심으로 한다는 것은 &amp;rarr; 협력자들의 책임과 역할에 따라 행위의 주체자를 결정한다는 것 &amp;rarr; 협력의 역할을 추상화 하였을 때 다형성의 진가가 드러난다..!!!!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 객체 인터페이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 일반적으로 인터페이스란, 어떤 두 사물이 마주치는 걍계 지점에서 [서로 상호작용할 수 있게 이어주는] 방법이나 장치&lt;br /&gt;✔ 인터페이스의 특징&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인터페이스 사용법을 익히며 내부구조를 몰라도 대상을 조작할 수 있다.&lt;/li&gt;
&lt;li&gt;인터페이스 자체는 변경하지 않고 [단순히 내부 구성이나 작동 방식만을 변경하는 것]은 [인터페이스 사용자에게 어떤 영향도 미치지 않는다.]&lt;/li&gt;
&lt;li&gt;대상이 변경되더라도 동일한 인터페이스를 제공하기만 하면 아무론 문제 없이 상호작용 할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 &quot;메시지 전송&quot; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 인터페이스와 구현의 분리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 훌령한 객체란 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;구현을 모른 채 인터페이스만 알면 쉽게 상호작용할 수 있는 객체&lt;/span&gt;를 의미한다.&lt;br /&gt;✔️ 이것은 객체를 설계할 때 객체 외부에 노출되는 인터페이스와 객체의 내부에 숨겨지는 구현을 명확하게 분리해서 고려해야 한다는 것을 의미한다. &amp;rarr; [&lt;b&gt;인터페이스와 구현의 분리 원칙&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; &amp;nbsp; 송신자와 수신자가 구체적인 구현 부분이 아니라 느슨한 인터페이스에 대해서만 결합되도록 하는 것 (인터페이스이 구현은 변경에서 자유롭기에 유연하다고 표현한다.)&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;br /&gt; 객체지향적 설계는 요구사항 변경시 유연하게 대비하기위한 목적을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 도메인 모델&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 도메인 모델이란, 사용자가 프로그램을 사용하는 대상 영역에 관한 지식을 선택적으로 단순화하고 의시적으로 구조화한 현태이다.&lt;br /&gt;✔ 도메인 모델은 소프트웨어가 목적하는 영역 내의 개념고 개념 간의 관계, 다양한 규칙이나 제약 등을 주의 깊게 추상화 한것이다.&lt;br /&gt;✔ 제품을 설계할 때 제품에 관한 모든 것이, 사용자들이 제품에 대해 가지고 있는 멘탈 모델과 정확하게 일치해야 한다. &amp;rarr; [최종 코드는 사용자가 도메인을 바라보는 관점을 반영해야 한다.]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 이러한 관점의 도메인 모델은 &quot;사용자 관점에서 설계하기 때문에&quot; 본질적인 측면을 가장 잘 이해하고 있고, 본질적이기에 변경이 비교적 적다는 특성을 가진다...(잘 이해가 안감)&lt;br /&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;br /&gt;  따라서 도메인 모델링에서 사용한 객체와 개념을 프로그래밍 설계에서의 객체와 클래스로 매끄럽게 변화할 수 있고 이같은 특성을, [객체지향의 연결완전성] 이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 다시 읽어보니, 처음 읽었을 때 얼마나 이해를 못하며 읽었는지 알 수 있었습니다..&lt;br /&gt;지금도 완벽하게 이해가된건 아닌거 같고, 1년 혹은 2년 뒤 도메인 모델 관점에 대한 경험을 쌓고 다시 읽으면 또 다른 인사이트를 얻을 수 있을 것만 같은 기분이 드네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;천천히더라도 꾸준히 공부해서 다시 읽을 수 있는 그날이 오길!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;</description>
      <category>  개발자 책 읽기/독후감</category>
      <category>객사오</category>
      <category>객체지향</category>
      <category>객체지향의 사실과 오해 리뷰</category>
      <category>객체지향의사실과오해</category>
      <category>백엔드도서</category>
      <category>백엔드책</category>
      <category>조영호</category>
      <category>책임주도개발</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/475</guid>
      <comments>https://thalals.tistory.com/475#entry475comment</comments>
      <pubDate>Sun, 7 Apr 2024 16:12:49 +0900</pubDate>
    </item>
    <item>
      <title>육각형 개발자 - 시니어 개발자로 성장하기 위한 10가지 핵심 역량 | 최범균</title>
      <link>https://thalals.tistory.com/474</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 진행하게 된 스터디에서 읽게된 책인데 편하게 잘 읽히고&lt;br /&gt;기억하고 싶은 내용이 있어 기록으로 남겨보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;1장 들어가며&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 개발이란&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;✔️ 개발은 단순히 경력을 쌓거나 관심 있는 기술을 사용하기 위한 과정이 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ &lt;b&gt;개발은 회사와 나에게 돈을 벌어주는 기능을 만드는 과정이기도 했다. 내가 만든 결과물은 직간접적으로 회사의 수익과 연결된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 회사 규모가 작을수록 &lt;b&gt;개발 결과물&lt;/b&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;  언젠가 기회는 온다. 책임감을 가지고 맡은 일에 최선의 결과를 내도록 노력하자  &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 개발에 필요한 것&lt;/h3&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;업무 관리와 요구 분석, 공유, 리드&amp;amp;팔로우&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;작업의 진척도를 개인들만이 알고 있다면, 프로젝트 전체의 진척도는 아무도 알지 못한다.&lt;/li&gt;
&lt;li&gt;공유하고 파악하고, 큰 그림을 그려 차근차근 프로젝트 진척도를 완성해 나가는 노력을 해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  다양한 경험을 쌓고 연습하자  &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h1&gt;2장 구현 기술과 학습&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 구현 기술&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개발자는 당연하게도 구현 기술을 능숙하게 다뤄야 한다.&lt;br /&gt;✔️ 기술을 선택하는데는 이유가 있어야한다.&lt;br /&gt;✔️ 기술을 선택하는데 두려움을 갖지말자&lt;br /&gt;✔️ 역할에 따라 선택해야하는 기술이 달라져야한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 학습 대상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개발자가 익혀야 할 구현 기술이 너무 많다. 잘 선택하자&lt;br /&gt;✔️ 학습하고자하는 구현 기술을 정할때 기준으로 삼으면 좋은 2가지&lt;/p&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;문제를 해결하기 위한 기술
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;당장 해결하기 위한 문제일 경우 빠르게 찾아서 기본 사용방법을 익히고 적용해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가까운 미래에 해결해야할 경우, 위험신호가 관찰되고 있다면 머지않은 미래에 큰 장애가 발생할 가능성이 높다. &amp;rarr; 파악하고 대비하자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 기술의 유명세에 휘둘리지 말자&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 기술 파기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개발자가 익혀야 할 기술이 많기 때문에 하나의 기술을 처음부터 끝까지 깊게 학슴하는 것은 물론 여러 기술을 일정 수준만큼 빠르게 학습해야 하는 것은 쉬운 일이 아니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 핸즈온 &amp;amp; 동영상 강의 &amp;amp; 튜토리얼 문서로 빠르게 감을 잡아보자&lt;/li&gt;
&lt;li&gt;필요할 때마다 기술을 조금씩 더 깊게 학습하자&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 문제를 해결하기 위해 더 나은 방법을 찾으려고 노력해야한다. &amp;rarr; 더 나은 방법이 있는지 항상 찾아보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgYqlo/btsGbRBdzfu/y2KW12Jwt6InmvHuQck5Yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgYqlo/btsGbRBdzfu/y2KW12Jwt6InmvHuQck5Yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgYqlo/btsGbRBdzfu/y2KW12Jwt6InmvHuQck5Yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgYqlo%2FbtsGbRBdzfu%2Fy2KW12Jwt6InmvHuQck5Yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;407&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;(개인적인 생각)&lt;/span&gt;&lt;br /&gt;&amp;rarr; 사진을 보았을 때 당연히 동그란 바퀴가 좋아보인다.&lt;br /&gt;&amp;rarr; 하지만, 현재의 기술을 냉철하게 바라볼만 시야를 가지긴 쉽지않다. 나는 쉽지 않았던 것 같다.&lt;br /&gt;&amp;rarr; &lt;b&gt;이런 상황에서 지금 내가 채택한 방법이 네모인지, 어떤 기술이 동그라미인지 어떻게 파악할 수 있을까?&lt;/b&gt;&lt;br /&gt;&amp;rarr;   고민끝에, 그것에 대한 기준점은 &quot;주어진 시간안에 문제가 해결되느냐&quot; 로 판단하기로 했다. 비지니스적인 관점에서 주어진 시간안에 문제를 해결할 수 있고, 목표치를 달성했다면 그자체로 동그라미가 아닐까?? (아닐까요..?)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 학습 전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 기술은 한순간에 사라진다. 과거에 머물지 말고 과감하게 포기하자&lt;br /&gt;✔️ 유행에 상관없는 기술을 익히자&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 프로토콜&lt;/li&gt;
&lt;li&gt;네트워크 프로그래밍&lt;/li&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;✔️ 답이 아닌 질문을 따라하기 - &quot;Copy the question, not the answer&quot;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜 유명한 기업이 특정 기술을 도입했는지 이유를 찾아보고 현재 상황과 비교해보자&lt;br /&gt;✔️ 기술 적용 전략에는 유연함이 필요&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h1&gt;3장 소프트웨어 가치와 비용&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 소프트웨어의 가치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 소프트웨어를 출시하려면 큰 노력이 필요하지만 &amp;rarr; 출시 후 부터가 시작이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 출시된 서비스가 반응이 없다면, 반응을 얻기위해 기능을 추가하고 수정해야한다. &amp;rarr; 즉, 소프트웨어를 유지보수 해야한다.&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;✔️ &quot;세상이 변해도 소프트웨어는 쓸모 있어야 한다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 그러기위해서는, 세상의 변화에 맞춰 소프트웨어도 함께 변해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 고객에게 유용함을 제공하지 못한다면 결국 망한다(쓰레기다).&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;  소프트웨어의 유용함을 유지하기위해서는  &lt;br /&gt;변경에 대한 유연성을 가지는 것이 가장 적합한 대응이다. &lt;br /&gt;언제나 개발 비용을 줄일 수 있는 방법을 염두해 두고 고민하면서 개발하자!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h1&gt;4장 코드 이해&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 코드 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 개발시간을 줄이고 싶다면 코드를 작성하는 시간 못지않게 코드를 이해하는 시간을 줄여한다.&lt;br /&gt;✔️ 시간을 줄이기 위해 필요한 2가지 역량&lt;/p&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;이해하기 쉬운 코드를 작성하는 역량&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 코드 이해 도구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 코드 분석에 도움이 되는 다양한 수단을 활용하자 (시각화, 코드 출력, 스크래치 리팩터링)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;코드 시각화 (UML) &amp;rarr; 링크 대체 : &lt;a href=&quot;https://thalals.tistory.com/469&quot;&gt;https://thalals.tistory.com/469&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;코드 출력(종이에) &amp;rarr; 직접해보니 레거시 흐름 분석에 도움이 됨&lt;/li&gt;
&lt;li&gt;스크래치 리팩터링 &amp;rarr; 수정변경해보고 다시 돌려놓기&lt;/li&gt;
&lt;li&gt;함께 모여 보기&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h1&gt;4, 5, 6, 7장 생략&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 좋은 코드를 작성하기 위한 노력, 테스트 코드, 리팩토링 기법 등의 내용을 다룸&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;8장 아키텍처, 패턴&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 아키텍처가 중요한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 주니어 개발자와 - 중니어, 시니어를 구분 짓는 요소 중 하나! 아키텍처 설계 역량!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 소프트웨어 아키텍처는 &quot;소프트웨어 시스템의 추상적인 구조&quot; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 아키텍처를 결정할 때는 크게 2가지를 고려해야한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기능 요구 사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어로 해결하고자 하는 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;품질 속성 또는 비기능 요구 사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;품질 속성 : 성능, 확장성&lt;/li&gt;
&lt;li&gt;요구사항에 들어남 : 사용자 수, 최대 트래픽 등과 같은 형태&lt;/li&gt;
&lt;li&gt;업무 도메인에서도 도출됨 : 법률 조건&lt;/li&gt;
&lt;li&gt;✔️ 품질 속성 대부분은, &quot;요구 사항에 없더라도 경험을 토대로 자연스럽게 도출된다&quot;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 가용성, 인증고 인가, 유지보수성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 품질속성을 높이면 [시스템의 복잡도] 가 증가한다. &amp;rarr; 트레이드 오프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 모든 품질 속성을 높이면 이상적이 겠지만 &quot;불가능 하다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 주요 품질 속성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bgvrk/btsGcPQoQAH/qTPvVkgKoMQXPKzN6bbXW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bgvrk/btsGcPQoQAH/qTPvVkgKoMQXPKzN6bbXW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bgvrk/btsGcPQoQAH/qTPvVkgKoMQXPKzN6bbXW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBgvrk%2FbtsGcPQoQAH%2FqTPvVkgKoMQXPKzN6bbXW1%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;794&quot; height=&quot;886&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ 아키텍처 변경은 흥미롭지만 단순히 재미삼아 해서는 안된다. &amp;rarr; &quot;품질 속성의 변화가 없다면 아키텍처를 함부로 변경해서는 안된다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;  모든게 완벽한 아키텍처가 아닌 가장 나쁘지않은 아키텍처를 선택해야 한다!!!! 시야를 넓히는 연습을 하자  &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h1&gt;9장 업무 관리&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 처음부터 끝까지 (일정관리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 하나의 일을 맡으면 [처음부터 끝까지 책임지는 것이 기본이다]&lt;br /&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;요구 사항 이해 및 변경 대응&lt;/li&gt;
&lt;li&gt;일정관리(또는 계획)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 업무 나누기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 일의 규모가 커지면 생각나는 대로 하면 안된다. 업무의 크기에 따라 일하는 방식을 배워야 한다.&lt;br /&gt;✔️ 업무 나누기&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;초반에 해야할 일 정리하기&lt;/li&gt;
&lt;li&gt;일의 덩어리가 크다면 더 작게 나누기&lt;/li&gt;
&lt;li&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;계획을 세울려면 규모를 파악 해야한다. &amp;rarr; 작업 목록이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드가 기대한 대로 동작할 때 비로소 완료된다. (테스트와 수정 기간 포함)&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;(개인적인 생각)&lt;/span&gt;&lt;br /&gt;  일정관리를 잘하는 것은 나에게 너무 어려운 일이었다.&lt;br /&gt;  스터디원 분중은 한 분은 &quot;항상 생각했던 기간의 3배수를 부른다(흥정을 위해)&quot; 고 하셨다&lt;br /&gt;  이상적인 기간 설정은 처음 생각했던 기간의 2배수라고 하셨다. &lt;br /&gt;  이 애기는 즉, 스스로를 믿지말고.. 항상 위험관리를 잘해야한다는 것을 의미하는 것과 동시에, 비지니스에서 시간 약속은 칼과 같아야하기 때문이 아닐까??&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 위험관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 본인이 느끼기에 뭔가 잘 진행이 되지않는다면 위험신호이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ &quot;반나절을 고민했는데도 모르겠다면 물어보자!!&quot;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 요구사항은 바뀐다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 요구사항 변경은 막을 수 없다. 막지도 말자. 비지니스 성공이 우선이다. 제발!! 기억해!!&lt;br /&gt;✔️ 왜 이런 요구사항을 요구하는지 이해할려고 노력하고 대화하면, 변경을 최소한으로 막을 수 있다.&lt;br /&gt;✔️ 일정과 비용을 처음부터 언급하지 말자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  개발자는 소프트웨어를 만들어 요구사항을 해결해주는 사람이다. 당연히 요구를 최대한 들어주기 위해 노력해야한다. &lt;br /&gt;  착각하지 말자!  &lt;br /&gt;  일정과 비용은 최후의 최후의 최후에 언급하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 그렇지만 &quot;개발자는 안된다고 말할 수 있어야한다.&quot; &amp;rarr; 무리한 요구를 한다면 단호하게 &quot;NO!&quot; 를 외치데, 할 수 없는 이유를 함께 설명하도록 노력하자 (설득)&lt;br /&gt;✔️ 안된다고 말하지말고 대안 제시하기 (동료간의 신뢰도를 높일 수 있는 가장 좋은 방법이라고 생각한다.)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) 수작업 줄이기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 코드 중복이 세번 이상 발생하면 중복을 제거하라&lt;br /&gt;✔️ 일반 작업도 3번 이상 발생하면 자동화 하자&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6) 이유와 목적 생각하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 단순히 시키는 일만 하지 말자, 일에는 이유와 목적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;기억하고 실천해보자&lt;br /&gt;자주 와서 다시 기억할 수 있는 글이 되었으면 좋겠다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd02ao/btsGbGUc6WQ/BKvkGj1GEkDI72i21c3hX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd02ao/btsGbGUc6WQ/BKvkGj1GEkDI72i21c3hX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd02ao/btsGbGUc6WQ/BKvkGj1GEkDI72i21c3hX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd02ao%2FbtsGbGUc6WQ%2FBKvkGj1GEkDI72i21c3hX1%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;300&quot; height=&quot;427&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  개발자 책 읽기/독후감</category>
      <category>개발도서</category>
      <category>개발독후감</category>
      <category>개발자</category>
      <category>백엔드</category>
      <category>서버개발자</category>
      <category>주니어</category>
      <category>주니어 개발자</category>
      <category>주니어 추천 책</category>
      <category>최범균</category>
      <category>함께자라기</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/474</guid>
      <comments>https://thalals.tistory.com/474#entry474comment</comments>
      <pubDate>Thu, 28 Mar 2024 23:39:54 +0900</pubDate>
    </item>
    <item>
      <title>Spring Rest Docs Enum class 문서화 하기</title>
      <link>https://thalals.tistory.com/473</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개발환경 : Java 17, Spring 3&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 사이드 프로젝트에서 Rest Docs + Swagger UI 를 사용 중 입니다. (참고 &amp;rarr; &lt;a href=&quot;https://thalals.tistory.com/433&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;restdocs + swagger ui 같이사용하기&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;보통 상태나 선택 특정 목록같은 딱딱 선택지가 정해져있는 상태값은 Enum 으로 관리하는 걸 선호하는 편인데,&lt;br /&gt;이게 RestDocs 를 사용하지만, Open API 를 사용하여 바로 Swagger UI 에 적용되는 json 파일을 자동적으로 만들어주다보니, Enum 클래스에 대한 문서 지원이 되지않아 (&lt;s&gt;찾지못해&lt;/s&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;그래서 이번 글에서는 Rest Docs 와 Open API를 이용한 Swagger UI 적용환경에서 ENUM 클래스를 어느정도 문서화하기 편한 상태로 만든 방법에 대해 공유하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 기존 상태 설명&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 저희는 위와같은 ENUM 클래스를 테이블의 상태값으로 사용한다고 가정합니다.&lt;/li&gt;
&lt;li&gt;Enum 을 사용한다면 String 으로 관리하는 것 보다 유지보수에 상당히 많은 장점을 가집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;public enum Gender {

    MALE(&quot;MALE&quot;),
    FEMALE(&quot;FEMALE&quot;),
    BISEXUAL(&quot;BISEXUAL&quot;);
 }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 Enum 을 상태변수로 가지는 User 라는 도메인있고 이 값을 Response 로 보내고, 이를 Rest Docs 로 문서화 한다면 아래와 같은 테스트코드가 작성될 것입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@WebMvcTest(UserController.class)
class UserJoinDocumentation {

    //생략
    
    @Test
    @DisplayName(&quot;유저 회원가입 docs&quot;)
    void userJoin() throws Exception {

        //생략
        
        //then
        ResultActions resultActions = mockMvc.perform(
            RestDocumentationRequestBuilders.post(DEFAULT_URL + &quot;/signup&quot;)
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .content(requestBody)
        ).andDo(
            document(&quot;유저 회원가입 docs&quot;,
                preprocessRequest(prettyPrint()),
                preprocessResponse(prettyPrint()),
                resource(
                    ResourceSnippetParameters.builder()
                        .tag(&quot;유저 - 회원가입&quot;)
                        .description(&quot;유저 일반 회원가입&quot;)
                        .requestFields(
                            fieldWithPath(&quot;username&quot;).description(&quot;닉네임&quot;),
                            fieldWithPath(&quot;email&quot;).description(&quot;이메일&quot;),
                            fieldWithPath(&quot;birthDay&quot;).description(&quot;생일&quot;),
                            fieldWithPath(&quot;gender&quot;).description(&quot;성별&quot;),
                        )
                        .responseFields(
                        )
                        .build()
                ))
        );

        resultActions.andExpect(MockMvcResultMatchers.status().isCreated());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qn4bC/btsFSZtkcoG/iqBehzaoDWH9baKaAobtW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qn4bC/btsFSZtkcoG/iqBehzaoDWH9baKaAobtW1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;207&quot; data-origin-width=&quot;281&quot; style=&quot;width: 44.351%; margin-right: 10px;&quot; data-widthpercent=&quot;44.87&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qn4bC/btsFSZtkcoG/iqBehzaoDWH9baKaAobtW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQn4bC%2FbtsFSZtkcoG%2FiqBehzaoDWH9baKaAobtW1%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;281&quot; height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sesrs/btsFRBGwiSO/vQTkHXTiT0SWNC2E2OsrE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sesrs/btsFRBGwiSO/vQTkHXTiT0SWNC2E2OsrE0/img.png&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;322&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.4862%;&quot; data-widthpercent=&quot;55.13&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sesrs/btsFRBGwiSO/vQTkHXTiT0SWNC2E2OsrE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsesrs%2FbtsFRBGwiSO%2FvQTkHXTiT0SWNC2E2OsrE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;537&quot; height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 확인해보면 이렇게 잘! 문서화 된 걸 볼 수 있습니다! 비록 문자열로 나오지만!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;2. 개선 방안&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 저 schema 의 문서에 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Enum 의 상태값을 모두 적어두어&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;클라이언트 사용자가 조금 더 용이하게 개발할 수 있게 바꿔보고자 합니다!&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1710775702565&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fieldWithPath(&quot;gender&quot;).description(&quot;성별 -[MALE, FEMALE]&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 생각한다면 이렇게 모두 직접 적어두어 수정하여도 원하는 결과를 뽑아냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  하지만 이 방법은 Enum 의 상태값이&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 추가되거나 변경된다면&lt;/b&gt;&lt;/span&gt; 컴파일시 잡을 수 없기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;문서가 틀어질 가능성이 몹시 높습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 자동화 하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자동화라는 단어가 적합한지는 모르겠으나, 저는 인터페이스를 사용하여 Enum 클래스를 한단계 추상화하기로 하였습니다.&lt;/li&gt;
&lt;li&gt;먼저 단순하게 Enum 클래스의 모든 변수값을 뽑아올 메소드를 추상화 해주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Interface&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface EnumModel {

    String getKey();

    String getValue();

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Enum&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Getter
@AllArgsConstructor
@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum Gender implements EnumModel{

    MALE(&quot;MALE&quot;),
    FEMALE(&quot;FEMALE&quot;),
    BISEXUAL(&quot;BISEXUAL&quot;);

    private final String value;

    @Override
    public String getKey() {
        return name();
    }

    @Override
    public String getValue() {
        return value;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Util Class&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;이후 Test 에서만 사용할 Util 클래스를 만듭니다.&lt;/li&gt;
&lt;li&gt;Enum class 에 상위 interface를 두었기 때문에 제네릭을 사용하여 하위 타입을 받아올 수 있습니다.&lt;/li&gt;
&lt;li&gt;enums.getEnumConstans() 메소드는 선언된 enum 클래스의 구성된 모든 값을 선언된 순서대로 포함하는 배열을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;public abstract class EnumDocsUtils {

    public static String getTypesFieldList(Class&amp;lt;? extends EnumModel&amp;gt; enums) {

        return Arrays.stream(enums.getEnumConstants())
                .map(EnumModel::getValue)
                .toList()
                .toString();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Test 에서만 사용할 util 클래스이기에 Test 패키지 내부에 만들었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ye4WO/btsFU7YbLwq/eBcb7V9kVa9H3hdhIVyMlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ye4WO/btsFU7YbLwq/eBcb7V9kVa9H3hdhIVyMlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ye4WO/btsFU7YbLwq/eBcb7V9kVa9H3hdhIVyMlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fye4WO%2FbtsFU7YbLwq%2FeBcb7V9kVa9H3hdhIVyMlk%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;1234&quot; height=&quot;492&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;darr;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Rest Docs&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;이후 util 클래스를 이용해 원하는 타입을 매개변수로 주입하면 모든 상태값을 출력해주도록 합니다.&lt;/li&gt;
&lt;li&gt;이렇게 하여 열거형 값이 추가, 변경, 삭제되어도 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기존의 코드를 수정하지 않아도 되는 구조&lt;/span&gt;를 만들 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;fieldWithPath(&quot;gender&quot;).description(String.format(&quot;성별 - %s&quot;, EnumDocsUtils.getTypesFieldList(Gender.class))),
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vm8uK/btsFUmuNWDl/GLuE0EsozmiMgNahnRNUiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vm8uK/btsFUmuNWDl/GLuE0EsozmiMgNahnRNUiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vm8uK/btsFUmuNWDl/GLuE0EsozmiMgNahnRNUiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVm8uK%2FbtsFUmuNWDl%2FGLuE0EsozmiMgNahnRNUiK%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;846&quot; height=&quot;238&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;238&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Test 를 위해 프로덕션 코드에 interface 를 추가하였지만,,, 문서화와 관련된 작업이고, 유지보수에 대한 공수를 굉장히 많이 줄여줄 수 있는 구조를 완성하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상화된 인터페이스에 key 와 value 로의 관리를 강제함으로써 Enum 클래스 자체의 값에 대해서도 변경을 용이하게 하는것 같아 나쁘지 않은 선택같았습니다.&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;코멘트는 언제나 환영입니다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>docs</category>
      <category>enum 문서</category>
      <category>java</category>
      <category>restdocs</category>
      <category>spring</category>
      <category>문서</category>
      <category>스프링</category>
      <category>스프링부트</category>
      <category>열거형</category>
      <category>자바</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/473</guid>
      <comments>https://thalals.tistory.com/473#entry473comment</comments>
      <pubDate>Tue, 19 Mar 2024 01:15:28 +0900</pubDate>
    </item>
    <item>
      <title>알고리즘 학습 플랫폼 코드 트리 후기</title>
      <link>https://thalals.tistory.com/472</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 오늘은 1달동안 사용해본 알고리즘 학습 플랫폼 코드트리를 소개해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.codetree.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.codetree.ai/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709202656819&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;코드트리 | 코딩테스트 준비를 위한 알고리즘 정석&quot; data-og-description=&quot;국가대표가 만든 코딩 공부의 가이드북 코딩 왕초보부터 꿈의 직장 코테 합격까지, 국가대표가 엄선한 커리큘럼으로 준비해보세요.&quot; data-og-host=&quot;www.codetree.ai&quot; data-og-source-url=&quot;https://www.codetree.ai/&quot; data-og-url=&quot;https://codetree.ai/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1Apbk/hyVuipoB6A/7SGtZ9f6J5AxKAWZcOds5K/img.png?width=3508&amp;amp;height=3508&amp;amp;face=0_0_3508_3508,https://scrap.kakaocdn.net/dn/K0LAE/hyVum6pvQ9/rSWH2sdWX9pOGE5oyQclkK/img.png?width=3508&amp;amp;height=3508&amp;amp;face=0_0_3508_3508&quot;&gt;&lt;a href=&quot;https://www.codetree.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.codetree.ai/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1Apbk/hyVuipoB6A/7SGtZ9f6J5AxKAWZcOds5K/img.png?width=3508&amp;amp;height=3508&amp;amp;face=0_0_3508_3508,https://scrap.kakaocdn.net/dn/K0LAE/hyVum6pvQ9/rSWH2sdWX9pOGE5oyQclkK/img.png?width=3508&amp;amp;height=3508&amp;amp;face=0_0_3508_3508');&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;코드트리 | 코딩테스트 준비를 위한 알고리즘 정석&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;국가대표가 만든 코딩 공부의 가이드북 코딩 왕초보부터 꿈의 직장 코테 합격까지, 국가대표가 엄선한 커리큘럼으로 준비해보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.codetree.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Code Tree&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드트리는 백준, 프로그래머스와 같은 알고리즘 학습 플랫폼입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드트리는 백준과 같이 GitHub 와 바로 연동되어 학습한 문제가 바로 Git 에 Commit되기도 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스처럼 단계별 알고리즘 학습이 체계적으로 나뉘어져 있어 굉장히 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 두 학습 플랫폼과 다르게 문제에 대한 &lt;b&gt;해설&lt;/b&gt;과 &lt;b&gt;틀린 테스트 케이스&lt;/b&gt;를 공개한다는 점이 저는 제일 만족스러웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Service&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 레벨 진단 및 단계별 학습 가이드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마치 수학을 공부하듯 학습 단계별로 체계적으로 알고리즘을 학습하도록 가이드가 나누어져 있습니다.&lt;/li&gt;
&lt;li&gt;이 단계또한 최소 4문제에서 ~ 최대11문제까지 풀수 있도록하여 꽤 정확도있게 측정이 된다는 느낌을 받았습니다.&lt;/li&gt;
&lt;li&gt;레벨 진단 또한 원할때 언제든 다시 받을 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0sY7G/btsFqX22mdi/s66pYpgeaxdk8GBuLTPok1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0sY7G/btsFqX22mdi/s66pYpgeaxdk8GBuLTPok1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0sY7G/btsFqX22mdi/s66pYpgeaxdk8GBuLTPok1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0sY7G%2FbtsFqX22mdi%2Fs66pYpgeaxdk8GBuLTPok1%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;800&quot; height=&quot;279&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSYfPP/btsFqYVa7TC/seeNfVXvWt27ZusFgM8CP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSYfPP/btsFqYVa7TC/seeNfVXvWt27ZusFgM8CP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSYfPP/btsFqYVa7TC/seeNfVXvWt27ZusFgM8CP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSYfPP%2FbtsFqYVa7TC%2FseeNfVXvWt27ZusFgM8CP0%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;800&quot; height=&quot;279&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;279&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;2) 자료구조 학습 개념 설명&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레벨진단 후 학습가이드를 따라가보면 먼저 해당알고리즘의 개념부터 설명해주고, 해당 알고리즘 문제의 유형에 대해 생각해볼 수 있도록 유도해주는 점이 좋았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pBoh5/btsFqLVUUv1/o1ngSJWcxXlFkBTslYMTak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pBoh5/btsFqLVUUv1/o1ngSJWcxXlFkBTslYMTak/img.png&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;700&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.8949%; margin-right: 10px;&quot; data-widthpercent=&quot;48.46&quot; id=&quot;kEditorPhotosEditingImage-2&quot; class=&quot;uploading&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pBoh5/btsFqLVUUv1/o1ngSJWcxXlFkBTslYMTak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpBoh5%2FbtsFqLVUUv1%2Fo1ngSJWcxXlFkBTslYMTak%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;702&quot; height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9sVUe/btsFlPyJUi1/aphQQFrTNd1q9tMQICzUMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9sVUe/btsFlPyJUi1/aphQQFrTNd1q9tMQICzUMk/img.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;750&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.54&quot; data-filename=&quot;blob&quot; style=&quot;width: 50.9423%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9sVUe/btsFlPyJUi1/aphQQFrTNd1q9tMQICzUMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9sVUe%2FbtsFlPyJUi1%2FaphQQFrTNd1q9tMQICzUMk%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;800&quot; height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 테스트 케이스 공개&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백준이나 프로그래머스를 사용하면서 가장 불편했던 점이 테스트 케이스를 직접 찾아서 디버깅을 해야한다는 점이었습니다.&lt;/li&gt;
&lt;li&gt;하지만 코드 트리는 통과되지 못한 테스트 케이스를 공개해준다는 점이 너무 만족스러웠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG2iIv/btsFlSa8MIL/c4aGoZkiDmK681OuqOm1M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG2iIv/btsFlSa8MIL/c4aGoZkiDmK681OuqOm1M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG2iIv/btsFlSa8MIL/c4aGoZkiDmK681OuqOm1M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG2iIv%2FbtsFlSa8MIL%2Fc4aGoZkiDmK681OuqOm1M0%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;800&quot; height=&quot;313&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 기업별 커리큘럼 및 다양한 학습 방법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아직 사용해 본 적은 없지만, 코드트리에서는 기업별 커리큘럼 알고리즘 문제를 제공해주고&lt;/li&gt;
&lt;li&gt;스프린트, 타이머, 기출 문제, 문제 은행 등 다양한 학습 방법을 제공해주고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NA1B0/btsFkV6U06D/1ss9WYNBVhgLpPQLotOGYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NA1B0/btsFkV6U06D/1ss9WYNBVhgLpPQLotOGYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NA1B0/btsFkV6U06D/1ss9WYNBVhgLpPQLotOGYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNA1B0%2FbtsFkV6U06D%2F1ss9WYNBVhgLpPQLotOGYk%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;800&quot; height=&quot;495&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;495&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Price&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 개인적인 생각으로는 백준이나 프로그래머스에 비해 많은 장점을 가지고 있지만, 별도의 비용이 발생한다는 점이 존재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n9IzJ/btsFn9QFDqi/MNOFMOJWQ8KjZ20wiEau8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n9IzJ/btsFn9QFDqi/MNOFMOJWQ8KjZ20wiEau8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n9IzJ/btsFn9QFDqi/MNOFMOJWQ8KjZ20wiEau8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn9IzJ%2FbtsFn9QFDqi%2FMNOFMOJWQ8KjZ20wiEau8K%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;732&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이상으로 개인적으로 직접 사용해보고 소개해본 코드트리에 대한글을 마치겠습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝..!&lt;/p&gt;</description>
      <category>기타 애매한 것</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/472</guid>
      <comments>https://thalals.tistory.com/472#entry472comment</comments>
      <pubDate>Thu, 29 Feb 2024 20:35:39 +0900</pubDate>
    </item>
    <item>
      <title>AWS EC2 auto scaling 이해하기</title>
      <link>https://thalals.tistory.com/471</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;AWS 단일 EC2 서버에 Auto Scaling 을 적용하기전 공식문서를 읽고 정리한 글 입니다.&lt;/blockquote&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 생태계에서 EC2 Auto Scaling 은 작은단위의 서비스로써, AWS의 다양한 서비스에 거의대부분 적용되는 서비스 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈식으로 서로서로 연계되는 AWS에서 Auto Scaling 알아보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[목차]&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Auto Scaling 을 사용하고자 하는 이유
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Scale up 과 Scale Out&lt;/li&gt;
&lt;li&gt;Blue Green 배포&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Auto Scaling 이란&lt;/li&gt;
&lt;li&gt;Auto Scaling 특징 및 장단점&lt;/li&gt;
&lt;li&gt;AWS EC2 Auto Scalig Group 적용 하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Auto Scaling 을 사용하고자 하는 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Scale up 과 Scale Out&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라를 확장하는 2가지 개념&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;scale up : 인프라 자체의 성능을 끌어올린다. (용량 증가, cpu 성능증가 등 &amp;rarr; 간편하지만 비용이 많이들고 비효율적이다.)&lt;/li&gt;
&lt;li&gt;scale out : 인프라를 확장한다. (1개의 서버 &amp;rarr;&amp;nbsp; 2개의 서버, 이런식으로 분산처리할 수 있도록 확장한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Blue Green 배포&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS Code Deploy 의 Blue/Grean 배포 방식을 사용하여 무중단 서비스 배포 인프라 환경을 구축하기 위해서는&lt;br /&gt;AWS EC2 Auto Scaling 이 적용되어야 합니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Auto Scaling 이란&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS 공식문서에 의하면, Auto Scaling 이란 AWS 에서 지원하는 EC2의 가용 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;인스턴스 수를 조절&lt;/span&gt;하는 기능이며,&lt;/li&gt;
&lt;li&gt;이를 Auto Scaling group으로 설정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;EC2 Auto Scaling Group에서는 최소 인스턴스와 최대 인스턴스 수를 정할 수 있으면 EC2의 가용 범위는 이를 넘지 않도록 조절한다고 합니다.&lt;/li&gt;
&lt;li&gt;즉 AWS Load Balancing과 함께 사용한다면 들어오는 트래픽에 따라서, 트래픽을 받아줄 EC2의 물리서버 자원을 줄이거나, 늘려서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;가용성을 확보할 수 있도록 하는 서비스입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v5Lq4/btsFipAlSmS/0R5KiIPSqEMJdLnlNCXUu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v5Lq4/btsFipAlSmS/0R5KiIPSqEMJdLnlNCXUu0/img.png&quot; data-alt=&quot;AWS Ec2 Auto Scaling group 가용성 예시 사진&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v5Lq4/btsFipAlSmS/0R5KiIPSqEMJdLnlNCXUu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv5Lq4%2FbtsFipAlSmS%2F0R5KiIPSqEMJdLnlNCXUu0%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;500&quot; height=&quot;341&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS Ec2 Auto Scaling group 가용성 예시 사진&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. Auto Scaling 특징 및 장단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS EC2 Auto Scaling Group을 사용하여 인스턴스환경을 구성한다면, EC2 인스턴스가 Auto Scaling 그룹으로 묶여 관리되어, 1개의 물리서버(인스턴스)를 논리적 단위로 취급할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이에 대한 장점은 아래와 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;비용 무료&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Auto Scaling 자체의 비용은 무료이며, 사용한 EC2 인스턴스 물리서버의 비용만 지불하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행중인 인스턴스의 상태 모니터링&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;auto scaling group을 설정하면 EC2 상태와 가용성을 자동으로 모니터링하여 사용자가 설정한 적정한 양의 정상적인 인스턴스 수를 유지하도록 조절한다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가용영역 간 균형 조정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS 는 각 지역(Region) 간 독립된 가용영역을 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;Auto Scaling Group으로 어떠어떠한 가용영역들을 사용할 것인지 선택하면 각 가용영역을 고르게 사용할 수 있도록 조정한다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로드 밸런싱 및 확장성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;Elastic Load Balancing 또는 VPC Lattice 로드 밸런싱 및 상태 확인을 사용하여 애플리케이션 트래픽을 정상 인스턴스에 고르게 분배할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;또한 이에 맞춰 필요한 용량을 줄였다가 늘렸다를 할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인스턴스 새로 고침&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AMI 또는 시작 템플릿을 업데이트할 때 순차적으로 인스턴스를 업데이트하는 메커니즘을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style4&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;비용 오버슈팅의 가능성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Auto Scaling을 사용하여 효과적으로 확장성과 가용성을 확보할 수 있지만, 예상밖의 이슈로 갑작스러운 오토스케일링이 촉발될 수 있는 경우가 있고 이로인한 폭발적인 요금증가가 생길 수도 있다고 합니다.&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;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스케일링 작업을 하는 동안 Ratency(지연 시간)이 늘어나고 장애가 발생할 확률이 존재한다고 합니다.&lt;/li&gt;
&lt;li&gt;이로인해 동적으로 리소스를 할당하기에 발생되는 기본적인 성능저하가 있을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Infra/AWS</category>
      <category>auto scaling group</category>
      <category>AWS</category>
      <category>EC2</category>
      <category>Scale-out</category>
      <category>Scale-up</category>
      <category>서버 확장</category>
      <category>서버확장</category>
      <category>스케일아웃</category>
      <category>아마존</category>
      <category>오토스케일링</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/471</guid>
      <comments>https://thalals.tistory.com/471#entry471comment</comments>
      <pubDate>Wed, 28 Feb 2024 18:23:53 +0900</pubDate>
    </item>
    <item>
      <title>Jenkins &amp;rarr; GitHub Action 이전기 (GitHub Action으로 AWS CICD 구축 하기, AWS Code Deploy)</title>
      <link>https://thalals.tistory.com/470</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이번글은 GitAction 의 사용 방법에 대한 기록입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;현재 사이드 프로젝트의 CI/CD 프로세서 구조는 Jenkins 를 활용하고있습니다.&lt;/li&gt;
&lt;li&gt;GItHub Merge &amp;rarr; Jenkins (Main Branch Build &amp;amp; Jar file deploy) &amp;rarr; ec2 shell script 실행 의 구조인데&lt;br /&gt;Jenkins 를 사용하면 몆가지 불편한 점이 생겨 Git Action을 사용하는 방안으로 변경해보고자 합니다.&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;size18&quot;&gt;&lt;b&gt;불편했던 점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;EC2 프리티어가 끝나고, test 코드가 많아지면서 빌드시간과 용량이 커져 비용이 늘어가는게 보인다.&lt;/li&gt;
&lt;li&gt;Jenkins 서버가 메모리 과부하로 종료되는 경우가 발생해 복구하는게 번거롭다&lt;/li&gt;
&lt;li&gt;Jenkins 자체적으로 Job의 다양한 구성방식이 존재해 여러 Job 의 관리가 번거롭다고 느껴진다. (각 Job의 구성방식이 다를경우)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;GitHub Action 을 사용하려는 이유&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용하는 Repository가 Public 일 경우 무료이다. (Private 일 경우, 1달에 500MB, 2,000분까지 무료)&lt;/li&gt;
&lt;li&gt;Jenkins 와 달리 GitHub Action 은 자체적인 컴퓨팅 자원을 제공해주어 따로 물리서버를 관리하지 않아도 된다.&lt;/li&gt;
&lt;li&gt;GitHub Action 의 컴퓨팅 스펙은 아래와 같아 사이드 프로젝트에 적용하기에 충분하고도 남는다고 생각이 들었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2-core CPU&lt;/li&gt;
&lt;li&gt;7 GB of RAM memory&lt;/li&gt;
&lt;li&gt;14 GB of SSD disk space&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Action 사용시 해당 Repo 에 대한 CI/CD 관리가 GitHub Repository 한 공간에서 관리할 수 있다.&lt;/li&gt;
&lt;li&gt;YAML 파일 하나로 구축할 수 있고, Git Repo에서 CI/CD 구축 과정에대한 버전관리가 가능해진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 사용하던 Jenkins 를 접고 GitHub Action으로 넘어가면서, 해당 과정을 기록하고자 합니당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[목차]&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GitHub Action 이란&lt;/li&gt;
&lt;li&gt;GitHub Action Workflow 생성하기&lt;/li&gt;
&lt;li&gt;Github Actions Workflows 구성하기&lt;/li&gt;
&lt;li&gt;Github Actions - Git Submodule 연동&lt;/li&gt;
&lt;li&gt;Github Action 배포
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Github Action 에서 AWS 에 Access 하기&lt;/li&gt;
&lt;li&gt;Github action으로 aws s3 upload 하기&lt;/li&gt;
&lt;li&gt;Github action 에서 AWS Code Deploy 실행하기&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Github Action 종속성 캐시 설정하기&lt;/li&gt;
&lt;li&gt;github action yml 전체 코드&lt;/li&gt;
&lt;li&gt;AWS CodeDeploy BlockTraffic 시간 단축&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. GitHub Action 이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Actions은 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 CI/CD(지속적 통합 및 지속적 전달) 플랫폼입니다.&lt;span&gt;&amp;nbsp;&lt;br /&gt;&lt;/span&gt;리포지토리에 대한 모든 풀 요청을 빌드 및 테스트하거나 병합된 풀 요청을 프로덕션에 배포하는 워크플로를 생성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 즉, GitHub Action 이란 Github에서 제공해주는 CI/CD 파이프라인 플랫폼 서비스 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN8kW7/btsEWzv79Cn/iAvRGqRLWlNzQkDKSekgS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN8kW7/btsEWzv79Cn/iAvRGqRLWlNzQkDKSekgS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN8kW7/btsEWzv79Cn/iAvRGqRLWlNzQkDKSekgS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN8kW7%2FbtsEWzv79Cn%2FiAvRGqRLWlNzQkDKSekgS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;241&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;241&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. GitHub Action Workflow 생성하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 생성을 원한는 Repository의 Actions 탭에 들어갑니다.&lt;/li&gt;
&lt;li&gt;처음부터 작성할 예정이면 &quot;set up a workflow yourself&quot; 를 클릭하시면되고, 어느정도 구성된 파일을 원한다면 아래 템플릿도 존재합니다.&lt;/li&gt;
&lt;li&gt;저는 [Publish Java Package with Gradle] 템플릿을 클릭해보았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2420&quot; data-origin-height=&quot;1428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3M8Xf/btsEtlSYNyh/YTrtmzCcYBCym2Um6Kxx8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3M8Xf/btsEtlSYNyh/YTrtmzCcYBCym2Um6Kxx8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3M8Xf/btsEtlSYNyh/YTrtmzCcYBCym2Um6Kxx8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3M8Xf%2FbtsEtlSYNyh%2FYTrtmzCcYBCym2Um6Kxx8k%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;2420&quot; height=&quot;1428&quot; data-origin-width=&quot;2420&quot; data-origin-height=&quot;1428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Action 을 사용하기 위해서는 해당 Repository 의 [./github/workflows] 폴더안에 Yml 파일이 존재해야합니다.&lt;br /&gt;해당 과정은 그걸 위함입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 GitHub 에서 바로 README.md 파일을 생성하든 YML 파일이생성되네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋 후 Actions 탭에 들어가보니 당연하게도 워크플로우 실행기록 또한 비어있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6a3Na/btsEuPMM2vC/OA2h7ZsmK9oQjwsP7iAGX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6a3Na/btsEuPMM2vC/OA2h7ZsmK9oQjwsP7iAGX0/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;674&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;43.65&quot; data-filename=&quot;blob&quot; style=&quot;width: 43.138%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6a3Na/btsEuPMM2vC/OA2h7ZsmK9oQjwsP7iAGX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6a3Na%2FbtsEuPMM2vC%2FOA2h7ZsmK9oQjwsP7iAGX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cClpE4/btsEqvOPvxD/1sXjKJcTuFb3l8UvWXUeh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cClpE4/btsEqvOPvxD/1sXjKJcTuFb3l8UvWXUeh1/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;522&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;56.35&quot; data-filename=&quot;blob&quot; style=&quot;width: 55.6992%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cClpE4/btsEqvOPvxD/1sXjKJcTuFb3l8UvWXUeh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcClpE4%2FbtsEqvOPvxD%2F1sXjKJcTuFb3l8UvWXUeh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Github Actions Workflows 구성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 이제 Main Branch 에 &lt;b&gt;Merge&lt;/b&gt; 혹은 &lt;b&gt;Push&lt;/b&gt; 했을 경우 GIt Actions 이 실행되면서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;build &amp;rarr; test &amp;rarr; deploy&lt;/span&gt; 가 되도록 workflow를 작성해보고자 합니다.&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;1) Name&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name : 해당 워크플로우에 대한 설명, 주석과 같은 느낌&lt;/li&gt;
&lt;li&gt;run-name : 옵션 선택사항이며, github actions 의 실행 리스트에서 표시되는 내용이라고 합니다. 해당 작업자가 누구인지 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buVfrU/btsExSaYXku/HhVVDcgl8HSa9ChOxUZn01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buVfrU/btsExSaYXku/HhVVDcgl8HSa9ChOxUZn01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buVfrU/btsExSaYXku/HhVVDcgl8HSa9ChOxUZn01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuVfrU%2FbtsExSaYXku%2FHhVVDcgl8HSa9ChOxUZn01%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;1458&quot; height=&quot;120&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) On&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;github actions 가 실행될 트리거를 설정하는 문법입니다.&lt;/li&gt;
&lt;li&gt;다양한 옵션이 있으며, 특정 PR 에 발행된 Tag 로 필터링 한다든지 등 다양한 옵션으로 트리거를 설정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;현재 해당 프로젝트는 아직 런칭하지 않았기 때문에 빠른 작업을 위해 Main Branch 에 Push (Merge 포함) 로 트리거를 설정해두었습니다.&lt;/li&gt;
&lt;li&gt;다른 예제 링크 : &lt;a href=&quot;https://docs.github.com/ko/actions/using-workflows/workflow-syntax-for-github-actions#on&quot;&gt;docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSb17a/btsEtr6sK5m/HSNN1H62ru31nclFKq8hpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSb17a/btsEtr6sK5m/HSNN1H62ru31nclFKq8hpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSb17a/btsEtr6sK5m/HSNN1H62ru31nclFKq8hpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSb17a%2FbtsEtr6sK5m%2FHSNN1H62ru31nclFKq8hpK%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;1468&quot; height=&quot;186&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style4&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) Jobs&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jobs : 워크플로우에 실행되는 모든 작업을 그룹화합니다.&lt;/li&gt;
&lt;li&gt;runs-on : 해당 작업들을 실행할 가상머신을 지정하는 구문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mpBGc/btsEvgDmEXU/a5mhMTAbwtQgF3HzWsXLW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mpBGc/btsEvgDmEXU/a5mhMTAbwtQgF3HzWsXLW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mpBGc/btsEvgDmEXU/a5mhMTAbwtQgF3HzWsXLW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmpBGc%2FbtsEvgDmEXU%2Fa5mhMTAbwtQgF3HzWsXLW0%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;1850&quot; height=&quot;318&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) Steps&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Actions 에서 실행되는 작업의 단계를 그룹화하는 구문입니다.&lt;/li&gt;
&lt;li&gt;uses : steps 하위의 &quot;uses&quot; 키워드를 통해서 각 단계의 실행 작업을 지정하는 것 같습니다.&lt;/li&gt;
&lt;li&gt;템플릿에 설정된 내용을 살펴보니 jdk 17 &amp;amp; gradle 을 설정한 후 해당 repository 를 빌드하는 것 같아 &lt;br /&gt;하위의 [./gradlew build] &amp;rarr; [./gradlew clean build] 로만 수정해 주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2052&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pxqYr/btsEqxy8P6h/qTnS8KCqiDFRL0Xh3uHK41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pxqYr/btsEqxy8P6h/qTnS8KCqiDFRL0Xh3uHK41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pxqYr/btsEqxy8P6h/qTnS8KCqiDFRL0Xh3uHK41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpxqYr%2FbtsEqxy8P6h%2FqTnS8KCqiDFRL0Xh3uHK41%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;2052&quot; height=&quot;614&quot; data-origin-width=&quot;2052&quot; data-origin-height=&quot;614&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;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4. Github Actions - Git Submodule 연동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 이제는 주요한 계정정보가 담겨있는 Private 저장소인 Git Submodule 을 연동해보고자 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1161&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2kj4M/btsEttQQ0MY/nn2vuh0heikg8lHdKJnuO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2kj4M/btsEttQQ0MY/nn2vuh0heikg8lHdKJnuO1/img.png&quot; data-alt=&quot;git submodule repo&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2kj4M/btsEttQQ0MY/nn2vuh0heikg8lHdKJnuO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2kj4M%2FbtsEttQQ0MY%2Fnn2vuh0heikg8lHdKJnuO1%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;1161&quot; height=&quot;155&quot; data-origin-width=&quot;1161&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;git submodule repo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 간단하게 작업할 수 있습니다. (이게 Github action의 GitHub에 대한 연동성 장점이 아닐까요..?)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;steps 에서 Check Out 작업 시점에 설정할 수 있었습니다.&lt;/li&gt;
&lt;li&gt;[&lt;b&gt;with&lt;/b&gt;] 키워드의 하위에 &lt;b&gt;submodules: true&lt;/b&gt; 를 통해 Submodules 을 연동할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UXMex/btsEtkmiwd8/Yl1H0hmAOavebpY9q2DDWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UXMex/btsEtkmiwd8/Yl1H0hmAOavebpY9q2DDWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UXMex/btsEtkmiwd8/Yl1H0hmAOavebpY9q2DDWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUXMex%2FbtsEtkmiwd8%2FYl1H0hmAOavebpY9q2DDWk%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;893&quot; height=&quot;135&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. token 은 Git Action 에서 Private Repository 에 접근하기 위해 필요한 값입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 Repository 의 [Setting - Security - Secretes and variables - actions] 탭에서 만들 수 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yxXH1/btsEy4BJ8V1/SOPmnWrP3bvWWBa2zFqXgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yxXH1/btsEy4BJ8V1/SOPmnWrP3bvWWBa2zFqXgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yxXH1/btsEy4BJ8V1/SOPmnWrP3bvWWBa2zFqXgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyxXH1%2FbtsEy4BJ8V1%2FSOPmnWrP3bvWWBa2zFqXgk%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;966&quot; height=&quot;205&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;205&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. Github Action 배포&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;배포는 AWS CodeDeploy 서비스를 사용하여 진행하고자 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ &lt;b&gt;AWS CodeDeploy&lt;/b&gt; 는 &lt;span style=&quot;color: #ee2323;&quot;&gt;aws 환경에서는 무료&lt;/span&gt;(s3 의 용량또한 미미함)이며, &lt;a href=&quot;https://docs.aws.amazon.com/codedeploy/latest/userguide/welcome.html#welcome-deployment-overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;blue-grean 배포 방식&lt;/span&gt;&lt;/a&gt;을 선택하여 실행할 수 있기때문에 무중단 배포에 가깝다는 장점이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[과정]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;git actions build &amp;rarr; make jar&lt;/li&gt;
&lt;li&gt;s3 uploade (버전 관리)&lt;/li&gt;
&lt;li&gt;code deploy 실행 &amp;rarr; s3 결과 가져와서 EC2 배포&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzvYd5/btsEEG8y3z2/8ARYJ95aTYZa2aU46RFd10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzvYd5/btsEEG8y3z2/8ARYJ95aTYZa2aU46RFd10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzvYd5/btsEEG8y3z2/8ARYJ95aTYZa2aU46RFd10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzvYd5%2FbtsEEG8y3z2%2F8ARYJ95aTYZa2aU46RFd10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;303&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Code Deploy에 대한 자세한 설명은 생략하도록 하겠습니다. (블로그 참고 : &lt;a href=&quot;https://velog.io/@server30sopt/CodeDeploy-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B3%A0-%ED%91%B8%EC%89%AC-%EC%84%9C%EB%B2%84%EC%99%80-API-%EC%84%9C%EB%B2%84-%EB%B6%84%EB%A6%AC%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;code deploy설명 글&lt;/a&gt; )&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) Github Action 에서 AWS 에 Access 하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;깃허브 액션의 워크플로우부터 설정해보면 아래와 같은 Job 단계가 필요합니다.&lt;/li&gt;
&lt;li&gt;AWS의 민감한 정보가 포함되니 위와같이 GitHub Action Secret 에 저장해주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVzQHe/btsEGh3ZfZa/qNXB7u1Vftq19zbkb7owKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVzQHe/btsEGh3ZfZa/qNXB7u1Vftq19zbkb7owKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVzQHe/btsEGh3ZfZa/qNXB7u1Vftq19zbkb7owKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVzQHe%2FbtsEGh3ZfZa%2FqNXB7u1Vftq19zbkb7owKK%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;1672&quot; height=&quot;298&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #212529; text-align: start;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;- AWS_REGION : 배포 환경이 존재하는 (EC2) 지역 위치
- AWS_ACCESS_KEY_ID : IAM 사용자 엑세스 키 ID
- AWS_SECRET_ACCESS_KEY : IAM 사용자 Secret 엑세스 키&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  AWS 에서 IAM 유저를 생성해 줄때에는 [AWSCodeDeployFullAccess, AmazonS3FullAccess] 이 2개의 정책을 할당해 주어야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) Github action으로 aws s3 upload 하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ AWS Configure 설정을 마쳤다면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;AWS cli&lt;/span&gt; 명령어를 사용할 수 있습니다.&lt;br /&gt;따라서, s3 에 upload 하고 ec2 deploy 하는 방법은 꽤 다양하게 할 수 있습니다. (참고 : &lt;a href=&quot;https://awscli.amazonaws.com/v2/documentation/api/latest/index.html#&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;aws cli docs&lt;/a&gt;)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[aws cli s3 upload 예시]&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적에 따라 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;`sync`&lt;/span&gt; 와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;`cp`&lt;/span&gt; 2가지 명령어를 사용하여 upload 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;저는 이왕 s3 를 사용하는겸 배포 버전관리를 위해 cp 를 이용하기로 했습니다. (s3 자체 설정으로 파일의 생명주기도 설정 할 수 있습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW3dZd/btsETiAVrBH/2blwbVfKKWkDb8XVazuvv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW3dZd/btsETiAVrBH/2blwbVfKKWkDb8XVazuvv1/img.png&quot; data-alt=&quot;yml&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW3dZd/btsETiAVrBH/2blwbVfKKWkDb8XVazuvv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW3dZd%2FbtsETiAVrBH%2F2blwbVfKKWkDb8XVazuvv1%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;1076&quot; height=&quot;66&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;yml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1708005905850&quot; class=&quot;groovy&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 서버의 로그들을 s3의 버킷에 동기화
aws s3 sync ./storage/logs s3://s3domain.com/logs

// 서버의 특정파일을 s3의 버킷에 복사 (이름지정해서 복사가능)
aws s3 cp ./storage/logs/mylog.log s3://s3domain.com/logs/mylog-cp.log

// 현재날짜 넣어서 복사
aws s3 cp ./storage/logs/mylog.log s3://s3domain.com/logs/mylog-`date +%Y%m%d`.log

// 현재날짜 넣어서 복사 (위와 동일함)
aws s3 cp ./storage/logs/mylog.log s3://s3domain.com/logs/mylog-`date +%F`.log&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T4DsN/btsEVhaqpKl/TjbJt7Pp03jNBkefjyEfmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T4DsN/btsEVhaqpKl/TjbJt7Pp03jNBkefjyEfmk/img.png&quot; data-alt=&quot;AWS cli 를 이용하여 s3 에 upload&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T4DsN/btsEVhaqpKl/TjbJt7Pp03jNBkefjyEfmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT4DsN%2FbtsEVhaqpKl%2FTjbJt7Pp03jNBkefjyEfmk%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;447&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS cli 를 이용하여 s3 에 upload&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3) Github action 에서 AWS Code Deploy 실행하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 s3 에 올라간 jar 파일을 Code deploy를 이용하여 배포하고자 합니다.&lt;/li&gt;
&lt;li&gt;Code Deploy를 사용하는 방법도 여러가지가 있으며, 제가 하고자하는 s3를 사용하여 EC2 에 즉시 배포하는 방법은 몆가지 조건이 필요합니다.&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;b&gt;github action workflow&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;조건을 맞추기 전에 먼저, github action 에서 CI 완료 후 code deploy 를 실행시키기 위한 AWS CLI를 작성해봅시다&lt;/li&gt;
&lt;li&gt;간단합니다. 먼저 AWS 콘솔에서 Code deploy 어플리케이션과 배포그룹을 만들어주어야 합니다.&lt;/li&gt;
&lt;li&gt;이후 [aws deploy create-deployment] 명령어를 사용해 위해서 작업했던 S3 에 저장된 파일을 사용해 Code Deploy 배포를 생성해 실행해 주는 과정을 거칩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;- name: Deploy EC2
  run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=${{ secrets.S3_DEPLOY_BUCKET }},key=deployfile-${{ steps.now.outputs.date }}.zip,bundleType=zip
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;조건&lt;/i&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Code Deploy&lt;/b&gt; 에서 사용할 &lt;u&gt;s3 에 저장된 파일은 압축 파일 형식&lt;/u&gt;일 것 (zip, tar, tar.gz, tgz)&lt;/li&gt;
&lt;li&gt;저장된 파일안에는 [실행할 어플리케이션, AppSpec File(appspec.yml)]이 포함될 것 (&lt;a href=&quot;https://docs.aws.amazon.com/codedeploy/latest/userguide/application-revisions-appspec-file.html#add-appspec-file-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고1&lt;/a&gt;, &lt;a href=&quot;https://docs.aws.amazon.com/codedeploy/latest/userguide/application-revisions-push.html#push-with-cli&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고2&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;배포할 EC2 물리서버에는 Code Deploy Agent 서비스가 다운받아져 있을 것 (&lt;a href=&quot;https://docs.aws.amazon.com/codedeploy/latest/userguide/codedeploy-agent.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;✔️ 1번 조건 (s3 압축파일 업로드)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자 원래는 jar파일만을 올리고 싶었으나, 어차피 github action 도 무료(조건부) - code deploy도 ec2 배포면 무료, s3 에 압축파일 올리면 용량도 얼마 안나갈거 같아서,, 프로젝트를 통으로 압축해서 올리고자 합니다.&lt;/li&gt;
&lt;li&gt;그래도 버전관리를 위해 Github action 에 Date 환경변수를 설정하여 넘겨주었습니다. (Time zone 을 설정하지 않으면 utc 기준입니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vYJwK/btsE0hHwVqJ/OKoN6hEb7zhAXyB54kEMG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vYJwK/btsE0hHwVqJ/OKoN6hEb7zhAXyB54kEMG0/img.png&quot; data-alt=&quot;zip 파일 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vYJwK/btsE0hHwVqJ/OKoN6hEb7zhAXyB54kEMG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvYJwK%2FbtsE0hHwVqJ%2FOKoN6hEb7zhAXyB54kEMG0%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;722&quot; height=&quot;323&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;323&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;zip 파일 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/emgrPW/btsEZnnRPKQ/YEkVU5Iww8m0CebscwUwik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/emgrPW/btsEZnnRPKQ/YEkVU5Iww8m0CebscwUwik/img.png&quot; data-alt=&quot;생성된 파일 s3 업로드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/emgrPW/btsEZnnRPKQ/YEkVU5Iww8m0CebscwUwik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FemgrPW%2FbtsEZnnRPKQ%2FYEkVU5Iww8m0CebscwUwik%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;1101&quot; height=&quot;77&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생성된 파일 s3 업로드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;✔️ 2번 조건 (appspec file 추가)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고 : &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/reference-appspec-file-structure-files.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/reference-appspec-file-structure-files.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;프로젝트를 통으로 압축하기로 했으니, 프로젝트 최상단 루트경로에 appspec file 을 추가해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EGoEl/btsE0rpKjR4/K7rY8qjesxMVpMQyOdsUgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EGoEl/btsE0rpKjR4/K7rY8qjesxMVpMQyOdsUgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EGoEl/btsE0rpKjR4/K7rY8qjesxMVpMQyOdsUgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEGoEl%2FbtsE0rpKjR4%2FK7rY8qjesxMVpMQyOdsUgk%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;274&quot; height=&quot;391&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1708102768232&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: 0.0
os: linux

files:
  - source: /
    destination: /home/ec2-user/directory
permissions:
  - object: /home/ec2-user/directory/
    owner: ec2-user
    group: ec2-user
hooks:
  AfterInstall:
    - location: deploy.sh
      timeout: 60
      runas: ubuntu&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;version&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;appspec.yml 파일 버전을 정의합니다. 현재는 0.0 이외의 버전이 지원되지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;files&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: left;&quot;&gt;배포할 파일 및 디렉토리를 정의합니다. &lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: left;&quot;&gt;source : 복사할 파일이 위치한 경로입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: left;&quot;&gt;destination : EC2 서버내 복사될 경로 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;permissions&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: left;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;애플리케이션 파일에 대한 권한을 정의합니다. 위의 예시에서는 &quot;&lt;b&gt;/home/{계정 이름}/directory&quot;&lt;/b&gt; 디렉토리의 그룹과 소유자를 설정합니다. (root 라면 ubuntu)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: left;&quot;&gt;&lt;b&gt;hooks&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: left;&quot;&gt;배포 단계에서 실행할 훅 스크립트를 정의합니다. 위의 예시에서는 배포 후 실행할 스크립트인&amp;nbsp;&lt;b&gt;deploy.sh&lt;/b&gt;&amp;nbsp;파일을 지정하고, 스크립트 실행 시간 제한을 60초로 설정합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #374151;&quot;&gt;AfterInstall&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;여러 배포 단계 중&amp;nbsp;&lt;b&gt;AfterInstall&lt;/b&gt;&amp;nbsp;단계에서 스크립트를 실행합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #374151;&quot;&gt;&lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;deploy.sh&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;#! /bin/sh

echo &quot;Java Version&quot;
java -version

echo &quot;Start Spring Boot Application!&quot;
CURRENT_PID=$(ps -ef | grep chatserver-0.0.1-SNAPSHOT.jar | grep java | awk '{print $2}')

echo $CURRENT_PID

if [ -z $CURRENT_PID ]; then
        echo &quot;&amp;gt;현재 구동중인 어플리케이션이 없으므로 종료하지 않습니다.&quot;

else
        echo &quot;&amp;gt; kill -15 $CURRENT_PID&quot;
        kill -15 $CURRENT_PID
        sleep 10
fi

echo &quot;&amp;gt;어플리케이션 배포 진행!&quot;

nohup java -jar ~/deploy/chatserver-0.0.1-SNAPSHOT.jar &amp;gt;&amp;gt; ~/deploy/logs/$(date '+%Y-%m-%d')_api.log 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;✔️ 3번 조건 (code deploy agent 설정)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2에 CodeDeploy로 지정한 위치에서 파일을 받아 실행하기 위해서는 Code Deploy Agent가 설치되있어야만 합니다.&lt;/li&gt;
&lt;li&gt;EC2에 콘솔로 접속한 후 아래의 명령어를 순서대로 입력해줍니다.&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;(1) Agent 설치파일을 다운&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot;&gt;&lt;code&gt;wget https://aws-codedeploy-ap-northeast-2.s3.amazonaws.com/latest/install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(2) &lt;span style=&quot;text-align: start;&quot;&gt;해당 파일에 실행권한을&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;추가&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cmake&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot;&gt;&lt;code&gt;chmod +x ./install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 설치 진행&lt;/p&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot;&gt;&lt;code&gt;sudo ./install auto&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 만약 `ruby ./ no such file or directory` 에러 문구가 발생한다면 해당 파일이 ruby로 이루어져있으므로 ruby 를 다운받아야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1708331022781&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt install ruby&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4) code deploy agent 실행 확인&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot;&gt;&lt;code&gt;sudo service codedeploy-agent status&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBOR5l/btsE6Jc41Sb/Av3oyQ6MFWbyjlnzYNk7I0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBOR5l/btsE6Jc41Sb/Av3oyQ6MFWbyjlnzYNk7I0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBOR5l/btsE6Jc41Sb/Av3oyQ6MFWbyjlnzYNk7I0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBOR5l%2FbtsE6Jc41Sb%2FAv3oyQ6MFWbyjlnzYNk7I0%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;653&quot; height=&quot;80&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(5) EC2 인스턴스 재부팅 시, 자도응로 agent 실행하도록 Sh 파일 생성&lt;/p&gt;
&lt;pre class=&quot;vim&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot;&gt;&lt;code&gt;sudo vim /etc/init.d/codedeploy-startup.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트 내용 작성&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot;&gt;&lt;code&gt;#!/bin/bash 
echo 'Starting codedeploy-agent' 
sudo service codedeploy-agent restart&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행권한 추가&lt;/p&gt;
&lt;pre class=&quot;awk&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot;&gt;&lt;code&gt;sudo chmod +x /etc/init.d/codedeploy-startup.sh&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6. 종속성 캐시&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고 : &lt;a href=&quot;https://docs.github.com/ko/actions/using-workflows/about-workflows#caching-dependencies&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.github.com/ko/actions/using-workflows/about-workflows#caching-dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;현재 프로젝트가 작아 build 속도가 그렇게 느리지 않지만 재밌는 문서를 봐서 미리 적용해 보고자합니다.&lt;/li&gt;
&lt;li&gt;gradle 의 반복적으로 다운되는 종속성들을 미리 캐싱해두어 github action에서 build 속도를 개선하는 방법입니다.&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;b&gt;&lt;i&gt;workflow&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;path : 캐싱할 경로 - In Action 이 모두 끝나고 Post Action 에서 이 경로에 있는 모든 데이터를 캐싱&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;key : 캐시 데이터를 구분하기 위한 키 - In Action 에서 캐시 데이터를 restore 할 때 사용&amp;nbsp;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;restore-keys: restore 를 못했을 경우 다른 키를 사용하기 위한 대체키&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;- name: Cache with Gradle
  uses: actions/cache@v3
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: ${{ github.repository }}-gradle-${{ hashFiles('**/*.gradle*') }}
    restore-keys: |
      ${{ github.repository }}-gradle-&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 적용하고 git action을 돌리면 아래 사진과 같이 &lt;span style=&quot;color: #333333; text-align: start; background-color: #f6e199;&quot;&gt;Cache not found for input keys &lt;/span&gt;로그가 나타납니다.&amp;nbsp;&lt;br /&gt;이는 에러가 아닌 첫 액션때 캐시 히트가 되지 않아서 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;409&quot; data-origin-height=&quot;28&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCBGE/btsE6B7onAc/TAOnQeMU49aKa6X0Q0PXnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCBGE/btsE6B7onAc/TAOnQeMU49aKa6X0Q0PXnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCBGE/btsE6B7onAc/TAOnQeMU49aKa6X0Q0PXnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCBGE%2FbtsE6B7onAc%2FTAOnQeMU49aKa6X0Q0PXnk%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;409&quot; height=&quot;28&quot; data-origin-width=&quot;409&quot; data-origin-height=&quot;28&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번째 acition부터는 이렇게 github 에 저장된 캐시 정보를 불러올 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1035&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKL2Qs/btsE9HAvBqs/zB7WxkTEmbL5ekLRrrfKNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKL2Qs/btsE9HAvBqs/zB7WxkTEmbL5ekLRrrfKNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKL2Qs/btsE9HAvBqs/zB7WxkTEmbL5ekLRrrfKNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKL2Qs%2FbtsE9HAvBqs%2FzB7WxkTEmbL5ekLRrrfKNK%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;1035&quot; height=&quot;156&quot; data-origin-width=&quot;1035&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 캐시항목을 보고싶다면 [action -&amp;gt; cache] 에서 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7L9QX/btsFbutwZqi/i8z69wszDy9x37kHCJ1Lo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7L9QX/btsFbutwZqi/i8z69wszDy9x37kHCJ1Lo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7L9QX/btsFbutwZqi/i8z69wszDy9x37kHCJ1Lo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7L9QX%2FbtsFbutwZqi%2Fi8z69wszDy9x37kHCJ1Lo0%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;1628&quot; height=&quot;399&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;399&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;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. github action yml 전체 코드&lt;/h2&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle

name: THT ChatServer
run-name: ${{ github.actor }} is learning GitHub Actions

on:
  push:
    branches:
      - main
env:
  AWS_CODE_DEPLOY_APPLICATION: aws-code-deploy-appliacation-name
  AWS_CODE_DEPLOY_GROUP: aws-code-deploy-appliacation-deploy-group-name

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: CheckOut
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.ACTION_TOKEN }}
          submodules: true

      - name: Set up JDK 17
        uses:
          actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
          settings-path: ${{ github.workspace }} # location for the settings.xml file

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0

      - name: Cache with Gradle
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ github.repository }}-gradle-${{ hashFiles('**/*.gradle*') }}
          restore-keys: |
            ${{ github.repository }}-gradle-
          
      - name: Build with Gradle
        run: ./gradlew clean build

      - name : set time zone
        uses: szenius/set-timezone@v1.2
        with:
          timezoneLinux: &quot;Asia/Seoul&quot;
          timezoneMacos: &quot;Asia/Seoul&quot;
          timezoneWindows: &quot;Seoul Standard Time&quot;

      - name: make env now date
        id: now
        run: echo &quot;date=`date +%Y%m%d_%H:%M:%S`&quot; &amp;gt;&amp;gt; &quot;$GITHUB_OUTPUT&quot;

      - name: Make Zip File
        id: file
        run: zip -qq -r ./deployfile-${{ steps.now.outputs.date }}.zip .
        shell: bash

      - name: AWS credential
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-region: ${{ secrets.AWS_REGION }}
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Upload to AWS S3
        run: aws s3 cp ./deployfile-${{ steps.now.outputs.date }}.zip s3://${{ secrets.S3_DEPLOY_BUCKET }}/deployfile-${{ steps.now.outputs.date }}.zip

      - name: Deploy EC2
        run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=${{ secrets.S3_DEPLOY_BUCKET }},key=deployfile-${{ steps.now.outputs.date }}.zip,bundleType=zip&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 id=&quot;page-title&quot; style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;8. AWS CodeDeploy BlockTraffic 시간 단축&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추가적으로, 잦은 배포를 위해서는 빠른 속도가 생명인데 Code Deploy 실행 시 BlocTraffic 단계에서 너무 많은 시간을 잡아먹어 이를 개선해보고자 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x5Rdd/btsE6FaMHp3/L673A4KCFJ1QYdcsBLGPD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x5Rdd/btsE6FaMHp3/L673A4KCFJ1QYdcsBLGPD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x5Rdd/btsE6FaMHp3/L673A4KCFJ1QYdcsBLGPD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx5Rdd%2FbtsE6FaMHp3%2FL673A4KCFJ1QYdcsBLGPD1%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;507&quot; height=&quot;46&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;46&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ec2 &amp;gt; load balancing &amp;gt; Target groups &amp;gt; ... &amp;gt; attribute &amp;gt; edit ]&amp;nbsp; 에서 Deregistration delay를 낮추면 됩니다.&lt;/li&gt;
&lt;li&gt;저는 300 &amp;rarr; 60으로 수정하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v7XmY/btsE6RWme2E/luHJkMGUZm2F5Xytlb2HDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v7XmY/btsE6RWme2E/luHJkMGUZm2F5Xytlb2HDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v7XmY/btsE6RWme2E/luHJkMGUZm2F5Xytlb2HDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv7XmY%2FbtsE6RWme2E%2FluHJkMGUZm2F5Xytlb2HDk%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;599&quot; height=&quot;44&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대략 80% 빨라졌네요  &lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1511&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zkPeZ/btsFbizVnal/18vidDE68VCidk4qK3TBLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zkPeZ/btsFbizVnal/18vidDE68VCidk4qK3TBLK/img.png&quot; data-alt=&quot;배포 완료!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zkPeZ/btsFbizVnal/18vidDE68VCidk4qK3TBLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzkPeZ%2FbtsFbizVnal%2F18vidDE68VCidk4qK3TBLK%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;1511&quot; height=&quot;186&quot; data-origin-width=&quot;1511&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;배포 완료!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하나씩 정리하다보니 쓸데없이 길어졌는데 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;단순한 EC2 에 jar 파일을 배포하여 실행하는 과정&lt;/span&gt;이며&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 순서는&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;github [commit/push]&lt;/li&gt;
&lt;li&gt;github action [CI]&lt;/li&gt;
&lt;li&gt;ec2 upload&lt;/li&gt;
&lt;li&gt;code deploy 실행 (blue/grean &amp;rarr; auto scaling) [CD]&lt;/li&gt;
&lt;li&gt;EC2 에 jar 배포 후 백그라운드 실행 입니다!&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;나중에 auto scaling으로 인한 scale-out 이 빈번하게 일어난다면 비용 및 관리상 ELB로 전환해보는 것도 생각해보면 좋을거같네요&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;끝!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #3d4144; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;  Jenkins &amp;rarr; GitHub Action 이전기&lt;/h4&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #3d4144; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;GitHub Action&lt;/b&gt;&lt;/h4&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #3d4144; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; AWS CI CD 구축 하기&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brGMf6/btsFaMOJPDe/Id0Eoq5qgBo4ruRBk3dWYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brGMf6/btsFaMOJPDe/Id0Eoq5qgBo4ruRBk3dWYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brGMf6/btsFaMOJPDe/Id0Eoq5qgBo4ruRBk3dWYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrGMf6%2FbtsFaMOJPDe%2FId0Eoq5qgBo4ruRBk3dWYK%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;327&quot; height=&quot;121&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;github action docs : &lt;a href=&quot;https://docs.github.com/ko/actions/learn-github-actions/understanding-github-actions&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.github.com/ko/actions/learn-github-actions/understanding-github-actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;aws cli docs : &lt;a href=&quot;https://awscli.amazonaws.com/v2/documentation/api/latest/index.html#&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://awscli.amazonaws.com/v2/documentation/api/latest/index.html#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;aws code deploy 튜토리얼 :&amp;nbsp; &lt;a href=&quot;https://docs.aws.amazon.com/codedeploy/latest/userguide/applications-create.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/codedeploy/latest/userguide/applications-create.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra/CI, CD</category>
      <category>aws cli gitaction</category>
      <category>aws s3 github</category>
      <category>backend</category>
      <category>CICD</category>
      <category>cicd 구축</category>
      <category>codedeploy s3</category>
      <category>git action</category>
      <category>git aws</category>
      <category>GitAction</category>
      <category>Github Action</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/470</guid>
      <comments>https://thalals.tistory.com/470#entry470comment</comments>
      <pubDate>Wed, 21 Feb 2024 18:00:35 +0900</pubDate>
    </item>
    <item>
      <title>개발자의 다이어그램 익숙해지기</title>
      <link>https://thalals.tistory.com/469</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;3주만에 쓰는 글입니다 ㅎㅎ&lt;br /&gt;요즘 글쓰는게 조금씩 부담감이 커져, 점점 안쓰게되는데.. 다시 가벼운 마음으로 가볍게 가볍게 포스팅을 이어나가볼까 합니당&amp;nbsp;&lt;/blockquote&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;u&gt;코드 이해를 위한 도구 중 하나&lt;/u&gt;로 &quot;&lt;b&gt;다이어그램&lt;/b&gt;&quot;이 나와 이에 대해 스터디 팀원들과 이야기를 해보았습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 요지는 다음과 같았습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서비스는 사회 환경의 변화에 맞춰 &lt;u&gt;함께 변화&lt;/u&gt;되어야한다.&lt;/li&gt;
&lt;li&gt;그러기위해서는 &lt;b&gt;코드비용(코드를 변경하는데 드는 인건비)이 적어야한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;개발자가 코드를 변경할때는 [&lt;span style=&quot;color: #ee2323;&quot;&gt;(1)코드 이해&lt;/span&gt;], [(&lt;span style=&quot;color: #ee2323;&quot;&gt;2) 코드 수정&lt;/span&gt;] 이 2가지 단계를 거친다.&lt;/li&gt;
&lt;li&gt;이 중 &quot;코드를 이해하는 시간&quot;을 줄이기 위해서는 2가지 역량이 요구된다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;코드를 제대로 이해할 수 있는 역량&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이해하기 쉬운 코드를 작성하는 역량&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;이 중 &quot;코드를 제대로 이해할 수 있는 역량&quot; 을 키우기 위한 효과적인 방법은 &quot;&lt;b&gt;코드 시각화&lt;/b&gt;&quot; 이고, 그 중 한가지 방법이 [&lt;b&gt;다이어그램&lt;/b&gt;]이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 사실 1년 6개월간의 짧은 현업기간 동안 다이어그램을 사용해본 적이 없습니다.&lt;br /&gt;하지만, 다른 팀원분들은 아주 효율적인 방법이다라고 이야기 하시더군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 왜 다이어그램을 사용할 생각을 안했을까.. 고민해보았더니&lt;br /&gt;그리기&lt;b&gt; 불편해서! 익숙하지 않아서! 효용성을 못느껴서!&lt;/b&gt; 정도의 이유가 나오더라구요...ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;효용성은 스터디분들의 대화에서 충분히 느꼈으니 &quot;&lt;u&gt;그리기 불편해서&lt;/u&gt;&quot; 와 &quot;&lt;u&gt;익숙하지 않아서&lt;/u&gt;&quot; 를 해결해 볼까 합니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[목차]&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;코드시각화 - 다이어그램의 종류
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;activity diagram&lt;/li&gt;
&lt;li&gt;sequence diagram&lt;/li&gt;
&lt;li&gt;class diagram&lt;/li&gt;
&lt;li&gt;state diagram&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;다이어그램 그리기 툴
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;draw.io (with. IDE)&lt;/li&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;figma&lt;/li&gt;
&lt;li&gt;ppt&lt;/li&gt;
&lt;li&gt;mermaid&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;시퀸스 다이어그램으로 레거시 코드 분석하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 코드시각화 - 다이어그램의 종류&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔️ &quot;육각형 개발자&quot; 에 나온 코드 시각화에 유용한 다이어그램에 대해서만 정리해 볼 예정입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔️&amp;nbsp;저자이신 최범균님께서는, &lt;b&gt;UML (Unified Modeling Language) 다이어그램&lt;/b&gt;을 주로 사용하신다고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔️ UML 다이어그램은 &lt;b&gt;표준화된 다양한 모델들이 존재&lt;/b&gt;하고, &lt;b&gt;도형마다 정해진 의미&lt;/b&gt;가 있기 때문에 보는 이로 하여금, &lt;b&gt;다르게 해석할 여지를 줄일 수 있는 장점&lt;/b&gt;이 존재합니다. (따라서 정해진 방법대로 그릴수 있도록, 숙달이 필요해 보입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1) 액티비티 다이어그램 (Activity)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;액티비티 다이어그램은 시스템에서 &lt;b&gt;단계별로 비지니스로직의 제어 흐름을 그림으로 표현&lt;/b&gt;한 다이어그램입니다.&lt;/li&gt;
&lt;li&gt;코드의 실행 흐름을 이해하는데 도움이 됩니다.&lt;/li&gt;
&lt;li&gt;아래의 그림같이, 코드의 실행 흐름을 분석하기 쉽게 시각화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;코드 한줄마다 그릴필요는 없고, &lt;span style=&quot;color: #ee2323;&quot;&gt;논리적인 단위로 묶어서 표시&lt;/span&gt;하는게 분석 팁이라고 합니다. (책에서)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6Zg0r/btsEcsDLWO2/u4G3b1xLce2T7XAYEvP8qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6Zg0r/btsEcsDLWO2/u4G3b1xLce2T7XAYEvP8qk/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;872&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 36.4484%; margin-right: 10px;&quot; data-widthpercent=&quot;36.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6Zg0r/btsEcsDLWO2/u4G3b1xLce2T7XAYEvP8qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6Zg0r%2FbtsEcsDLWO2%2Fu4G3b1xLce2T7XAYEvP8qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boqRNX/btsEfqZiLJd/r8ULO0rqh5MSrdL6K6YYkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boqRNX/btsEfqZiLJd/r8ULO0rqh5MSrdL6K6YYkk/img.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;900&quot; data-is-animation=&quot;false&quot; style=&quot;width: 62.3888%;&quot; data-widthpercent=&quot;63.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boqRNX/btsEfqZiLJd/r8ULO0rqh5MSrdL6K6YYkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboqRNX%2FbtsEfqZiLJd%2Fr8ULO0rqh5MSrdL6K6YYkk%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;1060&quot; height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;액티비티 다이어그램 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(좌)는 활동 다이어그램을 이해하기 쉽게 가져온 예시사진&lt;br /&gt;(우)는 실제 코드레벨에서 그려볼 수 있는 코드 이해를 위한 활동 다이어그램 예시사진&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 시퀀스 다이어그램 (Sequence)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시퀸스 다이어그램은 &lt;b&gt;객체의 특정 행동&lt;/b&gt;이 &lt;u&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;어떠한 순서&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;로 &lt;b&gt;어떠한 객체&lt;/b&gt;와 &lt;b&gt;서로 &lt;u&gt;상호작용&lt;/u&gt;을 하는지 표현한 다이어그램&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;런타임에 &lt;b&gt;객체와 객체&lt;/b&gt;, 혹은 &lt;b&gt;프로세스간의&lt;/b&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;상호 작용도&lt;b&gt; 시간의 흐름&lt;/b&gt;에 따라 정리&lt;/span&gt;할 수 있어 유용합니다.&lt;/li&gt;
&lt;li&gt;시퀀스 다이어그램은 계속 강조하다시피, 시간의 흐름에 따라 구성 요소간의 연동과정이 담겨있기 때문에&lt;br /&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;문제 발생 시점&lt;/span&gt;이나&lt;span style=&quot;background-color: #f6e199; color: #333333;&quot;&gt; 동시성 문제의 원인을 파악&lt;/span&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kOW3E/btsEf58DlAG/t6ke66JvJbIz9SdiWsK6O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kOW3E/btsEf58DlAG/t6ke66JvJbIz9SdiWsK6O0/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;643&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 42.6459%; margin-right: 10px;&quot; data-widthpercent=&quot;43.15&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kOW3E/btsEf58DlAG/t6ke66JvJbIz9SdiWsK6O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkOW3E%2FbtsEf58DlAG%2Ft6ke66JvJbIz9SdiWsK6O0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjEw2G/btsEba4GEIS/b6QaLk1sCIsNn0IjzR7J2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjEw2G/btsEba4GEIS/b6QaLk1sCIsNn0IjzR7J2K/img.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;732&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.1913%;&quot; data-widthpercent=&quot;56.85&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjEw2G/btsEba4GEIS/b6QaLk1sCIsNn0IjzR7J2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjEw2G%2FbtsEba4GEIS%2Fb6QaLk1sCIsNn0IjzR7J2K%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;900&quot; height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(좌) 출처 :&amp;nbsp;https://brownbears.tistory.com/511&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 클래스 다이어그램 (Class)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 다이어그램은 &lt;b&gt;어떤 클래스가 존재&lt;/b&gt;하고, 각 &lt;b&gt;클래스간 어떻게 연결&lt;/b&gt;되어있는지 파악할 때 쓰는 다이어그램입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클래스의 구성요소와 관계를 시각화&lt;/b&gt;하여 나타낼 수 있기 때문에 &lt;b&gt;코드의 정적인 구조를 이해하는데 도움이 됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;클래스 다이어그램은 도메인 모델을 분석할 때 유용하게 사용된다고 합니다.&lt;/li&gt;
&lt;li&gt;다만, 저자이신 최범균님은 대다수의 레거시 코드의 경우 비지니스로직이 하나의 객체에 몰려있어 이런 경우에는 클래스 다이어그램이 유용하지 않았다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w5oE6/btsEhBfXSL2/4ykEjo2tiokcOWbbgB8wkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w5oE6/btsEhBfXSL2/4ykEjo2tiokcOWbbgB8wkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w5oE6/btsEhBfXSL2/4ykEjo2tiokcOWbbgB8wkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw5oE6%2FbtsEhBfXSL2%2F4ykEjo2tiokcOWbbgB8wkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;371&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;371&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;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 상태 다이어그램 (State)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원의 상태(정산, 휴먼, 신규), 결제의 상태 (결제 대기, 완료, 배송 중 등) 처럼 &lt;b&gt;객체의 상태의 변화와 상태를 변경시키는 로직을 도식화한 다이어그램이 상태 다이어그램 입니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;행동 다이어그램(Activity)과 어찌보면 유사해보이지만, Activity 는 전체 시스템의 비지니스의 흐름을 도식화하는데 집중한다는 느낌을 받았고&lt;br /&gt;상태 다이어그램(State)은 특정 객체의 구성요소값(상태 값)이 변화하는 흐름과 과정을 도식화하는데 집중한다는 점에서 차이점이 느껴집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YYtFH/btsEj7Tn9R1/Zl0bn5FvEC4AiLrGZJtKE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YYtFH/btsEj7Tn9R1/Zl0bn5FvEC4AiLrGZJtKE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YYtFH/btsEj7Tn9R1/Zl0bn5FvEC4AiLrGZJtKE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYYtFH%2FbtsEj7Tn9R1%2FZl0bn5FvEC4AiLrGZJtKE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;316&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 다이어그램 그리기 툴 모음&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이제 실질적으로 다이어그램을 사용하기 위해서는 툴을 알아볼까 합니다.&lt;br /&gt;손으로 직접 그릴수는 없으니까요!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ Drawing TooL List&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;draw.io (with. IDE)&lt;/li&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;figma&lt;/li&gt;
&lt;li&gt;ppt? (는 너무 불편하더라고요.. 넘어가겠습니다 ㅎ)&lt;/li&gt;
&lt;li&gt;mermaid&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) draw.io (with. IDE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크 : &lt;a href=&quot;https://www.drawio.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.drawio.com/&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;drawio 는 별도의 회원가입 없는 무료사이트로, 웹으로 사용해도 되고 windows 와 mac용 프로그램을 다운받아 사용할 수 도있습니다.&lt;/li&gt;
&lt;li&gt;예전 전공과제할때 참 많이 사용했는데, UML 의 다양한 종류의 다이어그램을 대부분 지원해주고, 그에 맞춰 도형들 까지 존재하기 때문에 사용하기 나쁘지않았습니다.&lt;/li&gt;
&lt;li&gt;군더더기 없는 ui 도 썩 쓸만했던걸로 기억합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;하지만 일일히 하나씩 그려주고 이어주고 확대해주고,, 적어주는게 그렇게 편하지만은 않았습니다.&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 VsCode 를 사용한시다면 Draw.io 플러그인을 사용하여 편하게 사용할 수 있다고 합니다. (&lt;a href=&quot;https://enumclass.tistory.com/170&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;957&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfUjAB/btsEkgirerM/rnZfP6TjjDv8G1RKGxT6Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfUjAB/btsEkgirerM/rnZfP6TjjDv8G1RKGxT6Nk/img.png&quot; data-alt=&quot;이런 느낌&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfUjAB/btsEkgirerM/rnZfP6TjjDv8G1RKGxT6Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfUjAB%2FbtsEkgirerM%2FrnZfP6TjjDv8G1RKGxT6Nk%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;1918&quot; height=&quot;957&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;957&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이런 느낌&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.44em; letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;2) IntelliJ&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인텔리제이에서도 간단하게 다이어그램을 시각화할 수 있었습니다.&lt;/li&gt;
&lt;li&gt;인텔리제이에서 제공해주는 기본 기능을 이용하여 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Class Diagram&lt;/span&gt;을 볼 수 있고, (Ultimate 기준)&lt;/li&gt;
&lt;li&gt;별도의 플러그인을 설치하여 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;시퀀스 다이어그램&lt;/span&gt;을 나타낼 수 있었습니다.&lt;/li&gt;
&lt;li&gt;사용해보니 두 UML 을 굉장히 편하게 그릴 수 있어 좋았지만, &lt;b&gt;자동으로 생성되기 때문에 원하는만큼 디테일하지는 않기에&lt;/b&gt;&amp;nbsp;디테일한 분석을 위해 사용하기에 적당해 보이지는 않았습니다.&lt;/li&gt;
&lt;li&gt;다만, &lt;b&gt;빠른 분석 및 파악이 목적이라면 이 방법이 가장 효과적&lt;/b&gt;으로 보이네요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) Class Diagram&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;원하는 클래스의 마우스 오른쪽 클릭 - diagram&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btPWwr/btsElJep6Z7/NV25ylq5DQ4nFlpjUt54Wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btPWwr/btsElJep6Z7/NV25ylq5DQ4nFlpjUt54Wk/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;454&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 48.9239%; margin-right: 10px;&quot; data-widthpercent=&quot;49.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btPWwr/btsElJep6Z7/NV25ylq5DQ4nFlpjUt54Wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtPWwr%2FbtsElJep6Z7%2FNV25ylq5DQ4nFlpjUt54Wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Wm4c/btsEm9i7ScK/QdCPOikneyxklfrbSr0otk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Wm4c/btsEm9i7ScK/QdCPOikneyxklfrbSr0otk/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;445&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.9133%;&quot; data-widthpercent=&quot;50.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Wm4c/btsEm9i7ScK/QdCPOikneyxklfrbSr0otk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Wm4c%2FbtsEm9i7ScK%2FQdCPOikneyxklfrbSr0otk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) Sequence Diagram&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;Intellij 를 사용하면서 Sequence Diagram을 그리기 위해서는 아래의 플러그인을 설치하여야 합니다.&lt;/li&gt;
&lt;li&gt;플러그인 설치 후, 메소드 단위 혹은 클래스단위로도 시퀀스 다이어그램을 시각화 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/8286-sequencediagram&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://plugins.jetbrains.com/plugin/8286-sequencediagram&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706861333245&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;SequenceDiagram - IntelliJ IDEs Plugin | Marketplace&quot; data-og-description=&quot;SequenceDiagram is tool to generate simple sequence diagram(UML) from java, kotlin, scala(Beta) and groovy(limited) code. Warning : SequenceDiagram upgrade from free...&quot; data-og-host=&quot;plugins.jetbrains.com&quot; data-og-source-url=&quot;https://plugins.jetbrains.com/plugin/8286-sequencediagram&quot; data-og-url=&quot;https://plugins.jetbrains.com/plugin/8286-sequencediagram&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dexslk/hyVb401iV7/66ot0ucCD115qavzR8Kn21/img.png?width=600&amp;amp;height=400&amp;amp;face=0_0_600_400,https://scrap.kakaocdn.net/dn/N39zZ/hyVf3zot4Q/g7EaQ73rsFdiMhoGtvuC20/img.png?width=600&amp;amp;height=400&amp;amp;face=0_0_600_400&quot;&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/8286-sequencediagram&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://plugins.jetbrains.com/plugin/8286-sequencediagram&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dexslk/hyVb401iV7/66ot0ucCD115qavzR8Kn21/img.png?width=600&amp;amp;height=400&amp;amp;face=0_0_600_400,https://scrap.kakaocdn.net/dn/N39zZ/hyVf3zot4Q/g7EaQ73rsFdiMhoGtvuC20/img.png?width=600&amp;amp;height=400&amp;amp;face=0_0_600_400');&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;SequenceDiagram - IntelliJ IDEs Plugin | Marketplace&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SequenceDiagram is tool to generate simple sequence diagram(UML) from java, kotlin, scala(Beta) and groovy(limited) code. Warning : SequenceDiagram upgrade from free...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;plugins.jetbrains.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFRkEi/btsEjUntkHw/qDT57TPnZGMMiCXXNY1ic0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFRkEi/btsEjUntkHw/qDT57TPnZGMMiCXXNY1ic0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFRkEi/btsEjUntkHw/qDT57TPnZGMMiCXXNY1ic0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFRkEi%2FbtsEjUntkHw%2FqDT57TPnZGMMiCXXNY1ic0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;437&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용방법&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시퀀스 다이어그램을 그리기 희망하는 메소드 혹은 클래스로 가셔서, 상단의 Tool 옵션으로 선택할 수 있습니다.&lt;/li&gt;
&lt;li&gt;혹은 직접 메소드에서 오른쪽 마우스 우클릭으로 접근하거나, alt + s (option + s) 로도 적용할 수 있었습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEC2Y1/btsEmFQwRbC/fPxNaEAhdkuJocy39TDa3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEC2Y1/btsEmFQwRbC/fPxNaEAhdkuJocy39TDa3K/img.png&quot; data-alt=&quot;Tool - SequenceDiagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEC2Y1/btsEmFQwRbC/fPxNaEAhdkuJocy39TDa3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEC2Y1%2FbtsEmFQwRbC%2FfPxNaEAhdkuJocy39TDa3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;345&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Tool - SequenceDiagram&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Xvld/btsEjS4q1O1/ljct4Tpz6RM1yTK6M8ifG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Xvld/btsEjS4q1O1/ljct4Tpz6RM1yTK6M8ifG1/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;375&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;52.71&quot; data-filename=&quot;blob&quot; style=&quot;width: 52.0983%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Xvld/btsEjS4q1O1/ljct4Tpz6RM1yTK6M8ifG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Xvld%2FbtsEjS4q1O1%2Fljct4Tpz6RM1yTK6M8ifG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b58Jsr/btsEjSwBRbF/GUpm6xT0mXRUokIh9RU5wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b58Jsr/btsEjSwBRbF/GUpm6xT0mXRUokIh9RU5wk/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;418&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;47.29&quot; data-filename=&quot;blob&quot; style=&quot;width: 46.7389%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b58Jsr/btsEjSwBRbF/GUpm6xT0mXRUokIh9RU5wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb58Jsr%2FbtsEjSwBRbF%2FGUpm6xT0mXRUokIh9RU5wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;직접 접근 (좌) option + s / (우) 마우스 우클릭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1FtR1/btsElhba9a7/qNqYIp10m4eWouJcTZ9CJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1FtR1/btsElhba9a7/qNqYIp10m4eWouJcTZ9CJk/img.png&quot; data-alt=&quot;intellij sequece diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1FtR1/btsElhba9a7/qNqYIp10m4eWouJcTZ9CJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1FtR1%2FbtsElhba9a7%2FqNqYIp10m4eWouJcTZ9CJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;358&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;intellij sequece diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Figma (FigJam)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;피그마는 macOS 및 윈도우용 데스크톱 애플리케이션들에 의해 활성화되는 추가 오프라인 기능들을 갖춘 인터페이스 디자인을 위한 협업 웹 애플리케이션 입니다.&lt;/li&gt;
&lt;li&gt;Figma 프로그램에서, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;Figzam&lt;/b&gt;&lt;/span&gt; 이라는 별도의 도형과 선을 쉽고 간편하게 생성할 수 있는 툴이 존재합니다.&lt;/li&gt;
&lt;li&gt;또한 다양한 템플릿이 존재하기 때문에, 원하는 &lt;b&gt;UML 다이어그램의 템플릿&lt;/b&gt;을 불러와 사용할 수 있었습니다.&lt;/li&gt;
&lt;li&gt;링크 : &lt;a href=&quot;https://www.figma.com/files/recents-and-sharing?fuid=1156409960916260588&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.figma.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE1lAG/btsEj8FPQEh/DLsv0QgiXbmwnFhXofFTWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE1lAG/btsEj8FPQEh/DLsv0QgiXbmwnFhXofFTWk/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;401&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;46.6&quot; data-filename=&quot;blob&quot; style=&quot;width: 46.0626%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE1lAG/btsEj8FPQEh/DLsv0QgiXbmwnFhXofFTWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcE1lAG%2FbtsEj8FPQEh%2FDLsv0QgiXbmwnFhXofFTWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5JIKH/btsEmDrH0vB/AcquboZ0bYPYBk1RdBTD11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5JIKH/btsEmDrH0vB/AcquboZ0bYPYBk1RdBTD11/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;350&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;53.4&quot; data-filename=&quot;blob&quot; style=&quot;width: 52.7746%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5JIKH/btsEmDrH0vB/AcquboZ0bYPYBk1RdBTD11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5JIKH%2FbtsEmDrH0vB%2FAcquboZ0bYPYBk1RdBTD11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) mermaid&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mermaid 는 다이어그램과 차트를 그리기 위한 별도의 툴 프로그램입니다.&lt;/li&gt;
&lt;li&gt;Markdown에서 영감을 받은 텍스트 정의를 렌더링하여 다이어그램을 동적으로 생성하고 수정하는 JavaScript 기반 다이어그램 작성 및 차트 작성 도구라고 합니다.&lt;/li&gt;
&lt;li&gt;즉, &lt;b&gt;Markdown 언어로 다이어그램 or 차트를 생성할 수 있다&lt;/b&gt;는 말인데, 이러한 특성으로 GitHub 등과 같은 원격 저장소의 README.md 에도 손쉽게 차트를 넣을 수 있다는 점도 또 하나의 장점으로 보여지는 것 같습니다.&lt;/li&gt;
&lt;li&gt;또한, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;코드 기반으로 그려지는거기 때문에 수정에 매우매우 용이&lt;/b&gt;&lt;/span&gt;하며, &lt;b&gt;형상관리까지 가능&lt;/b&gt;하다는 장점이 존재하는 것 같습니다. &lt;br /&gt;(개인적으로 가장 큰 장점으로 여겨짐!)&lt;/li&gt;
&lt;li&gt;하지만 그렇기때문에, &lt;span style=&quot;color: #ee2323;&quot;&gt;특정 문법을 알아야한다는 학습곡선이 존재&lt;/span&gt;한다는 점은 단점으로 다가올 수도 있을 것 같습니다.&lt;/li&gt;
&lt;li&gt;가이드 : &lt;a href=&quot;https://mermaid.js.org/intro/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mermaid.js.org/intro/&lt;/a&gt;&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;코드(MarkDown 기반의 언어)로 구성된다는 특성때문에 Mermaid 를 활용하는 방법이 굉장히 다양한 것 같습니다.&lt;br /&gt;사용방법을 몇가지만 살펴보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ Mermaid 사용방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. mermaid aditor (&lt;a href=&quot;https://mermaid.live/edit#pako:eNpVjk2Lg0AMhv9KyGkL9Q94WGh1t5fCFurN6SFo7AztfDBGpKj_fcd62c0pvM_zhkzY-JYxx-7px0ZTFKhK5SDNoS50NL1Y6m-QZZ_ziQWsd_ya4fhx8tBrH4Jx993mH1cJium8agyijXssGyre_R_HM5T1mYL4cPtLqtHP8FWbi07n_xMdObW-647yjrKGIhQU3wru0XK0ZNr0_rQmCkWzZYV5WlvuaHiKQuWWpNIg_vpyDeYSB97jEFoSLg3dI9ktXH4B_cJWqw&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 방법은, 간단하게 머메이드 공식 홈페이지에서 제공해주는 에디터에서 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmnXgF/btsEkSJJb09/gSzdTB1nXKK3FrwumKGXv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmnXgF/btsEkSJJb09/gSzdTB1nXKK3FrwumKGXv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmnXgF/btsEkSJJb09/gSzdTB1nXKK3FrwumKGXv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmnXgF%2FbtsEkSJJb09%2FgSzdTB1nXKK3FrwumKGXv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;335&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&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;b&gt;2. markdown (with. IDE)&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;두번재는, 계속 말했다 시피 MarkDown을 이용하는 방법입니다.&lt;/li&gt;
&lt;li&gt;저는 인텔리제이를 사용했기 때문에 인텔리제이에서 제공해주는 플러그인을 다운받았습니다. (다운안받으면 안나옵니다 ㅠ)&lt;/li&gt;
&lt;li&gt;위에 머메이드 에디터에 나오는 코드를 그대로 가져다 붙여서 사용했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1706869767113&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 머메이드 테스트

```mermaid
flowchart TD
A[Christmas] --&amp;gt;|Get money| B(Go shopping)
B --&amp;gt; C{Let me think}
C --&amp;gt;|One| D[Laptop]
C --&amp;gt;|Two| E[iPhone]
C --&amp;gt;|Three| F[fa:fa-car Car]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vUkqI/btsEnabvsnK/DiFawYwLWaJkW6fTn7vqTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vUkqI/btsEnabvsnK/DiFawYwLWaJkW6fTn7vqTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vUkqI/btsEnabvsnK/DiFawYwLWaJkW6fTn7vqTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvUkqI%2FbtsEnabvsnK%2FDiFawYwLWaJkW6fTn7vqTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;434&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Notion&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;개발할때 특정 로직의 요구사항을 분석하고 정리하기 위해 Notion을 굉장히 많이 사용하는데, Notion에서도 바로 사용할 수 가 있습니다.&lt;/li&gt;
&lt;li&gt;Notion 의 코드블럭을 이용해서 Mermaid 를 사용할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lAsq6/btsEnZUWB1S/FNiDZkvtjiSIoHDdJSmNH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lAsq6/btsEnZUWB1S/FNiDZkvtjiSIoHDdJSmNH1/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;278&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;56.97&quot; data-filename=&quot;blob&quot; style=&quot;width: 56.3035%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lAsq6/btsEnZUWB1S/FNiDZkvtjiSIoHDdJSmNH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlAsq6%2FbtsEnZUWB1S%2FFNiDZkvtjiSIoHDdJSmNH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSmzfa/btsEmOzGHak/hX9LdGzzy0mKC88r6NnqFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSmzfa/btsEmOzGHak/hX9LdGzzy0mKC88r6NnqFK/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;368&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;43.03&quot; data-filename=&quot;blob&quot; style=&quot;width: 42.5337%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSmzfa/btsEmOzGHak/hX9LdGzzy0mKC88r6NnqFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSmzfa%2FbtsEmOzGHak%2FhX9LdGzzy0mKC88r6NnqFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Chat GPT&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;Mermaid 를 사용하기 위해서는 mermaid의 Markdown 문법을 알아야하는데 이걸 chat gpt로 만들 수 있더라고요.. (&lt;s&gt;좋은 세상)&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;별도의 플러그인이 존재하지만, 저는 유료사용자가 아니기에 그냥 시도해봤습니다. (&lt;a href=&quot;https://docs.mermaidchart.com/plugins/chatgpt&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.mermaidchart.com/plugins/chatgpt&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; Actor 라는 단어를 mermaid에서 에러로 받아들이기 User로만 수정한 후 결과를 보니 원하는데로 다이어그램이 그려짐을 확인했습니다.&lt;br /&gt;&amp;rarr; 간단한 요구사항이지만 정말 편하네요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nph4r/btsEmC7sXqL/TVvBcJmhMoKvEIG5ZhFEH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nph4r/btsEmC7sXqL/TVvBcJmhMoKvEIG5ZhFEH1/img.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;1330&quot; data-is-animation=&quot;false&quot; style=&quot;width: 43.2002%; margin-right: 10px;&quot; data-widthpercent=&quot;43.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nph4r/btsEmC7sXqL/TVvBcJmhMoKvEIG5ZhFEH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnph4r%2FbtsEmC7sXqL%2FTVvBcJmhMoKvEIG5ZhFEH1%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;1542&quot; height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t9EhZ/btsEkDsb7kP/qATLkyMhJyGVvn46sceSwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t9EhZ/btsEkDsb7kP/qATLkyMhJyGVvn46sceSwK/img.png&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1026&quot; data-is-animation=&quot;false&quot; style=&quot;width: 55.637%;&quot; data-widthpercent=&quot;56.29&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t9EhZ/btsEkDsb7kP/qATLkyMhJyGVvn46sceSwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft9EhZ%2FbtsEkDsb7kP%2FqATLkyMhJyGVvn46sceSwK%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;1532&quot; height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2986&quot; data-origin-height=&quot;1446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkXsB9/btsEjHu4O1S/WGObRFSl3MhxEVCoBJgRwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkXsB9/btsEjHu4O1S/WGObRFSl3MhxEVCoBJgRwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkXsB9/btsEjHu4O1S/WGObRFSl3MhxEVCoBJgRwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkXsB9%2FbtsEjHu4O1S%2FWGObRFSl3MhxEVCoBJgRwk%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;2986&quot; height=&quot;1446&quot; data-origin-width=&quot;2986&quot; data-origin-height=&quot;1446&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;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 시퀸스 다이어그램으로 레거시 코드 분석하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 사실 가장 사용해보고 싶었던 건 [시퀸스 다이어그램] 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 스터디원분들 중 2분이 실제 현업에서 사용중이시라고 하셔서 궁금했고, 사실상 &quot;레거시 분석&quot;이나 &quot;코드 설명&quot;에 가장 유용할 것 같다는 생각이 들었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✦ 다이어그램을 그릴 때 유의사항&lt;/p&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;의도에 맞게 불필요한 요소는 생략하기 위해서 어떤 정보를 제공하기 위해 다이어그램을 그리는지 분명히하고 목적에 맞는 요소만을 포함토록 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2398&quot; data-origin-height=&quot;1580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nwGtv/btsEmD6oy8v/7ApXUnR4SfqZu0agvWVWx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nwGtv/btsEmD6oy8v/7ApXUnR4SfqZu0agvWVWx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nwGtv/btsEmD6oy8v/7ApXUnR4SfqZu0agvWVWx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnwGtv%2FbtsEmD6oy8v%2F7ApXUnR4SfqZu0agvWVWx1%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;2398&quot; height=&quot;1580&quot; data-origin-width=&quot;2398&quot; data-origin-height=&quot;1580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2246&quot; data-origin-height=&quot;1324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sxJHd/btsEnS2LkgB/chRXoeKHKtp62yAzlLEXx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sxJHd/btsEnS2LkgB/chRXoeKHKtp62yAzlLEXx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sxJHd/btsEnS2LkgB/chRXoeKHKtp62yAzlLEXx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsxJHd%2FbtsEnS2LkgB%2FchRXoeKHKtp62yAzlLEXx1%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;2246&quot; height=&quot;1324&quot; data-origin-width=&quot;2246&quot; data-origin-height=&quot;1324&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀점 및 후기 (Sequece Diagram 및 Mermaid)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Notion 을 이용해 요구사항을 정리하고, Mermaid로 다이어그램을 그려보았습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;확실히 다이어그램을 그린다,, 생각하고 한 단계식 글로 흐름을 정리해보니 효과적으로 그 흐름이 머릿속에 들어오는 것 같았습니다.&lt;/li&gt;
&lt;li&gt;Notion 에 다이어그램과 함께 정리되니, 이후 유지보수를 위해 다시 분석할 때 굉장히 유용할 것 같습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일단, mermaid 문법에 익숙하지 않아 시간이 오래걸린다는게 지금의 문제점인거 같습니다.&lt;br /&gt;시간을두고 연습할만한 가치가 있지만, 어느정도의 학습곡선이 있다는게 부담으로 다가왔습니다.&lt;/li&gt;
&lt;li&gt;사실, 복잡한 쿼리문을 디테일하게 분석하고 싶은마음이 컸는데 시퀀스 다이어그램과는 성질이 맞지 않았던 것 같습니다. (제가 학습이 부족한 것일 수 도 있습니다.)&lt;/li&gt;
&lt;li&gt;시퀀스 다이어그램도 확실하게 한번 집고 넘어가는게 좋을거같다는 것을 느꼈습니다..ㅎ&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️&lt;span&gt;&amp;nbsp;느낀점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음으로 실무에 다이어그램을 적용해 보았습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;다이어그램으로 시각화하기 위해서는 요구사항 분석 단계에서 디테일하게 코드단위의 흐름을 생각하며 다시 한단계 한단계 정리해나가야한다는 점이 좋다고 생각되어졌습니다.&lt;/li&gt;
&lt;li&gt;지금은 익숙하지 않아 시간이 꽤 걸리지만, 계속 사용하다면 요구사항 분석 능력을 키우는데 도움이 되지않을까..? 생각되어지네요&lt;/li&gt;
&lt;li&gt;모든 로직에 적용하기는 힘들것 같고 앞으로, 핵심 비지니스 로직 또는 작았다가 커져가서 분리가 필요해진 로직을 분석하는데 주로 사용하게 될것 같습니다!&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;핵심 : Notion 을 자주 사용하고 더 많이 기록하는 습관을 들이자!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝..!&lt;/p&gt;</description>
      <category>  공대생은 성장 중/일잘하기</category>
      <category>drawio vs mermaid</category>
      <category>drawio 사용법</category>
      <category>figma vs mermaid</category>
      <category>intellij diagram</category>
      <category>java</category>
      <category>mermaid 사용법</category>
      <category>mermaid란</category>
      <category>다이어그램</category>
      <category>다이어그램툴</category>
      <category>백엔드</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/469</guid>
      <comments>https://thalals.tistory.com/469#entry469comment</comments>
      <pubDate>Fri, 2 Feb 2024 21:24:38 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] FCM 푸시 알림 연동하기 (AOS, IOS)</title>
      <link>https://thalals.tistory.com/468</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;오늘은 현재 진행중인 '채팅 서비스' 사이드 프로젝트에 적용할 목적으로&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  Spring 기반의 Server 에서 푸시알림을 전송하는 방법에 대해 공부해보고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우리가 흔히 아는 어플리케이션 Push 기능을 쉽게 구현하기 위해서 시중에 나와있는 서드파티 솔루션들을 이용할 수 있는데&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가볍게 찾아보았을때 가장 많이 나오는 솔루션을 2가지로 추릴 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Amozon SNS (Simple Notification Service)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;FCM (Firebase Cloud Messaging)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위의 2가지 솔루션 모두 믿음직스러운(?) 대기업에서 제공하는 기술이지만&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;FCM 이 조금 더 적용하기 쉬워보였고 (공식문서가 잘나와있음)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또, '&lt;span style=&quot;color: #ee2323;&quot;&gt;무.료&lt;/span&gt;.' 라는 점 (아마존은 알림 1백만 개당 0.50 USD)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드와 ios 개발자분들이 친숙하다는 점&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 2가지 장점으로 FCM을 적용하기로 결정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;✔️ FCM 이란&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Firebase 클라우드 메시징의 줄임말입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;FCM 은 메세지를 안정적이고 무료로 전송할 수 있는 크로스 플랫폼 메시징 솔루션입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Firebase는 2014년에 구글에 인수된 모바일 및 웹 어플리케이션 개발 플랫폼 서비스 회사입니다. (즉. 믿을만하다!)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;✔️ FCM 푸시 동작과정&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;사실 공식문서에 너무 잘나와있습니다. 쉽게쉽게 정리만 해보겠습니다. (&lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging/fcm-architecture?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;FCM 아키텍처 개요&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oIB3A/btsBuytijrc/VrAqLYGMLxYwJaMOEDkTO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oIB3A/btsBuytijrc/VrAqLYGMLxYwJaMOEDkTO0/img.png&quot; data-alt=&quot;fcm 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oIB3A/btsBuytijrc/VrAqLYGMLxYwJaMOEDkTO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoIB3A%2FbtsBuytijrc%2FVrAqLYGMLxYwJaMOEDkTO0%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;715&quot; height=&quot;388&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;fcm 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Firebase 에서 제공하는 GUI 혹은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;신뢰할 수 있는 서버 환경에서 Firebase Admin Sdk 나 FCM 서버 프로토콜을 사용&lt;/span&gt;하여 메세지를 FCM Backend 서버에 전송합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;FCM Backend 서버에서는 메세지 요청이 들어오면 메시지 요청 수락, 메시지 메터데이터 생성 등 실질적인 Push 메세지 포맷팅을 해줍니다.&lt;/li&gt;
&lt;li&gt;그런 다음 aos, ios, web 등의 풀랫폼 수준의 전송 레이어 기기로 메시지를 라우팅합니다.&lt;/li&gt;
&lt;li&gt;사용자 기기의 FCM SDK 에서 알림이 표시됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  여기서 사실상 저희가 push 알림을 위해 해줘야할 것은 &quot;(1) &amp;rarr; (2)&quot; 으로 가는 메세지 요청을 생성해주는 것 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCM 에서 플랫폼 레이어에 따라 알아서 메세지를 적용해주기 때문에 개발자 입장에서는 매우 편해진 것이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;✔️ FCM 메시지 유형&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging/concept-options?hl=ko#notifications_and_data_messages&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;FCM 공식문서&lt;/a&gt;에 따르면 2가지 유형의 메시지 타입을 사용하여, 클라이언트에게 전송할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&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;앱이 백그라운드로 실행 시 FCM SDK에서 자동 처리, 포그라운드에서 실행 중이면 앱의 코드에 따라 동작이 결정됩니다.&lt;/li&gt;
&lt;li&gt;그렇기 때문인지 사용자에게 표시되는 키 모음이 정의되어있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 메세지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트앱에서 처리&lt;/li&gt;
&lt;li&gt;커스텀한 키-값 쌍만 포함됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&amp;rarr; 현재는 채팅 알림이 목적이므로, &quot;알림메세지&quot; 유형의 메세지 데이터타입을 사용하는게 좋아보입니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;알림메세지 유형&lt;/span&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;a href=&quot;https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ko#Notification&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ko#Notification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1704550364660&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;{
  &quot;message&quot;:{
    &quot;token&quot;:&quot;bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...&quot;,
    &quot;notification&quot;:{
      &quot;title&quot;:&quot;Portugal vs. Denmark&quot;,
      &quot;body&quot;:&quot;great match!&quot;,
      &quot;image&quot; : &quot;image-url&quot;
    },
    //메세지 생명주기 defalut 는 4주
    &quot;apns&quot;:{	//ios
      &quot;headers&quot;:{
        &quot;apns-expiration&quot;:&quot;1604750400&quot;
      }
    },
    &quot;android&quot;:{
      &quot;ttl&quot;:&quot;4500s&quot;
    },
    &quot;webpush&quot;:{
      &quot;headers&quot;:{
        &quot;TTL&quot;:&quot;4500&quot;
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;darr;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;IOS 에서는 notification 타입에 정의도어있는 key-value 값 중에 icon 이미지를 변경할만한 정의된 키 값을 찾지못하여,&lt;/li&gt;
&lt;li&gt;데이터 메세지 타입을 함께 사용해주어 필요한 값을 같이 전송해주려고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우,&lt;b&gt; [백그라운드 상태]&lt;/b&gt; 일 경우 &lt;u&gt;notification 필드만 수신되어&lt;/u&gt; 사용자가&lt;b&gt; 알림을 탭한 경우&lt;/b&gt;에만 앱에서 데이터 페이로드를 처리한다고 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704632662993&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;message&quot;:{
    &quot;token&quot;:&quot;bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...&quot;,
    &quot;notification&quot;:{
      &quot;title&quot;:&quot;Portugal vs. Denmark&quot;,
      &quot;body&quot;:&quot;great match!&quot;,
      &quot;image&quot; : &quot;image-url&quot;
    },
    &quot;data&quot; : {
      //커스텀한 데이터 값
      &quot;custom-key&quot; : &quot;value&quot;
    },
    //메세지 생명주기 defalut 는 4주
    &quot;apns&quot;:{	//ios
      &quot;headers&quot;:{
        &quot;apns-expiration&quot;:&quot;1604750400&quot;
      }
    },
    &quot;android&quot;:{
      &quot;ttl&quot;:&quot;4500s&quot;
    },
    &quot;webpush&quot;:{
      &quot;headers&quot;:{
        &quot;TTL&quot;:&quot;4500&quot;
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;✔️ Spring 서버에 FCM 적용하기&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;결국 서버에서 구현해줘야할 부분은 FCM Backend 에 메세지를 요청하는 부분입니다.&lt;br /&gt;또한, 그러기 위해서는 FCM 서버(Backend) 와 상호작용하는 방법을 결정해야합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;-fcm-backend-서버와-통신을-위한-앱-서버의-옵션-선택사항&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;  FCM Backend 서버와 통신을 위한 앱 서버의 옵션 선택사항&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p id=&quot;1-firebase-admin-sdk&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1. Firebase Admin SDK &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;( )&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Node.js, Java, Python, C#, Go 등의 프로그래밍 언어 지원.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;기기에서 주제 구독 및 구독 취소가 가능하고, 다양한 타켓 플랫폼에 맞는 메세지 페이로드 구성.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;나머지 옵션과는 다르게, 초기화 작업만 잘 진행하면 인증 처리를 자동으로 수행한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;2-fcm-http-v1-api&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2. FCM HTTP v1 API&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;u&gt;가장 최신 프로토콜&lt;/u&gt;로서 보다 안전한 승인과 유연한 교차 플래폼 메시징 기능 제공 &lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #041e49; text-align: left;&quot;&gt;(&lt;span style=&quot;color: #006dd7;&quot;&gt;Firebase Admin SDK는 이 프로토콜을 기반&lt;/span&gt;으로 하며 모든 고유 이점을 제공함)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;23.12 기준 새 기능은 해당 API를 사용하는걸 권장한다고 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;3-기존의-http-프로토콜&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;3. 기존의 HTTP 프로토콜, &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; background-color: #ffffff; text-align: left;&quot;&gt;XMPP 서버 프로토콜 &amp;rarr; (원시 프로토콜들)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;23.12 기준 HTTP v1 으로 마이그레이션 하는걸 권장한다고 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마이그레이션 참고 : &lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ko&lt;/a&gt;&lt;/span&gt;&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;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 그러니 FCM HTTP v1 API를 기반으로 &quot;Firebase Admin SDK&quot; 를 사용하면 됩니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&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;b&gt;1) 종속성 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;gradle&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701932105083&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'com.google.firebase:firebase-admin:9.2.0'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;maven&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701932093615&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.google.firebase&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;firebase-admin&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;9.2.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) Firebase 프로젝트 생성&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;Firebase Admin SDK 를 사용하기 위해서는 &lt;a href=&quot;https://console.firebase.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Firebase 프로젝트&lt;/a&gt;를 생성해 주어야 합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;FCM 을 사용하는 서비스를 시작할 떄 SDK를 초기화하는 것을 권장하는데, Google 환경 (Google cloud 등)이 아니라면 만들어준 &lt;u&gt;Firebase 프로젝트의 서비스 계정 키 파일&lt;/u&gt;을 이용해야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;[프로젝트 생성] &amp;rarr; [프로젝트 개요] &amp;rarr; [서비스 계정] &amp;rarr; [새 비공개 키 생성]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;815&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8zcc0/btsBCkU12Dy/ivtJFidPveIvkdeVrxiFvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8zcc0/btsBCkU12Dy/ivtJFidPveIvkdeVrxiFvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8zcc0/btsBCkU12Dy/ivtJFidPveIvkdeVrxiFvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8zcc0%2FbtsBCkU12Dy%2FivtJFidPveIvkdeVrxiFvk%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;1392&quot; height=&quot;815&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;815&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;  비공개키는 Json 파일이고, 공개적인 레포지토리에 올려서는 안됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) FCM Admin SDK 초기화&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;Firebase Admin SDK 초기화를 위해 config class 를 Bean으로 올리고 &lt;br /&gt;@PostConstruct를 이용하여 1회만 호출되도록 설정해주었습니다.&lt;/li&gt;
&lt;li&gt;  공식문서에서는 'FileInputStream' 을 사용하지만, 저는 배포환경에서 Jar 로 어플리케이션을 실행시키기 때문에 ClassResourceLoader 를 사용하였습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FileInputStream 사용시 상대경로가 jar 의 실행위치에 따라서 달라지기 때문에 &quot;FileNotFound&quot;를 뱉습니다&lt;/li&gt;
&lt;li&gt;ClassResourceLoader 를 사용해야 ClassPath 기준으로 파일을 찾습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1701937791928&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class FCMConfig {

    @PostConstruct
    private void init() throws IOException {
        //FileInputStream serviceAccount = new FileInputStream(&quot;resources/security/Server-Security/fcm/tht-fcm-firebase-adminsdk-waqsh-7f9e6071b2.json&quot;);

        String fileResourceURL = &quot;security/Server-Security/fcm/tht-push-fcm-firebase-adminsdk-secretkey.json&quot;;
        ClassPathResource resource = new ClassPathResource(fileResourceURL);

        FirebaseOptions options = FirebaseOptions.builder()
            .setCredentials(GoogleCredentials.fromStream(resource.getInputStream()))
            .build();

        FirebaseApp.initializeApp(options);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 비지니스 로직 작성&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;완벽한 코드는 아니지만,&amp;nbsp; FCM push 역할을 하는 Util 클래스를 따로 추출하여 비지니스 로직에대한 책임을 전가해주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FcmUtils {

    private static final int FCM_PUSH_LIMIT_SIZE = 500;
    private static final long ONE_WEEK = (long) 60 * 60 * 24 * 7;
    private static final long EXPIRED_TIME_FOR_UNIX = new Date(new Date().getTime() + ONE_WEEK).getTime();

    public static void broadCast(final List&amp;lt;String&amp;gt; registrationTokens) {

        //limit 500
        limitSizeValidate(registrationTokens);

        MulticastMessage message = MulticastMessage.builder()
                .setNotification(Notification.builder()
                        .setTitle(&quot;이름&quot;)
                        .setBody(&quot;대화내용&quot;)
                        .build())
                .putData(&quot;senderThumbnail&quot;, &quot;보내는 사람 프로필 이미지&quot;)
                .setAndroidConfig(AndroidConfig.builder()
                        .setTtl(ONE_WEEK)
                        .setNotification(AndroidNotification.builder()
                                .setIcon(&quot;보내는사람 프로필 이미지&quot;)
                                .build())
                        .build())
                 .setApnsConfig(
                        ApnsConfig.builder()
                                .setAps(Aps.builder().build())
                        .putHeader(&quot;apns-expiration&quot;, Long.toString(EXPIRED_TIME_FOR_UNIX))
                        .build())
                .addAllTokens(registrationTokens)
                .build();

        BatchResponse response = null;

        try {
            response = FirebaseMessaging.getInstance().sendEachForMulticast(message);
        } catch (FirebaseMessagingException e) {
            //todo
            //전송 실패 exception
        } finally {
            assert response != null;
            pushSuccessValidate(registrationTokens, response);
        }

    }

    private static void limitSizeValidate(final List&amp;lt;String&amp;gt; registrationTokens) {
        if (registrationTokens.size() &amp;gt; FCM_PUSH_LIMIT_SIZE) {
            //todo exception
        }
    }

    private static void pushSuccessValidate(final List&amp;lt;String&amp;gt; registrationTokens, final BatchResponse response) {

        // See the BatchResponse reference documentation
        // for the contents of response.
        System.out.println(response.getSuccessCount() + &quot; messages were sent successfully&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 테스트를 하기위해서는, fcm-token이 필요한데 이 token 안드로이드난 IOS 클라이언트 코드로 구성되어있어, 서버에서는 자체적으로 테스트하기가 곤란합니다,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 : &lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging/android/first-message?hl=ko&amp;amp;authuser=0#java_1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/docs/cloud-messaging/android/first-message?hl=ko&amp;amp;authuser=0#java_1&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;  고민되는 점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1. 처리지연(latency)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금 구조라면 실제 채팅환경에서 채팅이 쳐질때마다 fcm push 가 동작할텐데 처리지연이 걸리지는 않을지, 클라이언트 입장에서 과부하가 되는건 아닐지 고민이 되는 부분입니다.. 이에 대해서는 클라이언트분들과 이야기를 해봐야 될 것 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2. 속도&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처리지연과 같은 맥락의 고민이지만, 현재구조상 db에서 fcm token을 조회하는 1차적인 과정이 있기 때문에, 실제 사용자가 조금만 생겨도 많은 양의 조회성 쿼리가 날아갈 수 도있겠다는 생각이 들었습니다&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;일단은,,끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;*참고&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;블로그 : &lt;a href=&quot;https://kbwplace.tistory.com/179&quot;&gt;https://kbwplace.tistory.com/179&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;FCM 에서 권장하는 토큰 리프레시 모범사례 :&amp;nbsp;&lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging/manage-tokens?hl=ko&quot;&gt;https://firebase.google.com/docs/cloud-messaging/manage-tokens?hl=ko&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;FCM 공식문서 : &lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/docs/cloud-messaging?hl=ko&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;아이폰 알림 (APNS) 정의된 키모음 :&amp;nbsp;&lt;a href=&quot;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification&quot;&gt;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>FCM</category>
      <category>fcm noti</category>
      <category>fcm push</category>
      <category>java</category>
      <category>java push</category>
      <category>spring</category>
      <category>spring fcm</category>
      <category>spring noti</category>
      <category>spring push</category>
      <category>springboot</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/468</guid>
      <comments>https://thalals.tistory.com/468#entry468comment</comments>
      <pubDate>Sun, 7 Jan 2024 22:44:15 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] 컨테이너 내부 데이터 영속적으로 관리하기 (Docker volume, mount)</title>
      <link>https://thalals.tistory.com/467</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker 에서 생성되는 데이터를 &lt;b&gt;영속적으로&lt;/b&gt; 관리하기 위한 효과적인 방법으로 볼륨과 마운트가 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;도커에서 제공하는 이 2가지 기능을 이요하여 로컬(Host)의 폴더와 컨테이너의 내부 폴더를 동기화 시킬 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker Volume&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker Bind Mount&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번 포스팅에서는 이 2가지 기능에 대해 정리해보고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[목차]&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker Container 내부에서 Data 저장 과정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker 의 영속적 데이터 저장 방법&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) Volumes&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(1) 익명 볼륨 - Anonymous Volumes&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(2) 이름이 있는 볼륨 (명명된 볼륨) - Named Volumes&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(3)❗️ Volume의 메모리 저장 위치 (Mount Point)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) Docker Bind Mount - 바인드 마운트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3) Tmpfs Mount&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;레츠고!&lt;/span&gt;&lt;br /&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. Docker Container 내부에서 Data 저장 과정&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;도커의 이미지와 컨테이너는 &quot;레이어&quot;를 쌓아가면서 만들어지고 &amp;rarr; 그렇기 때문에 도커 이미지는 &quot;불변&quot;을 유지할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 말을 실행중인 현재 파일을 저장하고자 하는&amp;nbsp;&lt;b&gt;&amp;nbsp;&quot;&lt;/b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;컨테이너 입장&lt;/span&gt;&lt;b&gt;&quot;&lt;/b&gt;에서 &quot;정리&quot;해보자면,&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker Image를 &lt;u&gt;빌드할 때 생성되는 레이어&lt;/u&gt;는 &lt;b&gt;&quot;호스팅 파일 시스템&quot;&lt;/b&gt;에서 분리된 &lt;b&gt;&quot;자체 내부 파일 시스템&quot;&lt;/b&gt;이 존재하고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 이미지를 기반으로 도커 컨테이너를 시작하면 이미지 위에 &lt;b&gt;&quot;read-write&quot; 레이어(Writable Layer)&lt;/b&gt; 로 컨테이너가 추가됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 기본적으로 &quot;read-write&quot; 레이어는 이미지 파일 시스템에 엑세스할 수 있지만!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;이미지 자체는 &quot;read-only&quot; 레이어&lt;/b&gt;이기 때문에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;컨테이너는 파일을 생성할 때 상단에 추가되는 자체 &quot;read-write&quot; 레이어에 저장&lt;/span&gt;합니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6vk8F/btsBqAjpZ2d/kEn76D0kKX2v8OXtptHnd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6vk8F/btsBqAjpZ2d/kEn76D0kKX2v8OXtptHnd1/img.png&quot; data-alt=&quot;이전 게시글 복붙!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6vk8F/btsBqAjpZ2d/kEn76D0kKX2v8OXtptHnd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6vk8F%2FbtsBqAjpZ2d%2FkEn76D0kKX2v8OXtptHnd1%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;1042&quot; height=&quot;515&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이전 게시글 복붙!&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/240&quot;&gt;이전 게시글&lt;/a&gt;에서 Docker 의 이미지를 지우는 방법 중 하나인 &quot;docker run --rm&quot; 옵션에 대해 말한적이 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;결국 우리가 사용하게되는 도커의 방식은 [코드 변경] &amp;rarr; [이미지 빌드] &amp;rarr; [컨테이너화 &amp;amp; 실행] 이기 때문에&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker 컨테이너는 굉장히 자주 삭제되는 품목입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(컨테이너를 삭제시키지 않고 Git 과 같은 VSC(Version Control System)&amp;nbsp;을 이용하여 계속 사용하는 방법도 있습니다.)&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 하지만 컨테이너를 제거한다면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;컨테이너 레이어에 저장된 데이터가 같이 삭제&lt;/span&gt;되기 때문에 컨테이너가 삭제되어도 생존해야만하는 데이터를 처리하는 방법이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예를들어) 에러로그, 실행로그, 실행파일, 환경변수 세팅 파일, 덥프파일 등등.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. Docker 의 영속적 데이터 저장 방법&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  Docker 에서는 Container 에 쓰여진 데이터가 도커 생명주기와 관련없이 영속적으로 저장이 되게하거나, Data를 안전하고 성능이 좋게 관리하기 위해 아래 3가지 방법을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Volumes&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Anonymous Volumes&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Named Voumes&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;bind mounts&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tmpfs mount (&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3가지&amp;nbsp;방식&amp;nbsp;모두&amp;nbsp;Docker에서&amp;nbsp;Data&amp;nbsp;를&amp;nbsp;안전하게(영속적으로)&amp;nbsp;저장시킬&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;방법이지만,&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;도커에서는&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;volume&lt;/span&gt;&amp;nbsp;을&amp;nbsp;가장&amp;nbsp;안전한&amp;nbsp;방법이라고&amp;nbsp;이야기&amp;nbsp;하고있으니,,&amp;nbsp;뭘&amp;nbsp;사용해야할지&amp;nbsp;모를때는&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;volume&lt;/span&gt;을&amp;nbsp;사용하라고&amp;nbsp;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) Volumes&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;도커 볼륨이란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;볼륨은 Docker 컨테이너에서 생성하고 사용하는 테이터를 지속하기 위해 선호되는 도커에서 제공하는 메커니즘입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker 볼륨은 &lt;b&gt;Docker 에서 완전하게 관리하는 호스트 머신의 폴더&lt;/b&gt;입니다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;컨테이너나 이미지에 있는게 아닌 호스트 컴퓨터에 장착된 하드 드라이브에 존재하여 사용가능하거나, 컨테이너로 매핑되는 것을 의미합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그렇기때문에 볼륨을 사용하면 컨테이너 내부에서 외부 파일에 접근할 수 있고, 외부에서 내부파일에 접근할 수 있는 &quot;링크&quot;를 제공해줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 이러한 매커니즘으로 보륨을 통해 데이터를 유지할 수 있고, 연결된 컨테이너가 종료되더라도 계속 존재합니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(단, 익명 불륨은 제외)&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이러한 도커 볼륨에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;익명 볼륨(Anonymous Volumes)&quot;&lt;/span&gt; 과 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;명명 볼륨(Named Volumes)&quot;&lt;/span&gt; 2가지가 존재하고, 명확한 차이점이 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(1) 익명 볼륨 - Anonymous Volumes&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;컨테이너 내부와 볼륨을 연결할 때, 연결할 볼륨을 명시하지 않으면 &lt;b&gt;Docker Host 내에서 임의의 고유한 이름의 볼륨을 부여&lt;/b&gt;하고, 이를 &lt;b&gt;이름이 부여되지 않았다 하여 익명 볼륨&lt;/b&gt;이라 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;익명 볼륨은 컨테이너가 존재하는 동안에만 존재합니다. 즉, 컨테이너가 종료된다면 볼륨도 사라집니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실질적으로 컨테이너가 제거될 때 자동으로 제거되는 것은 아니지만, 새로운 컨테이너가 실행되면 새로운 익명 볼륨이 매핑되므로 기존의 익명 볼륨이 사용되지 않고 무의미하게 쌓이게만 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  만약 &lt;span style=&quot;color: #ee2323;&quot;&gt;'--rm'&lt;/span&gt; 옵션으로 컨테이너를 실행한다면, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;컨테이너가 종료될 때 자동으로 익명볼륨도 삭제&lt;/span&gt; 됩니다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;[익명 볼륨을 컨테이너에 추가하는 방법 2가지]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;u&gt;1. DockerFile 에 추가하기&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이미지를 만드는 Dockerfile 에 &quot;VOLUME&quot; 명령어 추가하기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;VOLUME[&quot;컨테이너 내부 경로&quot;]&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내부 경로만 존재하기 때문에 도커에서 관리하는 특정한 폴더에서 볼륨이 관리되어집니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그렇기 호스트에서 해당 파일을 찾기 힘듭니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;편집이 필요없거나, 직접 볼 필요가 없는 중요한 데이터에 적합한 볼륨 방식 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;익명 볼륨은 외부 경로보다 컨테이너 내부 경로의 우선 순위를 높이는데 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;shell&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;FROM node
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
EXPOSE 80

#docker 익명 볼륨
VOLUME [&quot;app/feadback&quot;]

CMD [&quot;node&quot;, &quot;server.js&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KsKrb/btsBr6ccesd/QUKkGMikl4Cw1FposHknF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KsKrb/btsBr6ccesd/QUKkGMikl4Cw1FposHknF0/img.png&quot; data-alt=&quot;이렇게 유니크한 이름이 도커 자체적으로 부여됨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KsKrb/btsBr6ccesd/QUKkGMikl4Cw1FposHknF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKsKrb%2FbtsBr6ccesd%2FQUKkGMikl4Cw1FposHknF0%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;792&quot; height=&quot;285&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게 유니크한 이름이 도커 자체적으로 부여됨&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;u&gt;2. &quot;-v&quot; 옵션 사용하기&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;docker run &quot;-v&quot; 옵션을 사용하여 실행하는 컨테이너에 볼륨을 매핑할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;docker run -v ['컨테이너 내부경로'] ['이미지 이름']&lt;/span&gt; 으로 실행하면, 마찬가지로 컨테이너 내부경로만 존재하기 때문에 익명 볼륨이 매핑됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ex) docker run -v app/feadback anonymous-test-volumes1:latest&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(2) 이름이 있는 볼륨 (명명된 볼륨) - Named Volumes&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Named Volumes 은 DockerFile 내부에서 생성할 수는 없고 컨테이너를 실행할 때 볼륨명을 지정하여 해당 이름으로 Docker Host 내에 볼륨을 부여할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ex) docker run -d -p 3000:80 --rm --name &quot;컨테이너 이름&quot; -v &lt;span style=&quot;color: #ee2323;&quot;&gt;['볼륨 이름':'컨테이너 내부경로']&lt;/span&gt; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;['이미지 이름']&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  이름이 부여된 볼륨은&lt;span style=&quot;background-color: #f6e199;&quot;&gt; 컨테이너 종료 후에도 도커에 의해 삭제되지 않고 유지됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 볼륨이 생성되면, 다른 컨테이너에서도 해당 볼륨에 연결하여 데이터 공유가 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBXSFj/btsBqDBKMY3/xpXvm13NZy5Gtt8ViUwo4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBXSFj/btsBqDBKMY3/xpXvm13NZy5Gtt8ViUwo4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBXSFj/btsBqDBKMY3/xpXvm13NZy5Gtt8ViUwo4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBXSFj%2FbtsBqDBKMY3%2FxpXvm13NZy5Gtt8ViUwo4k%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;633&quot; height=&quot;334&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 1년전에 만들었던 Named 볼륨입니다. 실행중인 컨테이너가 없지만 해당 볼륨이 살아있는걸 볼 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 참고로 익명볼륨은 '--rm' 옵션으로 실행하면 사라지지만 명명된 볼륨은 해당옵션으로 실행해도 컨테이너를 종료할 때 사라지지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3uzSO/btsBr3fyJtg/bLh3j0YpVQoszohvV8uqh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3uzSO/btsBr3fyJtg/bLh3j0YpVQoszohvV8uqh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3uzSO/btsBr3fyJtg/bLh3j0YpVQoszohvV8uqh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3uzSO%2FbtsBr3fyJtg%2FbLh3j0YpVQoszohvV8uqh1%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;628&quot; height=&quot;257&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(3)❗️ Volume의 메모리 저장 위치 (Mount Point)&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;volume&amp;nbsp;은&amp;nbsp;Docker(Linux&amp;nbsp;에서는&amp;nbsp;/var/lib/docker/volume/)&amp;nbsp;가&amp;nbsp;관리하는&amp;nbsp;Host&amp;nbsp;File&amp;nbsp;System&amp;nbsp;의&amp;nbsp;일부에&amp;nbsp;Data가&amp;nbsp;저장됩니다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  단 OS 가 리눅스가 아닌, 윈도우 맥이라면, 저 경로로 찾을 수 없습니다&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Mac os, windows os 에서 volume 을 &lt;u&gt;찾을 수 없는 이유는&lt;/u&gt; 직접 os를 점유하지 않고, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;호스트 os 를 공유하는 도커의&amp;nbsp; 특성입니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리눅스 os 가 아니라면, Docker 는 Host 의 Kernel Socket을 공유합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;즉, Host Linux kernel을 대신할 것이 필요하기 때문에,&amp;nbsp;&lt;/b&gt;Host 가 리눅스라면 있는걸 그대로 사용하면 되지만&lt;b&gt;,&amp;nbsp;&lt;/b&gt;다른 OS 라면 Linux Kernel이 아니기 때문에&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&amp;nbsp;OS 위에 VM 으로 Linux 를 게스트 os 로 구동하고, 그 위에 Docker Engine을 구동합니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 위의 명령어를 치면 나오는 주소인 &quot;MountPoint&quot; 는 Vm의 &quot;MountPoint&quot; 임을 의미합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다른 OS 에서 Docker Volume에 접근하기 위해서는 screen 명령어 같은걸로 vm에 접속후 접근할 수 있다고 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAKmM7/btsBqs1vhsC/Qv0OE8k5ooNUxkb43JksYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAKmM7/btsBqs1vhsC/Qv0OE8k5ooNUxkb43JksYk/img.png&quot; data-alt=&quot;mac 환경에서 찾을 수 없는 경로&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAKmM7/btsBqs1vhsC/Qv0OE8k5ooNUxkb43JksYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAKmM7%2FbtsBqs1vhsC%2FQv0OE8k5ooNUxkb43JksYk%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;400&quot; height=&quot;254&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;mac 환경에서 찾을 수 없는 경로&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) Docker Bind Mount - 바인드 마운트&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;바인드 마운트란&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Host 파일을 로컬 폴더 원하는 곳에 저장시키는 방법입니다. (원하는 폴더 동기화)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;volume은&amp;nbsp;&amp;nbsp;고정된&amp;nbsp;위치에서&amp;nbsp;데이터를&amp;nbsp;관리하기&amp;nbsp;때문에&amp;nbsp;원하는&amp;nbsp;곳에&amp;nbsp;파일을&amp;nbsp;저장시킬&amp;nbsp;수&amp;nbsp;없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Bind&amp;nbsp;Mount를&amp;nbsp;사용하여&amp;nbsp;저장되는&amp;nbsp;Data는&amp;nbsp;System&amp;nbsp;File이거나&amp;nbsp;Directory일&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker&amp;nbsp;Host&amp;nbsp;또는&amp;nbsp;Docker&amp;nbsp;Container의&amp;nbsp;Non-Docker&amp;nbsp;프로세서들이&amp;nbsp;언제든지&amp;nbsp;저장된&amp;nbsp;Data를&amp;nbsp;수정할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1701765651000&quot; class=&quot;elixir&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -v '내가 지정할 로컬 경로':/app/dockerDir  --name nginx-conatiner2 -p 8082:80 -d nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  (:) 콜론 앞에 &lt;u&gt;로컬 머신 경로&lt;/u&gt;가 붙으면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;바인드 마운트&lt;/span&gt;가 되고, &lt;u&gt;경로가 아닌 것&lt;/u&gt;이 붙으면 볼륨 이름으로 취금되어 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;명명된 볼륨&lt;/span&gt;이 됩니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  바인드 마운트가 될 때, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;호스트 머신에 마운트된 파일이 &amp;rarr; 도커 내부 파일을 덮어 쓰기&lt;/span&gt; 때문에, 만약 이미지가 빌드될 떄 생성되는 종속성 폴더 같이 &lt;u&gt;컨테이너 내부에 생성되는 보호해야할 파일&lt;/u&gt;이 존재한다면, &lt;u&gt;익명 볼륨을 사용하여 우선순위를 높여 보호&lt;/u&gt;할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clgHfq/btsBjBxyQHW/lJAscCq6BcJMysoKXYkM0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clgHfq/btsBjBxyQHW/lJAscCq6BcJMysoKXYkM0K/img.png&quot; data-alt=&quot;-v 옵션을 사용하는 3가지 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clgHfq/btsBjBxyQHW/lJAscCq6BcJMysoKXYkM0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclgHfq%2FbtsBjBxyQHW%2FlJAscCq6BcJMysoKXYkM0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;275&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;-v 옵션을 사용하는 3가지 방법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3) Tmpfs Mount&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Tmpfs : Temp File System&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리에만 Data를 저장하는 방법입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 영구적인 기억 장치가 아닌 휘발성 메모리에 저장됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;4294&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;tmpfs mount&lt;/b&gt;는 Host System의 Memory에만 Data가 저장되며, 절대로 Host의 File System에는 저장되지 않는다고 합니다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Tmpfs 방식의 Mount 는 이 글에서 자세한 사항을 다루진않고 공식문서로 대체하겠습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://docs.docker.com/storage/tmpfs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.docker.com/storage/tmpfs/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701765974786&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;tmpfs mounts&quot; data-og-description=&quot;Using tmpfs mounts&quot; data-og-host=&quot;docs.docker.com&quot; data-og-source-url=&quot;https://docs.docker.com/storage/tmpfs/&quot; data-og-url=&quot;https://docs.docker.com/storage/tmpfs/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b8qfPH/hyUFe4ifcN/qr5hP5K6QbQbDvSkLACZk1/img.png?width=129&amp;amp;height=128&amp;amp;face=0_0_129_128&quot;&gt;&lt;a href=&quot;https://docs.docker.com/storage/tmpfs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.docker.com/storage/tmpfs/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b8qfPH/hyUFe4ifcN/qr5hP5K6QbQbDvSkLACZk1/img.png?width=129&amp;amp;height=128&amp;amp;face=0_0_129_128');&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;tmpfs mounts&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Using tmpfs mounts&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Docker 컨테이너의 내부 데이터를 영속적으로 관리하는 방법에대해 정리해보았습니다.&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;도커 공식문서에서는 Bind Mount 보다 Volume이 성능이 더 좋다고 하고 권장하는 방식이라고 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스가 환경이 아니라면 Host Os에서 Volume 디렉토리에 접근하기가 꽤 귀찮고 까다로워서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Bind Mount를 주로 사용하게 되는 경향이 있었습니다&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;하지만 도커에서는 엄연히 Volume을 선호하고 보통의 Cloud 환경또한 Linux 기반의 os 로 시작하는게 보편적이기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨을 유연하게 사용하는 방법에대해 고민해 봐야겠습니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;* 참고&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Docker Docs : &lt;a href=&quot;https://docs.docker.com/storage/volumes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.docker.com/storage/volumes/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;강의 : Udemy - Docker &amp;amp; Kubernetes : 실전 가이드&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Infra/Docker(도커)</category>
      <category>Docker</category>
      <category>도커</category>
      <category>도커 로컬 폴더</category>
      <category>도커 마운트</category>
      <category>도커 컨테이너</category>
      <category>도커 폴더</category>
      <category>도커 폴더 동기화</category>
      <category>도커볼륨</category>
      <category>바인드마운트</category>
      <category>컨테이너 데이터</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/467</guid>
      <comments>https://thalals.tistory.com/467#entry467comment</comments>
      <pubDate>Tue, 5 Dec 2023 18:02:09 +0900</pubDate>
    </item>
    <item>
      <title>Docker 이미지 &amp;amp; 컨테이너 이해하고 사용하기</title>
      <link>https://thalals.tistory.com/240</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://thalals.tistory.com/238&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Infra/Docker(도커)] - Docker - 도커란&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이전 글에서 Docker의 개념에대해 살짝 정리해보았습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이번 글에서는 Docker 실질적으로 사용하기위한 개념인 컨테이너와, 이미지에 대해 학습 정리를 하고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;[목차]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Docker Images, Container 차이점&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Contanier 이미지 사용하기 (이미지 만들기)&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;외부 빌드된 이미지 - Docker Hub 이미지 가져오기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;커스텀화된 이미지 - DockerFile 로 이미지 빌드하기&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;도커 이미지 레이어 이해하기&lt;/li&gt;
&lt;li&gt;도커 이미지 지우기&lt;/li&gt;
&lt;li&gt;Docker hub 에 커스텀 이미지 올리기&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; font-size: 1.62em; letter-spacing: -1px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Docker 로 EC2에 배포하기&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; font-size: 1.62em; letter-spacing: -1px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0We3q/btsAmwv8ktd/yvtDlMMuMIFnVzV64JOTb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0We3q/btsAmwv8ktd/yvtDlMMuMIFnVzV64JOTb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0We3q/btsAmwv8ktd/yvtDlMMuMIFnVzV64JOTb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0We3q%2FbtsAmwv8ktd%2FyvtDlMMuMIFnVzV64JOTb0%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;284&quot; height=&quot;159&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. Images vs Containers&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Docker 환경에서의 이미지와 컨테이너의 차이점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;gt; 도커는 컨테이너 격리 프로그램입니다&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;gt; 따라서 컨테이너라는 개념을 적용하고, 이를 구성하는 이미지가 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;✔️ &lt;u&gt;Docker Containers&lt;/u&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;컨테이너는&lt;/b&gt; 코드, 애플리케이션을 실행하는 전체 환경 등, 무엇이든 포함하는 작은 패키지라고 생각할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;컨테이너에는 &lt;b&gt;소프트웨어 실행 유닛이 존재&lt;/b&gt;하고, 컨테이너에서는 &lt;b&gt;이 유닛을 실행&lt;/b&gt;시킵니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;컨테이너는&lt;/b&gt; 실제로 실행되는 &lt;span style=&quot;background-color: #f6e199; color: #ee2323;&quot;&gt;&lt;b&gt;프로세스&lt;/b&gt;&lt;/span&gt; 입니다.&lt;/span&gt;&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;✔️ &lt;u&gt;Docker Images&lt;/u&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;도커 이미지란, &lt;b&gt;컨테이너의 템플릿 혹은 블루포인트(시작점) &lt;/b&gt;입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이미지는 실제로 코드와 코드를 실행하는데 필요한 도구를 포함하고 &amp;rarr; 그런 다음 컨테이너가 실행되어 코드가 실행됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이렇게 이미지와, 컨테이너의 개념을 분리함으로써 이미지를 통해 여러대의 컨테이너를 손쉽게 만들 수 있게 됩니다.&lt;/span&gt;&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;  즉, 이미지는 모든 설정 명령과 모든 코드가 포함된 공유 가능한 패키지 이며 컨테이너는 그러한 이미지의 구체적인 실행 인스턴스(어플리케이션) 입니다&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;  Dokcer 를 사용한다는 것은 이미지를 기반으로하는 컨테이너를 실행하는 것이고, 이것이 도커의 핵심 기본 개념이며 모든 것 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b21fE1/btsAaXHRgRf/43S1KVscE39XdBNtoEWqu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b21fE1/btsAaXHRgRf/43S1KVscE39XdBNtoEWqu1/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;325&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.9451%; margin-right: 10px;&quot; data-widthpercent=&quot;50.53&quot; id=&quot;kEditorPhotosEditingImage-1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b21fE1/btsAaXHRgRf/43S1KVscE39XdBNtoEWqu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb21fE1%2FbtsAaXHRgRf%2F43S1KVscE39XdBNtoEWqu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs5zxK/btsAaVXAvf6/ZKgGHraWq853nXNblkeBW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs5zxK/btsAaVXAvf6/ZKgGHraWq853nXNblkeBW0/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;332&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;49.47&quot; style=&quot;width: 48.8921%;&quot; id=&quot;kEditorPhotosEditingImage-2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs5zxK/btsAaVXAvf6/ZKgGHraWq853nXNblkeBW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs5zxK%2FbtsAaVXAvf6%2FZKgGHraWq853nXNblkeBW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Docker 이미지와 컨테이너의 관계&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. Docker Image 사용하기 (Docker Build Image)&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;nbsp;실제로 컨테이너를 실행할 수 있도록 이미지를 생성하거나 가져오는 2가지 방법이 존재합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;br /&gt;1) 외부 이미지 사용 - &quot;Docker Hub&quot;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이미 존재하는 이미지를 사용하는 것&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;동료, 혹은 미리 구축된 공식 이미지나 커뮤니티에서 공유한 이미지를 사용하는 방법입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker Hub&lt;/a&gt; 에서 공식 이미지들을 찾을 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJFJS/btsz91xpJ0t/V0Ort5md2StWZV2YvJHtck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJFJS/btsz91xpJ0t/V0Ort5md2StWZV2YvJHtck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJFJS/btsz91xpJ0t/V0Ort5md2StWZV2YvJHtck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJFJS%2Fbtsz91xpJ0t%2FV0Ort5md2StWZV2YvJHtck%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;1324&quot; height=&quot;327&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;327&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Docke hub에서 이미지 내려받기&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;nbsp;로컬환경 shell script(명령 프롬프트) 환경에서 Docker HUB에 올라가있는 공식 이미지의 이름을 사용합니다,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;`docker pull - `를 사용해서 이미지를 받기만해도 되고, `docker run - `을 사용하여 컨테이너를 바로 실행시킬 수 도 있습니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;맥(mac)환경이라면 Docker desktop 을 실행시켜 Docker 를 활성화 시켜주어야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;`docker run ` 시 로컬환경에 해당 이미지가 없다면 자동으로 Docker hub에서 이미지를 pull 받습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;`docker run `으로 실행시켰기 때문에 contatner name 도 자동으로 설정되어 실행되어집니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;shell&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;//기본적인 Docker run 포맷
docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E7x6i/btsAjUpGbGv/7T2zLmnQCTYtuukDrCwkj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E7x6i/btsAjUpGbGv/7T2zLmnQCTYtuukDrCwkj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E7x6i/btsAjUpGbGv/7T2zLmnQCTYtuukDrCwkj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE7x6i%2FbtsAjUpGbGv%2F7T2zLmnQCTYtuukDrCwkj0%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;1113&quot; height=&quot;433&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;✔️ docker run 시 status 가 exit 인 이유&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만 위의 사진처럼 단순하게 docker run [image name] 로 실행한다면 컨테이너는 EXIT 상태가 되어집니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그 이유는, 컨테이너는 격리되어 실행되기 때문에 노드에 의해 노출된 인터렉티브 쉘이 우리(Host)에게는 노출되지 않기 때문입니다.&lt;/span&gt;&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;b&gt;✔️&amp;nbsp;&lt;/b&gt;docker run 시 컨테이너 내부 노출시키기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;docker-run-options&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Docker run options 으로 어떻게 Host 에게 노출시킬것인지 정할 수 있습니다.&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;-it 컨테이너 실행시 내부에서 호스팅으로 대화형 세션(명령 프롬프트)를 노출시킵니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;-p : &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;호스트에 연결된 컨테이너의 특정 포트를 외부에 노출시킵니다. 해당 포트를 이용하여 로컬에서 특정 포트를 바인딩해 컨테이너 내부로 연결시킬 수 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;shell&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;// -it 옵션
$ docker run -it ubuntu:20.04

// -p 옵션
$ docker run -p 127.0.0.1:80:8080 ubuntu:20.04 bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하자만 중요한 것은 나의 어플리케이션을 Docker 컨테이너로 실행시키는 것!&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이렇게 컨테이너를 띄운 후, &lt;u&gt;노출된 &lt;b&gt;컨테이너 내부에서 깃허브&lt;/b&gt;와 같은 &lt;b&gt;외부 저장소에서&lt;/b&gt;&lt;/u&gt; 코드를 끌어다 쓸 수도 있지만&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이미지의 &quot;쉬운 공유&quot; 장점을 살리기 위해선 우리의 &lt;span style=&quot;color: #ee2323;&quot;&gt;어플리케이션까지 같이 빌드된 커스텀화된 이미지&lt;/span&gt;가 있으면 좋겠죠&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2) Dockerfile 을 사용하여 자체 이미지 빌드하기&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;gt; DockerFile을 이용하여 커스텀화된 이미지를 빌드할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. Dockrfile&amp;nbsp; 만들기&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;DockerFile이란 도커에의해 식별되는 특별한 이름을 가진 파일입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;확장자 없이 &quot;DockerFile&quot;이란 이름의 파일을 생성하면 도커에서 이미지를 빌드하기위한 기준파일이구나~ 인식하게됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;IDE를 사용하시다면 각 IDE에서 지원하는 &quot;도커 플러그인&quot;을 사용하시다면 조금 더 편하게 작성할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. Dockerfile 문법 (Dockerfile 구조)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Dockerfile 작성법에 대해 기본적인 개념만 정리해보고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699946145041&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//node 서버를 실행하는 Dockerfile

FROM node                  //(1)
WORKDIR /app               //(3)
COPY . /app                //(2)
RUN npm install            //(4)
EXPOSE 80                  //(5)
CMD [&quot;node&quot;, &quot;server.js&quot;]  //(6)&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;FROM&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;베이스 이미지를 불러옵니다. 베이스 이미지는 외부 빌드된 이미지 - 즉, Docker Hub 에서 가져옵니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이론적으로 처음부터 도커 이미지 레이어를 구축 할 수도 있지만, 그건 너무 손이 많이가는일이니 미리 구축되어있는 공식 운영체제 레이어를 사용하는 걸 추천한다고 합니다.&lt;br /&gt;( &amp;rarr; Docker Hub 에 등록된 공식 이미지들도 보통 Linux의 이미지인 'alpine' 이나 'debian'을 사용합니다. &lt;a href=&quot;https://github.com/nodejs/docker-node/blob/main/Dockerfile-alpine.template&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docker node repo&lt;/a&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: left;&quot;&gt;Docker 이미지는 base 이미지부터 시작해 기존 이미지위에 새로운 이미지를 중첩해서 여러 단계의 이미지 층( layer)를 쌓아가며 만들어집니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;nbsp;&lt;b&gt;COPY&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: left; background-color: #ffffff;&quot;&gt;이미지를 만들 때 &quot;어떤 파일&quot;을 복사해서 &quot;어디에&quot; 저장할 것인지를 의미 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;COPY [복사할 로컬 파일경로] [붙여넣을 컨테이너 내부 경로]&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;nbsp;이때 첫번째 경로에 Dockerfile이 존재한다면 복사 시 제거됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;두번째 경로로 루트 폴더 즉, 도커 컨테이너의 루트 엔트리(/.)를 사용하지 않고 전적으로 사용자가 선택한 &quot;/app 경로를 사용한다면 컨테이너 내부의 app 폴더 에 저장되고, 폴더가 없으면 생성됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;b&gt;WORKDIR&lt;/b&gt;&amp;nbsp;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;상대경로를 적었다면 이 폴더 내부에서 명령어를 실행하라고 알리는 것 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그렇지않다면 이미지와 컨테이너로 인해 실행된 인스턴스는 루트에서 아래 정의된 모든 명령어를 실행할 것 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Run&lt;/b&gt;&amp;nbsp;&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;RUN 명령어는 도커파일로부터 도커 이미지를 빌드하는 순간에 실행이 되는 명령어입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;RUN 명령어를 이용하여 어플리케이션을 실행시킬 수도 있지만, 이는 권장되는 방식입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;도커 이미지는 실행환경을 세팅하는 템플릿이고, 어플리케이션은 컨테이너가 실행될때 같이 실행되는게 권장되는 방식이기 때문입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;따라서 &lt;b&gt;RUN&lt;/b&gt; 명령어는 라이브러리를 설치하는 용도로 주로 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; letter-spacing: 0px;&quot;&gt;EXPOSE 80&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; letter-spacing: 0px;&quot;&gt;EXPOSE 명령어는 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; letter-spacing: 0px;&quot;&gt;해당 Dockerfile로 빌드되는 이미지가 어떤 포트를 노출할 것인지 명시적으로 알리기위한 용도라고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;&amp;nbsp;따라서&amp;nbsp;Document 에 가까워서 사실 안해줘도 되지만, 공식문서에서는 Dockerfiledp 'EXPOSE'를 추가하여 이 동작을 문서화하는 것을 모범적인 사용법이라고 명시하고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;EXPOSE 명령어는 문서화이기 때문에, docker run [이미지] 실행시 -p 옵션(publish) 을 주어 실질적으로 EXPOSE 에 명시된 포트를 노출시켜주어야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;CMD&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이미지가 생성될 때 실행되지 않고 이미지를 기반으로 컨테이너가 시작할때 해당 명령어가 실행 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;문법이 조금 다른데&lt;/span&gt; 배열을 이용해 전달합니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ex) CMD [&quot;node&quot;, &quot;server.js&quot;] &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;rarr;&amp;nbsp; 명령을 두 개의 문자열로 분열 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;rarr;&amp;nbsp;도커에게 이미지를 기반으로 컨테이너가 생성될 때마다 그 컨테이너 내부에 있는 node 명령어를 사용하여 server.js 파일을 실행하도록 지시&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xhhtY/btsAjuMfV17/BAYqdpZOkMdVuIOHIgekEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xhhtY/btsAjuMfV17/BAYqdpZOkMdVuIOHIgekEk/img.png&quot; data-alt=&quot;JDK Dockerfile 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xhhtY/btsAjuMfV17/BAYqdpZOkMdVuIOHIgekEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxhhtY%2FbtsAjuMfV17%2FBAYqdpZOkMdVuIOHIgekEk%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;543&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JDK Dockerfile 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;❗️이미지를 실행 명령어들 (RUN, ENTRYPOINT, CMD 차이점)&lt;/b&gt;&lt;br /&gt;&amp;rarr; 해당 사진은, 과거 저의 포스팅에서 가져온건데, ENTRYPOINT를 사용했군요&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;RUN : 위에서 말했다시피, 이미지가 빌드될 떄 실행합니다. 따라서 어플리케이션 실행으로는 권장되지 않는 명령어입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CMD, ENTRYPOINT : 2개 명령어 모두 컨테이너가 실행시 동작할 명령어를 정의 합니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;차이점&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ENTRYPOINT&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어는 오버라이딩이 어렵고&lt;span&gt;&amp;nbsp;&lt;/span&gt;CMD&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어는 오버라이딩이 쉽다.&lt;/li&gt;
&lt;li&gt;docker run&lt;span&gt;&amp;nbsp;&lt;/span&gt;시에 다른 실행 명령어가 있으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;CMD&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어에 써준 내용은 무시된다. 반면에&lt;span&gt;&amp;nbsp;&lt;/span&gt;ENTRYPOINT&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어는 무시되지 않고&lt;span&gt;&amp;nbsp;&lt;/span&gt;docker run에 붙여준 실행 명령어를 인자로 받아서 컨테이너를 실행한다.&lt;/li&gt;
&lt;li&gt;CMD&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어는&lt;span&gt;&amp;nbsp;&lt;/span&gt;docker run&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령 내에 명시된 매개변수가 있는 경우 Daemon에서 무시된다.&lt;/li&gt;
&lt;li&gt;ENTRYPOINT&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어는 무시되지 않고 대신 명령의 인수로 취급하여 매개변수로 추가된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;color: #2d2f31; text-align: start;&quot;&gt;
&lt;p data-purpose=&quot;transcript-cue&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 이미지 빌드하기&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자 다왔습니다! 이제 Dockerfile이 있는 디렉토리 경로에서 빌드만 하면됩니다!&lt;/li&gt;
&lt;li&gt;태그 : 태그는 도커 레포지토리(이미지)의 버전을 관리하기 위함입니다. default는 lateast입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1638759639563&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//docker build -t {이미지명}:{태그} {Dockerfile 파일 위치}
//-t : 이름을 나타냄

docker build -t 이미지이름 .&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;4. 도커 이미지 생성 확인&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&amp;gt; 아래의 명령어로 이미지 생성 목록을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638758801866&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker images&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cibVnw/btsAmwJBgoy/tyVRZMcF4aRdZ04q6Jqtf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cibVnw/btsAmwJBgoy/tyVRZMcF4aRdZ04q6Jqtf1/img.png&quot; data-alt=&quot;도커 이미지 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cibVnw/btsAmwJBgoy/tyVRZMcF4aRdZ04q6Jqtf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcibVnw%2FbtsAmwJBgoy%2FtyVRZMcF4aRdZ04q6Jqtf1%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;800&quot; height=&quot;94&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커 이미지 목록&lt;/figcaption&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;5. 도커 이미지를 컨테이너화 시키기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이미지를 만들어주었으니 Docker Hub 이미지를 사용할 때와 마찬가지로 실행시키면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;-p : 로컬포트에서 접속시 어떤 도커포트랑 연결할 것인지를 의미하는 명령문입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;-d : 백그라운드 실행을 의미합니다. (detach : 종료하지 않고 분리시킴)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1638759740036&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --name maru-spring -p 8080:8080 -d maru-spring

docker run --name '이미지 이름' -p '로컬포트:도커포트' -d 이미지이름 (-d는 백그라운드 실행을 의미)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;도커가 실행이 완료되면 요상한 문자가 나타납니다. 어떤 도커 이미지가 실행중인지 확인해 봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638759888972&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker ps&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1433&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCakpa/btrm56xgFPl/wp0gi8dcU6vl51DQsP1Dp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCakpa/btrm56xgFPl/wp0gi8dcU6vl51DQsP1Dp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCakpa/btrm56xgFPl/wp0gi8dcU6vl51DQsP1Dp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCakpa%2Fbtrm56xgFPl%2Fwp0gi8dcU6vl51DQsP1Dp1%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;1433&quot; height=&quot;147&quot; data-origin-width=&quot;1433&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;사진의 PORTS를 보면 로컬의 어떤 포트가, 도커의 포트랑 연결되어있는지 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Docker Image 레이어 이해하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Docker &lt;span style=&quot;color: #ee2323;&quot;&gt;image 는 레이어 기반&lt;/span&gt;으로 이루어져있고, 그렇기에 Docker는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;레이어 기반 아키텍처&lt;/span&gt;를 가진다라고도 이야기합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제로 이미지를 빌드해보면 각각의 명령어별로 무언가 수행됨을 볼 수 있습니다. &lt;br /&gt;(EXPOSE는 문서화, CMD 는 컨테이너가 실행될 때 수행)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/erIAac/btsAqZrnSvE/OKqaWl1PKmMZKLSHx1p7DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/erIAac/btsAqZrnSvE/OKqaWl1PKmMZKLSHx1p7DK/img.png&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;371&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.2563%; margin-right: 10px;&quot; data-widthpercent=&quot;40.73&quot; id=&quot;kEditorPhotosEditingImage-8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/erIAac/btsAqZrnSvE/OKqaWl1PKmMZKLSHx1p7DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FerIAac%2FbtsAqZrnSvE%2FOKqaWl1PKmMZKLSHx1p7DK%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;496&quot; height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biUjLa/btsAmEWpsiA/NVsVg5pgDYOAtHKz0vqx61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biUjLa/btsAmEWpsiA/NVsVg5pgDYOAtHKz0vqx61/img.png&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;422&quot; data-is-animation=&quot;false&quot; style=&quot;width: 58.5809%;&quot; data-widthpercent=&quot;59.27&quot; id=&quot;kEditorPhotosEditingImage-9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biUjLa/btsAmEWpsiA/NVsVg5pgDYOAtHKz0vqx61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiUjLa%2FbtsAmEWpsiA%2FNVsVg5pgDYOAtHKz0vqx61%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;821&quot; height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ Docker  Image build 동작과정&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이미지가 빌드될 떄 명령어가 레이어에 쌓여가는 과정은 아래 사진과 같이 명령어 별로 Read Only(읽기 전용) 쌓아가는 것 입니다.&lt;/li&gt;
&lt;li&gt;이미지의 모든 명령어는 도커에서 관리하는 &quot;읽기 전용&quot; 레이어가 되고, 해당 이미지를 컨테이로 실행시킬 때 &quot;쓰기 가능한&quot; 최종 레이어가 생성됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특성 때문에 &lt;u&gt;도커의 이미지는 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;불변&lt;/span&gt;&quot;&lt;/u&gt;한다고 이야기 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TIe4F/btsAq1isxzT/gYABKfadfDnNyzx8akIoy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TIe4F/btsAq1isxzT/gYABKfadfDnNyzx8akIoy0/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;342&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 47.6216%; margin-right: 10px;&quot; data-widthpercent=&quot;48.18&quot; id=&quot;kEditorPhotosEditingImage-10&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TIe4F/btsAq1isxzT/gYABKfadfDnNyzx8akIoy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTIe4F%2FbtsAq1isxzT%2FgYABKfadfDnNyzx8akIoy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw77Wj/btsArmmynKr/aK1mnjsyrXLZFFAKueKW40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw77Wj/btsArmmynKr/aK1mnjsyrXLZFFAKueKW40/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;318&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.82&quot; data-filename=&quot;blob&quot; style=&quot;width: 51.2156%;&quot; id=&quot;kEditorPhotosEditingImage-11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw77Wj/btsArmmynKr/aK1mnjsyrXLZFFAKueKW40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw77Wj%2FbtsArmmynKr%2FaK1mnjsyrXLZFFAKueKW40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;https://www.44bits.io/ko/post/building-docker-image-basic-commit-diff-and-dockerfile&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 아래와 같이 3가지 명령어를 가지는 Dockerfile 을 &amp;amp;&amp;amp; 명령어를 사용해 2가지 명령어로 수정한다면 레이어의 개수 또한 줄어듭니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1700099146011&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM ubuntu:18.04
RUN apt update
RUN apt install -y git

&amp;darr;

FROM ubuntu:18.04
RUN apt update &amp;amp;&amp;amp; apt install -y git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I4H30/btsAnCwYnE8/xLCRjFRmgMtu7eIl5uN6L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I4H30/btsAnCwYnE8/xLCRjFRmgMtu7eIl5uN6L1/img.png&quot; data-alt=&quot;https://www.44bits.io/ko/post/building-docker-image-basic-commit-diff-and-dockerfile&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I4H30/btsAnCwYnE8/xLCRjFRmgMtu7eIl5uN6L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI4H30%2FbtsAnCwYnE8%2FxLCRjFRmgMtu7eIl5uN6L1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;349&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.44bits.io/ko/post/building-docker-image-basic-commit-diff-and-dockerfile&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;docker inspect [이미지]&lt;/b&gt;&quot; 명령어를 사용하면 아래와같이 어떤 레이어로 구성되어있는지 확인할 수 있는데, 실제 Dockerfile 의 명령어 개수보다 많은 레이어로 구성되는 이유는 &quot;FROM node&quot; 라는 &lt;u&gt;&lt;b&gt;베이스 이미지에서 구축된 레이어를 포함&lt;/b&gt;&lt;/u&gt;하기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chubis/btsAm5lT7kU/8qqpHJZyQGhikerHdYxSHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chubis/btsAm5lT7kU/8qqpHJZyQGhikerHdYxSHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chubis/btsAm5lT7kU/8qqpHJZyQGhikerHdYxSHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchubis%2FbtsAm5lT7kU%2F8qqpHJZyQGhikerHdYxSHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ Docker Cache를 통해 build 최적화 하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&amp;nbsp;&quot;읽기 전용 레이어&quot; 라는 뜻은 이미지를 다시 빌드할 때&lt;u&gt; 변경된 부분의 명령과 그 이후의 모든 명령&lt;/u&gt;이 재평가된다는 의미입니다.&lt;br /&gt;&amp;rarr; 아무런 변경도 없이 Dockerfile 을 이용해 새롭게 이미지를 빌드하면 이전과는 다르게 매우 빠르게 빌드가 되는 것을 확인할 수 있었습니다. (3.5s &amp;rarr; 1.5s)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PDwO0/btsAm1xh50O/iaIvKrInjKBtzPptoIQJJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PDwO0/btsAm1xh50O/iaIvKrInjKBtzPptoIQJJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PDwO0/btsAm1xh50O/iaIvKrInjKBtzPptoIQJJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPDwO0%2FbtsAm1xh50O%2FiaIvKrInjKBtzPptoIQJJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;310&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  그 이유는 &lt;b&gt;Docker Cache&amp;nbsp;&lt;/b&gt;를 사용하기 때문입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;동일한 작업 디렉토리에&lt;/li&gt;
&lt;li&gt;복사한 코드(COPY로 불러오는 어플리케이션 코드) 는 전혀 변경되지 않았고&lt;/li&gt;
&lt;li&gt;새 파일도 없고 변경된 파일도 없다면&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커는 실제로 그 명령을 다시 거칠 필요가 없다고 추론합니다. &amp;rarr;&amp;nbsp; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;도커는 이미지를 빌드할 때 마다 모든 명령 결과를 캐시&lt;/span&gt;하고 다시 빌드할때 명령을 다시 실행할 필요가 없다고 판단되면 캐시된 결과를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이렇기때문에 도커는 레이어 기반 아키텍처라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 명령은 Dockerfile의 레이어를 나타냅니다.&amp;nbsp;&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 data-ke-size=&quot;size16&quot;&gt;  하지만 여기서 &lt;b&gt;하위 레이어가 변경이 일어난다면&lt;/b&gt;, 도커는 변경점이 일어났다고 판단하기 때문에 &lt;b&gt;그 위에 쌓여진 모든 레이어를 다시 빌드합니다.&amp;nbsp;&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;따라서 Dockerfile을 아래처럼 변경하여 최적화 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;COPY . /app 으로 불러오는 어플리케이션 코드가 변경되더라도, 다운받아야하는 종속성이 변경되지 않았다면 추가적인 작업을 하지 않도록 하위레이어로 쌓는 것 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1700026115463&quot; class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;//node 서버를 실행하는 Dockerfile
FROM node                  //(1)
WORKDIR /app               //(3)
COPY . /app                //(2)
RUN npm install            //(4)
EXPOSE 80                  //(5)
CMD [&quot;node&quot;, &quot;server.js&quot;]  //(6)

             &amp;darr;

FROM node                  //(1)
WORKDIR /app               //(3)
COPY package.json /app     // 종속성이 추가되거나 변경됨을 감지하기위한 COPY 명령어
RUN npm install            // 종속성파일이 변경되지않으면 npm install을 또 할 필요가 없음
COPY . /app                // 어플리케이션이 변경되면 그 위의 레이어만 영향이 가도록
EXPOSE 80                  //(5)
CMD [&quot;node&quot;, &quot;server.js&quot;]  //(6)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;4. Docker 이미지 지우기&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이미지를 빌드해서 만들고, 컨테이너를 생성해보았으니 이제 지워볼까 합니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 컨테이너 지우기 or&amp;nbsp; 종료&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;Stop&lt;/b&gt; 명령어 중지 후에&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;rm&lt;/b&gt; 명령어로 간단하게 지울 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ps -a&lt;/b&gt; : 실행중인 프로세스와 종료중인 프로세스 모두 보여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8zy6d/btsAopj6JLC/WIKG1p1EjuYRFxurW5CM51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8zy6d/btsAopj6JLC/WIKG1p1EjuYRFxurW5CM51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8zy6d/btsAopj6JLC/WIKG1p1EjuYRFxurW5CM51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8zy6d%2FbtsAopj6JLC%2FWIKG1p1EjuYRFxurW5CM51%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;669&quot; height=&quot;165&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️&lt;span&gt;&amp;nbsp;이미지&lt;/span&gt;&amp;nbsp;지우기&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;docker&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;rmi &lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지는 rmi 명령어를 이용하여 개별 이미지를 선택해 삭제할 수 있습니다.&lt;/li&gt;
&lt;li&gt;단, 해당 이미지를 사용중인 컨테이너가 존재한다면(시작 or 중지) 그 컨테이너가 속한 이미지를 지울 수 없습니다.&lt;/li&gt;
&lt;li&gt;ex) docker &lt;b&gt;rmi&lt;/b&gt; [이미지 id] [이미지 id] [이미지 id] (복수 삭제 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;docker image &lt;b&gt;prune&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 실행중인 컨테이너에서 사용중이지 않은 모든 이미지를 지우는 명령어 입니다.&lt;/li&gt;
&lt;li&gt;docker image prune : 사용되지 않는 이미지 모두 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;docker run &lt;b&gt;&quot;--rm&quot;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중지된 컨테이너 자동으로 제거하는 docker run 옵션입니다.&lt;/li&gt;
&lt;li&gt;보통 중지된 컨테이너를 새로 시작할때는 &amp;rarr; 코드에 변경이 일어나 이미지를 재빌드 해야할 때가 많습니다.&lt;/li&gt;
&lt;li&gt;그렇기 때문에 컨테이너를 중지시킬때 아예 지워버리는게 경험상 유용하다고 합니다&lt;/li&gt;
&lt;li&gt;하지만... 음... 해당 옵션을 활성화하기 위해서는 에러 로그나, 덤프파일 같이  비정상적인 다운 시 모니터링을 위한 파일은 따로 빼둘 수있는 작업이 선행되어지는게 좋아보입니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;5. Docker hub에 이미지 업로드 (push)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. 먼저 도커허브에 레포지토리를 만들어 둡니다.&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;(&lt;a href=&quot;https://hub.docker.com/&quot;&gt;https://hub.docker.com/&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byBgGf/btrmZH63ZLa/wKWvmwCgLNRbVSxqNYt6R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byBgGf/btrmZH63ZLa/wKWvmwCgLNRbVSxqNYt6R0/img.png&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;680&quot; style=&quot;width: 41.6587%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;42.15&quot; id=&quot;kEditorPhotosEditingImage-16&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byBgGf/btrmZH63ZLa/wKWvmwCgLNRbVSxqNYt6R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyBgGf%2FbtrmZH63ZLa%2FwKWvmwCgLNRbVSxqNYt6R0%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;1020&quot; height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfgNr5/btrm3O5uvGt/r098puZ8WKW2ZziH2i5P9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfgNr5/btrm3O5uvGt/r098puZ8WKW2ZziH2i5P9k/img.png&quot; data-origin-width=&quot;1015&quot; data-origin-height=&quot;493&quot; style=&quot;width: 57.1786%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;57.85&quot; id=&quot;kEditorPhotosEditingImage-17&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfgNr5/btrm3O5uvGt/r098puZ8WKW2ZziH2i5P9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfgNr5%2Fbtrm3O5uvGt%2Fr098puZ8WKW2ZziH2i5P9k%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;1015&quot; height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. 터미널에서 도커에 로그인을 해야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638767574644&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker login&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctJFMK/btsAjvqUdoy/oh4uq8UJDnZcq148LSDMDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctJFMK/btsAjvqUdoy/oh4uq8UJDnZcq148LSDMDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctJFMK/btsAjvqUdoy/oh4uq8UJDnZcq148LSDMDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctJFMK%2FbtsAjvqUdoy%2Foh4uq8UJDnZcq148LSDMDk%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;800&quot; height=&quot;83&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3. 그 다음 업로드할 이미지를 태깅해 주어야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638767937827&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker tag maru-spring thalals/maru-spring

docker push {리파지토리 이미지명}:{태그}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHWskM/btsAjNZmn7G/rL6FK3RXmy55AKcPtIQFpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHWskM/btsAjNZmn7G/rL6FK3RXmy55AKcPtIQFpK/img.png&quot; data-alt=&quot;태깅 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHWskM/btsAjNZmn7G/rL6FK3RXmy55AKcPtIQFpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHWskM%2FbtsAjNZmn7G%2FrL6FK3RXmy55AKcPtIQFpK%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;800&quot; height=&quot;170&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;태깅 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;4. 태깅한 이미를 도커허브의 레포지토리에 push해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638768009994&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker push thalals/maru-spring

docker push {리파지토리 이미지명}:{태그}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tii47/btsAmFGxeLH/owwZdvwpBWCGA1PMg4xUVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tii47/btsAmFGxeLH/owwZdvwpBWCGA1PMg4xUVk/img.png&quot; data-alt=&quot;푸시 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tii47/btsAmFGxeLH/owwZdvwpBWCGA1PMg4xUVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftii47%2FbtsAmFGxeLH%2FowwZdvwpBWCGA1PMg4xUVk%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;800&quot; height=&quot;149&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;푸시 완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;489&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpZiQ7/btsAjS0Gfpd/L6mYGKTLZlFzqiTulECRNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpZiQ7/btsAjS0Gfpd/L6mYGKTLZlFzqiTulECRNk/img.png&quot; data-alt=&quot;도커 허브 레포지토리에 이미지 업로드 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpZiQ7/btsAjS0Gfpd/L6mYGKTLZlFzqiTulECRNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpZiQ7%2FbtsAjS0Gfpd%2FL6mYGKTLZlFzqiTulECRNk%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;800&quot; height=&quot;489&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;489&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커 허브 레포지토리에 이미지 업로드 완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;6. Docker 이용해서 EC2에 배포하기&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실 별건 아닙니다. Docker hub 에 이미지를 올렸기 때문에 pull 받아서 손쉽게 배포가 가능함을 보이기 위함입니다.&lt;/li&gt;
&lt;li&gt;먼저 EC2를 하나 생성하고, 터미널에서&amp;nbsp; EC2로 접속합니다.&lt;/li&gt;
&lt;li&gt;EC2 프리티어 생성 및 접속 : &lt;a href=&quot;https://thalals.tistory.com/120&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://thalals.tistory.com/120&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1) EC2에 도커 설치하기&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1638786434486&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi install.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;install.sh&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638786509388&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get update

sudo apt-get -y install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
&quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable&quot;

sudo apt-get update

sudo apt-get install -y docker-ce

sudo usermod -aG docker $USER&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;59&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oA9XQ/btrneZqMoX9/38HdoV5LLKzST5uFsKUYrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oA9XQ/btrneZqMoX9/38HdoV5LLKzST5uFsKUYrK/img.png&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oA9XQ/btrneZqMoX9/38HdoV5LLKzST5uFsKUYrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoA9XQ%2FbtrneZqMoX9%2F38HdoV5LLKzST5uFsKUYrK%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;312&quot; height=&quot;59&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;59&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;install 파일에 실행권한을 준다음(chmod 777) 파일을 실행시킵니다&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638786787258&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;chmod 777 install.sh
./install.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfmXyM/btrnevi3LGe/aVrbLItz8Ifr0xGsCbjhH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfmXyM/btrnevi3LGe/aVrbLItz8Ifr0xGsCbjhH0/img.png&quot; data-alt=&quot;쉘스크립트 파일 설치&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfmXyM/btrnevi3LGe/aVrbLItz8Ifr0xGsCbjhH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfmXyM%2Fbtrnevi3LGe%2FaVrbLItz8Ifr0xGsCbjhH0%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;343&quot; height=&quot;63&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;쉘스크립트 파일 설치&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;55&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qIKsK/btrm5zAOYaR/A1IT9uqKo0TkPwF6p5KByK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qIKsK/btrm5zAOYaR/A1IT9uqKo0TkPwF6p5KByK/img.png&quot; data-alt=&quot;도커 잘 깔렸는지 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qIKsK/btrm5zAOYaR/A1IT9uqKo0TkPwF6p5KByK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqIKsK%2Fbtrm5zAOYaR%2FA1IT9uqKo0TkPwF6p5KByK%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;431&quot; height=&quot;55&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;55&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커 잘 깔렸는지 확인&lt;/figcaption&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2) Docker image pull&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ec2에서 도커허브에 올라가있는 이미지를 pull 받아서 외부 빌드된 이미지랑 똑같이 사용하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1638787411145&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker pull {도커 허브 이미지명}:{태그}
docker run --name {컨테이너 이름} -p 5000:5000 -d {이미지 이름}:{태그}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2YCsU/btsAc3PMm5m/VQ7pCBGiihq1TbjYx1Pli0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2YCsU/btsAc3PMm5m/VQ7pCBGiihq1TbjYx1Pli0/img.png&quot; data-alt=&quot;docker pull&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2YCsU/btsAc3PMm5m/VQ7pCBGiihq1TbjYx1Pli0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2YCsU%2FbtsAc3PMm5m%2FVQ7pCBGiihq1TbjYx1Pli0%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;617&quot; height=&quot;229&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;docker pull&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhOVJO/btsAjVQG0Is/i0Xl1dSG33J2DyuYCrNtQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhOVJO/btsAjVQG0Is/i0Xl1dSG33J2DyuYCrNtQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhOVJO/btsAjVQG0Is/i0Xl1dSG33J2DyuYCrNtQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhOVJO%2FbtsAjVQG0Is%2Fi0Xl1dSG33J2DyuYCrNtQk%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;800&quot; height=&quot;74&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그러면 인자 localhost:8080으로 접속할 수 있는데 &amp;rarr; &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그 전에 AWS에서 8080포트를 열어주어야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dk0RQR/btsAjWhLTv1/3eYwjeWzYjAX3EDxTs8MR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dk0RQR/btsAjWhLTv1/3eYwjeWzYjAX3EDxTs8MR1/img.png&quot; data-alt=&quot;EC2인스턴스 선택 &amp;amp;gt; 보안 &amp;amp;gt; 보안그룹 &amp;amp;gt; 인바운드 규칙 편집 &amp;amp;gt; 규칙 저장&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dk0RQR/btsAjWhLTv1/3eYwjeWzYjAX3EDxTs8MR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdk0RQR%2FbtsAjWhLTv1%2F3eYwjeWzYjAX3EDxTs8MR1%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;800&quot; height=&quot;349&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;EC2인스턴스 선택 &amp;gt; 보안 &amp;gt; 보안그룹 &amp;gt; 인바운드 규칙 편집 &amp;gt; 규칙 저장&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 style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker docs CMD vs ENTRYPOINT: &lt;a href=&quot;https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;유데미 Docker &amp;amp; Kubernetes : 실전가이드&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Infra/Docker(도커)</category>
      <category>Docker</category>
      <category>docker hub</category>
      <category>dockerfile</category>
      <category>dockerfile 사용</category>
      <category>도커</category>
      <category>도커 ec2</category>
      <category>도커 배포</category>
      <category>도커 장점</category>
      <category>도커허브이용하기</category>
      <category>이미지 차이</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/240</guid>
      <comments>https://thalals.tistory.com/240#entry240comment</comments>
      <pubDate>Thu, 16 Nov 2023 11:28:50 +0900</pubDate>
    </item>
    <item>
      <title>Docker - 도커란</title>
      <link>https://thalals.tistory.com/238</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JcjAv/btrm0p5cyPd/ntT2nqKbIndsgUmsEDYKs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JcjAv/btrm0p5cyPd/ntT2nqKbIndsgUmsEDYKs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JcjAv/btrm0p5cyPd/ntT2nqKbIndsgUmsEDYKs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJcjAv%2Fbtrm0p5cyPd%2FntT2nqKbIndsgUmsEDYKs1%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;177&quot; height=&quot;149&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;도커란&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;도커(Docker)는 컨테이너 가상화 기술 중 제일 잘나가는(?) 기술입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;도커는 프로세스 격리 기술들을 사용해 컨테이너를 생성하고 관리하기 위한 도구입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;쉽게 말하면, 한 컴퓨터(물리적 자원)안에서 여러개의 시스템과 환결설정들을 충돌하지 않고 동시에 사용할 수 있도록 격리시켜서 실행하는 프로그램입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;도커는 별도의 개별적인 개발환경을 구성할 수 있는 컨테이너를 구축하고 관리할 수 있는 도구이며&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;컨테이너는, 공유되거나 재생성 복사 혹은 그자체로의 배포까지 가능한 상당히 편리한 개발도구로써 사용할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;(내가 원하는대로 구성하고 빌려주고, 사용할 수 있는 피크닉 박스와 유사 개념)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wVZwe/btszLj6Bt0X/V1F0yYr9BEZJX2b0NmUzWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wVZwe/btszLj6Bt0X/V1F0yYr9BEZJX2b0NmUzWK/img.png&quot; data-alt=&quot;도커 컨테이너&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wVZwe/btszLj6Bt0X/V1F0yYr9BEZJX2b0NmUzWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwVZwe%2FbtszLj6Bt0X%2FV1F0yYr9BEZJX2b0NmUzWK%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;400&quot; height=&quot;311&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커 컨테이너&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;도커의 구조&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;다시 이해하자면, 도커는 컨테이너를 관리하는 플랫폼 도구입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;컨테이너는 프로세스 격리 기술입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;도커는 이러한 컨테이너를 &quot;이미지&quot;라는 일련의 틀, 프레임을 기반으로 구축합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 &quot;이미지&quot;를 사용자가 어떻게 구성하는냐에 따라 사용자화된 개발 환경을 구축할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;같은 의미로 &quot;이미지&quot;만 있다면 어떤 개발 배포환경이든 원하는 &quot;개발환경(컨테이너)&quot;를 손쉽게 구축할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Usw1e/btszLpS75jb/9pfARPEQCelcDapxhGMia0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Usw1e/btszLpS75jb/9pfARPEQCelcDapxhGMia0/img.png&quot; data-alt=&quot;도커-이미지-컨테이너&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Usw1e/btszLpS75jb/9pfARPEQCelcDapxhGMia0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUsw1e%2FbtszLpS75jb%2F9pfARPEQCelcDapxhGMia0%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;400&quot; height=&quot;301&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커-이미지-컨테이너&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;VM(Virtual OS) 과 Docker의 차이&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;그렇다면 가상 환경(vm)과 Docker의 차이점은 무엇일까&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;✔️ Virtual Machines&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;물리적 자원(한대의 피씨(os))에서 충돌이나는 프로그램과 환경설정을 돌려야하는 상황이라면, Vitrual Machine 을 이용해 새로운 OS를 깔아 자원을 나누어 사용을 할 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;기존의 가상화 방식은 주로 OS를 가상화하였습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;우리에게 익숙한 VMware나 VirtualBox같은 가상머신은 호스트 OS위에 게스트 OS 전체를 가상화하여 사용하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;  하지만, 가상화 방식에는 전가상화, 반가상화가 있는 추가적인 OS를 실제 물리적 자원(Your Operating System)에 설치하여 가상화하는 방법이기때문에,&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;메모리, CPU, 하드 드라이브의 공간에 대한 낭비&lt;/span&gt;와 성능상의 문제가 있을 수 밖에 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0lI8/btszQCqD95S/z5rJaDtMH4pXxaOkFZvwTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0lI8/btszQCqD95S/z5rJaDtMH4pXxaOkFZvwTK/img.png&quot; data-alt=&quot;자원을 점유하는 VM 의 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0lI8/btszQCqD95S/z5rJaDtMH4pXxaOkFZvwTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0lI8%2FbtszQCqD95S%2Fz5rJaDtMH4pXxaOkFZvwTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;327&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자원을 점유하는 VM 의 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VM 의 단점&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;자원의 중복 복제, 즉 &lt;u&gt;낭비되는 공간&lt;/u&gt; 발생 &lt;br /&gt;(ex. 복수의 vm 에서 동일한 linux 환경을 사용한다고 하더라도 각각 개별환경마다 동일한 linux 를 깔아야한다.)&lt;/li&gt;
&lt;li&gt;호스트 시스템 위에 추가 시스템이 실행되고 있기 때문에 &lt;u&gt;성능저하를 유발&lt;/u&gt;한다.&lt;/li&gt;
&lt;li&gt;공유할 수 있는 단일 구성 파일이 없기 때문에, &lt;u&gt;재생산 및 공유가 자유롭지 않다.&lt;/u&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;✔️ &lt;b&gt;Docker&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;도커는 이러한 vm의 단점들을 대부분 극복하기 위해 나온 컨테이너 격리 시스템입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;도커의 장점&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;도커는 하나의 OS에 몆 대의 머신을 설치하지 않습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VM은 Host OS 에 복수개의 별도 GuestOS 를 받아야하지만,&lt;/li&gt;
&lt;li&gt;Docker 는 HostOS에 Docker 가 있다면 HostOS에 Built-in 된 내장 컨테이너, 혹은 Docker 를 지원하는 VM 의 내장컨테이너를 활용하여 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Docker Engine&lt;/span&gt; 이라는 도구만을 실행시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도커 엔진은 하나의 도구(vm에 비해 가벼움)에 불과하며, 여러개의 컨테이너로 분리될 수 있습니다.&lt;/li&gt;
&lt;li&gt;각각의 컨테이너에는, 어플리케이션의 실행에 관련한 것만이 존재하고 OS 혹은 물리자원 실행에 필요한 도구들이 존재하지 않습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도커 엔진은 프로그램(프로세스)이기 때문에 Host OS의 자원을 점유하지않고 필요할 때 공유 (메모리 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컨테이너는 구성파일이 존재하며 (이미지) 이를 통해 컨테이너가 어떤 목적으로 구성되었는지 설명할 수 있고, 쉽게 공유 or 배포할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beac3b/btrmYECtjSi/lkiNNz41yram16VUG8LwFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beac3b/btrmYECtjSi/lkiNNz41yram16VUG8LwFk/img.png&quot; data-alt=&quot;가상 머신과 도커&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beac3b/btrmYECtjSi/lkiNNz41yram16VUG8LwFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbeac3b%2FbtrmYECtjSi%2FlkiNNz41yram16VUG8LwFk%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;517&quot; height=&quot;320&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가상 머신과 도커&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;다음 포스팅에서는&amp;nbsp; Docker 의 실질적인 개념인 &lt;br /&gt;Docker 컨테이너와 Docker 이미지에대해 다루어보았습니다.&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://thalals.tistory.com/240?category=522348&quot;&gt;https://thalals.tistory.com/240?category=522348&lt;/a&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1700124742361&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eE60X/hyUynMfvQ9/jgutjcErHqvuESJRPLxP8K/img.png?width=600&amp;amp;height=342&amp;amp;face=0_0_600_342,https://scrap.kakaocdn.net/dn/hocmV/hyUyxuxRZ5/MK7xvyEAMORkSwNAbwjsWK/img.png?width=600&amp;amp;height=342&amp;amp;face=0_0_600_342,https://scrap.kakaocdn.net/dn/D5Kv5/hyUuUSt2oL/NR7LQMej9m9yHRfOUtfD60/img.png?width=1280&amp;amp;height=543&amp;amp;face=0_0_1280_543&quot; data-og-url=&quot;https://thalals.tistory.com/240&quot; data-og-source-url=&quot;https://thalals.tistory.com/240?category=522348&quot; data-og-host=&quot;thalals.tistory.com&quot; data-og-description=&quot;[Infra/Docker(도커)] - Docker - 도커란 이전 글에서 Docker의 개념에대해 살짝 정리해보았습니다. 이번 글에서는 Docker 실질적으로 사용하기위한 개념인 컨테이너와, 이미지에 대해 학습 정리를 하고자&quot; data-og-title=&quot;Docker 이미지 &amp;amp; 컨테이너 이해하고 사용하기&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://thalals.tistory.com/240?category=522348&quot; data-source-url=&quot;https://thalals.tistory.com/240?category=522348&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eE60X/hyUynMfvQ9/jgutjcErHqvuESJRPLxP8K/img.png?width=600&amp;amp;height=342&amp;amp;face=0_0_600_342,https://scrap.kakaocdn.net/dn/hocmV/hyUyxuxRZ5/MK7xvyEAMORkSwNAbwjsWK/img.png?width=600&amp;amp;height=342&amp;amp;face=0_0_600_342,https://scrap.kakaocdn.net/dn/D5Kv5/hyUuUSt2oL/NR7LQMej9m9yHRfOUtfD60/img.png?width=1280&amp;amp;height=543&amp;amp;face=0_0_1280_543');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Docker 이미지 &amp;amp; 컨테이너 이해하고 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;[Infra/Docker(도커)] - Docker - 도커란 이전 글에서 Docker의 개념에대해 살짝 정리해보았습니다. 이번 글에서는 Docker 실질적으로 사용하기위한 개념인 컨테이너와, 이미지에 대해 학습 정리를 하고자&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;thalals.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;참고&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Infra/Docker(도커)</category>
      <category>Container란</category>
      <category>Docker</category>
      <category>docker vs VM</category>
      <category>Docker 개념</category>
      <category>도커 vs vm</category>
      <category>도커 단점</category>
      <category>도커 이미지</category>
      <category>도커 이해</category>
      <category>도커 장점</category>
      <category>도커 컨테이너</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/238</guid>
      <comments>https://thalals.tistory.com/238#entry238comment</comments>
      <pubDate>Tue, 7 Nov 2023 11:15:37 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Embedded MongoDB! 통합테스트를 위한 인메모리 몽고디비 설정하기</title>
      <link>https://thalals.tistory.com/466</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;현재 mongodb와 mysql 을 함께 사용하고있는 프로젝트에서 &lt;br /&gt;통합테스트를 위해 mongodb 도 h2 같은 in-memory db로 사용할 수 있는게 없을까 찾아보다가 알게된 방법에 대해 정리하는 글 입니다.&lt;/blockquote&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 Document 에 언급된 in memory 방식의 nosql db 를 사용할 수 있는 방버을 찾지못해 오픈소스 라이브러리를 사용하였습니다. 참고해주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✔️ Spring Boot 환경에서 Embedded MongoDB 를 사용하는 3가지 방법&lt;/h2&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;1) de.flapdoodle.embed.mongo.spring&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;embeded mongodb 라고 검색하면 가장 많이 나오는 오픈소스 라이브러인 것 같습니다.&lt;/li&gt;
&lt;li&gt;나온지 10년 가까이된 것 같고, 지금까지도 활발하게 contribute 가 되어 관리되어지는 오픈소스 라이브러리로 보입니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo.spring&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo.spring&lt;/a&gt;&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;b&gt;2) mongo-java-server (&lt;span style=&quot;color: #ee2323;&quot;&gt;채택&lt;/span&gt;)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;java 로 가짜 mongoDB 서버를 구현하는 오픈소스 라이브러리입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bwaldvogel/mongo-java-server#fuzzy-backend&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/bwaldvogel/mongo-java-server#fuzzy-backend&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) Docker TestContainers&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 경우에 있어서 테스트 컨테이너가 유용한 방법이 될거같지만, 러닝 커브도 있고 설정하는게 좀 귀찮아서 저는 패스했습니다&lt;/li&gt;
&lt;li&gt;Java 코드로 Docker Container 를 구성할 수 있지 있지만, 당연하게도 개발환경에서 Docker 를 실행시켜야합니다.&lt;/li&gt;
&lt;li&gt;보통의 StackOverFlow 에서 가장 많이 추천하는 방식이라고 느껴졌습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 저는 (2) 번 mongo-java-server 를 사용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 번을 처음 고려했으나, 가이드라인을 보았을 때 &lt;span style=&quot;background-color: #f6e199; color: #111827; text-align: left;&quot;&gt;@ExtendWith(SpringExtension.class) &lt;/span&gt;&lt;span style=&quot;color: #111827; text-align: left;&quot;&gt;와 &lt;/span&gt;&lt;span style=&quot;background-color: #f7f7f8; color: #111827; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@DirtyContext&lt;/span&gt; &lt;/span&gt;&lt;span style=&quot;color: #111827; text-align: left;&quot;&gt;를 사용하는게 좀 꺼림칙하기도 했고, 기존의 통합테스트 설정을 부분을 공통적으로 추상화 시켜놓았는데 @SpringBootTest 를 사용했기 때문에 패스했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(사실 테스트코드이기때문에,,, 제가 편한걸 선택했습니다 ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&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;✔️ 격리된 통합테스트 환경에서 MongoDB&lt;span&gt;&amp;nbsp;사용하기&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;aka. mongo-java-server 사용하기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;gt; 기본적으로 오픈소스 가이드라인과 거의 유사합니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span&gt;Config&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fake mongo server 를 설정을 Bean 으로 올리고 어노테이션을 만들어줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MongoTestServerConfig.class)
public @interface EnableMongoTestServer {

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;public class MongoTestServerConfig {

    @Bean
    public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) {
        return new MongoTemplate(mongoDatabaseFactory);
    }

    @Bean
    public MongoDatabaseFactory mongoDatabaseFactory(MongoServer mongoServer) {
        String connection = mongoServer.getConnectionString();
        return new SimpleMongoClientDatabaseFactory(connection + &quot;/test&quot;);
    }

    @Bean(destroyMethod = &quot;shutdown&quot;)
    public MongoServer mongoServer() {
        MongoServer mongoServer = new MongoServer(new MemoryBackend());
        mongoServer.bind();
        return mongoServer;
    }
}&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;/li&gt;
&lt;li&gt;각 테스트 메소드끼리 영향을 주지 않기위해, 시작전 mongoDB 를 초기화 시켜주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@EnableMongoTestServer
@EnableMongoRepositories(&quot;com.mongoDB.repository&quot;)
public abstract class AcceptanceTestWithMongo {

    @Autowired
    MongoTemplate mongoTemplate;

    @BeforeEach
    public void setup() {

        mongoTemplate.getDb().drop();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;Test Code&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class CRUDServiceTest extends AcceptanceTestWithMongo {

    @Autowired
    MessageService messageService;

    @Test
    public void run1() {

        messageService.create(&quot;홍길동&quot;, &quot;니가가라 하와이&quot;);
        messageService.create(&quot;임창정&quot;, &quot;내가 임마!&quot;);
        messageService.create(&quot;전창조&quot;, &quot;1AM&quot;);

        List&amp;lt;Message&amp;gt; list = messageService.findAll();

        for (Message message : list) {
            System.out.println(message.toString());
        }

        Assertions.assertThat(list).hasSize(3);
    }

    @Test
    public void run2() {

        messageService.create(&quot;아델&quot;, &quot;이츠미~&quot;);

        List&amp;lt;Message&amp;gt; list = messageService.findAll();

        for (Message message : list) {
            System.out.println(message.toString());
        }

        Assertions.assertThat(list).hasSize(1);

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mNHXI/btszSHY2eUL/gsHih0LYLwsNS8yKTEpsV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mNHXI/btszSHY2eUL/gsHih0LYLwsNS8yKTEpsV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mNHXI/btszSHY2eUL/gsHih0LYLwsNS8yKTEpsV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmNHXI%2FbtszSHY2eUL%2FgsHih0LYLwsNS8yKTEpsV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;189&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;간단하게 끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 : &lt;a href=&quot;https://github.com/thalals/SpringStudy/tree/main/inmemory-mongodb-for-test&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;spring mongodb 설정 : &lt;a href=&quot;https://thalals.tistory.com/447&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;blog posting&amp;nbsp;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo.spring&quot; data-analytics-event=&quot;{&amp;quot;category&amp;quot;:&amp;quot;SiteHeaderComponent&amp;quot;,&amp;quot;action&amp;quot;:&amp;quot;context_region_crumb&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;de.flapdoodle.embed.mongo.spring&amp;quot;,&amp;quot;screen_size&amp;quot;:&amp;quot;full&amp;quot;}&quot; data-view-component=&quot;true&quot;&gt;de.flapdoodle.embed.mongo.spring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;mongo-java-server
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/pulse/spring-boot-integration-test-in-memory-mongodb-java-server-khabale/&quot;&gt;https://www.linkedin.com/pulse/spring-boot-integration-test-in-memory-mongodb-java-server-khabale/&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bwaldvogel/mongo-java-server#building-a-fat-jar-that-contains-all-dependencies&quot;&gt;https://github.com/bwaldvogel/mongo-java-server#building-a-fat-jar-that-contains-all-dependencies&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Test-Driven Develop</category>
      <category>java</category>
      <category>mongo</category>
      <category>mongodb</category>
      <category>mongodb test</category>
      <category>nosql</category>
      <category>spring</category>
      <category>springboot</category>
      <category>몽고 통합테스트</category>
      <category>몽고디비</category>
      <category>통합테스트</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/466</guid>
      <comments>https://thalals.tistory.com/466#entry466comment</comments>
      <pubDate>Mon, 6 Nov 2023 14:13:13 +0900</pubDate>
    </item>
    <item>
      <title>[회고] &amp;quot;간헐적 메모리 장애&amp;quot; 삽질부터 트러블 슈팅까지</title>
      <link>https://thalals.tistory.com/465</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 글은 사수도 동료도 있었는데 없어진 1인 주니어 백엔드 개발자의 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;간헐적인 서버 메모리 장애 오류 (ShutDown)&amp;nbsp; 삽질 회고입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개발환경 [ Linux, Docker, Java 11, Spring 2.4.x ]&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;| 회고&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;안녕하십니까 여러분 &lt;span style=&quot;text-align: start;&quot;&gt; &lt;/span&gt;&lt;br /&gt;제 블로그를 정기적으로 봐주시는 분이 얼마나 있을지 모르겠으나&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상-당히 오랜만에 글을 쓰게 되었네요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그 동안&amp;nbsp; 황금연휴로 (9.29 ~ 10. 09) 8박9일 말레이시아 여행을 다녀오기도 했고, 사내에서 잡고있던 장애 슈팅이 잘 풀리지 않아 번아웃이라는 핑계거리가 생겨버려서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이런저런 이유를 대며 개인공부를 살짝 손에서 놓았습니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 돌아왔다는건. 문제를 해결했다는 것..!!!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-resize.gif&quot; data-origin-width=&quot;180&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWf44d/btszi5TYIWj/erpAR2yA4dJ7JTK8ISLTm0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWf44d/btszi5TYIWj/erpAR2yA4dJ7JTK8ISLTm0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWf44d/btszi5TYIWj/erpAR2yA4dJ7JTK8ISLTm0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cWf44d/btszi5TYIWj/erpAR2yA4dJ7JTK8ISLTm0/img.gif&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;180&quot; height=&quot;260&quot; data-filename=&quot;ezgif.com-resize.gif&quot; data-origin-width=&quot;180&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[목차]&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;직면한 문제 상황&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;그래서 뭐가 뭐가 문제의 원인일까? &lt;/b&gt;✨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;이제 어떻게 해결해야할까? &lt;b&gt;✨&lt;/b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OOM 모니터링 환경을 구축해보자&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;b&gt;이제 메모리 누수를 해결해볼까? &lt;b&gt;&lt;b&gt;✨&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;더 개선할 방법은 없을까?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트러블 슈팅의 과정 및 결과 정리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마무리 회고&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;border-bottom: 3px solid #707070; font-weight: bold; padding: 5px;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;직면한 문제 상황&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 포스팅 제목에서도 알 수 있다시피, 약 2달간 저는 간헐적인 메모리 장애와의 치열한 (&lt;s&gt;혼자만의&lt;/s&gt;) 싸움을 했습니다.&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  문제)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사실 이전부터 특정 서버가, &lt;span style=&quot;color: #ee2323;&quot;&gt;간헐적으로 죽는(Shut-Down) 현상&lt;/span&gt;이 목격되었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;일정한 시간에 터지는 것도 아니고, 가끔 Out-of-Memory를 남길 때도 있고, 남기지 않을 때도 있는 곤란한 상황이었습니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(슈뢰딩거의 서버다운)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;점점 이런 서버의 간헐적 shut-down이 업무시간 뿐만아니라, &lt;b&gt;새벽&lt;/b&gt;에도 터지고, &lt;b&gt;퇴근 직전&lt;/b&gt;에도 터지고, &lt;b&gt;연휴 전날&lt;/b&gt;에도 터지고.. 팝콘처럼 계속 터지는건 물론이고 &lt;u&gt;그 주기도 점점 짧아져갔습니다.&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvmeL1/btszfZ8twqa/KnV7F98K0ASZzQ7odP8A61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvmeL1/btszfZ8twqa/KnV7F98K0ASZzQ7odP8A61/img.png&quot; data-alt=&quot;펑펑 ~ 터져버리는 서버들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvmeL1/btszfZ8twqa/KnV7F98K0ASZzQ7odP8A61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvmeL1%2FbtszfZ8twqa%2FKnV7F98K0ASZzQ7odP8A61%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;1570&quot; height=&quot;682&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;펑펑 ~ 터져버리는 서버들&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;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존에는, 스타트업의 특성상 이슈를 빠르게 쳐야했기 때문에, 다른 신규 기능 업무를 먼저 치고, 장애가 생겨 서버가 죽었을 때 살리자! &lt;br /&gt;였지만.. 지금은 사내 서비스를 관리할 인원도 줄고 어느정도 여유시간이 생겨 더 늦기전에 &lt;u&gt;본질적인 원인을 파악&lt;/u&gt;해보고자 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HafRv/btszlIcFD1A/Fdpk4hUz7NCKIP43yEzsH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HafRv/btszlIcFD1A/Fdpk4hUz7NCKIP43yEzsH1/img.png&quot; data-alt=&quot;출처 - 인스타 데브경수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HafRv/btszlIcFD1A/Fdpk4hUz7NCKIP43yEzsH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHafRv%2FbtszlIcFD1A%2FFdpk4hUz7NCKIP43yEzsH1%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;400&quot; height=&quot;356&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 - 인스타 데브경수&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;더이상 죽고싶지 않았어요!&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래서&amp;nbsp; 뭐가 원인일까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;먼저, 서버가 터졌을 때의 로그들을 분석해보기로 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;과정 1) 남겨진 로그 확인하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  서버가 갑작스럽게 죽었을 때 봐볼만한 로그는 아래 3가지입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;was log (어플리케이션 로그)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg (시스템 로그)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;hs_err_pid{%pid}.log (톰캣이 비정상 종료되었을 때 생성되는 로그)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마침 추석 전날 새벽 03:30분경에 서버가 ShutDown 되어 3가지 로그를 확인해볼 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbSbNk/btszk5lOG9g/smtM9jaSggmk1Tqumty5tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbSbNk/btszk5lOG9g/smtM9jaSggmk1Tqumty5tk/img.png&quot; data-alt=&quot;(연휴전날에 서버가 죽다니! 정말 운이좋아!)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbSbNk/btszk5lOG9g/smtM9jaSggmk1Tqumty5tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbSbNk%2Fbtszk5lOG9g%2FsmtM9jaSggmk1Tqumty5tk%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;706&quot; height=&quot;143&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(연휴전날에 서버가 죽다니! 정말 운이좋아!)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(1) 번 &lt;b&gt;어플리케이션의 실행로그(was log)&lt;/b&gt;는 별도의 log 파일로 날짜마다 기록하고있었지만, 어느 시점(서버가 죽기 몆분전)부터 로그가 끊겨 어플리케이션이 다운될 당시의 로그는 &lt;u&gt;기록되어있지 않았습니다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(3) 번 로그&lt;b&gt;(hs_err_pid.log)&lt;/b&gt; 또한, 톰캣의 비정상 종료이므로 로그가 남겨져있을 줄 알았지만, 마찬가지로 &lt;u&gt;생성되지 않았습니다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(2) 번로그(&lt;span style=&quot;background-color: #f6e199;&quot;&gt;시스템로그&lt;/span&gt;) 에서는 다행히도 남겨진 것이 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;과정 2) dmesg (시스템 로그) 확인&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리눅스 환경이기에, dmesg 명령어로 서버가 죽었을 당시의 시스템로그를 살펴보았습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg 는 리눅스 커널에서 발생하는 다양한 메세지들을 출력해주는 명령어 입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템로그로 커널에서 서버가 죽었을당시 &lt;b&gt;OOM Killer&lt;/b&gt; 가 동작하는 것을 확인했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-27 오후 7.45.57.jpg&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vckwW/btszjzHZC5G/OXDcluDuuT29pQFvnoCZl0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vckwW/btszjzHZC5G/OXDcluDuuT29pQFvnoCZl0/img.jpg&quot; data-alt=&quot;oom killer 동작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vckwW/btszjzHZC5G/OXDcluDuuT29pQFvnoCZl0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvckwW%2FbtszjzHZC5G%2FOXDcluDuuT29pQFvnoCZl0%2Fimg.jpg&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;1218&quot; height=&quot;429&quot; data-filename=&quot;스크린샷 2023-10-27 오후 7.45.57.jpg&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;oom killer 동작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;darr;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-27 오후 7.46.57.jpg&quot; data-origin-width=&quot;955&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEnYNB/btszgZUZoGm/CCeX2J9IWdai5zK350wP1K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEnYNB/btszgZUZoGm/CCeX2J9IWdai5zK350wP1K/img.jpg&quot; data-alt=&quot;실행중인 java 어플리케이션 강제 종료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEnYNB/btszgZUZoGm/CCeX2J9IWdai5zK350wP1K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEnYNB%2FbtszgZUZoGm%2FCCeX2J9IWdai5zK350wP1K%2Fimg.jpg&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;955&quot; height=&quot;86&quot; data-filename=&quot;스크린샷 2023-10-27 오후 7.46.57.jpg&quot; data-origin-width=&quot;955&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실행중인 java 어플리케이션 강제 종료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt; &amp;nbsp; 문제 원인 추측&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;❗️ 커널에서 oom killer 가 발생했다는 것은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;물리 서버의 가용메모리 자체가 부족&lt;/b&gt;&lt;/span&gt;해져서 프로세스에 할당할 메모리가 없다는 것!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;❗️ 이 말은 즉슨, &lt;span style=&quot;color: #ee2323;&quot;&gt;어딘가에서 메모리를 계속 잡아먹고 있다는 것&lt;/span&gt; (메모리 급증(틱) or 메모리 누수 의심)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;❗️&amp;nbsp;hs_err_pid.log 가 기록되지 않는건, 톰캣의 비정상적인 오류로 서버가 죽는게 아닌, oom_killer 가 kill 명령어로&amp;nbsp; 프로세스를 종료시키기 때문이라고 추측!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;❗️was_log 가 도중에 끊겨 서버가 종료될 당시의 에러가 전부 기록되지 않는 원인은, 물리서버의 가용메모리 자체가 부족하게되어 어플리케이션 (톰캣)이 로그를 기록할 메모리조차 부족해진 것.. 이라고 추측되어집니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;과정3) 서버 스펙 및 구축 환경 살펴보기&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템로그를 살펴보았을 때 물리서버의 메모리 부족임을 확인할 수 있었습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그럼 현재 환경이 메모리가 부족해서 증설해야하는 상황인지 살펴보겠습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 저희 서버는 AWS 의 t2.micro 스팩의 물리서버(ec2)를 사용하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;vCPU 2개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 4G&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pWfKQ/btszszULVBG/I1KnSkBUckCjMZMeB4pjK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pWfKQ/btszszULVBG/I1KnSkBUckCjMZMeB4pjK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pWfKQ/btszszULVBG/I1KnSkBUckCjMZMeB4pjK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpWfKQ%2FbtszszULVBG%2FI1KnSkBUckCjMZMeB4pjK1%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;746&quot; height=&quot;295&quot; data-origin-width=&quot;746&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 해당 서버에는&lt;u&gt;&lt;b&gt; 3개의 Docker 컨테이너에 3개의 어플리케이션을 각각 다른 포트로 이용&lt;/b&gt;&lt;/u&gt;하고있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 메모리 4GB 스펙에 어플리케이션(jvm) 3개 돌아가 있는 상황입니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;음.. 애매합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대용량 배치성 어플리케이션, 혹은 실시간 크롤링, 순간적인 대규모 트래픽이 아닌이상 4GB의 메모리가 현재 상황에서 부족할 것이라고 생각되어지진 않았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한, 현재 트래픽이 과거(6개월) 대비 크게 늘지 않았음에도 불구하고 메모리가 계속 급증하는게 단순하게 물리서버의 메모리가 적게 잡혀서인 것 같지는 않았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;s&gt;그리고 회사에 돈이 없었습니다.&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Scale Up은 항상 마지막의 보류이니까요!&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;과정 4) 구축환경 뜯어보기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 메모리 사용률을 먼저 살펴보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각 컨테이너가 사용하고있는 메모리는 각각 아래 사진과 같고, 3개의 어플리케이션이 정상적으로 실행중일 때 보통 100m~250m 정도의 가용메모리(버퍼캐시 제외)가 남아있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzQH9u/btszkyvTiFu/6Vasw84KNGbxYZtbmka8Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzQH9u/btszkyvTiFu/6Vasw84KNGbxYZtbmka8Fk/img.png&quot; data-alt=&quot;docker ps&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzQH9u/btszkyvTiFu/6Vasw84KNGbxYZtbmka8Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzQH9u%2FbtszkyvTiFu%2F6Vasw84KNGbxYZtbmka8Fk%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;965&quot; height=&quot;80&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;docker ps&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4Z40S/btszls237ke/VXHBGJE1bQLTNIE82MpIWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4Z40S/btszls237ke/VXHBGJE1bQLTNIE82MpIWk/img.png&quot; data-alt=&quot;top&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4Z40S/btszls237ke/VXHBGJE1bQLTNIE82MpIWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Z40S%2Fbtszls237ke%2FVXHBGJE1bQLTNIE82MpIWk%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;551&quot; height=&quot;344&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;top&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문제가 의심되는 어플리케이션은&amp;nbsp;&lt;b&gt;server1&lt;/b&gt;&amp;nbsp;컨테이너에 올라가있는 JVM 이었습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 어플리케이션은 내부 관리자 페이지의 API 를 담당하는 어플리케이션인데 레거시 코드가 다수 포진되어있고, 해당 서버에 제일 주요하게 사용되고, 가장 많이 혹은 가장 먼저 죽는 어플리케이션입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;darr;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 컨테이너 환경에서 실행되는 JVM에 할당된 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;heap 메모리 사이즈&lt;/span&gt;를 확인해보았습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아래 2가지 명령어를 이용해 jvm의 heap size를 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698641634015&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -XX:+PrintFlagsFinal -version 2&amp;gt;&amp;amp;1 | grep -i -E 'heapsize|permsize|version'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS6wsZ/btsznDQS7WH/IneNdPTqEoXupxgxD7YdH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS6wsZ/btsznDQS7WH/IneNdPTqEoXupxgxD7YdH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS6wsZ/btsznDQS7WH/IneNdPTqEoXupxgxD7YdH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS6wsZ%2FbtsznDQS7WH%2FIneNdPTqEoXupxgxD7YdH0%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;683&quot; height=&quot;196&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1698641642880&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; java -XX:+PrintCommandLineFlags -version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZZhWY/btszl06GcV8/psB5fKvct9YJy63zkQmkPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZZhWY/btszl06GcV8/psB5fKvct9YJy63zkQmkPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZZhWY/btszl06GcV8/psB5fKvct9YJy63zkQmkPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZZhWY%2Fbtszl06GcV8%2FpsB5fKvct9YJy63zkQmkPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;102&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각각의 도커들은 물리서버의 메모리를 공유하여 사용하고, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #fbf3db;&quot; data-token-index=&quot;1&quot;&gt;jvm 의 heap memory max size가 default 로 설정되어 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;1GB&quot; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fbf3db;&quot; data-token-index=&quot;3&quot;&gt;의 메모리를 잡고있었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-XX:InitialHeapSize=64600640 (64mb)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-XX:MaxHeapSize=1033610240 (1033mb)&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;오라클 java 문서(&lt;a href=&quot;http://www.oracle.com/technetwork/java/ergo5-140223.html&quot;&gt;http://www.oracle.com/technetwork/java/ergo5-140223.html&lt;/a&gt;)&amp;nbsp;에 따르면, jvm을 server class로 실행하면&amp;nbsp;&lt;b&gt;초기 heap size는 메모리의 1/64이고, 최대 heap size는 1/4&lt;/b&gt; 까지 늘어단다고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  원인 파악&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이상적인 흐름이라면 가용가능한 메모리가 고갈되어서 minor gc가 빠르게 돌아가든, full gc 가 돌아가든지 해서 메모리를 빠르게 확보해야하는 상황이지만&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;jvm 에 설정된 heap 메모리가 실제 가용가능한 메모리보다 높게 잡혀있어 &lt;span style=&quot;color: #ee2323;&quot;&gt;Full GC가 동작하기전에 프로세스가 물리서버의 메모리를 모두 잡아먹는게 문제&lt;/span&gt;였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;server1 컨테이너에서 해당 어플리케이션 jvm이 점유하고있던 heap 메모리가 300~400m 정도여서 heap size는 600~700m 의 여유가 있는데 실제 가용가능한 메모리는 100~200m 밖에 없으니 oom killer 가 발생한게 아닌가 생각되어집니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(점유중인 heap 메모리는 heap dump 후 eclipse mat 사용, 또는 jstat -gc 명령어로 확인할 수 있습니다.)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이제 어떻게 해결해야 할까?&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️ 목표는 jvm 이 정상적으로 Full GC를 이용해 메모리 관리를 할 수 있도록 적절한 heap memory를 할당해주는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;jvm 실행 옵션 중 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;-Xmx&lt;/b&gt;&lt;/span&gt; 를 사용하여 jvm heap max size를 설정할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;IBM 의 Tuning JVMs (Java &lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: left;&quot;&gt;Virtual Machine) 이&lt;/span&gt;라는 글을 보았을 때 저장해둔건데 다시 찾을려니 못찾겠네요..&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 글에서는, -Xmx 옵션을 사용할 때 가용가능한 메모리보다 max heap size가 크다면 심각한 성능저하를 일으킨다는 문장이 존재하였습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실제로 jvm 이 사용하는 메모리가 물리서버의 가용메모리를 넘어섰을때 ec2 의 cpu 사용량이 급증하면서 서버가 다운되는 현상을 확인하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFmv7z/btszlnnTZif/5Ai2p9Mkcv6WTu12PoFn0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFmv7z/btszlnnTZif/5Ai2p9Mkcv6WTu12PoFn0K/img.png&quot; data-alt=&quot;출처 : ibm - &amp;quot;tuning jvms&amp;quot;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFmv7z/btszlnnTZif/5Ai2p9Mkcv6WTu12PoFn0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFmv7z%2FbtszlnnTZif%2F5Ai2p9Mkcv6WTu12PoFn0K%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;836&quot; height=&quot;322&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : ibm - &quot;tuning jvms&quot;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️ 적절한 Max heap size 설정&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 1024mb (1g) 가 최대 힙 사이즈이지만, 정상적인 상황에서 jvm 의 힙 사용량이 350~450m 을 점유 및 유지하고 있고, 이때 버퍼캐시를 제외한 물리서버의 가용 메모리가 100~200m 이므로, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;최대 heap size는 550m&lt;/b&gt;&lt;/span&gt; 가 정도가 적당해 보였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 heap size를 조절해주고 nGrinder 를 사용한 부하테스트를 진행해보니 정상적으로 Full GC가 돌기 시작했습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  하지만 운영환경에서는 또다른 문제가 발견되었습니다..!&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번에는 Full GC가 제 생각보다 너무 많이 돌면서, was 단에서 OOME(Out-Of-Memory Exception)이 터지더군요,,,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OOM 모니터링 환경을 구축해보자&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;저는 지금 사내 1인 개발자이기도 하고, 실시간으로 빠르게 장애를 대응할만한 경험도 지식도 부족하기 때문에 모니터링 환경이 무엇보다 시급했습니다.&lt;br /&gt;서버가 죽기전에 장애를 대응하는게 제일 좋겠지요..!&lt;br /&gt;그게 아니라면 죽은 후 트러블 슈팅이라도 제대로 할 수 있게 자료라도 남겨야겠져..!&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OOM 모니터링을 위해 저는 아래 3가지 작업을 진행했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dump 파일 기록&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;oom 발생시 Slack 알림메세지 전송&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ec2 Docker Container 의 실시간 메모리 사용량 모니터링 (AWS agent)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1) oom 발생 시&amp;nbsp; 기록 남기기 (dump)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Heap Dump&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Thread Dump&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Tcp Dump ( &amp;rarr; 네트워크 오류를 파악하기위한 덤프 파일입니다. 링크(&lt;a href=&quot;https://thalals.tistory.com/463&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://thalals.tistory.com/463)&lt;/a&gt;로 대체합니다.)&lt;/span&gt;&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;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;✔️ &lt;u&gt;OOM 발생할 경우에 힙덤프 파일 자동 생성하기&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사실 OOM 은 Heap Dump가 최고입니다. Dump 파일에 유용한 정보를 담기 위해서는 장애 즉시, 그 시점에 따는게 제일 좋습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이것 또한 Jvm 실행 옵션 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;HeapDumpOnOutOfMemoryError&quot;&lt;/span&gt; 를 이용해 OOM 발생시점에 덤프 파일을 생성할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(예시)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;java -jar -Xms1024M -Xmx1024M 
		-XX:+HeapDumpOnOutOfMemoryError 
   		-XX:HeapDumpPath=src/main/resources/dump/heap/heapDump_$date.hprof //heapDump_$DATE.hprof
   		-XX:OnOutOfMemoryError=&quot;kill -15 %p&quot; 
	 	-XX:NativeMemoryTracking=summary
   		application-0.0.1-SNAPSHOT.jar&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Xms : 이 설정은 Java 힙의 초기 크기를 제어합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Xmx : Java 힙의 최대 크기를 제어합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;XX:+HeapDumpOnOutOfMemoryError : OOM이 발생할 경우에 힙덤프 파일을 생성을 한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;XX:HeapDumpPath : 힙덤프가 생성되는 폴더 경로를 지정한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;XX:OnOutOfMemoryError : OOM이 발생할 경우, 수행할 스크립트를 지정한다(보통은 OOM이 발생하면 애플리케이션이 다운되기 때문에 재시작 스크립트를 다시 수행하기도 한다)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-XX:NativeMemoryTracking=summary : heap 메모리가 아닌, native 메모리 모니터링을 위한 옵션&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;✔️&amp;nbsp;&lt;/span&gt;&lt;u&gt; 쓰레드 덤프 기록하기&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OOM 발생시 혹시 스레드가 교착상태(데드락, Dead Lock) 에 빠져있는지 확인하기 위해서는 스레드 덤프를 활용하여 알 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Thread Dump 파일은 Jstack -l 명령어를 사용해 기록할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;교착상태를 확인하기위해서는 적어도 5초단위로 3번을 나누어 기록하는게 좋다고 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;저는 jvm 의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;OnOutOfMemoryError&quot;&lt;/span&gt; 옵션을 통해 스크립트 파일을 별도로 만들어 실행하도록 해주었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;java -jar -XX:OnOutOfMemoryError=&quot;thread_dump.sh&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;i&gt;thread_dump.sh&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash

echo &quot;start slack alarm and record thread dump&quot;

date=$(date +%Y)-$(date +%m)-$(date +%d)_$(date +%H):$(date +%M):$(date +%S)
pid=$(lsof -t -i:8080)


jstack -l $pid &amp;gt; src/main/resources/dump/thread/threadDump1_$date.txt
sleep 5

jstack -l $pid &amp;gt; src/main/resources/dump/thread/threadDump2_$date.txt
sleep 5

jstack -l $pid &amp;gt; src/main/resources/dump/thread/threadDump3_$date.txt
sleep 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2) oom 발생 시&amp;nbsp;  Slack 메세지 전송하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그 다음으로 진행한 작업은 Slack 메세지 연동입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 팀 메신저로 Slack 을 사용하고 있고 dump 파일을 기록한다고 하더라도, OOM 발생 시 즉각적인 대응을 위해 메신저 연동 작업을 진행했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 작업 또한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;OnOutOfMemoryError&quot;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;옵션을 이용했고, 위의 Thread_dump.sh 스크립트 파일에 수정하여 진행했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Slack WebHook 을 이용해 api 요청으로 메세지를 전송할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash

echo &quot;start slack alarm and record thread dump&quot;

date=$(date +%Y)-$(date +%m)-$(date +%d)_$(date +%H):$(date +%M):$(date +%S)
pid=$(lsof -t -i:8080)

echo  $pid

json=&quot;{\&quot;text\&quot;: \&quot; *[Admin Server]*  \n\`OutOfMemory Exception\` 발생  \n발생 시간 : $date \&quot;}&quot;

my_slack_webhook=&quot;https://hooks.slack.com/services/{ slack webhook api key}&quot;

curl -X POST -H 'Content-type: application/json' --data &quot;$json&quot; $my_slack_webhook


jstack -l $pid &amp;gt; src/main/resources/dump/thread/threadDump1_$date.txt
sleep 5

jstack -l $pid &amp;gt; src/main/resources/dump/thread/threadDump2_$date.txt
sleep 5

jstack -l $pid &amp;gt; src/main/resources/dump/thread/threadDump3_$date.txt
sleep 5&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxiLO/btszllDMqsQ/6eGezUAV6EzhylUjQB42PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxiLO/btszllDMqsQ/6eGezUAV6EzhylUjQB42PK/img.png&quot; data-alt=&quot;메신저 결과물&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxiLO/btszllDMqsQ/6eGezUAV6EzhylUjQB42PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxiLO%2FbtszllDMqsQ%2F6eGezUAV6EzhylUjQB42PK%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;414&quot; height=&quot;125&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메신저 결과물&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3) ec2 실시간 메모리 조회&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;추가적으로 AWS CloudWatch Agent 를 이용해서 1s, 5s, 10s ..등의 단위로 실시간 메모리 사용률과 점유율, 디스크 사용량 등의 모니터링 및 알람 설정을 진행하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 설정으로 인해 CPU 사용량 뿐만 아니라, 메모리 사용률이 일정 퍼센트를 넘어갈 떄 슬랙으로 알림이 오도록 연동할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnKDvB/btszmybCtst/pWlUF76M2YIz3Wd4wN5OPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnKDvB/btszmybCtst/pWlUF76M2YIz3Wd4wN5OPk/img.png&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;653&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.3919%; margin-right: 10px;&quot; data-widthpercent=&quot;48.96&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnKDvB/btszmybCtst/pWlUF76M2YIz3Wd4wN5OPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnKDvB%2FbtszmybCtst%2FpWlUF76M2YIz3Wd4wN5OPk%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;1373&quot; height=&quot;653&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r9asN/btszvCKmqKr/k3jsR6Bsv9oEaEPTY8ekFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r9asN/btszvCKmqKr/k3jsR6Bsv9oEaEPTY8ekFK/img.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;318&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.4453%;&quot; data-widthpercent=&quot;51.04&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r9asN/btszvCKmqKr/k3jsR6Bsv9oEaEPTY8ekFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr9asN%2FbtszvCKmqKr%2Fk3jsR6Bsv9oEaEPTY8ekFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4) 그 외의 사용했던 도구들&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;heap dump 분석&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;분석툴&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Ecplise mat&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;메모리 사용량 ui 모니터링 툴&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;VisualVm (로컬환경에서 쓰기 편함)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;jconsole&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리눅스 환경이라면&amp;nbsp;&lt;u&gt;jmap -dump&lt;/u&gt;&amp;nbsp;옵션을 이용해 heap dump 파일을 생성할 수 도 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ex)&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&amp;nbsp;jmap -dump:file=파일이름.hprof {$pid}&lt;/span&gt;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Thread dump 분석&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;분석툴&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://fastthread.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fastthread.io/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실시간 thread 모니터링&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;VisualVm&lt;/span&gt;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;GC 분석&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;로그 기록&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;jvm 실행 옵션 &quot;-xlog:gc&quot; 를 설정해주면 어플리케이션 실행중 동작되는 GC 로그를 파일로 기록해둘 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예시)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1698729381120&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-verbose:gc \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-Xlog:gc=debug:file=파일경로/gc_$ date.log:time,uptime,level,tags:filecount=50,filesize=100m \
-XX:+UseGCLogFileRotation \
-XX:GCLogFileSize=1m \
-XX:NumberOfGCLogFiles=100 \&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;분석툴&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://gceasy.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://gceasy.io/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실시간 gc 모니터링&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;VisualVm의 VisualGC 플러그인을 다운받으면 GC 동작과정 모니터링이 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리눅스 환경이라면, jstat -gcutil 옵션으로 콘솔로 GC 동작 현황 모니터링이 가능합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ex) &lt;span style=&quot;background-color: #f6e199;&quot;&gt;jstat -gcutil pid 3s(표출 주기)&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&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;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이제 메모리 누수를 해결해 볼까? (Memory Leak)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이제 모든 준비가 끝났습니다. 터져줘 OOM!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;heap dump 파일은 eclipse의 mat 프로그램을 사용하여 분석할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;mat 을 사용하면 dump 파일에서 memory Leak 이 의심되는 부분을 분석해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;저는 &quot;&lt;b&gt;java.util.concurrent.ConcurrentHashMap&quot;&lt;/b&gt;&amp;nbsp;객체에서 상당히 많은 메모리를 차지하고 있었습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;mat 을 사용하면 해당 객체의 outgoing reference 와 incoming reference 를 추적할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5h4j2/btszvCqpitw/rhRRpjVNEL1Fqgwu2jPbX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5h4j2/btszvCqpitw/rhRRpjVNEL1Fqgwu2jPbX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5h4j2/btszvCqpitw/rhRRpjVNEL1Fqgwu2jPbX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5h4j2%2FbtszvCqpitw%2FrhRRpjVNEL1Fqgwu2jPbX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;184&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;추적 후 의심가는 API 를 특정할 수 있었고, WAS log 에서도 request 로그만 있고 response는 로그는 없다는 걸 확인했습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 해당 API 의 사용부분을 찾아보니 1만건도 안되는 데이터를 불러오는데 26초나 걸리더군요&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ESD2c/btszrK4w0RX/LilvWdW8JBd5uUzoiMopL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ESD2c/btszrK4w0RX/LilvWdW8JBd5uUzoiMopL0/img.png&quot; data-alt=&quot;26초 걸림&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ESD2c/btszrK4w0RX/LilvWdW8JBd5uUzoiMopL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FESD2c%2FbtszrK4w0RX%2FLilvWdW8JBd5uUzoiMopL0%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;800&quot; height=&quot;276&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;26초 걸림&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;✔️ 메모리누수 리팩토링&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;막상 레거시 부분을 까보니, 수정하기에는 상당히 쉬운 상황이었습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;List&amp;lt;&amp;gt; 의 요소로 HashMap&amp;lt;&amp;gt; 이 들어가고 다시 그 value 값으로 HashMap&amp;lt;&amp;gt;을 넣고 있더군요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;i&gt;다 쓴 참조 - obsolete reference&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, GC 가 동작하더라도 Map 에서 내부 객체에대한 참조를 계속 가지고있기 때문에 GC의 대상이 되지않아 heap memory를 잡아먹게되는 구조입니다.&amp;nbsp;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;얼추 아래와 비슷한 상황이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698721766869&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;HashMap&amp;lt;String, Object&amp;gt;&amp;gt; get2() {

    List&amp;lt;HashMap&amp;lt;String, Object&amp;gt;&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

    for (int i = 0; i &amp;lt; 500000; i++) {

        HashMap&amp;lt;String, Object&amp;gt; e = new HashMap&amp;lt;&amp;gt;();

        e.put(&quot;key1&quot;, &quot;value1&quot;);
        e.put(&quot;key2&quot;, &quot;value2&quot;);
        e.put(&quot;key3&quot;, &quot;value3&quot;);

        HashMap&amp;lt;String, Object&amp;gt; a = new HashMap&amp;lt;&amp;gt;();
        a.put(&quot;key1&quot;, &quot;value1&quot;);
        a.put(&quot;key2&quot;, &quot;value2&quot;);
        a.put(&quot;key3&quot;, &quot;value3&quot;);

        e.put(&quot;HashMap&quot;, a);
        
        list.add(e);
    }

    return ResponseEntity.ok(list);

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;해결방안&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;간단합니다. Map 객체를 사용하고 난 후 해당 참조를 &lt;span style=&quot;color: #ee2323;&quot;&gt;null 로 초기화&lt;/span&gt;시켜주면 참조를 잃어버리므로 GC의 대상이 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;혹은, Reference 클래스의 SoftReference 와 WeekReference 클래스를 사용하면 해당 클래스가 특정 시점에 GC 의 대상이되도록 할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Map 은 WeakRefence 를 사용한 &lt;span style=&quot;color: #ee2323;&quot;&gt;WeakHashMap&lt;/span&gt;이 존재합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;혹은 &lt;span style=&quot;color: #ee2323;&quot;&gt;Map을 아예 사용하지 않는 방법&lt;/span&gt;도 있겠죠&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;내가 적용한 것&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;저는 (3)번 방법, Map 을 아예 사용하지 않았습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 코드에서는 Map 객체를 단순히 DB 데이터를 담는 용도로 사용하고 있기 때문에, Map 을 사용할 이유가 전혀 없었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;JVM 설계자들의 &lt;b&gt;Weak Generational Hypothesis&lt;/b&gt; 에 의한 메모리 설계에 의고하여&lt;b&gt; Data 를 담는 DTO 객체를 사용해 1회성 객체로 해당 역할을 대체&lt;/b&gt;하는게 좋다고 판단했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;더 개선할 방법은 없을까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;❓어플리케이션들이 메모리 자체를 덜 사용하도록 할 수는 없을까?&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;애초에 어플리케이션이 실행될 때 기본적으로 1500m 가까이 메모리를 잡아먹는게 좀 많은게 아닌가라는 생각이 들었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&quot;mvn spring-boot:run&quot;&lt;/span&gt; 시 돌아가는 Runcher 프로세스가 생각보다 많은 메모리를 잡아먹고 있더군요&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmEcH5/btszw7KLowW/jGzVhynBnKuOfbTEhv1KS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmEcH5/btszw7KLowW/jGzVhynBnKuOfbTEhv1KS1/img.png&quot; data-alt=&quot;mvn run 실행시 메모리 점유 모니터링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmEcH5/btszw7KLowW/jGzVhynBnKuOfbTEhv1KS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmEcH5%2Fbtszw7KLowW%2FjGzVhynBnKuOfbTEhv1KS1%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;614&quot; height=&quot;368&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;mvn run 실행시 메모리 점유 모니터링&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;현재 배포 상황&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재는 스크립트 파일로 프로젝트 루트 폴더에서 mvn spring-boot:run 명령어를 실행시키는 구조입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;mvn spring-boot:run 명령어는 &quot;spring-boot-starter-web&quot; 종속성만이 pom,xml 에 추가되어있다면 플러그인은 pom 구성을 읽고 pom.xm에 추가되어있는 모든 외부 종속성을 톰캣이 시작할 때 다운로드 및 초기화합니다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;개선 후&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아무런 설정을 하지 않은 채, maven 으로 빌드한 프로젝트 jar 혹은 war 파일은 외부 종속성 라이브러리들이 포함되어있지만&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Spring Boot 공식 Docs 를 보았을 때, 특정 플러그인 설정으로 모든 종속성이 포함된 jar 파일을 만들 수 있었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#packaging.repackage-goal.parameter-details.executable&quot;&gt;https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#packaging.repackage-goal.parameter-details.executable&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;&amp;lt;plugin&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
    &amp;lt;configuration&amp;gt;
        &amp;lt;executable&amp;gt;true&amp;lt;/executable&amp;gt;
    &amp;lt;/configuration&amp;gt;
&amp;lt;/plugin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;darr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  결과&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;어플리케이션 실행시 메모리 점유율 1500m 애서 &amp;rarr; 580m 로 약 61% 감소하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이로인해 줄였던 heap size도 다시 1gb 로 늘릴 수 있어 GC 효율또한 챙겨갈 수 있었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xsM4H/btszxtUxuD7/uZBHthkKWOPLKn0THiJeh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xsM4H/btszxtUxuD7/uZBHthkKWOPLKn0THiJeh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xsM4H/btszxtUxuD7/uZBHthkKWOPLKn0THiJeh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxsM4H%2FbtszxtUxuD7%2FuZBHthkKWOPLKn0THiJeh0%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;644&quot; height=&quot;331&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;331&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;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정리&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️ 문제 원인&amp;nbsp;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하나의 물리서버(ec2) 에 docker container 3개로 3개의 어플리케이션 (jvm) 이 구동되고 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각각의 도커들은 물리서버의 메모리를 공유하여 사용하고, jvm 의 heap memory, 혹은 가용가능한 메모리가 default 로 설정되어 &amp;ldquo;1GB&amp;rdquo; 의 메모리를 잡고있다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1024mb (1g) 가 최대 힙 사이즈이지만, 정상적인 상황에서 jvm 의 힙 사용량이 350~450m 을 유지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때 물리서버의 가용 메모리가 100~200m 이므로, 최대 heap size는 550m 가 정도가 적당해 보인다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실제로는, 어플리케이션 실행중 메모리가 부족하여  Full GC 가 돌아야하는 상황이지만, jvm 에 설정된 heap 메모리가 실제 가용가능한 메모리보다 높게 잡혀있어 정상적으로 Full GC가 돌아 메모리를 확보하기 전에 물리서버의 가용 메모리가 고갈되어 docker 자체가 먹통이 된다.&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-XX:InitialHeapSize=64600640 (64mb)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-XX:MaxHeapSize=1033610240 (1033mb)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;jvm 의 Heap Size를 어느정도 낮추어 full gc 가 정상적으로 동작하도록 설정한다.&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이유1) 속도가 중요한 클라이언트 어플리케이션 서버가 아니기 때문에&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이유2) 서버개발자가1인으로써 실시간 대응이 어렵기 때문에 성능저하보다는 장애 빈도를 줄이고자 했음 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 했더니 full gc 가 너무 많이 돌더라(linux: jstat gcutil 실시간 모니터링)&amp;nbsp;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원인 1) 비효율적인 레거시 코드&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;DB CRUD의 전체 공통화 및 추상화로 인한 불필요한 query connection 다수 발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;같은 이유로 인한 2중~3중 반복문&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원인 2) HashMap&amp;lt; String, HashMap&amp;lt;&amp;gt; &amp;gt; 사용으로인한 메모리 누수&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;JVM 설계자들의 &lt;b&gt;Weak Generational Hypothesis 에 의거하여&lt;/b&gt; Data를 담는 용도로 Map 을 사용할 이유가 없음 &amp;rarr; DTO 분리&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;mvn spring-boot:run 으로 실행시 Runcher process 가 생각보다 많은 메모리를 잡아먹음&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️ 문제 해결&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;HashMap 을 사용하지 않고, DTO 객체를 반환 &amp;rarr; 메모리 누수 개선&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각각의 테이블조회하고 1 : N 관계는 다시 반복문으로 개별 query를 날리던 query 문 개선 (대략 1만 쿼리? &amp;rarr; 1개 쿼리)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;join 후 어플리케이션 단의 Stream group by 및 중복제거 로직으로 기능개선 (QueryDsl transform 과 성능비교 후 채택)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;mvn run 커멘드 동작방식을 jar 로 변경 &amp;rarr; 메모리 점유량 감소 &amp;rarr; jvm Heap Size 확보&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️ 결과&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2s 700ms (local 에서는 1m40s 걸림) &amp;rarr; 230ms(local은 300ms) 로 조회 속도 개선&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;간헐적인 메모리 성능 장애 개선 및 메모리 점유율 61% 감소 (1500m &amp;rarr; 580m)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;GC 빈도 성능 개선&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TPS 성능개선&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개선 전 - 너무 낮은 TPS 로 부하 테스트 자체가 불가, vuser per agent 를 1자리수로 잡아도 마찬가지 To Many ERROR 로 테스트 불가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개선 후&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Agent(1), Vuser (100) 10분&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TPS : 25&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ERROR : 0&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Successful Test : 14,613&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; &amp;nbsp;테스트하는동안 OOME는 발생하지 않음&amp;hellip;!&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7EOK4/btszvBTolzH/j7akylZa18YSARWkaVWNIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7EOK4/btszvBTolzH/j7akylZa18YSARWkaVWNIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7EOK4/btszvBTolzH/j7akylZa18YSARWkaVWNIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7EOK4%2FbtszvBTolzH%2Fj7akylZa18YSARWkaVWNIK%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;1007&quot; height=&quot;573&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;573&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마무리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;글을 최대한 문제해결 과정의 이상적인 순서대로 정리해보고자 노력했지만, 2달간의 트러블 슈팅이 이렇게 순탄하지만은 않았습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OOM에 대한 대응도 처음이고, Dump 파일의 존재도 몰랐기 때문에 장애 원인파악 삽질에 대해 정말 굉장히 많은 &quot;삽질&quot;을&amp;nbsp; 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전혀 상관없는 부분을 파헤쳐보기도 하고, 빙빙 돌아 1달전 부하테스트 했던 자료를 꺼내 비교해보기도 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;많은 문서를 찾아보았고, 자료를 참고하여 [가설 &amp;rarr; 테스트 &amp;rarr; 검증 &amp;rarr; 실패] 의 반복작업이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;심지어 dmesg 의 oom killer 는 이 글을 쓰면서 확인이나 한번 해보자는 느낌이었는데 정확하게 들어맞아 허무하기도 했습니다... &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이걸 처음부터 확인했다면 얼마나 좋았을까하는 생각에,, 현타가 오기도하고, 그러기에 더욱더 꼼꼼히 적아야겠다는 생각이 들기도 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대용량 부하 처리도, Scale-out 없고 비동기 처리같은 화려한 작업은 아니지만, 누군가에게 혹은 몆년후의 나에게 도움이 되길 바라며 글을 정리해 보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brA5dH/btszuuf3N7F/zhIsqC4Ixttyzbn3cqwB6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brA5dH/btszuuf3N7F/zhIsqC4Ixttyzbn3cqwB6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brA5dH/btszuuf3N7F/zhIsqC4Ixttyzbn3cqwB6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrA5dH%2Fbtszuuf3N7F%2FzhIsqC4Ixttyzbn3cqwB6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;537&quot; height=&quot;409&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;23년 2월경에 적어놓은건데 1개의 작업이라도 마무리 지을 수 있어서 기쁘네요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;더 좋은 해결 방법, 더 편한 모니터링 방법, 경험 혹은 피드백 주신다면 감사하겠습니다 :)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;끝!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;참고&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=w4fWgLgop5U&quot;&gt;https://www.youtube.com/watch?v=w4fWgLgop5U&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://d2.naver.com/helloworld/1326256&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;하나의 메모리 누수를 잡기까지&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://d2.naver.com/helloworld/6043&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;letter-spacing: -1px;&quot;&gt;Garbage Collection 모니터링 방법&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://incheol-jung.gitbook.io/docs/q-and-a/java/heap-dump-feat.-oom&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; letter-spacing: -1px;&quot;&gt;heap dump 분석하기 (feat. OOM)&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://cafe24.zendesk.com/hc/ko/articles/18528155800345-JVM-heap-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%82%98%EC%9A%94-&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; letter-spacing: -1px;&quot;&gt;JVM heap 모니터링은 어떻게 하나요?&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #006dd7; letter-spacing: -1px; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7; text-align: left;&quot; href=&quot;https://gem1n1.tistory.com/89&quot;&gt;
&lt;div&gt;[JAVA 메모리 트러블 슈팅] 콘솔에서 JVM Heap 메모리 추적하기 : jstat, jmap&lt;/div&gt;
&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://junstar92.tistory.com/165&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;letter-spacing: -1px;&quot;&gt;[Docker] 07. 컨테이너 리소스 할당 제한&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://meoru-tech.tistory.com/81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;letter-spacing: -1px;&quot;&gt;  [CS지식] Garbage Collection에 대하여&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://thalals.tistory.com/458&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;letter-spacing: -1px;&quot;&gt;[Linux] free - 메모리 사용량 확인하기&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://thalals.tistory.com/405&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;letter-spacing: -1px;&quot;&gt;[Linux] top - 리눅스 CPU 사용량 보기&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://thalals.tistory.com/457&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;letter-spacing: -1px;&quot;&gt;[Linux] dmesg - 리눅스 커널 로그 메세지 확인하기 (OOME, SYN Flooding)&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://www.nextree.io/memory-leak/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;메모리 누수의 개념과 방지방법&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>회고/일상 후기 회고</category>
      <category>EC2</category>
      <category>memory leak</category>
      <category>OOM</category>
      <category>oom killer</category>
      <category>oome</category>
      <category>outofmemory</category>
      <category>shutdown</category>
      <category>spring</category>
      <category>간헐적 메모리 오류</category>
      <category>간헐적 서버 다운</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/465</guid>
      <comments>https://thalals.tistory.com/465#entry465comment</comments>
      <pubDate>Thu, 2 Nov 2023 17:56:33 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] 리눅스 성능 분석 및 트러블 슈팅 실제사례 정리</title>
      <link>https://thalals.tistory.com/464</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;해당 포스팅은 인프런 &quot;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot;&gt;리눅스 성능 분석 시작하기&lt;/a&gt;&quot; 를 수강하고 정리한 글입니다 :)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;리눅스 기반 os 에서 돌아가는 서버 시스템의 성능 측정 및 장애 대응에 대한 학습 내용 정리 글 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwiaTE/btstazzyAkO/oBip11gowxi8mV4QoN8yDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwiaTE/btstazzyAkO/oBip11gowxi8mV4QoN8yDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwiaTE/btstazzyAkO/oBip11gowxi8mV4QoN8yDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwiaTE%2FbtstazzyAkO%2FoBip11gowxi8mV4QoN8yDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;265&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;265&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;nginx miss configuration&lt;/li&gt;
&lt;li&gt;간헐적인 네트워크 응답 지연&lt;/li&gt;
&lt;li&gt;간헐적인 커넥션 동료 에러&lt;/li&gt;
&lt;li&gt;간헐적인 타임아웃&lt;/li&gt;
&lt;li&gt;EC2 CPU MHz이상 동작 에러&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;1. nginx miss configuration&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;nginx workers 설정 미숙으로 인한 장애&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 장애 현상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트래픽 증가와 함께 서버의 &lt;span style=&quot;color: #ee2323;&quot;&gt;응답 지연&lt;/span&gt; 발생 &amp;rarr; 응답 지연은 컴퓨팅 리소스 부족이 원인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 트러블 슈팅 과정&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 메트릭 수집&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 응답지연은 컴퓨팅 리소스 자원의 부족을 원으로 보기 때문에 아래 2가지 관점에서의 메트릭 수집을 진행
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;cpu usage : cpu 사용량이 많아서 응답을 못하는 것인가?&lt;/li&gt;
&lt;li&gt;MEM Usage : 메모리 사용량이 높아서 OOM이 발생 했는가?&lt;/li&gt;
&lt;/ol&gt;
&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://thalals.tistory.com/405&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;top&lt;/a&gt; 으로&lt;/span&gt; cpu와 memory에 대한 메트릭을 수집하고 파악가능&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단, 멀티 코어일 경우 반드시 모든 코어의 CPU 사용률을 확인해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2) 실제 장애 현상&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) top 으로 확인해 본 결과 &amp;rarr; 8개의 CPU 중 한 개의 CPU 100%를 사용중인 장애현상이 발생&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3) 문제 원인 해결&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, Nignx의 worker_processes 가 잘못 설정되어있었다고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nginx worker_processes 란, 설정 중 워커 프로세스의 개수를 설정하는 항목&lt;/li&gt;
&lt;li&gt;워커 프로세스란, 사용자의 요청 처리하는 프로세스를 말합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biMdLK/btss3EhwkGg/JF4GNX3bWMqOObDkP58130/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biMdLK/btss3EhwkGg/JF4GNX3bWMqOObDkP58130/img.png&quot; data-alt=&quot;nginx 오픈소스 코드 config 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biMdLK/btss3EhwkGg/JF4GNX3bWMqOObDkP58130/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiMdLK%2Fbtss3EhwkGg%2FJF4GNX3bWMqOObDkP58130%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;1067&quot; height=&quot;543&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nginx 오픈소스 코드 config 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; nginx 의 기본 설정 worker_process 의 default 값은 1로 설정 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 실제 동작하는 프로세스의 개수를 1로 제한한다는 의미이고, 이로인해 멀티코어임에도 불과하고, 1개의 cpu 만이 돌아가는 싱글 코어상태로 동작하는 문제였다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BLKoJ/btss8h7n5VX/C5k3x9luB5cuNRaj2QLYcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BLKoJ/btss8h7n5VX/C5k3x9luB5cuNRaj2QLYcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BLKoJ/btss8h7n5VX/C5k3x9luB5cuNRaj2QLYcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBLKoJ%2Fbtss8h7n5VX%2FC5k3x9luB5cuNRaj2QLYcK%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;486&quot; height=&quot;288&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;486&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;해당 사진 처럼 worker_process를 auto (cpu 개수에 맞게 만들겠다는 옵션) 로 설정한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시같은 문제 상황일 경우 워커프로세스 1 &amp;rarr; auto로 수정 시 서버가 8배 늘어난 것과 같은 성능 업이 일어납니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 결론&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 사용량 모니터링을 잘하자 (멀티 코어일 떄도)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;2. 간헐적인 네트워크 응답 지연&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Read Timeout으로 인한 장애&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 장애 현상&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;간헐적으로 API 호출 시 타임아웃이 발생&lt;/li&gt;
&lt;li&gt;응답을 안받았는데 먼저 커넥션을 종료시켜 버림 (타임아웃)&lt;/li&gt;
&lt;li&gt;이 후, 커넥션 종료 후 응답이 오는 상황&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWLm6a/btss74G6Q76/GfE8s4XRvApRdqfk7BXXnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWLm6a/btss74G6Q76/GfE8s4XRvApRdqfk7BXXnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWLm6a/btss74G6Q76/GfE8s4XRvApRdqfk7BXXnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWLm6a%2Fbtss74G6Q76%2FGfE8s4XRvApRdqfk7BXXnk%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;826&quot; height=&quot;343&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;메트릭 수집&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;netstat 을 통해 네트워크 연결은 잘 되어 있는지 확인 &amp;rarr;establish 상태인지, close_wait 상태인지는 아닌지&lt;/li&gt;
&lt;li&gt;tcpdump를 통해 피킷들의 흐름을 수집 후 분석해야함&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ tcpdump 낚시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;간헐적으로 발생하기 때문에, 긴 호흡으로 패킷을 수집해야 함&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thalals.tistory.com/463&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tcpdump 참고 링크&lt;/a&gt;&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;b&gt;1) 패킷 수집 순서&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;tcpdump -vvv -nn -A -G 3600 -w /var/log/tcpdump/$(hostname)_%Y%m%d-%H%M%S.pcap : 1시간에 1번씩 해당 위치에 패킷 파일을 재생성&lt;/li&gt;
&lt;li&gt;타임아웃이 발생한 순간 &amp;rarr; tcpdump 수집을 멈추고 해당 순간이 pcap 파일을 분석&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 클라이언트의 타이아웃 설정 살펴보기&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  Timeout 이란&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;현재 상태가 정상이라고 판단할 때까지 얼마나 기다릴 것인가&amp;rdquo; 에 대한 설정 &amp;rarr; 이 시간이 지나면 에러라고 판단&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  Timeout의 종류&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Connection Timeout
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;종단 간 연결을 처음 맺을 때의 time out&lt;/li&gt;
&lt;li&gt;3way tcp handshake&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Read Timeout
&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;이미 커넥션을 맺은 상태에서, 데이터를 주고 받을 때 일어나는 time out&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Round Trip Time (RTT)
&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;서울 &amp;harr; 부산 왕복보다 // 서울 &amp;harr; 미국을 왕복하는데 훨씬 많은 시간이 걸린다.&lt;/li&gt;
&lt;li&gt;이 시간을 Round Trip Time 이라 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  Round Trip Time (RTT)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타임아웃 시 RTT 를 고려해야하는데, 아래 사진처럼 RTT의 요청시간이 존재한다고할 때&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Read timeout 은 RTT + 평균 요청 처리시간&lt;/span&gt;이 되어야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKydOp/btssVIktZqe/NRox3MXFnyzZEdqUKPEOp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKydOp/btssVIktZqe/NRox3MXFnyzZEdqUKPEOp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKydOp/btssVIktZqe/NRox3MXFnyzZEdqUKPEOp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKydOp%2FbtssVIktZqe%2FNRox3MXFnyzZEdqUKPEOp1%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;590&quot; height=&quot;286&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;286&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  Timeout 설정시 고려해야하는 상황 2가지&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. RTT를 모를 때&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;패킷이 한 번도 흘러본 적이 없으니, RTT &lt;u&gt;시간이 얼마나 걸릴지 알 수 없습니다.&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;initRTO : RTT를 모를 때 사용하는 커널의 패킷 초기 타임아웃 값 (리눅스는 1초로 하드코딩 되어있습니다.)종단 간 커넥션을 처음 맺을 때&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Connection Timeout 설정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3-Way Handshake 과정 중 최소한 한 번의 패킷 유실 정도는 방어할 수 있어야 합니다.&lt;/li&gt;
&lt;li&gt;그렇기 때문에, 3초(1초 + RTT 고려) 정도로 설정하는 것이 좋습니다. &amp;rarr; (패킷이 2번 유실 되었을 때 파악하기까지 대략 3초가 걸림)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; src=&quot;https://blog.kakaocdn.net/dn/ubb7v/btssUJxjrt5/Ec2kuoE4tCvk5LtVeCGKt1/img.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;328&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&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: #000000;&quot;&gt;2. 패킷이 유실되었을 때&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 상황에서는, Retransmission TimeOut(RTO) 를 고려해야 합니다. (재선송에 소요되는 시간)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패킷에 대한 응답이 RTO 이내에 도착하지 않으면 유실로 간주 합니다.&lt;/li&gt;
&lt;li&gt;RTO는 RTT를 기반으로 계산 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 하지만 RTT가 아무리 짧아도 RTO 의 최소값은 있습니다. ( = 1/5초 = 200ms)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xoPo7/btssZdY4ew3/nhVkgeBodT7eUfkM2wyPZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xoPo7/btssZdY4ew3/nhVkgeBodT7eUfkM2wyPZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xoPo7/btssZdY4ew3/nhVkgeBodT7eUfkM2wyPZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxoPo7%2FbtssZdY4ew3%2FnhVkgeBodT7eUfkM2wyPZK%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;500&quot; height=&quot;237&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) RTT가 10ms 라고 가정하면, 310ms 에 처리 가능한 요청이 중간에 패킷 유실로 인해(+200ms) 510ms 가 소요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PR4VQ/btss9h7bco4/emL6f4USpEfocLudndDK6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PR4VQ/btss9h7bco4/emL6f4USpEfocLudndDK6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PR4VQ/btss9h7bco4/emL6f4USpEfocLudndDK6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPR4VQ%2Fbtss9h7bco4%2FemL6f4USpEfocLudndDK6K%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;500&quot; height=&quot;245&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Read Timeout 설정&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;그래서 1초(프로세싱 시간 + RTO 고려) 정도로 설정하는 것이 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 문제 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  하지만 프로세싱 시간이 전부 다름으로 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &amp;rarr; 프로세싱 시간이 Read Timeout 시간을 넘겼을 때&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;간헐적으로 Time Out 이 빈번하게 일어날 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IObbV/btss3XuJidM/okbAgnXT4J4SH38tGn6K40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IObbV/btss3XuJidM/okbAgnXT4J4SH38tGn6K40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IObbV/btss3XuJidM/okbAgnXT4J4SH38tGn6K40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIObbV%2Fbtss3XuJidM%2FokbAgnXT4J4SH38tGn6K40%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;1022&quot; height=&quot;391&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의에서또한, ReadTimeout 은 3초인데 &amp;rarr; 종종 프로세싱 시간이 6초가 걸리는 로직이 있어 TimeOut 이 발생했다고 합니다.&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: #ee2323;&quot;&gt; Read Timeout 시간을 늘려서 서비스의 가용성&lt;/span&gt;을 챙기는 것도 문제를 해결하는 한가지 방법이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;3. 간헐적인 커넥션 종료 에러&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;HTTP Keepalive Timeout 으로 인한 장애&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 장애 현상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간헐적으로 API 호출 시 커넥션 에러가 발생&amp;nbsp;&lt;/li&gt;
&lt;li&gt;error code : Connection prematurely closed BEFORE response&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWkz8S/btss9yHHQwS/U83BNgdLdBbAtKi3k2R7wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWkz8S/btss9yHHQwS/U83BNgdLdBbAtKi3k2R7wk/img.png&quot; data-alt=&quot;로그 수집 화면 - 간헐적으로 connection 문제가 생김&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWkz8S/btss9yHHQwS/U83BNgdLdBbAtKi3k2R7wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWkz8S%2Fbtss9yHHQwS%2FU83BNgdLdBbAtKi3k2R7wk%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;1048&quot; height=&quot;318&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로그 수집 화면 - 간헐적으로 connection 문제가 생김&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 메트릭 수집&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;netstat 을 통해 네트워크 연결은 잘 되어 있는지 확인&lt;/li&gt;
&lt;li&gt;tcpdump 를 통해 패킷들의 흐름을 수집 후 분석&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 장애 상황&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;83 번 패킷에 대한 응답으로 84번 패킷이 FIN 패킷을 보내는 것을 확인&lt;/li&gt;
&lt;li&gt;상대방 서버가 갑자기 연결을 끊었다..?&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cca7E7/btssVLuMSC9/VJOHRpHSJoSKQBWanGRdi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cca7E7/btssVLuMSC9/VJOHRpHSJoSKQBWanGRdi1/img.png&quot; data-alt=&quot;tcpdump 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cca7E7/btssVLuMSC9/VJOHRpHSJoSKQBWanGRdi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcca7E7%2FbtssVLuMSC9%2FVJOHRpHSJoSKQBWanGRdi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;393&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tcpdump 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ HTTP KeepAlive&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 원래 Stateless 의 특징을 가지고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서, HTTP 통신을 하고 나면 연결을 끊는 것이 원래 스펙 입니다.&lt;/li&gt;
&lt;li&gt;하지만 HTTP/ 1.1 이 되면서 자원 낭비를 줄이기 위해 Connection: Keep-Alive 헤더를 제공 합니다.&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RgdVi/btssVIY8hhu/3ZD5g369sLzbSTEfgn9L50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RgdVi/btssVIY8hhu/3ZD5g369sLzbSTEfgn9L50/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;303&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 50.7971%; margin-right: 10px;&quot; data-widthpercent=&quot;51.39&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RgdVi/btssVIY8hhu/3ZD5g369sLzbSTEfgn9L50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRgdVi%2FbtssVIY8hhu%2F3ZD5g369sLzbSTEfgn9L50%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;500&quot; height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3PIaN/btss3FtYhfY/9l5fSGz7gd98gOF6cJUVa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3PIaN/btss3FtYhfY/9l5fSGz7gd98gOF6cJUVa1/img.png&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;594&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.0402%;&quot; data-widthpercent=&quot;48.61&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3PIaN/btss3FtYhfY/9l5fSGz7gd98gOF6cJUVa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3PIaN%2Fbtss3FtYhfY%2F9l5fSGz7gd98gOF6cJUVa1%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;927&quot; height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(좌) HTTP 1.0 (우) HTTP 1.1&lt;/figcaption&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;(우) : 한번 연결이 되면 계속 연결상태를 유지하다가 &amp;rarr; HTTP KeepAlive 시간 안에 요청이 들어오지 않으면 연결을 끊음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 장애 상황 분석&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KeepAlive 타이밍이 안 맞는 상황&lt;/li&gt;
&lt;li&gt;양방향 통신 중 KeepAlive 시간안에 요청이 들어오지 않아 상대방 쪽에서 연결을 끊는 상황&lt;/li&gt;
&lt;li&gt;이러한 상황은, 매우 정상적인 상황이기도 함 &amp;rarr; 하지만 간헐적으로 계속 connection error 가 나오기 때문에 대응은 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 장애 대응&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요청할 때 Connection: close 헤더를 포함시켜서 HTTP KeepAlive 를 사용하지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단점 : 성능이 저하 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상대방 서버의 HTTP keepalive-timeout 을 늘려 달라고 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단점 : 에러 발생 빈도는 줄겠지만 에러가 사라지지는 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상대방 서버보다 idle timeout을 짧게 가져간다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;호출하는 쪽의 idle Timeout 을 수정&lt;/li&gt;
&lt;li&gt;상대방 서버의 keepalive-timeout보다 호출하는 쪽의 idle-Timeout을 짧게 하여 연결을 먼저 끊도록 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 해당 방법은 Reactor Netty의 공식문서 대응 가이드에도 명시되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beUp05/btss86YXFXl/ICLn8OTyt7X5ZwYY3YTCC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beUp05/btss86YXFXl/ICLn8OTyt7X5ZwYY3YTCC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beUp05/btss86YXFXl/ICLn8OTyt7X5ZwYY3YTCC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeUp05%2Fbtss86YXFXl%2FICLn8OTyt7X5ZwYY3YTCC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;266&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(예시)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상대방 서버의 keepalive-timeout이 5초로 예상되어 &amp;rarr; Idle Timeout을 3초로 수정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상대방과 연결을 맺고 HTTP 요청을 보낸 후 3초 동안 새롭게 보낼 요청이 없다면 연결을 먼저 끊는다.&lt;/li&gt;
&lt;li&gt;이렇게 되면, 호출하는 쪽이 연결을 먼저 끊기 때문에 Connection-timeout 이 발생하지 않습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;4. 간헐적인 타임아웃&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JVM Full-GC로 인한 장애&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 장애 현상&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간헐적으로 API 호출 시 타임아웃 에러가 발생&lt;/li&gt;
&lt;li&gt;error code : [WARN] - from application in application-akka.actor.default-dispatcher-3 xxx is too slow [ElpasedTime: 1086ms]&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️&amp;nbsp;메트릭 수집&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tcpdump 낚시 &amp;rarr; 타임아웃이 발생했던 순간의 pcap 파일 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&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;응답 늦게 받은 것 x, 못 받은 것 도 x 아님 &amp;nbsp;&amp;rarr; 패킷 생성 자체가 늦어졌다?&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVhCDN/btss3YtEEHV/hnYUbCs0A7wHeR2gkOeH3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVhCDN/btss3YtEEHV/hnYUbCs0A7wHeR2gkOeH3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVhCDN/btss3YtEEHV/hnYUbCs0A7wHeR2gkOeH3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVhCDN%2Fbtss3YtEEHV%2FhnYUbCs0A7wHeR2gkOeH3K%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;1126&quot; height=&quot;263&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;263&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 장애 대응&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;2가지 상황을 생각해볼 수 있습니다.&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버에 부하가 극심해서 패킷 생성 자체가 늦어졌다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;체크포인트 [Load Average 및 CPU 사용량 체크] &amp;rarr; 높지 않았다면 아래 이유일 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션의 프로세싱 시간이 길어져서 패킷 생성이 늦어졌다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수집한 데이터를 바탕으로 개발팀과 논의 &amp;rarr; 네트워크나 인프라적인 문제가 보이지 않았기 때문에&lt;/li&gt;
&lt;li&gt;애플리케이션에 영향을 줄 만한 로직이 있는지?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결론적으로 &amp;rarr; JVM Full-GC 로 인한 멈춤 현상이였다고 합니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ Full GC 란&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GC 쓰레드를 제외한 다른 쓰레드들이 멈추는 현상&lt;/li&gt;
&lt;li&gt;&amp;rarr; 요청 순간에 java 에서 사용하는 일부 라이브러리가 Full-GC를 일으키고 이로인해 &amp;rarr; 요청을 제때 하지 못한 상황 &amp;rarr; 에러&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;5. EC2 CPU 이상 동작 사례&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;물리서버의 CPU 불량으로 인한 장애&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️&amp;nbsp;장애 현상&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간헐적으로 CPU Usage 높다는 알람 발생&lt;/li&gt;
&lt;li&gt; &amp;nbsp; 여기서 포인트 &amp;rarr; 모든 서버에서 발생하지는 않고 &quot;&lt;b&gt;일부 서버&lt;/b&gt;&quot; 에서만 발생했다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXRvTE/btss8h7pkQu/RB8uCsv36F8KJhtkq4VCK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXRvTE/btss8h7pkQu/RB8uCsv36F8KJhtkq4VCK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXRvTE/btss8h7pkQu/RB8uCsv36F8KJhtkq4VCK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXRvTE%2Fbtss8h7pkQu%2FRB8uCsv36F8KJhtkq4VCK0%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;500&quot; height=&quot;240&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;240&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 메트릭 수집&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;top 을 통해 어떤 프로세스가 CPU를 사용하는지 확인하였더니 &amp;rarr; JAVA 프로세스가 100% 사용하고 있었음&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;b&gt;1번째 조치&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;구글링을 통해 JAVA, 특히 tomcat 이 CPU를 점유하는 현상이 검색되었다고 합니다,&lt;/li&gt;
&lt;li&gt;/dev/random 블로킹 이슈 (난수 생성을 위한 코드가 있을경우, /dev/random 을 읽어들일때 CPU 점유율이 올라감) 조치했지만 &amp;rarr; 해결되지 않았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 잘못된 점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저게 원인이였다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;모든 서버에서 CPU 이슈가 발생했어야 합니다.&lt;/span&gt;&amp;nbsp;(똑같은 tomcat, os 이기 때문)&lt;/li&gt;
&lt;li&gt;&lt;u&gt;결과적으로, 전역적인 원인이 아니였음&lt;/u&gt;&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;b&gt;2번째 조치&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이슈가 발생하는 서버만 서비스에서 제외
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ASG (Auto-Scaleling Group) 에서 제외시키고, 삭제 방지 기능 활성화(ASG 에서 EC2를 제외할 시 ASG가 인스턴스를 종료 후 재실행 시키기 때문에 에러파악이 힘들어짐) 후 분석 시작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그런데 다시 top 으로 확인하니 이상한 것들도 CPU 사용률을 많이 먹고있었다고 합니다.&lt;/li&gt;
&lt;li&gt;즉, 서버 자체가 이상함을 알 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;597&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2qtYQ/btstcQA4hmN/K6PRHzNw1ddPDjkZjuShi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2qtYQ/btstcQA4hmN/K6PRHzNw1ddPDjkZjuShi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2qtYQ/btstcQA4hmN/K6PRHzNw1ddPDjkZjuShi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2qtYQ%2FbtstcQA4hmN%2FK6PRHzNw1ddPDjkZjuShi0%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;597&quot; height=&quot;276&quot; data-origin-width=&quot;597&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; /proc/cpuinfo &lt;/span&gt;를 확인하였더니&amp;nbsp; (CPU 자체에 대한 의구점) &amp;rarr; MHz 가 너무 작게 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ8Vdq/btss3YUHqB9/tBAokSnsGzUQlaffdmtFBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ8Vdq/btss3YUHqB9/tBAokSnsGzUQlaffdmtFBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ8Vdq/btss3YUHqB9/tBAokSnsGzUQlaffdmtFBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ8Vdq%2Fbtss3YUHqB9%2FtBAokSnsGzUQlaffdmtFBk%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;476&quot; height=&quot;422&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;422&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ CPU MHz가 변경되는 상황&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;CPU의 C-state 와 P-State 이 두 기능에 의해 MHz가 유동적일 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C-State&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;전력 소모량을 줄이기 위해서 일부 CPU 코어를 비활성화 하는 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;총 4개인 코어를 평상시에 코어를 1,2 개만 사용한다면 안쓰는 코어를 disable 시킴&lt;/li&gt;
&lt;li&gt;다시 써야하면 하나씩 깨움 &amp;rarr; 약간의 성능저하 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;P-State&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;작업 부하에 따라서 CPU 의 전압과 MHz를 조절하는 기능&lt;/li&gt;
&lt;li&gt;서버의 처리량에 따라 MHz를 유동적으로 변경하는 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 다시 문제해결 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws 문서에서는 &amp;rarr; turbostat 명령을 이용해 강제로 CPU MHz를 끌어오릴 수 있는 방법이 소개되어있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W5tIY/btssUMOcS4c/q69h0ApyrqB3fq9uHks1t1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W5tIY/btssUMOcS4c/q69h0ApyrqB3fq9uHks1t1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W5tIY/btssUMOcS4c/q69h0ApyrqB3fq9uHks1t1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW5tIY%2FbtssUMOcS4c%2Fq69h0ApyrqB3fq9uHks1t1%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;824&quot; height=&quot;494&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;494&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;  하지만 따라해봤자 MHz 회복되지않았고 &amp;rarr; 결과적으로는 하드웨어의 문제였다고 합니다. (aws 팀과 문의했다고 함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T0lAE/btssT58sMK6/JMuPGjQgsuUzUI0R0XZY7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T0lAE/btssT58sMK6/JMuPGjQgsuUzUI0R0XZY7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T0lAE/btssT58sMK6/JMuPGjQgsuUzUI0R0XZY7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT0lAE%2FbtssT58sMK6%2FJMuPGjQgsuUzUI0R0XZY7K%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;1242&quot; height=&quot;262&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이걸로 강의 정리 끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>운영체제/Linux</category>
      <category>linux</category>
      <category>network</category>
      <category>nginx</category>
      <category>nginx 사례</category>
      <category>nginx 장애</category>
      <category>server connection</category>
      <category>리눅스</category>
      <category>리눅스 사례</category>
      <category>리눅스 장애 대응</category>
      <category>서버 장애</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/464</guid>
      <comments>https://thalals.tistory.com/464#entry464comment</comments>
      <pubDate>Tue, 5 Sep 2023 23:52:14 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] tcpdump - 네트워크 트러블 슈팅 도구 (리눅스 패킷 수집 및 분석하기)</title>
      <link>https://thalals.tistory.com/463</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;해당 포스팅은 인프런 &quot;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot;&gt;리눅스 성능 분석 시작하기&lt;/a&gt;&quot; 를 수강하고 정리한 글입니다 :)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;리눅스 기반 os 에서 돌아가는 서버 시스템의 성능 측정 및 장애 대응에 대한 학습 내용 정리 글 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  리눅스 성능 분석의 기본 명령어&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%; height: 154px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/456&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;uptime&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템 가동 시간, Load Average 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/457&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커널 메세지 확인 (OOME 발생 여부, SYN Flooding 여부)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/458&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;free&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 사용 현황 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/461&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;df&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디스크 여유 공간 및 inode 공간 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/405&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;top&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로세스들의 상태, CPU 사용률, 메모리 사용률 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/462&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;netstat&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 연결 정보 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://thalals.tistory.com/463&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tcpdump&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 트러블 슈팅 분석을 위한 패킷 수집 명령어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: left;&quot;&gt;  tcpdump 명령어&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'&lt;span style=&quot;color: #ee2323;&quot;&gt;tcpdump&lt;/span&gt;' 은 네트워크 패킷 수집을 위한 명령어.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tcpdump로 네트워크 패킷의 흐름을 볼 수 있습니다. (어떤 패킷이 어떻게 이동하는지)&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;pre id=&quot;code_1693550691989&quot; class=&quot;shell&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo tcpdump -nn -vvv -A&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;-nn : 프로토콜과 포트 번호를 숫자 그대로 표현합니다.&lt;/li&gt;
&lt;li&gt;-vvv : 출력 결과에 더 많은 정보를 담습니다.&lt;/li&gt;
&lt;li&gt;-A : 패킷의 내용도 함께 출력합니다. (단, ssh 를 사용할경우 패킷의 정보가 암호화 되어 표출됩니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDCUJW/btssSDo7zMI/A13Inqmfqq3flLxs4q2Zr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDCUJW/btssSDo7zMI/A13Inqmfqq3flLxs4q2Zr1/img.png&quot; data-alt=&quot;옵션과 함께 사용한 tcpdump 출력 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDCUJW/btssSDo7zMI/A13Inqmfqq3flLxs4q2Zr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDCUJW%2FbtssSDo7zMI%2FA13Inqmfqq3flLxs4q2Zr1%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;877&quot; height=&quot;334&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;옵션과 함께 사용한 tcpdump 출력 결과&lt;/figcaption&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;✔️ tcpdump를 트러블 슈팅의 목적으로 할 때 사용방법&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트러블 슈팅의 경우 A &amp;harr; B 간의 통신에서 문제가 생기는 것이기 때문에 &amp;rarr; &lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;트러블 슈팅을 위해 살펴봐야할 목적지와 포트가 명확&lt;/span&gt;합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;port&lt;/span&gt; 와 &lt;span style=&quot;color: #ee2323;&quot;&gt;host&lt;/span&gt; 옵션&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 포트와 호스트 주소로 오고가는 패킷 정보만을 필터링할 수 있도록 해주는 옵션입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693554820318&quot; class=&quot;shell&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo tcpdump -nn -vvv -A port 80 and host 10.1.1.1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 4-way handshake 도 확인해 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS6ctu/btssT47DdN0/kCbIi3LH2dMKuv6FmHN7dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS6ctu/btssT47DdN0/kCbIi3LH2dMKuv6FmHN7dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS6ctu/btssT47DdN0/kCbIi3LH2dMKuv6FmHN7dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS6ctu%2FbtssT47DdN0%2FkCbIi3LH2dMKuv6FmHN7dk%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;1083&quot; height=&quot;629&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;이걸 pcap 파일로 저장한 후 wireshark 프로그램을 이용해서 사용자가 보기 편한 ui 환경을 구성할 수 있습니다.&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;b&gt;  wireshark&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;tcpdump가 패킷 로그를 수집하여 pcap 파일을 생성&lt;/li&gt;
&lt;li&gt;sftp 명령어로 리눅스 환경(ex - ec2)에서 생성된 파일을 로컬로 가져옴&lt;/li&gt;
&lt;li&gt;wireshark 프로그램으로 해당 파일 분석 (&lt;a href=&quot;https://www.wireshark.org/download.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다운로드 링크&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vSLsv/btssSnfL0bp/EvqqDLoGn7KnbRJ07OzeQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vSLsv/btssSnfL0bp/EvqqDLoGn7KnbRJ07OzeQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vSLsv/btssSnfL0bp/EvqqDLoGn7KnbRJ07OzeQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvSLsv%2FbtssSnfL0bp%2FEvqqDLoGn7KnbRJ07OzeQ0%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;500&quot; height=&quot;246&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;darr;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&amp;nbsp;tcpdump 로그 채집 과정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. -w 옵션으로 해당 로그를 파일로 저장시킬 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1693559128236&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tcpdump -vvv -nn -A -G 3600 -w /var/log/tcpdump/$(hostname)_%Y%m%d-%H%M%S.pcap

//백그라운드 실행
nohup sudo tcpdump -vvv -nn -A -G 3600 port 8080 -w /var/log/tcpdump/api/%Y%m%d-%H%M%S.pcap &amp;amp;

//백그라운드 확인
jobs&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;로그를 무한정 저장하는 것 보다 기준을 두는게 좋기 때문에 -G 옵션으로 하나의 파일을 저장할 시간을 지정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;-w 옵션으로 저장할 파일의 위치와 이름을 지정해 줄 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZZHkk/btssMGHuG5o/mbSIOCvZ2CZq09fPYNN5TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZZHkk/btssMGHuG5o/mbSIOCvZ2CZq09fPYNN5TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZZHkk/btssMGHuG5o/mbSIOCvZ2CZq09fPYNN5TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZZHkk%2FbtssMGHuG5o%2FmbSIOCvZ2CZq09fPYNN5TK%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;993&quot; height=&quot;141&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. pcap 파일 불러오기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 이렇게 시간단위로 저장을하면, 에러가 발생한 시간의 pcap 파일만 로컬로 불러와 분석하면 된다는 장점이 있습니다.&lt;/li&gt;
&lt;li&gt;sftp 명령어로 해당 로그 파일을 로컬로 가져온 후 wireshark 프로그램으로 열면 아래 화면과 같이 [.] [f] [f.] 으로 표시되던 패킷도, SYN, ACK 와 같은 읽기 편한 구조로 구성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baNSzM/btssN39ZCzQ/oiEBRGvKnYZrEVi7y3utv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baNSzM/btssN39ZCzQ/oiEBRGvKnYZrEVi7y3utv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baNSzM/btssN39ZCzQ/oiEBRGvKnYZrEVi7y3utv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaNSzM%2FbtssN39ZCzQ%2FoiEBRGvKnYZrEVi7y3utv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;422&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tcpdump 명령어를 이용해서 넽트워크 패킷을 수집하고 분석할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-vvv -nn -A 옵션을 이용해서 tcpdump 를 좀 더 효율적으로 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;host, port 문구를 이용해서 특정 목적지, 특정 포트로 필터링 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;tcpdump로 pcap 파일을 생성하고 wireshark 로 분석할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>운영체제/Linux</category>
      <category>http 통신 로그</category>
      <category>nginx 로그 수집</category>
      <category>tcp 로그</category>
      <category>로그 모니터링</category>
      <category>로그 수집</category>
      <category>로그 파일 저장</category>
      <category>리눅스</category>
      <category>리눅스 tcp</category>
      <category>리눅스 매트릭 수집</category>
      <category>트러블슈팅</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/463</guid>
      <comments>https://thalals.tistory.com/463#entry463comment</comments>
      <pubDate>Sat, 2 Sep 2023 10:11:43 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] netstat - 리눅스 네트워크 연결 정보</title>
      <link>https://thalals.tistory.com/462</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;해당 포스팅은 인프런 &quot;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot;&gt;리눅스 성능 분석 시작하기&lt;/a&gt;&quot; 를 수강하고 정리한 글입니다 :)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;리눅스 기반 os 에서 돌아가는 서버 시스템의 성능 측정 및 장애 대응에 대한 학습 내용 정리 글 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  리눅스 성능 분석의 기본 명령어&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%; height: 154px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/456&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;uptime&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템 가동 시간, Load Average 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/457&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커널 메세지 확인 (OOME 발생 여부, SYN Flooding 여부)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/458&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;free&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 사용 현황 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/461&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;df&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디스크 여유 공간 및 inode 공간 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/405&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;top&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로세스들의 상태, CPU 사용률, 메모리 사용률 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/462&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;netstat&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 연결 정보 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/463&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tcpdump&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 트러블 슈팅 분석을 위한 패킷 수집 명령어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: left;&quot;&gt;  netstat 명령어&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'&lt;span style=&quot;color: #ee2323;&quot;&gt;netstat&lt;/span&gt;' 은 네트워크 연결정보를 위해 사용되는 명령어 입니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Local Address : 로컬 주소&lt;/li&gt;
&lt;li&gt;Foreign Address : 상대방 주소&lt;/li&gt;
&lt;li&gt;state : 소켓 상태&lt;/li&gt;
&lt;li&gt;PID/Program name : 리눅스 서버에서 어떤 프로세스가 해당 연결을 사용하고 있는지&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;pre id=&quot;code_1693546295971&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo netstat -napo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzzUcO/btssHgWDHFe/PVGCUcnM71boFY6BodBP11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzzUcO/btssHgWDHFe/PVGCUcnM71boFY6BodBP11/img.png&quot; data-alt=&quot;결과하면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzzUcO/btssHgWDHFe/PVGCUcnM71boFY6BodBP11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzzUcO%2FbtssHgWDHFe%2FPVGCUcnM71boFY6BodBP11%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;885&quot; height=&quot;615&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과하면&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;연결되어있는 네트워크 소켓을 확인할려면 grep -i est (establish) 명령어를 하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1693546739456&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo netstat -napo | grep -i est&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qxfaf/btssInOJxe7/fGVM7HLiWax9jgJKlma8X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qxfaf/btssInOJxe7/fGVM7HLiWax9jgJKlma8X0/img.png&quot; data-alt=&quot;소켓이 establish 상태인 것만 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qxfaf/btssInOJxe7/fGVM7HLiWax9jgJKlma8X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqxfaf%2FbtssInOJxe7%2FfGVM7HLiWax9jgJKlma8X0%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;887&quot; height=&quot;70&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;소켓이 establish 상태인 것만 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ State : 소켓 상태&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;nbsp;syn_sent, syn_receive 는 상당히 빨리 지나가기 때문에 가장 자주 보게 될 상태는 아래 3개 입니다&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;LISTEN&lt;/b&gt; : 통신이 이루어지기 위해 누군가 소켓을 &quot;듣고&quot; 있는 상태&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ESTABLUSHED&lt;/b&gt; : 연결된 상태&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TIME_WAIT&lt;/b&gt; : 특정 시간동안 추가 요청이 들어오지 않은 상태&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. &lt;/span&gt;LISTEM&lt;/span&gt; 상태인 소켓에 &lt;b&gt;telnet&lt;/b&gt; 으로 연결을 시도하면 &amp;rarr; 해당 소켓 상태가 정상적으로 연결되면서 &lt;span style=&quot;color: #ee2323;&quot;&gt;ESTABLISHED&lt;/span&gt; 로 변경됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baTj0t/btssOro54LP/ZkvfkKiCb74TbCgK8FAjLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baTj0t/btssOro54LP/ZkvfkKiCb74TbCgK8FAjLk/img.png&quot; data-alt=&quot;established&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baTj0t/btssOro54LP/ZkvfkKiCb74TbCgK8FAjLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaTj0t%2FbtssOro54LP%2FZkvfkKiCb74TbCgK8FAjLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;344&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;established&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그 다음 정상적으로 연결된 후, 아무런 행동을 취하지 않으면, 소켓 상태가 TIME_WAIT으로 변경됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2lfYX/btssJj6pEDM/9Mg1HstKKP1QkVDqkF9RL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2lfYX/btssJj6pEDM/9Mg1HstKKP1QkVDqkF9RL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2lfYX/btssJj6pEDM/9Mg1HstKKP1QkVDqkF9RL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2lfYX%2FbtssJj6pEDM%2F9Mg1HstKKP1QkVDqkF9RL1%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;800&quot; height=&quot;389&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;389&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;✔️ TIME WAIT 이 되는 이유&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Keepalive_timeout&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP/1.1 스펙 중 하나이며 연결을 유지하는 설정 때문에 타임아웃이 발생합니다.&lt;/li&gt;
&lt;li&gt;&amp;rarr; Nginx 의 conf 파일의 열어보면, keepalive default 값이 65초 로 설정되어있습니다.&lt;/li&gt;
&lt;li&gt;&amp;rarr; keepalive 값에 설정된 시간동안 Nginx 연결된 소켓은 HTTP 요청에 대해 커넥셕을 새로 맺지 않아도 되도록 새로운 요청을 기다립니다.&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;  원래 HTTP는 stateless 하기 때문에, 상태를 저장하지 않습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 HTTP/1.0 에서는 연속된 요청에 &quot;연결-종료&quot; &amp;rarr; &quot;연결 - 종료&quot; 단건 단건 연결을 했지만&lt;/li&gt;
&lt;li&gt;리소스의 지나친 낭비로 인해 HTTP/1.1 에서 부터는 KeepAlive 설정값을 추가하여 일정한 시간이 지나면 연결을 종료시키도록 동작한다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 즉, TIME_WAIT 상태가 문제가 되는 상황은 아닙니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;netstat&lt;/span&gt; 명령을 이용해서 네트워크 연결 정보를 확인할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커넥션의 상태와 종단 간 IP 정보 등 서버의 네트워크 연결 정보를 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LISTEN, ESTABLISHED, TIME_WAIT 은 흔히 만나게되는 소켓 상태이므로 &amp;rarr; 많이 보인다고해서 문제가 발생하지는 않습니다.&lt;/li&gt;
&lt;li&gt;단, &lt;span style=&quot;color: #ee2323;&quot;&gt;CLOSE_WAIT&lt;/span&gt; 상태가 발생한다면 어딘가에서 타임루프가 생성되어 커넥션 종료를 위한 4-way handshake 가 정상적으로 동작하지 않는 것이므로 반드시 조치가 필요하다고 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>운영체제/Linux</category>
      <category>linux</category>
      <category>linux 연결 확인</category>
      <category>network</category>
      <category>nginx</category>
      <category>nginx 소켓 연결 정보</category>
      <category>네트워크</category>
      <category>네트워크 모니터링</category>
      <category>리눅스</category>
      <category>연결정보</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/462</guid>
      <comments>https://thalals.tistory.com/462#entry462comment</comments>
      <pubDate>Fri, 1 Sep 2023 15:43:06 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] top - 리눅스 CPU 사용량 보기</title>
      <link>https://thalals.tistory.com/405</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;해당 포스팅은 인프런 &quot;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot;&gt;리눅스 성능 분석 시작하기&lt;/a&gt;&quot; 를 수강하고 정리한 글입니다 :)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;리눅스 기반 os 에서 돌아가는 서버 시스템의 성능 측정 및 장애 대응에 대한 학습 내용 정리 글 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  리눅스 성능 분석의 기본 명령어&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%; height: 154px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/456&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;uptime&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템 가동 시간, Load Average 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/457&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커널 메세지 확인 (OOME 발생 여부, SYN Flooding 여부)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/458&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;free&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 사용 현황 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/461&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;df&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디스크 여유 공간 및 inode 공간 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/405&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;top&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로세스들의 상태, CPU 사용률, 메모리 사용률 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/462&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;netstat&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 연결 정보 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/463&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tcpdump&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 트러블 슈팅 분석을 위한 패킷 수집 명령어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: left;&quot;&gt; &amp;nbsp;top 명령어&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널 혹은 쉘스크립트 환경에서 간단하게 CPU 및 메모리 사용량을 확인해 볼수 있는 명령어 &amp;rarr; '&lt;span style=&quot;color: #ee2323;&quot;&gt;top&lt;/span&gt;'&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1675926265989&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//계속 실행하며 확인
$ top

//hot key 사용 (순서대로 입력)
$ top &amp;rarr; 1 &amp;rarr; d &amp;rarr; 1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JM9jI/btrYJaiuv8S/hVAIeymOPHoBYKZIIZ19vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JM9jI/btrYJaiuv8S/hVAIeymOPHoBYKZIIZ19vK/img.png&quot; data-alt=&quot;Linux 프로세스 실행 상태를 보여주는 top&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JM9jI/btrYJaiuv8S/hVAIeymOPHoBYKZIIZ19vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJM9jI%2FbtrYJaiuv8S%2FhVAIeymOPHoBYKZIIZ19vK%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;957&quot; height=&quot;505&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Linux 프로세스 실행 상태를 보여주는 top&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;전체 CPU 정보&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;5:16 : 5시간 16분 전에 서버가 구동&lt;/li&gt;
&lt;li&gt;load average
&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;3개의 숫자는 1분, 5분, 15분 간의 평균 실행/대기 중인 프로세스의 수를 표현합니다.&lt;/li&gt;
&lt;li&gt;CPU 코어수 보다 적어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Tasks : 프로세스 개수&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;us&lt;/span&gt; : USER 를 의미하며, 프로세스의 일반적인 CPU 사용량&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;wa&lt;/span&gt; :watiting 을 의미하며, I/O 작업을 대기할 때의 CPU 사용량&lt;/li&gt;
&lt;li&gt;KiB Mem, Swap : 각 메모리의 사용량&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;span style=&quot;background-color: #f6e199;&quot;&gt;프로세스 CPU 할당 정보&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PR : 실행 우선순위&lt;/li&gt;
&lt;li&gt;VIRT, RES, SHR : 메모리 사용량
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VIRT
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스가 사용하고 있는 virtual memory의 전체 용량&lt;/li&gt;
&lt;li&gt;프로세스에 할당된 가상 메모리 전체 (SWAP + RES)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RES
&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;실제로 메모리에 올려서 사용하고 있는 물리 메모리 size&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SHR
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 프로세스와 공유하고 있는 shared memory의 양&lt;/li&gt;
&lt;li&gt;예시로 라이브러리를 들 수 있음. 대부분의 리눅스 프로세스는 glibc라는 라이브러리를 참고하기에 이런 라이브러리를 공유 메모리에 올려서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;S : 프로세스 상태(작업중, I/O 대기, 유휴 상태 등)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;D&lt;/b&gt; : uninterruptible sleep (I/O) - [I/O 대기 상태 == 'vmstat의 b 상태']&lt;/li&gt;
&lt;li&gt;&lt;b&gt;R&lt;/b&gt; : running (CPU)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;S&lt;/b&gt; : sleeping - 작업 x&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Z&lt;/b&gt; : zombie - 부모 프로세스를 잃어버린 상태, cpu나 메모리를 사용하지는 않음
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 리소스를 사용하지는 않습니다.&lt;/li&gt;
&lt;li&gt;하지만 전체 생성가능한 프로세스 수의 총량을 잡아먹기 때문에 PID 고갈을 일으킬 수 있습니다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;'sudo sysctl -a | grep -i pid_max'&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 동시에 존재가능한 프로세스 총량을 알 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;&lt;b&gt;I&lt;/b&gt;&amp;nbsp; :&amp;nbsp; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;IDLE - 프로그램을&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수행하지&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;않는 상태(프로세스가 아님)&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;&lt;b&gt;T&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;:&lt;/span&gt; 작업 제어 신호(Job Control Signal)에 의해 중지됨&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot;&gt;&lt;b&gt;t&lt;/b&gt; : Trace 중 디버거에 의해 중지됨&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&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;p style=&quot;background-color: #ffffff; color: #151515; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; D, R 상태인 프로세스만 load average에 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 좀비 프로세스 특징&lt;/p&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;하지만 전체 생성가능한 프로세스 수의 총량을 잡아먹기 때문에 PID 고갈을 일으킬 수 있습니다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;'sudo sysctl -a | grep -i pid_max'&lt;/span&gt; 로 동시에 존재가능한 프로세스 총량을 알 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 id=&quot;top&quot; data-ke-size=&quot;size23&quot;&gt;✔️ top&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템의 상태를 전반적으로 가장 빠르게 파악 가능한 상태를 보여주는 리눅스 명령어 (CPU, Memory, Process)&lt;/li&gt;
&lt;li&gt;옵션 없이 입력하면&lt;span style=&quot;background-color: #ffc9af;&quot;&gt; interval 간격(기본 3초)&lt;/span&gt;으로 화면을 갱신하며 정보를 보여줍니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;top 실행 후 명령어&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;shift + p&lt;/b&gt; : CPU 사용률 내림차순&lt;/li&gt;
&lt;li&gt;&lt;b&gt;shit + m&lt;/b&gt; : 메모리 사용률 내림차순&lt;/li&gt;
&lt;li&gt;&lt;b&gt;shift + t&lt;/b&gt; : 프로세스가 돌아가고 있는 시간 순&lt;/li&gt;
&lt;li&gt;&lt;b&gt;k&lt;/b&gt; : kill. k 입력 후 PID 번호 작성. signal은 9&lt;/li&gt;
&lt;li&gt;&lt;b&gt;f&lt;/b&gt; : sort field 선택 화면 -&amp;gt; q 누르면 RES순으로 정렬&lt;/li&gt;
&lt;li&gt;&lt;b&gt;a&lt;/b&gt; : 메모리 사용량에 따라 정렬&lt;/li&gt;
&lt;li&gt;&lt;b&gt;b&lt;/b&gt; : Batch 모드로 작동&lt;/li&gt;
&lt;li&gt;&lt;b&gt;1&lt;/b&gt; : CPU Core별로 사용량 보여줌&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;메모리 용량 단위 변경&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;&lt;b&gt;E&lt;/b&gt;를 누르면 상단에 표기되는 데이터 단위가 변경됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;&lt;b&gt;e&lt;/b&gt;를 누르면 하단에 표기되는 데이터 단위가 변경됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ps와 top의 차이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ps는 ps한 시점에 proc에서 검색한 cpu 사용량&lt;/li&gt;
&lt;li&gt;top은 proc에서 일정 주기로 합산해 cpu 사용율 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 id=&quot;top&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✔️ hot key 사용법&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;top 명령어에서는 hot key를 이용해 더 원할하게 원하는 정보를 볼 수 있습니다.&lt;/blockquote&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;1) 키패드 1&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;키패드 숫자 1을 누르면 Cpu(s) 가 각각의 CPU로 변경됩니다.&lt;/li&gt;
&lt;li&gt;Cpu(s) 는 모든 cpu 사용량의 평균치를 보여주기 때문에, 혹여 cpu 불균형이 일어났을 때 확인할 수가 없으므로, hot key (1) 을 사용하여 개별적인 사용랼을 확인하는게 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;97&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C9rMk/btssvc1iQ2h/XsX4pAXudEBQzBmKJghW5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C9rMk/btssvc1iQ2h/XsX4pAXudEBQzBmKJghW5K/img.png&quot; data-alt=&quot;기본모드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C9rMk/btssvc1iQ2h/XsX4pAXudEBQzBmKJghW5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC9rMk%2Fbtssvc1iQ2h%2FXsX4pAXudEBQzBmKJghW5K%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;582&quot; height=&quot;97&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;97&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기본모드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vdhFS/btssGxCFP1U/k7l2Jj0qvJ94ZhYb0K11Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vdhFS/btssGxCFP1U/k7l2Jj0qvJ94ZhYb0K11Xk/img.png&quot; data-alt=&quot;hot key 1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vdhFS/btssGxCFP1U/k7l2Jj0qvJ94ZhYb0K11Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvdhFS%2FbtssGxCFP1U%2Fk7l2Jj0qvJ94ZhYb0K11Xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;113&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hot key 1&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;&lt;b&gt;2) 영문자 d&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;영문자 d를 누르면 기본 3초인 인터벌을 변경할 수 있습니다.&lt;/li&gt;
&lt;li&gt;해당 핫키를 입력하면 아래 화면 처럼 &quot;change delay from 3.o to &quot; 뒤에 원하는 초를 입력하면 인터벌이 변경됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdKwUc/btssv1kTVkM/dL846Wn67Q6N7d2jxrHMS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdKwUc/btssv1kTVkM/dL846Wn67Q6N7d2jxrHMS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdKwUc/btssv1kTVkM/dL846Wn67Q6N7d2jxrHMS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdKwUc%2Fbtssv1kTVkM%2FdL846Wn67Q6N7d2jxrHMS0%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;616&quot; height=&quot;124&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://zzsza.github.io/development/2018/07/18/linux-top/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://zzsza.github.io/development/2018/07/18/linux-top/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>운영체제/Linux</category>
      <category>cpu 모니터링</category>
      <category>CPU 사용량</category>
      <category>cpu 사용량 모니터링</category>
      <category>linux</category>
      <category>top 프로세스 종류</category>
      <category>top 핫키</category>
      <category>리눅스</category>
      <category>리눅스 실시간 cpu</category>
      <category>메모리</category>
      <category>모니터링</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/405</guid>
      <comments>https://thalals.tistory.com/405#entry405comment</comments>
      <pubDate>Thu, 31 Aug 2023 12:01:16 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] df - 디스크 사용량 모니터링 하기</title>
      <link>https://thalals.tistory.com/461</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;해당 포스팅은 인프런 &quot;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot;&gt;리눅스 성능 분석 시작하기&lt;/a&gt;&quot; 를 수강하고 정리한 글입니다 :)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;리눅스 기반 os 에서 돌아가는 서버 시스템의 성능 측정 및 장애 대응에 대한 학습 내용 정리 글 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  리눅스 성능 분석의 기본 명령어&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%; height: 154px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/456&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;uptime&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템 가동 시간, Load Average 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/457&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커널 메세지 확인 (OOME 발생 여부, SYN Flooding 여부)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/458&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;free&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 사용 현황 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/461&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;df&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디스크 여유 공간 및 inode 공간 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/405&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;top&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로세스들의 상태, CPU 사용률, 메모리 사용률 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/462&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;netstat&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 연결 정보 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/463&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tcpdump&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 트러블 슈팅 분석을 위한 패킷 수집 명령어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: left;&quot;&gt;✅ df &lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;df는 리눅스 환경에서, 디스크의 여유 공간과&amp;nbsp; inode 의 공간을 확인할 수 있는 명령어 입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&quot;df -h&quot; 명령어로, 파티션들을 볼 수 있습니다. ( -h : human readable 사람이 해석하기 좋은 출력결과로 해주는 옵션)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;파티션 별로 사용중인 used 영역, 전체 크기, 더 사용할 수 있는 크기, 사용중인 %, 각각의 파티션들의 어디에 마운트 되어있는지 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO49G6/btsrS4is9Yg/OGnvv1wW7dnR8rlgT6qo8K/img.jpg&quot; data-lightbox=&quot;lightbox&quot; data-alt=&quot;free&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg9R0U/btsscEjiSZy/i6WfGqKd6OlwPOUiTtON51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg9R0U/btsscEjiSZy/i6WfGqKd6OlwPOUiTtON51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg9R0U/btsscEjiSZy/i6WfGqKd6OlwPOUiTtON51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg9R0U%2FbtsscEjiSZy%2Fi6WfGqKd6OlwPOUiTtON51%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;543&quot; height=&quot;356&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;356&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 style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 디스크 사용량 모니터링이 중요한 이유&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;파일 시스템이 100%가 된다면?&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사진처럼 root 파티션이 꽉차게 되면 &quot;No space left on device&quot; 라는 에러를 띄우면서 디스크가 full이 되었음을 알려줍니다.&lt;/li&gt;
&lt;li&gt;이렇게 되면 어떤 명령어도 제대로 동작하지 않고, 최악의 경우 ssh 접속도 불가능해지는 경우도 생길 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lUoBH/btssfZNXiXp/5MxixYavwP2KxnhfHkbKu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lUoBH/btssfZNXiXp/5MxixYavwP2KxnhfHkbKu1/img.png&quot; data-alt=&quot;그렇기 때문에 디스크 사용량 모니터링은 꼭 해야함..!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lUoBH/btssfZNXiXp/5MxixYavwP2KxnhfHkbKu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlUoBH%2FbtssfZNXiXp%2F5MxixYavwP2KxnhfHkbKu1%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;795&quot; height=&quot;417&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그렇기 때문에 디스크 사용량 모니터링은 꼭 해야함..!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 디렉터리 별 사용량 측정&lt;/h4&gt;
&lt;pre id=&quot;code_1693184644867&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;du -sh ./*&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;root(/) 디렉터리 하위 디렉토리 중 어떤 마운트 포인트가 디스크를 과하게 사용하고 있는지 확인하는 방법&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u8EWv/btssgHsxhnB/IffsDkcoBPlJllyIqYPw5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u8EWv/btssgHsxhnB/IffsDkcoBPlJllyIqYPw5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u8EWv/btssgHsxhnB/IffsDkcoBPlJllyIqYPw5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu8EWv%2FbtssgHsxhnB%2FIffsDkcoBPlJllyIqYPw5K%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;829&quot; height=&quot;472&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗️ 만약, 아래 사진과 같이파일을 지웠는데 용량이 안늘어난다면 파일 핸들을 확인해 보아야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일을 삭제해도 df 로 확인하면 파일 용량을 그대로 잡고있음을 확인할 수 있음&lt;/li&gt;
&lt;li&gt;막상 해당 디렉토리를 확인하면 파일은 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edFlCr/btssimuATh1/310KUZnmCCzvBkFJbpTxxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edFlCr/btssimuATh1/310KUZnmCCzvBkFJbpTxxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edFlCr/btssimuATh1/310KUZnmCCzvBkFJbpTxxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedFlCr%2FbtssimuATh1%2F310KUZnmCCzvBkFJbpTxxK%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;1000&quot; height=&quot;542&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;542&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 파일 핸들&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 핸들(Handle)이란, A 라는 프로세스가 어떤 파일을 읽을 때, 이 파일을 누군가가 참조하고 있음을 기록하는 것입니다.&lt;/li&gt;
&lt;li&gt;핸들은 운영체제 내부에 있는 어떤 리소스의 주소를 정수로 치환한 값입니다.&lt;/li&gt;
&lt;li id=&quot;SE-b3fe449c-99bf-4a79-81be-54da3644cdec&quot;&gt;그리고 리소스의 주소와 이 핸들 값을 한 쌍으로 묶어서 관리하는데 이것을 '핸들 테이블'이라고 합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;  결론적으로 이 파일 핸들이 지워지지 않으면, 파일도 용량이 확보되지 않습니다.&lt;/span&gt;&lt;/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;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;lsof : 파일 핸들을 확인하는 명령어&lt;/li&gt;
&lt;li&gt;lsof 를 이용해 grep 으로 삭제할 파일을 찾은 후 어떤 프로세스가 삭제할 파일을 참조하고 있느지 (열고 있는지) 확인할 수 있음&lt;/li&gt;
&lt;li&gt;&amp;rarr; 이렇게 프로세스를 종료하면 파일 핸들이 반환되고 실제로 파일이 지워집니다.&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1013&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lhVNu/btssc6NAtbO/k2RVOuxrTmZQapmrQ2OFB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lhVNu/btssc6NAtbO/k2RVOuxrTmZQapmrQ2OFB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lhVNu/btssc6NAtbO/k2RVOuxrTmZQapmrQ2OFB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlhVNu%2Fbtssc6NAtbO%2Fk2RVOuxrTmZQapmrQ2OFB0%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;1013&quot; height=&quot;452&quot; data-origin-width=&quot;1013&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: left;&quot;&gt;✅ &lt;/span&gt;inode 사용률 확인하기&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;df 명령어는 디스크 여유 공간 및 inode 공간을 확인할 수 있습니다. 이제 inode 에 대해서 알아보고자 합니다&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ inode란&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;pre id=&quot;code_1693193375430&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df -i&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;df -h 는 용량을, df -i는 개수를 표현&lt;/li&gt;
&lt;li&gt;아래 그림에서 IUsed가 각 파티션에서 사용하고 있는 파일과 디렉터리의 존재 개수를 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsiZzZ/btsshoe9z0l/pkOs2VIqZbTlZwUbQDB5XK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsiZzZ/btsshoe9z0l/pkOs2VIqZbTlZwUbQDB5XK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsiZzZ/btsshoe9z0l/pkOs2VIqZbTlZwUbQDB5XK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsiZzZ%2Fbtsshoe9z0l%2FpkOs2VIqZbTlZwUbQDB5XK%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;645&quot; height=&quot;347&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;✔️df 명령어를 이용해서 디스크 여유 공간 및 inode 공간을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;✔️&amp;nbsp;간혹 파일 핸들이 남아서 파일을 지웠지만, 용량이 확보되지 않는경우가 있음 그때는 lsof 명령어로 해당 파일핸들(어떤 프로세스에서 파일을 참조하고있는지) 를 확인하자&lt;/li&gt;
&lt;li&gt;✔️&amp;nbsp;inode는 파일과 디렉터리의 개수로 생각하면 된다.&lt;/li&gt;
&lt;li&gt;✔️inode 에도 최대값이 있으며 그 이상 파일을 만들수 없다는 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>운영체제/Linux</category>
      <category>linux</category>
      <category>linux disk monitoring</category>
      <category>디스크 모니터링</category>
      <category>디스크 사용량 확인</category>
      <category>디스크 용량 확인</category>
      <category>리눅스</category>
      <category>리눅스 디렉토리 용량</category>
      <category>리눅스 디스크</category>
      <category>리눅스 사용률 모니터링</category>
      <category>용량 모니터링</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/461</guid>
      <comments>https://thalals.tistory.com/461#entry461comment</comments>
      <pubDate>Mon, 28 Aug 2023 20:33:58 +0900</pubDate>
    </item>
    <item>
      <title>[YOUTHCON'23]  유스콘 2023 오프라인 참가 후기</title>
      <link>https://thalals.tistory.com/460</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;좋은 기회로 유스콘 오프라인 행사에 다녀올 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;많은 인사이트와 동기부여를 얻을 수 있었던 좋은경험이었기에, 짧게남아 글로 남겨보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;border-bottom: 3px solid #707070; font-weight: bold; padding: 5px;&quot; data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유스콘을 처음 알게된것은, 여러군데 들어가있던 개발 오픈단톡방 중 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;rarr; 이후, 지인의 유스콘 오프라인 신청 권유, ATDD 슬랙방의 홍보, 다른 단톡방에서의 언급 등등으로 생각보다 큰 행사임을 깨달았고, 오프란인 참가 자격을 얻어 다녀왔습니다. (300명이 지원했고 140여명이 오프라인 참가자격을 얻었다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFnKB/btssijxVBj1/AHdaGjnDKkpsk1r6VYga1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFnKB/btssijxVBj1/AHdaGjnDKkpsk1r6VYga1k/img.png&quot; data-alt=&quot;유스콘 23 소개페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFnKB/btssijxVBj1/AHdaGjnDKkpsk1r6VYga1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFnKB%2FbtssijxVBj1%2FAHdaGjnDKkpsk1r6VYga1k%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;1682&quot; height=&quot;198&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;유스콘 23 소개페이지&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;&lt;b&gt;&amp;nbsp;&quot;주니어 개발자가 발표하는 컨퍼런스&quot;&amp;nbsp;&lt;/b&gt;라는 키워드에서, 나와 비슷한 연차의 사람들이 이런 무대에서 발표도 할수 있구나,, 라는 생각에 굉장히 흥미로웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;705&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj62y3/btssfVEM5Qt/ORxNk7Kona7G22K8OkljeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj62y3/btssfVEM5Qt/ORxNk7Kona7G22K8OkljeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj62y3/btssfVEM5Qt/ORxNk7Kona7G22K8OkljeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj62y3%2FbtssfVEM5Qt%2FORxNk7Kona7G22K8OkljeK%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;1337&quot; height=&quot;705&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;705&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 로 나뉘어, 동시간대에 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;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;그렇게 개발자가 된다. - 박경태님&lt;/li&gt;
&lt;li&gt;ELK 스택을 활용해 통계성 데이터를 제공하는&amp;nbsp; API 구현하기 - 유기성님&lt;/li&gt;
&lt;li&gt;함께해요 멀티 모듈 - 정명구님&lt;/li&gt;
&lt;li&gt;너 납치된거야 (feat. DevOps) - 김보경님&lt;/li&gt;
&lt;li&gt;복잡함은 끝, 간결함의 시작 : 버티컬 슬라이스 아키텍쳐 - 이중석님&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 1시간 가량의 시니어 분들과의 수다 타임 (with. 부릉, LG 유플러스, 모두의 프린터(피로곰), 당근마켓, 토비님)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;오프라인 장소는 우아한 형제들 테크살롱에서 진행되었습니다 (좋더라고요 ㅎㄷㅎㄷ)&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pzaZO/btssfT74Vve/UM7SAPFMK3eeACq8ipdXx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pzaZO/btssfT74Vve/UM7SAPFMK3eeACq8ipdXx1/img.png&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;361&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;63.13&quot; style=&quot;width: 62.3934%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pzaZO/btssfT74Vve/UM7SAPFMK3eeACq8ipdXx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpzaZO%2FbtssfT74Vve%2FUM7SAPFMK3eeACq8ipdXx1%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;464&quot; height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rshd9/btssk0LAZkZ/NTNtA9AoOIbP6vXZ9vN1j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rshd9/btssk0LAZkZ/NTNtA9AoOIbP6vXZ9vN1j0/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;666&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 36.4438%;&quot; data-widthpercent=&quot;36.87&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rshd9/btssk0LAZkZ/NTNtA9AoOIbP6vXZ9vN1j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frshd9%2Fbtssk0LAZkZ%2FNTNtA9AoOIbP6vXZ9vN1j0%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;500&quot; height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;키햐&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;성장해서 서비스를 만들려고 했다가, 서비스를 만들면서 성장한 이야기 - 이현호님&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 트랙이기도 하고, 발표를 굉장히 잘하셔서 기억에 남았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이현호님은 개발자리라는 유튜브 채널을 운영하고 계시고 10여개의 ios 어플을 배포한 경험이 있다고 하셨습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;발표 내용&lt;/b&gt;&lt;/p&gt;
&lt;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;컴공 전공자이시고, 당장 사용하지 않는 많은 CS와 지식들을 공부하던 학습방법에 지쳤던 경험들을 이야기하실 때 저도 컴공으로써 공감이 많이 되었습니다.&lt;/li&gt;
&lt;li&gt;언제가는 쓰이게되지 않을까? 라는 관점의 지식의 사냥(무작위 학습)은 결국 필요할때 필요한 지식이 되어주지 못한다.&lt;/li&gt;
&lt;li&gt;개발을 위한 공부가아닌 &amp;rarr; 공부를 위한 개발이 되어야한다. (공부를 하고 개발 X &amp;rarr;&amp;nbsp; 개발을 해보고 모르는걸 공부 O)&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;b&gt;학습 곡선&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;문제를 먼저 정의하고&lt;/li&gt;
&lt;li&gt;문제해결을 위한 지식, 기술을 습득하고&lt;/li&gt;
&lt;li&gt;이후 배운것을 정리하자&lt;/li&gt;
&lt;li&gt;이렇게 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;나의 책갈피&lt;/span&gt;를 만들어가면 이게 추후에 어떠한 것이든 결과물로 돌아와 준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q&amp;amp;A&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 개발을 적용한다고 해서 좋은 서비스가 나오는가? 에 대해서 한번 더 생각해 보자&lt;/li&gt;
&lt;li&gt;어떤 지식을 습득했을 때, 성장이 고민되어진다면 먼저 지저분하게 만들고 문제를 발견해서 개선하는 경험을 해보는게 어떤가?&lt;/li&gt;
&lt;li&gt;(개인 질문 - 현재 내 상황에서 고민중인 부분을 따로 찾아가서 질문드림) &lt;br /&gt;서비스의 데드라인은 항상 잡고 기획을 짜시는지, 데드라인을 정했지만 계속 미루어질 때 어떻게 하셨는지?&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데드라인은 무조건적으로 지킬려고한다.&lt;/li&gt;
&lt;li&gt;지킬 수 있는 데드라인을 정해야하고, 그걸 잘 못하기 때문에 주니어인 것&lt;/li&gt;
&lt;li&gt;데드라인이 정해졌다면, 완벽한 서비스를 만들려고 하지말고 배포할 수 있는 만큼의 작은 서비스를 완성하고 개선해보시는게 어떠신가요~ 라는 답변을 받았습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;그렇게 개발자가 된다. - 박경태님&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;제일 좋았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 발표도 경태님의 성장 곡선에 대한 발표 느낌을 받았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;발표 내용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;li&gt;개인적으로 이렇게 유스콘에 발표를 하는 것도, 혹은 들으러 오는 것도 문화에 기여한 것이라고 생각함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;경태님은 3.5개월 국비교육을 들은 후, SI 회사로 들어가 혼자 모든 일을 하셨다고 함
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 지금 6을 알고있다면, 3,4 혹은 5를 알고있는 사람에게는 6,7을 알려줄 수 있다.&lt;/li&gt;
&lt;/ul&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 data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 아버지는, 아이가 세상에 나왔을 때부터 아버지가 되는게 아니라&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;아이를 키우면서 아버지가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q&amp;amp;A&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문화를 만들고 전파할 때, 팀원을 설득할때는 관심을 먼저 끌어보자 - 후킹 포인트를 먼저 던지고, 준비한 것을 준다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;경태님은, 인수테스트 기획자분들이랑 같이하면 진짜 좋데요~ 관심있으세요? 사실 제가 자료 다준비했어요~ 식으로 설득하신다고 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;설득은 무조건 공식문서에서 한다. 그것이 아니라면 권위에서 나오는 설득이 될 뿐이다. &lt;br /&gt;(유명한 사람이 한 말, 블로그 글)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;함께해요 멀티 모듈 - 정명구님&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸즈온 세션이지만, 노트북을 가져가지 않아 열심히 듣고 기록만 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티모듈에대한 키워드만 알다가, 어떤것인지 개념과 적용 방법에 대해 정말 기초부터 알 수 있어서 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티모듈이란 이런것이 이렇게 적용한다는 것을 알았으니 추후에 직접 해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 레포지토리에서 참고 가능하고 브랜치멸로 step1,2,3 순서대로 진행되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/dding94/youthcon23&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/dding94/youthcon23&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693153065430&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - dding94/youthcon23: 함께해요 멀티모듈&quot; data-og-description=&quot;함께해요 멀티모듈. Contribute to dding94/youthcon23 development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/dding94/youthcon23&quot; data-og-url=&quot;https://github.com/dding94/youthcon23&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eia5Lg/hyTMeh94iZ/uG4P2mjYe3Xa2z85wcKg4K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/dding94/youthcon23&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/dding94/youthcon23&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eia5Lg/hyTMeh94iZ/uG4P2mjYe3Xa2z85wcKg4K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - dding94/youthcon23: 함께해요 멀티모듈&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;함께해요 멀티모듈. Contribute to dding94/youthcon23 development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;멀티 모듈에 대한 이해
&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;li&gt;모듈화를 통해서 책임과 기능을 분리할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q&amp;amp;A&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티모듈을 설계할 때는 항상 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;추이 종속성&lt;/span&gt;을 생각해야한다 &amp;rarr; 멀티모듈은 오히려 관리포인트의 증가로 이어질 수 있다.&lt;/li&gt;
&lt;li&gt;섣부른 멀티모듈은 오버 엔지니어링이 될 가능성이 크다.&lt;/li&gt;
&lt;li&gt;멀티모듈을 적용하는 시점은, 멀티 모듈을 적용했을 때 관리하는 포인트가 더 적어진다고 느껴지는 시점
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;EX - API 서버와 동떨어진 배치 프로세스작업을 할 때, 어떤 서드파티 작업을 진행할 때&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;복잡함은 끝, 간결함의 시작 : 버티컬 슬라이스 아키텍쳐 - 이중석님&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 신선했던 발표  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중석님은 백명석님이 CTO로 계시는 케이타운포유의 근무하십니다.&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;&amp;nbsp;&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;버티컬 슬라이스 아키텍쳐란
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;하나의 클래스에 API 수신, 송신 비지니스 로직을 담음 (Controller 하나에 비지니스 로직을 때려 넣음)&lt;/li&gt;
&lt;li&gt;Controller 와 서비스가 합쳐진 레이어드 아키텍쳐라는 느낌을 받았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;li&gt;&amp;rarr; 기획의 수정으로 이어짐&lt;/li&gt;
&lt;li&gt;헥사고날 아키텍쳐는 너무 많은 클래스를 수정해야하므로 빠른 피쳐를 요구하는 신규 서비스에는 적합하지 않다고 생각함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;수시로 변하는 요구사항과 기획에 빠른 대처가 가능해짐&lt;/li&gt;
&lt;li&gt;단 버티컬 슬라이스 아키텍쳐는, 자칫 잘못하다가 스파게티 코드가 될 수 있음.&lt;/li&gt;
&lt;li&gt;버티컬 아키텍쳐를 가지지만, 여기도 어느정도 룰과 규칙을 정해야함을 발표를 들으면서 느낌&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0LPfZ/btssqA6Sv4B/hWuUvHHS6PRFOBKUzesdYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0LPfZ/btssqA6Sv4B/hWuUvHHS6PRFOBKUzesdYK/img.png&quot; data-alt=&quot;버티컬 슬라이스 아키텍쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0LPfZ/btssqA6Sv4B/hWuUvHHS6PRFOBKUzesdYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0LPfZ%2FbtssqA6Sv4B%2FhWuUvHHS6PRFOBKUzesdYK%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;252&quot; height=&quot;200&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;버티컬 슬라이스 아키텍쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q&amp;amp;A&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;완전히 새로 접한 내용이라, 발표때 다른분들이 했던 질문은 기억이 나지 않습니다...&lt;br /&gt;한참 생각하고 고민하다가 따로 찾아가 드렸던 개인 질문들입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;질문) 코드의 재사용성을 고려했을 때, 버티컬 슬라이스 아키텍쳐를 적용하는 것 보다 단순한 레이어드 아키텍쳐를 적용하면 더 좋지 않을까요?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답) 비지니스 로직을 재사용한다는 상황에서 설계에 대해 다시 고민해볼 수 있는 지점이라고 생각한다.&lt;br /&gt;비지니스 로직을 도메인이 가져간다면, 비지니스 로직이 중복될 일이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;질문) 버티컬 슬라이스 아키텍쳐를 적용한다면, 결국 멀티모듈과 같이 패키지가 단일 도메인 위주로 분리될 거 같은데&lt;br /&gt;2개 이상의 도메인을 참조하는 비지니스 로직을 가지는 새로운 도메인은 패키지의 설계를 어디로 두어야하나요?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답) 이것도 고민했던 부분인데, 버티컬 슬라이스 아키텍쳐가 적용되는 부분 자체가 굉장히 단순한 비지니스 로직을 가져가는 부분들에만 적용을 했다.&lt;/li&gt;
&lt;li&gt;답) 패키지 의존성을 단방향으로 설정하면 좋지만, 트레이드 오프를 생각했을 때 어느정도 허용하고 개발 편의성을 올리는 방향으로 설계했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;추후, 서비스가 커졌을 때 버티컬 슬라이스 아키텍쳐로 설계했던 부분을 다시 헥사고날 아키텍쳐로 리펙토링 할 것인가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답) 아니다. 추후 리펙토링을 하더라도 헥사고날로는 돌아가지 않을 것 같다.&lt;/li&gt;
&lt;li&gt;차라리 레이어드 아키텍쳐를 적용할 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;버티컬 슬라이스 아키텍쳐를 사용할려면 팀원 모두가 클린코드에 대한 이해도가 상당해야하고, 코드 리뷰문화가 필수로 있지 않으면 적용하기 힘들 것 같다는 생각이 들었습니다.&lt;/li&gt;
&lt;li&gt;또한,&amp;nbsp; Rich Domain 위주의 DDD 구조도 이해하고 있어야한다고도 생각이 들었습니다&lt;/li&gt;
&lt;li&gt;도메인의 순수성 vs 완전성에 대한 호불호가 있는 만큼 올바른 적용 상황을 잘 판단해야겠다는 생각도 들었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;수다 타임&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 세미나가 끝나고, 이렇게 시니어 개발자 5분과의 질의 응답 시간이 있었습니다.&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;심근우님 (LG 유플러스,컨테이너 인프라 환경 구축을 위한 쿠버네티스/도커의 저자)&lt;/li&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 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;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 1시간 가량의 대화에서 개인적으로 5분에게 느껴졌던 것이 있었습니다.&lt;/p&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;성장을 위한 환경을 찾기보다, 정말 &lt;b&gt;지금 환경&lt;/b&gt;에서 성장할 수는 없는가에 대해 생각해보자&lt;/li&gt;
&lt;li&gt;개발자는 서비스를 만드는 사람, 좋은 개발보다는 좋은 서비스가 우선이 아닐까?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 퇴사하신 전 팀장님도 가끔 이것과 비슷한 뉘앙스의 조언을 해주신적이 있었는데 &lt;br /&gt;다시한번 &quot;&lt;u&gt;나는 어떤 개발자가 되어야 하는가&lt;/u&gt;&quot; 에 대해 생각하게되는 시간이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; background: url('../image/divider-line.svg') center -304px / 200px 420px no-repeat; width: 200px; height: 19px; padding: 18px 20px 17px;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;font-weight: bold; border-bottom: 1px solid #d83c3c; margin: 10px 0px 5px; border-left: 5px solid #d83c3c; letter-spacing: -0.07em; line-height: 30px; padding: 0px 10px 1px;&quot; data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 유스콘을 시작할 때. 오픈 연설(?)을 짧게 해주셨는데 그말이 아직까지도 참 좋은 울림으로 남아있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;다른 컨퍼런스는 듣는이를 위한 무대이지만, 유스콘은 발표자를 위한 무대입니다. 여러분들은 들러리이고 박수 많이 쳐주시면 감사하겠습니다!&quot;&lt;/span&gt;&lt;/blockquote&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;br /&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;너무 신선한 문장이었고, &quot;이런 분들의 도움아래에 컨퍼런스 발표 경험을 할 수 있다면 나도 도전해 볼까?&quot; 라는 용기도 조금 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업계에 선한 영향력을 행사하기위한 노력과 생기있는 기운이 느껴졌다.&lt;br /&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아무튼 많은 동기부여와 좋은 인사이트를 얻을 수 있어서 보람찬 시간이었다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;내년에도 발표를 하든 참가를 하든 하자!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OELlu/btssqnGu1xq/omMRg0jyj5Skh6DVZ6KMCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OELlu/btssqnGu1xq/omMRg0jyj5Skh6DVZ6KMCk/img.png&quot; data-alt=&quot;피지컬이 좋으셨던 박재성님과 연예인 느낌나는 토비님과도 한컷&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OELlu/btssqnGu1xq/omMRg0jyj5Skh6DVZ6KMCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOELlu%2FbtssqnGu1xq%2FomMRg0jyj5Skh6DVZ6KMCk%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;500&quot; height=&quot;333&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;피지컬이 좋으셨던 박재성님과 연예인 느낌나는 토비님과도 한컷&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고/일상 후기 회고</category>
      <category>youthcon</category>
      <category>youthcon23</category>
      <category>개발자 컨퍼런스</category>
      <category>유스콘</category>
      <category>유스콘 2023</category>
      <category>유스콘 회고</category>
      <category>유스콘23</category>
      <category>주니어</category>
      <category>주니어 세미나</category>
      <category>주니어 컨퍼런스</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/460</guid>
      <comments>https://thalals.tistory.com/460#entry460comment</comments>
      <pubDate>Mon, 28 Aug 2023 01:51:34 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] free - 메모리 사용량 확인하기</title>
      <link>https://thalals.tistory.com/458</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;해당 포스팅은 인프런 &quot;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot;&gt;리눅스 성능 분석 시작하기&lt;/a&gt;&quot; 를 수강하고 정리한 글입니다 :)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;리눅스 기반 os 에서 돌아가는 서버 시스템의 성능 측정 및 장애 대응에 대한 학습 내용 정리 글 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  리눅스 성능 분석의 기본 명령어&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%; height: 154px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/456&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;uptime&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템 가동 시간, Load Average 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/457&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커널 메세지 확인 (OOME 발생 여부, SYN Flooding 여부)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/458&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;free&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 사용 현황 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/461&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;df&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디스크 여유 공간 및 inode 공간 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/405&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;top&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로세스들의 상태, CPU 사용률, 메모리 사용률 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/462&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;netstat&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 연결 정보 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/463&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tcpdump&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 트러블 슈팅 분석을 위한 패킷 수집 명령어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: left;&quot;&gt;✅ free&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;현재 사용중인 메모리 사용양과, 사용가능한 메모리양 등 전반적인 사용 현황을 확인할 수 있는 명령어 입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;-m 옵션을 사용하여 megabite 단위로 조회할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-23 오후 5.54.55.jpg&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO49G6/btsrS4is9Yg/OGnvv1wW7dnR8rlgT6qo8K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO49G6/btsrS4is9Yg/OGnvv1wW7dnR8rlgT6qo8K/img.jpg&quot; data-alt=&quot;free&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO49G6/btsrS4is9Yg/OGnvv1wW7dnR8rlgT6qo8K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO49G6%2FbtsrS4is9Yg%2FOGnvv1wW7dnR8rlgT6qo8K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;137&quot; data-filename=&quot;스크린샷 2023-08-23 오후 5.54.55.jpg&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;free&lt;/figcaption&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;✔️ free 와 available 의 차이점&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;free&lt;/b&gt; : 어느 누구도 사용하고 있지 않은 메모리 (완전하게 자유인 메모리)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;available&lt;/b&gt; : 애플리케이션에 실질적으로 할당 가능한 메모리 (누군가가 사용중인 메모리 포함)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 왜 이런 차이가 발생하까? (free, available)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  buff/cache 영역 때문에&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;buff : 블록 디바이스가 가지고 있는 블록 자체에 대한 캐시&lt;/li&gt;
&lt;li&gt;cache : I/O 성능 향상을 위해 사용하는 &lt;b&gt;페이지 캐시 (페이지 캐시가 중요함)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 페이지 캐시란&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 A 가 open() System Call 을 사용하여 &amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;블록디바이스(EBS) &lt;/span&gt;에서 test.txt 라는 파일을 불러오는 상황이 있다고 한다면&lt;/li&gt;
&lt;li&gt;아래의 사진과 같은 흐름이 될 것 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk8WdV/btsrVtPuWwQ/oOkDoYaieCEUhLPK0tNODk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk8WdV/btsrVtPuWwQ/oOkDoYaieCEUhLPK0tNODk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk8WdV/btsrVtPuWwQ/oOkDoYaieCEUhLPK0tNODk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk8WdV%2FbtsrVtPuWwQ%2FoOkDoYaieCEUhLPK0tNODk%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;500&quot; height=&quot;189&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;but&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 실제로는 블록 디바이스에서 바로 불러오는게 아닌, &lt;span style=&quot;color: #ee2323;&quot;&gt;커널이 가운데에서 페이지 캐시를 이용&lt;/span&gt;해서 파일을 돌려줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지 캐시에서 test.txt 를 저장해 두었다가 애플리케이션이 페이지 캐시에서 조회함&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;페이지 캐시는 (&lt;b&gt;buff/cache&lt;/b&gt; 영역의 &lt;b&gt;cache&lt;/b&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4eyp5/btsr4Wo3grt/jGV1IQKXYdC4WmlBdeX0FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4eyp5/btsr4Wo3grt/jGV1IQKXYdC4WmlBdeX0FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4eyp5/btsr4Wo3grt/jGV1IQKXYdC4WmlBdeX0FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4eyp5%2Fbtsr4Wo3grt%2FjGV1IQKXYdC4WmlBdeX0FK%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;500&quot; height=&quot;172&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 페이지 캐시 존재 이유&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;블록 디바이스는 디스크 메모리이기 때문에, 지속적으로 읽기 현상이 일어나면 속도 지연이 생깁니다. &amp;rarr; 메모리가 무조건 빠름&lt;/li&gt;
&lt;li&gt;그래서 첫 조회때 페이지 캐시에 저장해두었다가 2번째부터는 페이지 캐시에서 바로 읽어들여&amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;I/O 성능을 향상(⬆️)&lt;/span&gt;시킬 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(블록 디바이스가 아닌 메모리에서 파일의 내용을 가져오기 때문에)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대표적인 애플리케이션이 = Elastic Search&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;페이지 캐시가 많다는 건 &amp;rarr; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;I/O 가 많이 일어나는 서버라는 것&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ 그래서 available 이란?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;buff/cache 는 I/O 성능 향상을 위해 존재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;애플리케이션에서 메모리를 필요한다면 OOM 이 일어나는데&amp;nbsp;&lt;/li&gt;
&lt;li&gt;OOM이 일어나기 전에 buff/cache 영역이 남아있으면, 이 영역을 해제하고&amp;nbsp;&lt;/li&gt;
&lt;li&gt;애플리케이션이 사용할 수 있는 영역으로 바꿔 버립니다. &lt;i&gt;(프로세스를 종료해서 메모리를 확보하는 것 보다는 나으니까)&lt;/i&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;❗️ available = free + buff/cache&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;근데 buff/cache 가 높다면 I/O가 많이 일어나는 환경이기 때문에 &amp;rarr; 이럴때는 더 많은 메모리를 주는게 합리적이라고 합니다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ swap 이란&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다시 free 명령어를 보면 Mem 아래 Swap 영역이 표시됩니다.&lt;/li&gt;
&lt;li&gt;Swap 영역이란, &lt;u&gt;메모리가 부족한 상황에서 사용되는 가상 메모리 공간&lt;/u&gt;으로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;주로 블록 디바이스(EBS)의 일부 영역을 사용&lt;/span&gt; 합니다. &lt;br /&gt;(메모리가 아님)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-23 오후 5.54.55.jpg&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO49G6/btsrS4is9Yg/OGnvv1wW7dnR8rlgT6qo8K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO49G6/btsrS4is9Yg/OGnvv1wW7dnR8rlgT6qo8K/img.jpg&quot; data-alt=&quot;free&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO49G6/btsrS4is9Yg/OGnvv1wW7dnR8rlgT6qo8K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO49G6%2FbtsrS4is9Yg%2FOGnvv1wW7dnR8rlgT6qo8K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;137&quot; data-filename=&quot;스크린샷 2023-08-23 오후 5.54.55.jpg&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;free&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;&lt;b&gt;OS Swap 동작과정 (Swap In/Out)&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메모리가 가득찬 상태에서 프로세스가 메모리를 요구하면, 커널에서 특정 메모리를 Swap space 에 적재합니다. (Swap In)&lt;/li&gt;
&lt;li&gt;여유공간이 생긴 메모리 공간을 프로세스에게 할당해 줍니다.&lt;/li&gt;
&lt;li&gt;다시, Swap In 된 메모리 영역을 사용중이었던 또 다른 프로세스가 작업 요청을 보내면 Swap space 에서 해당 공간을 꺼내서 다른 메모리영역을 Swap In 시키고, 해당 공간에 메모리를 적재합니다. (Swap out)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  특징&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;swap in 이 되어도 swap 영역에서 지워지진 않습니다. &amp;rarr; 나중에 또 swap in/out 이 될 수 있기 때문에 &amp;rarr; 이를 &lt;span style=&quot;color: #ee2323;&quot;&gt;swap cache&lt;/span&gt; 라 함&lt;/li&gt;
&lt;li&gt;swap 영역은 블록 디바이스기 때문에, I/O 가 발생 &amp;rarr; swap 영역이 사용되면 성능 저하 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;최근 트렌드는 swap 영역 비활성화&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;API 서버 1,2 대가 죽었다가 살아나는 것은 크게 크리티컬하지 않다고 생각한다고 합니다/&lt;/li&gt;
&lt;li&gt;단, DB 서버가 죽어버리면 문제가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt; 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;✔️ free 명령어를 통해 메모리 사용 현황 (가용 메모리양) 을 확인할 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;✔️ SWAP은 상황에 맞게 잘 판단해서 사용하는게 좋습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;✔️ 메모리 공간에 여유가 없다면, 어디선가 메모리 누수가 일어나고 있는것은 아닌지 확인해 보는게 좋습니다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Swap Space 할당 방법 &amp;rarr;&lt;a href=&quot;https://thalals.tistory.com/431&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://thalals.tistory.com/431&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692782823706&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Jenkins 설치 및 사용 가이드] ec2 가 계속 죽음 &amp;rarr; 빌드 자동화 개선 (with 프리티어에서 젠킨스 사&quot; data-og-description=&quot;이번 게시물에서는 Jenkins 사용방법에 대해서 기록해보고자 합니다. 초기 설정부터 하는건 처음이라 두근두근 하네요 ===== Jenkins 간단 CI/CD 구축해보기 Series. =====   [Jenkins 설치 및 사용 가이&quot; data-og-host=&quot;thalals.tistory.com&quot; data-og-source-url=&quot;https://thalals.tistory.com/431&quot; data-og-url=&quot;https://thalals.tistory.com/431&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vQFz6/hyTIMNaiqG/TIqFAcdJTMdTy8gR6RMZGk/img.jpg?width=775&amp;amp;height=507&amp;amp;face=0_0_775_507,https://scrap.kakaocdn.net/dn/dkdudy/hyTIQINu41/LGPAnlrX3dbktueL7v87Ck/img.jpg?width=775&amp;amp;height=507&amp;amp;face=0_0_775_507,https://scrap.kakaocdn.net/dn/bAGBeA/hyTIDCGQIO/6sK4zkRFx6N9x8BPkVqQQk/img.png?width=600&amp;amp;height=562&amp;amp;face=0_0_600_562&quot;&gt;&lt;a href=&quot;https://thalals.tistory.com/431&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://thalals.tistory.com/431&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vQFz6/hyTIMNaiqG/TIqFAcdJTMdTy8gR6RMZGk/img.jpg?width=775&amp;amp;height=507&amp;amp;face=0_0_775_507,https://scrap.kakaocdn.net/dn/dkdudy/hyTIQINu41/LGPAnlrX3dbktueL7v87Ck/img.jpg?width=775&amp;amp;height=507&amp;amp;face=0_0_775_507,https://scrap.kakaocdn.net/dn/bAGBeA/hyTIDCGQIO/6sK4zkRFx6N9x8BPkVqQQk/img.png?width=600&amp;amp;height=562&amp;amp;face=0_0_600_562');&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;[Jenkins 설치 및 사용 가이드] ec2 가 계속 죽음 &amp;rarr; 빌드 자동화 개선 (with 프리티어에서 젠킨스 사&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 게시물에서는 Jenkins 사용방법에 대해서 기록해보고자 합니다. 초기 설정부터 하는건 처음이라 두근두근 하네요 ===== Jenkins 간단 CI/CD 구축해보기 Series. =====   [Jenkins 설치 및 사용 가이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;thalals.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>운영체제/Linux</category>
      <category>Free</category>
      <category>linux</category>
      <category>가상 메모리</category>
      <category>리눅스</category>
      <category>리눅스 메모리량 확인</category>
      <category>메모리 누수</category>
      <category>메모리 모니터링</category>
      <category>메모리 사용량</category>
      <category>메모리 체크</category>
      <category>현재 메모리양</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/458</guid>
      <comments>https://thalals.tistory.com/458#entry458comment</comments>
      <pubDate>Thu, 24 Aug 2023 19:29:19 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] dmesg - 리눅스 커널 로그 메세지 확인하기 (OOME, SYN Flooding)</title>
      <link>https://thalals.tistory.com/457</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;해당 포스팅은 인프런 &quot;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot;&gt;리눅스 성능 분석 시작하기&lt;/a&gt;&quot; 를 수강하고 정리한 글입니다 :)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;리눅스 기반 os 에서 돌아가는 서버 시스템의 성능 측정 및 장애 대응에 대한 학습 내용 정리 글 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  리눅스 성능 분석의 기본 명령어&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%; height: 154px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/456&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;uptime&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템 가동 시간, Load Average 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/457&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커널 메세지 확인 (OOME 발생 여부, SYN Flooding 여부)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/458&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;free&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 사용 현황 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/461&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;df&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디스크 여유 공간 및 inode 공간 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/405&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;top&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로세스들의 상태, CPU 사용률, 메모리 사용률 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/462&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;netstat&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 연결 정보 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/463&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tcpdump&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 트러블 슈팅 분석을 위한 패킷 수집 명령어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: left;&quot;&gt;✅ dmesg&lt;/span&gt; 명령어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dmesg는&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;커널에서 발생하는 다양한 메시지들을 출력&lt;/span&gt;하는 명령어 입니다.&lt;/li&gt;
&lt;li&gt;리눅스 커널이란 (Kernel), 리눅스 운영체제(OS)의 주요 구성 요소이자 컴퓨터 하드웨어와 프로세스를 잇는 핵심 인터페이스로 시&lt;u&gt;스템의 모든 것을 완전히 제어하는 프로그램&lt;/u&gt;입니다.&lt;/li&gt;
&lt;li&gt;커널도 하나의 소프트웨어이기 때문에, 커널의 요러 요소들에서 나오는 메세지들을 dmesg 명령어로 꺼내서 볼수 있습니다.&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;b&gt;dmesg -T (T옵션은 타임스탬프를 보기좋게 바꿔주는 옵션)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgGSUX/btsrH61Lyuv/GuCu06GG3dXnYIvu5AQpn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgGSUX/btsrH61Lyuv/GuCu06GG3dXnYIvu5AQpn0/img.png&quot; data-alt=&quot;dmesg -T&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgGSUX/btsrH61Lyuv/GuCu06GG3dXnYIvu5AQpn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgGSUX%2FbtsrH61Lyuv%2FGuCu06GG3dXnYIvu5AQpn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;271&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;271&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;dmesg -T&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❓ dmesg 출력문 중 무엇을 봐야할까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 성능분석과, 트러블 슈팅의 관점에서 봐야할 2가지&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OOME (Out-Of-Memory Error) 메세지&lt;/li&gt;
&lt;li&gt;SYN Flooding&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;  1. OOME 란&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;OOME 상황이 오면 커널에서는 OOM Killer 를 동작 시킵니다.&lt;/li&gt;
&lt;li&gt;OOM Killer가 동작과정
&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;다른 프로세스를 종료(kill)해 메모리 자원을 커널에게 돌려주어서&lt;/li&gt;
&lt;li&gt;&amp;rarr; 다른 프로세스에게 할당한다.&lt;/li&gt;
&lt;/ol&gt;
&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;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;✔️ &lt;/span&gt;커널의 관점에서 OOME 상황 해결 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OOM 상황 발생&lt;/li&gt;
&lt;li&gt;프로세스 선택(kill 할)&lt;/li&gt;
&lt;li&gt;프로세스 종료&lt;/li&gt;
&lt;li&gt;시스템 안정화 (서비스가 안정되는게 아닌, 메모리를 확보에서 시스템을 안정시킴)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;✔️ &lt;/span&gt;커널은 어떻게 종료 시킬 프로세스를 선택할까❓&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) oom_score 란&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OOM Killer가 종료시킬 프로세스를 선택하는 기준&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 data-ke-size=&quot;size16&quot;&gt;2) oom score 확인 방법&lt;/p&gt;
&lt;pre id=&quot;code_1692753696765&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd /proc/2526
cat oom_score&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;proc : 프로세스들의 메타데이터를 볼 수 있는 폴더&lt;/li&gt;
&lt;li&gt;2526 : 확인할 프로세스 번호&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4kWe3/btsrUsJgqwe/0qSq1xryuy5RRFanhd8qxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4kWe3/btsrUsJgqwe/0qSq1xryuy5RRFanhd8qxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4kWe3/btsrUsJgqwe/0qSq1xryuy5RRFanhd8qxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4kWe3%2FbtsrUsJgqwe%2F0qSq1xryuy5RRFanhd8qxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;195&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 해당 폴더로 들어가 &quot;cat oom_score&quot; 명령어로 확인한 666 점이 oom_score 입니다.&lt;br /&gt;&amp;rarr; grep 2526 으로 들어가 확인한 프로세스들 -bash : 단순 쉘 스크립트 파일 입니다. &lt;br /&gt;&amp;rarr; OOM Killer 가 발생하면 쉘스크립트 종료! 메모리 확보!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;dmesg OOME 확인 방법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;dmesg -T | grep -i oom&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;oom 이 떴던 시간과 커널 메세지를 잡아서 확인할 수 있다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PMb9m/btsr3dRFyiQ/bOhjA8wVWHWSQYtugiXdq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PMb9m/btsr3dRFyiQ/bOhjA8wVWHWSQYtugiXdq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PMb9m/btsr3dRFyiQ/bOhjA8wVWHWSQYtugiXdq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPMb9m%2Fbtsr3dRFyiQ%2FbOhjA8wVWHWSQYtugiXdq0%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;1178&quot; height=&quot;211&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;211&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;   &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; SYN Flooding&lt;/span&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;공격자가 대량의 SYN 패킷만 보내서 소켓을 고갈시키는 공격 &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;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;✔️ &lt;/span&gt;SYN Flooding 이 일어나는 과정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 3-way handshake (연결을 맺기 위한 과정)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;처음 Client 에서 SYN 요청을 보내 - 연결 요청 신호를 보냅니다.&lt;/li&gt;
&lt;li&gt;Server 에서 SYN 요청을 보내면 SYN + ACK 를 보내 연결을 허가하고&lt;/li&gt;
&lt;li&gt;다시 Client 에서 ACK 를 보내 establish 가 됩니다. (연결)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6bTWg/btsrNoA863n/1VIGEZH96dxDNSkkwYOGTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6bTWg/btsrNoA863n/1VIGEZH96dxDNSkkwYOGTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6bTWg/btsrNoA863n/1VIGEZH96dxDNSkkwYOGTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6bTWg%2FbtsrNoA863n%2F1VIGEZH96dxDNSkkwYOGTK%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;500&quot; height=&quot;410&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&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;TCP 3-way handshake 시 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;커널&lt;/b&gt;&lt;/span&gt;에서 일어나는 일&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;listen( ) : system call로 소켓이 열림&lt;/li&gt;
&lt;li&gt;SYN Backlog : SYN 패킷을 받았을 때 SYN Backlog 에 SYN 패킷을 저장 시킴&lt;/li&gt;
&lt;li&gt;Listen Backlog : ACK 를 받았을 떄 SYN Backlog 에 저장 SYN 패킷을 &amp;rarr; Listen Backlog 로 옮김&lt;/li&gt;
&lt;li&gt;accept() : accept() System Call 을 통해 실제 어플리케이션에게 통신의 처리권을 위임 &amp;rarr; HTTP 등등 연결 시작&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&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;악의적인 사용자가 SYN 패킷만을 계속 보낼때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SYN Backlog의 허용 용량이 가득차고 (Listen Backlog 로 가지 않아서)&lt;/li&gt;
&lt;li&gt;신규로 들어오는 요청인 SYN 패킷을 받지못해 연결을 하지 못하는 상황이 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbGaaD/btsrR0fzp8m/ESeXZojGZpLAfH5cptFlhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbGaaD/btsrR0fzp8m/ESeXZojGZpLAfH5cptFlhK/img.png&quot; data-alt=&quot;SYN Flooding&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbGaaD/btsrR0fzp8m/ESeXZojGZpLAfH5cptFlhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbGaaD%2FbtsrR0fzp8m%2FESeXZojGZpLAfH5cptFlhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;390&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SYN Flooding&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;&lt;b&gt;✔️SYN Cookie&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;SYN Flooding 을 대비해 리눅스 커널에는 SYN Cookie 가 존재합니다.&lt;/li&gt;
&lt;li&gt;SYN Cookie 란 SYN 패킷 정보들을 바탕으로 쿠키를 만들어 SYN + ACK 의 시퀀스 번호로 만들어 응답합니다.&lt;/li&gt;
&lt;li&gt;따라서 SYN Backlog에 저장하지않고 Cookie값으로 계산(IP 주소 등의 정보가 담김) 하여 요청 연결을 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2k2zL/btsrNAhdFiB/XlMCTudAJF28AbzkaOiVi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2k2zL/btsrNAhdFiB/XlMCTudAJF28AbzkaOiVi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2k2zL/btsrNAhdFiB/XlMCTudAJF28AbzkaOiVi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2k2zL%2FbtsrNAhdFiB%2FXlMCTudAJF28AbzkaOiVi1%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;500&quot; height=&quot;437&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; SYN Cookie 가 동작하면 SYN Backlog 에 SYN 패킷을 쌓지 않기때문에, 자원 고갈 현상이 발생하지 않습니다.&lt;br /&gt;&amp;rarr; 하지만 &lt;b&gt;TCP Option 헤더를 무시&lt;/b&gt;하기 때문에, &lt;span style=&quot;color: #ee2323;&quot;&gt;Windows Scalling 같은 성능 향샹 옵션이 동작하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;dmesg SYN Flooding 확인 방법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;dmesg -T | grep -i &quot;syn flooding&quot;&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요즘은 SYN Flooding 공격이 잘 발생하지 않지만 그래도 dmesg 를 통해 확인할 수 있다!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt; 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;✔️ dmesg 명령어를 통해 커널 환경에서 &lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;OOME 에러 혹은 SYN Flodding 공격 발생등의&lt;/span&gt;&amp;nbsp;메세지들을 확인할 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;✔️ OOME 에러라면 메모리 누수를 확인하거나 추가 메모리를 확보하여 성능을 향상시킬 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;✔️ SYN Flooding 공격이 발생했다면 방화벽을 확인하여 사전에 차단하거나, SYN Cookie를 사용하여 서비스는 정상적으로 작동되도록 유지할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>운영체제/Linux</category>
      <category>cmd 로그 확인</category>
      <category>linux</category>
      <category>linux system log</category>
      <category>로그확인 명령어</category>
      <category>리눅스 로그</category>
      <category>리눅스 에러 메세지 확인</category>
      <category>리눅스 커널</category>
      <category>커널 로그</category>
      <category>커널 메세지</category>
      <category>커널이란</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/457</guid>
      <comments>https://thalals.tistory.com/457#entry457comment</comments>
      <pubDate>Wed, 23 Aug 2023 11:22:59 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] uptime - 서버가 받고있는 부하 평균 측정 및 대응하기</title>
      <link>https://thalals.tistory.com/456</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;해당 포스팅은 인프런 &quot;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot;&gt;리눅스 성능 분석 시작하기&lt;/a&gt;&quot; 를 수강하고 정리한 글입니다 :)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;리눅스 기반 os 에서 돌아가는 서버 시스템의 성능 측정 및 장애 대응에 대한 학습 내용 정리 글 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  리눅스 성능 분석의 기본 명령어&lt;/h4&gt;
&lt;table style=&quot;color: #666666; text-align: center; border-collapse: collapse; width: 100%; height: 154px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/456&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;uptime&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템 가동 시간, Load Average 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/457&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dmesg&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커널 메세지 확인 (OOME 발생 여부, SYN Flooding 여부)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/458&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;free&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 사용 현황 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/461&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;df&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디스크 여유 공간 및 inode 공간 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/405&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;top&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로세스들의 상태, CPU 사용률, 메모리 사용률 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/462&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;netstat&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left; height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 연결 정보 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/463&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tcpdump&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;네트워크 트러블 슈팅 분석을 위한 패킷 수집 명령어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;업타임은 동작 중이면서 사용 가능한 기계의 시간을 백분율로 나타낸 시스템의 신뢰성의 측정이다 (위키백과)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: left;&quot;&gt;✅ &lt;/span&gt;uptime 명령어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리눅스 uptime 명령어는, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;시스템의 가동 시간, 로그인한 사용자 수, Load Average (1분, 5분, 15분) 를 확인&lt;/span&gt;할 수 있는 명령어 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-22 오후 12.11.09.jpg&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;57&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDVDmW/btsrH4B29pg/Cllf0MGYzXQUctX17Fu6Q0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDVDmW/btsrH4B29pg/Cllf0MGYzXQUctX17Fu6Q0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDVDmW/btsrH4B29pg/Cllf0MGYzXQUctX17Fu6Q0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDVDmW%2FbtsrH4B29pg%2FCllf0MGYzXQUctX17Fu6Q0%2Fimg.jpg&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;412&quot; height=&quot;57&quot; data-filename=&quot;스크린샷 2023-08-22 오후 12.11.09.jpg&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;57&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;load average 란&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버가 받고 있는 부하 평균 값&lt;/span&gt; (CPU Usage 와 비슷와 비슷하지만 서버가 얼마나 일하고 있느지를 표현하는 또다른 방식)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;= 단위 시간 (1분, 5분, 15분) 동안의 R과 D 상태의 프로세스 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BTVnE/btsrTrJIVXn/Vkmbz77SEA27m6FEMLnJOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BTVnE/btsrTrJIVXn/Vkmbz77SEA27m6FEMLnJOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BTVnE/btsrTrJIVXn/Vkmbz77SEA27m6FEMLnJOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBTVnE%2FbtsrTrJIVXn%2FVkmbz77SEA27m6FEMLnJOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;239&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로세스의 개수가 왜 중요할까?&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;❓ 왜 프로세스 개수를 평균을 내서 load average라는 값을 낼까&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;간단하게 정리하여,&lt;span&gt; load average는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;사용중인 프로세스의 개수&lt;/span&gt; 이기 때문에&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;cpu &lt;span style=&quot;color: #333333;&quot;&gt;1대&lt;/span&gt;, 실행중인 프로세스가 &lt;span style=&quot;color: #ee2323;&quot;&gt;1대&lt;/span&gt; 일 때 = load average 는 &lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt; 입니다.&lt;/li&gt;
&lt;li&gt;cpu &lt;span style=&quot;color: #333333;&quot;&gt;1대, 실행&lt;/span&gt;중인 프로세스가 &lt;span style=&quot;color: #ee2323;&quot;&gt;2대&lt;/span&gt; 일 때 = load average 는 &lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt; 입니다.&amp;nbsp; (이 때는 os 스케쥴링 알고리즘에의한 context switching 이 일어납니다.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjhs80/btsrEx51R1y/2SQ4EPbHxct1BGiZ9pvVB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjhs80/btsrEx51R1y/2SQ4EPbHxct1BGiZ9pvVB1/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;291&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 48.8803%; margin-right: 10px;&quot; data-widthpercent=&quot;49.46&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjhs80/btsrEx51R1y/2SQ4EPbHxct1BGiZ9pvVB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjhs80%2FbtsrEx51R1y%2F2SQ4EPbHxct1BGiZ9pvVB1%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;500&quot; height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DfWIZ/btsrUuzUze6/m7j2qD2ugFkM00EnqoaP1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DfWIZ/btsrUuzUze6/m7j2qD2ugFkM00EnqoaP1K/img.png&quot; data-origin-width=&quot;1015&quot; data-origin-height=&quot;578&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.9569%;&quot; data-widthpercent=&quot;50.54&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DfWIZ/btsrUuzUze6/m7j2qD2ugFkM00EnqoaP1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDfWIZ%2FbtsrUuzUze6%2Fm7j2qD2ugFkM00EnqoaP1K%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;1015&quot; height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Load Average는 상대적인 값입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래 사진처럼 똑같이 Load Average가 각각 1, 2 이라고 해도, CPU의 개수에 따라서 의미가 달라집니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cpu 2, 프로세스 &lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt; = load average &lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;cpu 2. 프로세스 &lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt; = load average &lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt;&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;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9uMRI/btsrH4oVKnC/JOHgy9l6tP7OBLLqntCcOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9uMRI/btsrH4oVKnC/JOHgy9l6tP7OBLLqntCcOK/img.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;578&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.8949%; margin-right: 10px;&quot; data-widthpercent=&quot;51.49&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9uMRI/btsrH4oVKnC/JOHgy9l6tP7OBLLqntCcOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9uMRI%2FbtsrH4oVKnC%2FJOHgy9l6tP7OBLLqntCcOK%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;1012&quot; height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ312B/btsrS42D30l/YXRN1CVnaSxJRxKcRaciV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ312B/btsrS42D30l/YXRN1CVnaSxJRxKcRaciV0/img.png&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;633&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.9423%;&quot; data-widthpercent=&quot;48.51&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ312B/btsrS42D30l/YXRN1CVnaSxJRxKcRaciV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ312B%2FbtsrS42D30l%2FYXRN1CVnaSxJRxKcRaciV0%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;1044&quot; height=&quot;633&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❗️ 정리하자면&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;load Average 가 CPU 개수보다 크다.&amp;nbsp; &amp;rarr; 즉, 처리 가능량을 넘어섰다는 것 &amp;rarr; 서버에 부하가 간다는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buMql7/btsrTo02wK3/0Xc362bzdX6bV3K3pU6WkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buMql7/btsrTo02wK3/0Xc362bzdX6bV3K3pU6WkK/img.png&quot; data-alt=&quot;결국 이게 중요하다~!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buMql7/btsrTo02wK3/0Xc362bzdX6bV3K3pU6WkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuMql7%2FbtsrTo02wK3%2F0Xc362bzdX6bV3K3pU6WkK%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;457&quot; height=&quot;142&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결국 이게 중요하다~!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CPU 개수 측정 명령어&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;linux : lscpu -e&lt;/li&gt;
&lt;li&gt;mac os : sysctl hw.physicalcpu hw.logicalcpu&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uTKrG/btsrS5gaA4X/8iA4EatG0qsMc28h4urcp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uTKrG/btsrS5gaA4X/8iA4EatG0qsMc28h4urcp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uTKrG/btsrS5gaA4X/8iA4EatG0qsMc28h4urcp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuTKrG%2FbtsrS5gaA4X%2F8iA4EatG0qsMc28h4urcp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;242&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;하지만 항상 괜찮을까~~? 그럴수도 있고 아닐수도 있따~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  Load Average가 높은 이유는 뭘까? (이게 중요하다)&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Load Average 는 R과 D 상태의 프로세스 개수의 평균값이라고 정의했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️OS 에서의 프로세스 상태를 크게 분류하면 6가지 상태로 분류할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;new (생성 상태) : &lt;/b&gt;프로세스 생성 상태&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;ready (준비 상태) : &lt;/b&gt;&lt;span style=&quot;color: #222426; text-align: start;&quot;&gt;생성된 프로세스가 CPU를 얻을때 까지 기다리는 상태&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;running (실행 상태) : &lt;/b&gt;프로세스의 명령어를 실행 중인 상태&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;waiting (대기 상태) :&lt;/b&gt; &lt;span style=&quot;color: #222426; text-align: start;&quot;&gt;입출력을 요구한 프로세스가 입출력이 완료될 때 까지 기다리는 상태&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #222426; text-align: start;&quot;&gt;&lt;span style=&quot;color: #222426; text-align: start;&quot;&gt;효율성을 높이기 위해 입출력을 요청한 프로세스를 실행 상태에 두지 않고 대기상태로 따로 분류한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;terminated (완료 상태) : &lt;/b&gt;프로세스가 종료된 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwdkav/btsrIe6rqk6/koqRRRjIK2Fi17I89bQkI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwdkav/btsrIe6rqk6/koqRRRjIK2Fi17I89bQkI0/img.png&quot; data-alt=&quot;5가지 프로세스 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwdkav/btsrIe6rqk6/koqRRRjIK2Fi17I89bQkI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdwdkav%2FbtsrIe6rqk6%2FkoqRRRjIK2Fi17I89bQkI0%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;500&quot; height=&quot;202&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5가지 프로세스 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ &lt;b&gt;보류상태&lt;/b&gt; : &lt;span style=&quot;color: #222426; text-align: start;&quot;&gt;프로세스가 메모리에서 잠시 쫒겨난 상태 ( = 일시적 상태)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분 컴퓨터 성능을 떨어뜨리거나 실행을 미루어도 큰 지장이 없는 프로세스&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYpBr/btsr0tmHjLy/sFHXUN4KBFQZYK7QsQ4m81/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYpBr/btsr0tmHjLy/sFHXUN4KBFQZYK7QsQ4m81/img.webp&quot; data-alt=&quot;보류상태를 포함하는 프로세스 상태도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYpBr/btsr0tmHjLy/sFHXUN4KBFQZYK7QsQ4m81/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYpBr%2Fbtsr0tmHjLy%2FsFHXUN4KBFQZYK7QsQ4m81%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;512&quot; height=&quot;385&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;보류상태를 포함하는 프로세스 상태도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 프로세스 상태들을 리눅스에서는 아래의 코드로 표현합니다. (&lt;a href=&quot;https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;출처&lt;/a&gt;)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 144px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 6.00772%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;코드&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 60.659%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;설명(영어)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;설명(한국어)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 6.00772%; height: 18px; text-align: center;&quot;&gt;D&lt;/td&gt;
&lt;td style=&quot;width: 60.659%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;Uninterruptible sleep (usually IO)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;깨울 수 없는 잠 (보통 IO)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 6.00772%; height: 18px; text-align: center;&quot;&gt;R&lt;/td&gt;
&lt;td style=&quot;width: 60.659%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;Running or runnable (on run queue)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;실행중 또는 실행가능 (실행 큐에 있음)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 6.00772%; height: 18px; text-align: center;&quot;&gt;S&lt;/td&gt;
&lt;td style=&quot;width: 60.659%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;Interruptible sleep (waiting for an event to complete)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;깨울 수 있는 잠&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 6.00772%; height: 18px; text-align: center;&quot;&gt;T&lt;/td&gt;
&lt;td style=&quot;width: 60.659%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;Stopped, either by a job control signal or because it is being traced.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;중지됨 (작업 제어 신호를 받거나 트레이싱 때문에)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 6.00772%; height: 18px; text-align: center;&quot;&gt;W&lt;/td&gt;
&lt;td style=&quot;width: 60.659%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;paging (not valid since the 2.6.xx kernel)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;페이징 (커널 2.6.xx 버전부터는 유효하지 않음)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 6.00772%; height: 18px; text-align: center;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 60.659%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;dead (should never be seen)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;죽음 (이런 게 보이면 안되는데)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 6.00772%; height: 18px; text-align: center;&quot;&gt;Z&lt;/td&gt;
&lt;td style=&quot;width: 60.659%; height: 18px;&quot;&gt;&lt;span style=&quot;background-color: #f7f8f9; color: #000000; text-align: left;&quot;&gt;Defunct (&quot;zombie&quot;) process, terminated but not reaped by its parent.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;좀비 프로세스, 종료되었으나 부모 프로세스에 의해 수습되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  R과 D 상태의 프로세스&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;즉 R과 D 상태란 실행상태와 대기상태를 의미합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행중인 프로세스 상태 확인 방법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;vmstat&lt;/span&gt; 명령어로 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;vmstat 1&lt;/span&gt; 하면 백그라운드 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R (실행 상태) : cpu 위주의 작업&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;cpu를 필요로하는(연산) 프로세스가 많다는 거기 때문에 CPU의 개수를 늘리거나&lt;/li&gt;
&lt;li&gt;스레드의 개수를 조절해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; CPU를 늘려서 더 많은 연산을 처리할 수 있도록 해주는게 일반적&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/97T3V/btsrYUkGSJe/OjG5oE2pSwtPKu39BUZ2QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/97T3V/btsrYUkGSJe/OjG5oE2pSwtPKu39BUZ2QK/img.png&quot; data-alt=&quot;출처 : 강의 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/97T3V/btsrYUkGSJe/OjG5oE2pSwtPKu39BUZ2QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F97T3V%2FbtsrYUkGSJe%2FOjG5oE2pSwtPKu39BUZ2QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;281&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : 강의 이미지&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;D (대기상태 = b): I/o 위주의 작업&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;D상태의 프로세스가 많다면, CPU보다 I/O작업에서 병목이 일어난다는 것 입니다.&lt;/li&gt;
&lt;li&gt;IOPS가 높은 디바이스로 변경하거나 처리량을 줄여 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cpu 가 2대, load average 2 (D상태) 이면 CPU를 늘려도 load average 가 내려가지 않습니다. &amp;rarr; 소용이 없음&lt;/li&gt;
&lt;li&gt;더 많은 I/O를 처리할 수 있는 환경을 만들어 줘야한다.&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;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 더 많은 &lt;b&gt;IOPS&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;(Input/Output Operations Per Second,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;IOPS&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;)&lt;/span&gt; 를 가지는 ebs로 교체하는게 성능향상으로 이어지는 길!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vZ0N8/btsrR5OrWsf/XNFGA84UopNzX27KvrRku1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vZ0N8/btsrR5OrWsf/XNFGA84UopNzX27KvrRku1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vZ0N8/btsrR5OrWsf/XNFGA84UopNzX27KvrRku1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvZ0N8%2FbtsrR5OrWsf%2FXNFGA84UopNzX27KvrRku1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;408&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;✔️ uptime 명령어를 통해 리눅스 환경에서 - cpu 가 얼마나 많은 부하를 받고있는지 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;✔️ load average가 cpu 의 개수 보다 많은 부하를 받고있다면 &amp;rarr; 어떤 종류의 프로세스때문인지 확인합니다. (vmstat)&lt;/li&gt;
&lt;li&gt;✔️ r 이면 cpu 바운드, b 면 I/O 바운드 이기 때문에 각각 상황에 맞는 대응을 해주어서 부하를 줄여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>운영체제/Linux</category>
      <category>cpu 개수</category>
      <category>CPU 사용량</category>
      <category>Linux CPU</category>
      <category>리눅스 서버 축정</category>
      <category>서버 모니터링</category>
      <category>서버 부하</category>
      <category>서버 부하 측정</category>
      <category>프로세스 감당</category>
      <category>프로세스 사용량</category>
      <category>프로세스 상태란</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/456</guid>
      <comments>https://thalals.tistory.com/456#entry456comment</comments>
      <pubDate>Tue, 22 Aug 2023 17:53:14 +0900</pubDate>
    </item>
    <item>
      <title>넥스트스텝 ATDD with Spring 수료 회고</title>
      <link>https://thalals.tistory.com/455</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;으아아아아아 수료했다아아아아 &lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;약 2달전에 NEXTSTEP 에서 진행하는 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;ATDD, 클린 코드 with Spring 7기&lt;/span&gt;&quot; 라는 교육을 수강했다! (&lt;a href=&quot;https://edu.nextstep.camp/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://edu.nextstep.camp/&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1달 반정도 같이있었던 첫 개발 사회생활의, 첫 번째 사수님이 수강하셨고 추천해주셨던 강의인데,, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그때 당시 &quot;&lt;u&gt;단위 테스트도 잘 못하는데 무슨 인수테스트냐;;&lt;/u&gt;&quot; 라는 생각에&amp;nbsp;&lt;b&gt;꼭 나중에 들어봐야지&lt;/b&gt; 다짐하고 1년이 지난 지금 드디어 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;수강을했고, 수료를 했다&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;넥스트스텝은 다양한 주제의 시즌제(?) 강의들이 존재하는 교육 플랫폼이다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사수님 덕분에 알게되었지만, 큰 관심을 가지게된건 여러가지 복합적인 이유가 있지만 가장 큰건 &lt;a href=&quot;https://catsbi.oopy.io/8f66658d-cc49-406b-aaed-fa27c3cc8017&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;catsbi&lt;/a&gt; 님의 회고글 이였다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(신기하게도 catsbi 님도 리뷰어 중에 있었다! &lt;s&gt;물론 나는 리뷰를 못 받았지만?!)&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;되게 흥미롭게 읽었고, 한창 TDD 나 객체지향에 관심을 가지고 구글링을 해보면 양질의 자료는 NEXTSTEP이 출처인 경우가 종종 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;catsbi 님의 회고글을 읽고&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt; TDD &amp;rarr; ATDD &amp;rarr; 인프라공방&lt;/u&gt;&lt;/span&gt;&lt;/b&gt; 이 순으로 꼭 수강해야지! 했지만 가격이 상당히 쎄기에,,, TDD는 패스!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5kLlZ/btsrsRhuWSg/26WaFhTtspJeSCEJo1frh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5kLlZ/btsrsRhuWSg/26WaFhTtspJeSCEJo1frh1/img.png&quot; data-alt=&quot;비싸!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5kLlZ/btsrsRhuWSg/26WaFhTtspJeSCEJo1frh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5kLlZ%2FbtsrsRhuWSg%2F26WaFhTtspJeSCEJo1frh1%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;800&quot; height=&quot;282&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비싸!&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자! 이제 약 6주간 ATDD 를 수강하면서 배운것을 짧막하게 정리하면서 회고글을 써보고자 합니다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[목차]&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ATDD 교육 소개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배운점&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;try?&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. ATDD 미션 소개&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;교육은 약 6주간 매 주차의 미션을 수행하는 형식으로 진행된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;강의는 매주 목요일마다 있었고, 해당 주차의 미션 리뷰, 질문, 피드백, 다음주차 미션 소개, 그 사이의 짤막한 지식, 키워드, 미션 힌트를 위한 강의 등을 알려주신다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 근데 6주가 지났는데, 수강자들의 리뷰가 활발하다고 1주 더 빠른 리뷰 받을 수 있게 교육기간을 늘려주기도 했습니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;괜히 유명한 교욱이 아니라는 생각이 들었다 &lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) ATDD 미션&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;미션의 베이스 목표는 지하철 노선도를 구현하는 것&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1주차&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주제 : 인수 테스트와 E2E 테스트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;목표 : 인수테스트 이해하기 + RestAssured 로 통합테스트 환경 구축하기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://github.com/thalals/atdd-subway-map/tree/thalals&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/thalals/atdd-subway-map/tree/thalals&lt;/a&gt;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2~3주차&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주제 : 인수 테스트와 TDD&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;목표&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;요구사항에서 인수 조건 추출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트 기반, 요구사항 변경에 따른 기존 코드 리팩토링&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Rich Domain Model 학습 (내가 느끼기에)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://github.com/thalals/atdd-subway-path/tree/thalals&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/thalals/atdd-subway-path/tree/thalals&lt;/a&gt;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4주차&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주제 : 인수테스트와 인증&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;목표 : 인증 및 인가가 필요한 API 인수테스트 (feat OAuth)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://github.com/thalals/atdd-subway-favorite/tree/thalals&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/thalals/atdd-subway-favorite/tree/thalals&lt;/a&gt;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;5주차&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주제 : 인수 테스트와 리팩터링&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;목표 : RestAssured 로 API 문서화 (RestDocs)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://github.com/thalals/atdd-subway-fare/tree/thalals&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/thalals/atdd-subway-fare/tree/thalals&lt;/a&gt;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  좋았던 점은, 미션 위주지만, 해당 미션을 수행하는게 목적이 아닌, 더 나은 방법으로 수행이 목적이였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;5주차의 미션을 보면, RestDocs의 문서화가 주된 목적이지만, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;미션세부사항에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;문서화를 위한 코드와 인수테스트를 위한 코드가 중복 해결&quot;&lt;/span&gt; 등과 같은 실제 업무에서도 사용할 수 있을정도의 강도로 교육이 진행되었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) ATDD 강의 후기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 사실 내가 생각했던 완전(?) 강의는 아니였다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;약간 학교나, 세미나, 혹은 지식 전달의 강의일줄 알았는데 &lt;b&gt;&quot;정보 공유의 장&quot;&lt;/b&gt; 느낌이 나에게는 강하게 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;강사님은 거의 모든 질문에 답변을 해주셨다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실시간 질문뿐만 아니라 매 주차 강의 전에 구글폼으로 질문을 와장창 받으시고, 이걸 강의에 꼭 Q&amp;amp;A를 해주시는게 참 신기했다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현업에서 일하시는, 굉장한 커리어를 가지시는 분이 모든 질문에 친절하게 눈높이를 낮춰서 답을 해주시고, 같이 답을 찾아나갈려고 하시니까 듣는 재미가 있었다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;솔직히 이렇게 어떤 상황(미션 코드 or 주제)을 공유하면서, 모르거나 확신을 못가졌던 관점에 대해 물어볼 수 있는 기회가 생가보다 별로 없는데 이 부분은 너무 좋았다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또, 강의를 같이 수강하시는 분들이 대부분 현직자분들이다보니 수준이 상당히 높다고 느껴졌다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;질문에 대해 같이 토론하시는데 답변들이 하나같이 굉장(?)했다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 교육이 매 주차의 미션위주이고, 강의또한 미션에 맞춰지기 때문에 어쩔수없이 다른 미션 진행자분들의 진행률에 따라 강의가 밀리는 느낌도 있었다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 부분은 누군가에게는 장점일 수도 있고, 단점이 될 수도 있다 생각하는데,, 나는 너무 밀리는 느낌도 받지않았고 녹강도 제공해주셔서 솔직히 좋았다!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brEvX4/btsrr0eCWF8/peDXerOF4bkPgs2KLCzw70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brEvX4/btsrr0eCWF8/peDXerOF4bkPgs2KLCzw70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brEvX4/btsrr0eCWF8/peDXerOF4bkPgs2KLCzw70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrEvX4%2Fbtsrr0eCWF8%2FpeDXerOF4bkPgs2KLCzw70%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;589&quot; height=&quot;299&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;355&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 교육기간동안 배운 것 정리&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;솔직히 너무 많아서 키워드만이라도 정리하자는 마음으로 적어봅니당&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내가 배운거&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인수테스트란, 인수테스트 하는법, ATDD, TDD&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개발 프로세스&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;요구사항으로 인수조건 도출 &amp;rarr; 문서화 &amp;rarr; 인수 테스트 &amp;rarr; 로직 구현 &amp;rarr; &amp;nbsp;단위테스트 &amp;rarr; 로직 구현(반복) &amp;rarr; 인수테스트 검증&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리뷰하는 법?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;PR 보내는 방법&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커밋 쪼개기 (작게, 작게)&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그 외에 뭐 도메인 주도 아키텍쳐, 기술들 많이 배웠지 정리 잘하자&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리치도메인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;도메인 주도 아키텍쳐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;패키지 의존성 개선&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://github.com/next-step/atdd-subway-favorite/pull/517&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; 직접 참조 vs 간접 참조&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;책임연쇄 패턴의 적용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;HandlerArgumentResolver (@AuthenticationPrincipa 구현체 Custom)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 인수테스트란&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ATDD 란 Acceptance Test Driven Development 를 의미합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Acceptance는 사전적인 의므로 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;허락&lt;/span&gt;&quot;을 뜻합니다. 즉 인수테스트란 마지막 허가를 위한 테스트로 저는 이해를 이했습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) Acceptance Test (in extreme programming)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️&amp;nbsp;애자일(xp)에서의 인수테스트&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사용자 스토리를 검증하는 &quot;&lt;b&gt;기능 테스트&quot; &lt;/b&gt;를 인수테스트라 지칭합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사용자 스토리(기획)로 테스트할 시나리오를 지정 후 &amp;rarr; 시나리오 기반의 요구사항을 테스트하는 과정을 거칩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️&amp;nbsp;원래 인수테스트 의미&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;소프트웨어 이외 다른 분야에서도 사용되는 용어&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;보통 &lt;b&gt;마지막 단계에서 수행하는 테스트&lt;/b&gt;를 의미 &amp;rarr; 승인을 위해 ( &amp;nbsp;마지막 단계에서 Accpetance 되기 전에 수행하는 테스트라고 추정)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;최종 단계에서 작업을 종료 시켜도 되는지 검증하는 테스트&lt;/span&gt;가 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;인수테스트&lt;/span&gt; !!&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  교육 미션 진행시에도 &amp;rarr; 실제 운영환경과 유사한 테스트환경을 구현하여, 해당 기능이 동작했을때 의도했던데로 잘 동작하는지 E2E 테스를 작성하여 진행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 인수테스트 = 등대 테스트 = 수용 테스트 = 통합 테스트 = 인수 테스트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;어떤 관점&lt;/b&gt;으로 바라보는가, or &lt;b&gt;테스트의 목적&lt;/b&gt;이 무엇인가에 따라서 부르는 이름은 얼마든지 달라질 수 있다고 생각합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;교육에서의 인수테스트는, 개발자가 아닌 기획자와 함께 바라볼 수 있게! 라는 목적도 포함하였습니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(그래서 한글 메소드 사용 + 가독성 중시)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) ATDD를 하는 이유&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;구현전에 인수 테스트를 수행하는 경우 팀의 생산성이 2배가 되는 것을 확인했다. - 제프 서덜런드&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개발 자체의 기간은 늘어날 수 있다. (솔직히 작업량이 꽤 생기는거 같음)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단, 요구사항을 명확하게 인지하면서 개발하기 때문에 &amp;rarr; 불필요한 수정 과정을 줄일 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ATDD는 작업의 명확한 시작과 끝을 제시한다. &amp;rarr; 테스트가 성공되는 순간. 작업이 끝나는 것.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TDD의 부족한 부분 보완 가능&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TDD는 도메인도 개발해나가면 단위테스트로 기능의 수행을 검증하는데 매우 뛰어나지만&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전체적인 흐름이나 시나리오를 놓치게되는 경우가 생김&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;빠른 피드백&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;구현한 기능을 배포하지 않고 테스트로 확인 가능&lt;/span&gt;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 인수테스트가 결국 E2E 테스트이기때문에 메모리 디비(H2) 로 내가 수행할려는 상황을 먼저 세팅해주어야 하기 때문에 이부분이 상당히 귀찮았다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 하지만, 그럼에도 불구하고 통합테스트시, post-man 으로 내가 확인하는거보다 코드상으로 남기고, 확인하는게 편할때도 있었다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr;   코드로 남긴다는게 중요한거 같음&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3) ATDD (인수테스트 주도 개발) 프로세스&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️ 요구사항 분석 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt; 인수조건 도출 &amp;rarr; 문서화 &amp;rarr; 인수 테스트 &amp;rarr; 로직 구현 &amp;rarr; &amp;nbsp;단위테스트 &amp;rarr; 로직 구현(반복) &amp;rarr; 인수테스트 검증&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(예시)&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;h-tag-4&quot; style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;인수 조건 정의&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #24292f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사용자 스토리 (누가/무엇을/왜)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인수 테스트가 충족해야하는 조건&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인수 조건을 표현하는 시나리오 기반 표현 방식 사용(ex. Given/When/Then)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인수 조건 작성 방법&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;검증하고자 하는&amp;nbsp;when&amp;nbsp;구문 먼저 작성&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기대 결과를 의미하는&amp;nbsp;then&amp;nbsp;구문 작성&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;when과 then에 필요한 정보를&amp;nbsp;given을 통해 제공&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;/**
 * Feature: 지하철 경로 검색 
 * Scenario: 두 역의 최소 시간 경로를 조회 
 * Given 
 * 지하철역이 등록되어있음 And 지하철 노선이 등록되어있음 And 지하철 노선에 지하철역이 등록되어있음 
 * When 
 * 출발역에서 도착역까지의 최소 시간 기준으로 경로 조회를 요청
 * Then 
 * 최소 시간 기준 경로를 응답 And 총 거리와 소요 시간을함께 응답함 And 지하철 이용 요금도 함께 응답함
 */&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;h-tag-5&quot; style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;인수 테스트 작성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #24292f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인수 조건을 검증하는 테스트&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실제 요청/응답 환경과 유사하게 테스트 환경 구성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@DisplayName(&quot;두 역의 최소 시간 경로 조회&quot;)
@Test
void findPathByDuration() {

    //given
    교대역 = 지하철역_생성_요청(&quot;교대역&quot;).jsonPath().getLong(&quot;id&quot;);
    강남역 = 지하철역_생성_요청(&quot;강남역&quot;).jsonPath().getLong(&quot;id&quot;);
    양재역 = 지하철역_생성_요청(&quot;양재역&quot;).jsonPath().getLong(&quot;id&quot;);
    남부터미널역 = 지하철역_생성_요청(&quot;남부터미널역&quot;).jsonPath().getLong(&quot;id&quot;);

    이호선 = 지하철_노선_생성_요청_후_id_추출(&quot;2호선&quot;, &quot;green&quot;, 교대역, 강남역, 10, 30);
    신분당선 = 지하철_노선_생성_요청_후_id_추출(&quot;신분당선&quot;, &quot;red&quot;, 강남역, 양재역, 10, 10);
    삼호선 = 지하철_노선_생성_요청_후_id_추출(&quot;3호선&quot;, &quot;orange&quot;, 교대역, 남부터미널역, 2, 20);

    지하철_노선에_지하철_구간_생성_요청(삼호선, createSectionCreateParams(남부터미널역, 양재역, 3, 30));
    
    // when
    var response = 두_역의_타입에따른_경로_조회를_요청(교대역, 양재역, PathType.DURATION.name());

    // then
    assertThat(response.jsonPath().getList(&quot;stations.id&quot;, Long.class)).containsExactly(교대역, 강남역, 양재역);
    assertThat(response.jsonPath().getInt(&quot;distance&quot;)).isEqualTo(20);
    assertThat(response.jsonPath().getInt(&quot;duration&quot;)).isEqualTo(40);
    assertThat(response.jsonPath().getInt(&quot;fare&quot;)).isEqualTo(DEFAULT_FARE + 200);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4) ATDD 테스트 도구&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Spring Boot Test&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트에 사용할 ApplicationContext를 쉽게 지정하게 도와줌 &amp;rarr; 실제 어플리케이션에 등록된 빈을 테스트 환경에서도 쓸 수 있게 해준다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 @ContextConfiguration 의 발전된 기능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;SpringApplication에서 사용하는 ApplicationContext 를 생성해서 작동&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;참고 :&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://thalals.tistory.com/453&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Spring] 테스트 환경 격리 시키기&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;RestAssured&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;REST-assured는 Rest API 의 테스트 도구&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;지금까지 MockMvc만 썻는데, RestAssured가 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;가독성과 문서화에서의 중복제거&lt;/span&gt;면에서는 훨씬 좋았다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;JsonPath&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;RestAssured랑 같이 사용하면 불필요한 Response 객체를 만들 필요성이 사라짐&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 관리 의존성 vs 비관리 의존성&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;관리 의존성&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;의존하는 대상이 어플리케이션 내부에서만 접근할 수 있는 경우&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내 어플리케이션이 관리할 수 있기 떄문에 관리 의존성 !&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;비관리 의존성&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;의존하는 대상이 내부 뿐만 아니라 외부에서도 접근이 가능한 경우 &amp;rarr; 비관리 의존성의 경우 테스트가 매우 힘듬&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  강의에서는 비관리 의존성은 대체(mocking)을 관리 의존성은 실제 협력객체를 추천한다고 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;의존성 테스트 방법&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실제 외부 의존성 사용&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;비관리 의존성은 실제 외부 의존성을 사용할 경우 속도도 느리고, 외부 동작에 의존적인 테스트가 됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실제 의존이 불가능 하기도 함 &amp;rarr; 결제 테스트&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Fake&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ex 깃허브 로그인&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깃허브 로그인 시 호출하는 외부 API를 실제 깃허브에 보내지 않음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트용 properties를 이용하여 테스트 서버에 요청 보내게 하기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Stub&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. 리팩토링의 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인수테스트가 있으면 &amp;rarr; 리팩토링 하다가 hot fix 처리건이 와도 안전하게 인수테스트가 성공했던 시점의 커밋으로 언제든 돌아올 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인수테스트가 없다면 &amp;rarr; 인수테스트를 작성하고 &amp;rarr; 리팩토링 하고자하는 프로덕션 코드의 단위테스트를 수정해야한다.&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로덕션 코드를 보호하는 단위테스트를 유지하기 위해 (복붙) 을 추천&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TDD 사이클&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;구조를 설계하기&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;요구사항에 맞춰 구조를 설계하는게 먼저이다. (테스트 전에 요구사항 기반으로 설계가되어야한다.)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그런 다음 확장성을 고려하여 구조를 설계하는데 &amp;rarr; 이때 TDD 가 큰 도움이 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;새로운 테스트 만들기&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 프로덕션 코드와, 테스트 코드를 두고 새로운 테스트 코드를 만든다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기능 구현하기 (테스트 성공 후 리팩터링)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;새로운 테스트코드를 먼저 작성하고 &amp;rarr; 테스트에 성공하는 코드로 리팩토링한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. 커밋 작게 쪼개기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;미션을 진행하면서, 리뷰어님의 리뷰를 받기위해 반나절~하루를 기다려야하기 때문에&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한번 받을때 알차게(?) 리뷰를 받고싶었습니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  커밋에 대해서 크게 신경써본적이 없어서 그냥저냥 커밋 컨벤션만 맞춰서 작업이 끝나면 일괄적으로 파일만 나눠서 아래처럼 커밋을하는 버릇이 있었는데&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRYrQw/btsrDEXjTXK/NGQg9VMe9jqLZkang2QbcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRYrQw/btsrDEXjTXK/NGQg9VMe9jqLZkang2QbcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRYrQw/btsrDEXjTXK/NGQg9VMe9jqLZkang2QbcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRYrQw%2FbtsrDEXjTXK%2FNGQg9VMe9jqLZkang2QbcK%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;872&quot; height=&quot;182&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리뷰할떄 내 개발흐름을 보여줄 수 있으면 리뷰어님들도 리뷰하기 편하지 않을까?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;더 좋은 리뷰를 받을 수 있지 않을까? 라는 생각에 커밋 잘하기를 시도해 보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커밋을 작은 단위로&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;작업 끝 커밋이 아닌 &amp;rarr; 개발 도중도중 커밋&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내 개발 흐름을 알 수 있도록 번호 붙히기&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;darr;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  훨씬 깔끔하고 보기 좋은 커밋이 된거 같습니당&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;점진적으로 커밋을 작게 &amp;rarr; 흐름을 알 수있도록 개선해 나갔는데 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;커밋 내역에 따라, 더 자세하고 소프트적인 리뷰를 받을 수 있었습니다 (느낌적인 느낌?)&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FR53G/btsrCycfBw2/Funm6xp79Jx4B4OKZhsYx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FR53G/btsrCycfBw2/Funm6xp79Jx4B4OKZhsYx1/img.png&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;422&quot; data-is-animation=&quot;false&quot; style=&quot;width: 55.6038%; margin-right: 10px;&quot; data-widthpercent=&quot;56.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FR53G/btsrCycfBw2/Funm6xp79Jx4B4OKZhsYx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFR53G%2FbtsrCycfBw2%2FFunm6xp79Jx4B4OKZhsYx1%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;889&quot; height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzHidr/btsrCugBrHm/WEYqNVy1K94MpMTjrfsLi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzHidr/btsrCugBrHm/WEYqNVy1K94MpMTjrfsLi0/img.png&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;569&quot; data-is-animation=&quot;false&quot; style=&quot;width: 43.2334%;&quot; data-widthpercent=&quot;43.74&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzHidr/btsrCugBrHm/WEYqNVy1K94MpMTjrfsLi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzHidr%2FbtsrCugBrHm%2FWEYqNVy1K94MpMTjrfsLi0%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;932&quot; height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;+ PR 잘보내기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;미션이 진행되고 과제가 어려워질수록 좋은 리뷰를 받기위해, PR 도 신경써서 보내게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;처음에는 &quot;PR 후 &amp;rarr; 리뷰를 받으면 궁금한 점 질문&quot; 이런식이여서 답답한감이 있었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래서 추후에는 &amp;rarr; PR 자체가 하나의 리포트인 것처럼 먼저 궁금한 부분이나 봐줬으면 좋겠다 싶은 부분에 코멘트를 남겨놓았습니다&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuK2hq/btsrH7q956s/l9N0LhlpRHkrli3NRDmOO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuK2hq/btsrH7q956s/l9N0LhlpRHkrli3NRDmOO0/img.png&quot; data-alt=&quot;ㅋㅋㅋㅋ,,,, 초반의 PR&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuK2hq/btsrH7q956s/l9N0LhlpRHkrli3NRDmOO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuK2hq%2FbtsrH7q956s%2Fl9N0LhlpRHkrli3NRDmOO0%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;759&quot; height=&quot;151&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ㅋㅋㅋㅋ,,,, 초반의 PR&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6BwNY/btsrEzhaCWC/uXBKN2LuzcZnvskc1Pkq30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6BwNY/btsrEzhaCWC/uXBKN2LuzcZnvskc1Pkq30/img.png&quot; data-alt=&quot;후반 부 PR&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6BwNY/btsrEzhaCWC/uXBKN2LuzcZnvskc1Pkq30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6BwNY%2FbtsrEzhaCWC%2FuXBKN2LuzcZnvskc1Pkq30%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;743&quot; height=&quot;234&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;후반 부 PR&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;+&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;❗️추가적으로 ATDD 교육의 장점 중 하나가 같은 요구사항을 수행하는 다른 개발자분들을 PR 훔쳐보기(?) 인데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;취준때 같이 스파르타 부트캠프를 진행했던분의 PR 을 보고 많은 반성을 하게 되었다.(&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://github.com/next-step/atdd-subway-path/pull/688&quot;&gt;링크&lt;/a&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이런 PR 을 받으면 진짜 리뷰할 맛 나고,,, 너무 좋을거같다는 생각이 들었고,, 이런게 같이 일하고싶은 개발자겠구나 생각이 들었다.. 배우자&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnNNBf/btsrIfW0JRJ/kK4WfKLvzSIIHYHZV8thj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnNNBf/btsrIfW0JRJ/kK4WfKLvzSIIHYHZV8thj1/img.png&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;800&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.7875%; margin-right: 10px;&quot; data-widthpercent=&quot;48.35&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnNNBf/btsrIfW0JRJ/kK4WfKLvzSIIHYHZV8thj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnNNBf%2FbtsrIfW0JRJ%2FkK4WfKLvzSIIHYHZV8thj1%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;821&quot; height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY2CI3/btsrCTf8bnP/cKcfZurSOG2BL3uVKrksik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY2CI3/btsrCTf8bnP/cKcfZurSOG2BL3uVKrksik/img.png&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;758&quot; data-is-animation=&quot;false&quot; style=&quot;width: 51.0497%;&quot; data-widthpercent=&quot;51.65&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY2CI3/btsrCTf8bnP/cKcfZurSOG2BL3uVKrksik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY2CI3%2FbtsrCTf8bnP%2FcKcfZurSOG2BL3uVKrksik%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;831&quot; height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. 회고 KPT&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Keep&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 교육이 시작한다고 떴을 때. 여유로운 상황이 아니였기 때문에 할까말까 망설이다 냅다 결제부터 했는데, 참 잘한거 같다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;망설이다가 또 못들었으면 다음 교육도 못들었을거라는 확신이 든다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;역시 무서워도 도전해야, 죽이되든 밥이되든 결과가 나오는 것 같다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아무것도 하지않으면 아무일도 일어나지 않으니,, 앞으로도 냅다 갈겨버리자!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N4nSQ/btsrDDqCw6G/pVPk5ozlnkPQRNX6dGLf81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N4nSQ/btsrDDqCw6G/pVPk5ozlnkPQRNX6dGLf81/img.png&quot; data-alt=&quot;지노형 사랑해&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N4nSQ/btsrDDqCw6G/pVPk5ozlnkPQRNX6dGLf81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN4nSQ%2FbtsrDDqCw6G%2FpVPk5ozlnkPQRNX6dGLf81%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;300&quot; height=&quot;272&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;지노형 사랑해&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 솔직히 미션하다가,, 나쁜마음 계속 들었는데, 그래도 계속 계속 조금씩 커밋했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;끝까지 해서 수료하니까 얻어갈 수 있는게 참 많이 생겼다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ex) &quot;이거 리뷰 다 반영해야할까..? 귀찮은데... 어려울거같은데.. 내가 한 방법도 리뷰어님이 틀린건 아니라고 했는데,,&quot;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;없다! 열심히 했고 재밌게 했다!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;굳이 아쉬운 점을 뽑자면, 매주 배운것을 정리하지 못했다는 점?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 체력이 안되었던 걸!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Try&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기억은 금방 사라진다. 잊기 전에 블로그에 잘 정리하자 꼭!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인수테스트는 도구일 뿐! 내 코드에 필요한 부분 잘 생각해서 적용해보자, 안 써도 문제는 없지만 썻을 때 편한점도 느꼈잖아?!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커밋 쪼개는 습관 앞으로도 잊지말고 들이자&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;돈 많이 벌어서 인프라공방도 수강하자~~ 끝!&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2023-06-26(월) ~ 2023-08-09(수) 꽉 채워 겨우 수료해서 받은 나의 수료증! 야호&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb7GaV/btsrNmattwh/du57ZQACVoNnaM7f3etsgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb7GaV/btsrNmattwh/du57ZQACVoNnaM7f3etsgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb7GaV/btsrNmattwh/du57ZQACVoNnaM7f3etsgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb7GaV%2FbtsrNmattwh%2Fdu57ZQACVoNnaM7f3etsgk%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;500&quot; height=&quot;390&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEaQ2M/btsrgi2rJnc/Vy7ipcgttzhmKY00bLCvk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEaQ2M/btsrgi2rJnc/Vy7ipcgttzhmKY00bLCvk1/img.png&quot; data-alt=&quot; &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEaQ2M/btsrgi2rJnc/Vy7ipcgttzhmKY00bLCvk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEaQ2M%2Fbtsrgi2rJnc%2FVy7ipcgttzhmKY00bLCvk1%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;800&quot; height=&quot;220&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고/일상 후기 회고</category>
      <category>atdd</category>
      <category>ATDD 수료</category>
      <category>tdd</category>
      <category>넥스트스텝</category>
      <category>백엔드 교육</category>
      <category>백엔드 회고</category>
      <category>인수테스트</category>
      <category>인수테스트 예시</category>
      <category>자바개발자</category>
      <category>통합테스트</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/455</guid>
      <comments>https://thalals.tistory.com/455#entry455comment</comments>
      <pubDate>Mon, 21 Aug 2023 11:17:10 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 테스트 환경 격리 시키기 - 각 테스트마다 DB 분리하기 (@Transactional 을 사용하면 안되는 이유)</title>
      <link>https://thalals.tistory.com/453</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt; 현재 NEXT STEP 에서 진행하는 ATDD 강의를 수강하고 있습니다.&lt;br /&gt;강의에서 예전부터 고민하던 통합테스트 환경에서 데이터베이스를 격리하는 방법에 대해 알게되어 기록을 남겨보고자 합니다.&lt;/blockquote&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합테스트 환경에서 시도한건 아니지만 단위테스트에서 JPA 에 의존하는 Repository 를 온전히 격리해 어떤상황에서든 성공하는 단위테스 환경을 구축하고자 했던적이 있습니다.  &amp;nbsp; &lt;a href=&quot;https://thalals.tistory.com/395&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;service 단위 테스트 하기 - DB 와 독립된 테스트 환경 구축 (service unit test)&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만&lt;/span&gt; &lt;br /&gt;1. 단위테스트 환경에서는 사실상 Mocking 을 하는것보다 큰 장점이 없었고&lt;br /&gt;2. 필요로하는 Repsitory 기능을 매번 정의해주어야해서 많이 번거로웠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  그치만 통합테스트 환경에서는 실제 DataBase에 의존하지 않을 수 있으니 어느정도 의미가 있는 행위가 되겠죠? &lt;br /&gt;(그치만 여전히 복잡하고 번거로운 것은 사실입니다)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[그 외에 테스트 격리 방법]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번글에서 정리해볼 내용이고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&quot;테스트환경에서의 H2 메모리 데이터베이스를 사용&quot;&lt;/span&gt; 한다는걸 가정으로 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@SpringBootTest 시 각 테스트마다 컨텍스트 새로올리기&lt;/li&gt;
&lt;li&gt;각 테스트가 시작할때마다 데이터 지우기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;@SpringBootTest 시 각 테스트마다 컨텍스트 새로올리기&lt;/li&gt;
&lt;li&gt;테스트가 시작할 때 마다 데이터 지우기 (with. Truncate)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 요약 : EntityManager 를 사용한 DB Truncate 추천&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;레츠고 !&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.&amp;nbsp;   테스트를 격리해야하는 이유&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;먼저 테스트환경에서 각각의 테스트를 왜 번거롭게 격리해야하는지 살펴봅니다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넥스트스텝의 ATDD 과정은 인수테스트(&lt;i&gt;어떤 관점에서는 통합테스트라고 볼 수도 있음&lt;/i&gt;)를 E2E 테스트로 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기때문에 @SpringBootTest를 사용하여 컨텍스트를 로드하고 1개의 메모리 디비 (H2)를 컨텍스트에서 공유하기 때문에 각 테스트가 서로 다른 테스트에 영향을 주게되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼&amp;nbsp;통합테스트 (인수테스트)가 다른 테스트에 의해 실패하게됩니다!&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;ex) 삭제 테스트 A 에서 생성 &amp;rarr; 삭제 &amp;rarr; 조회 시 0개의 데이터가 나와야하는데&amp;nbsp;&lt;/li&gt;
&lt;li&gt;다른 생성테스트 B에서 생성했던 데이터가 있다면 디비를 공유하기 때문에 1개의 데이터가 조회됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; src=&quot;https://blog.kakaocdn.net/dn/blv1bx/btsmrS1qHKP/etvWOFtrXmPZUbXac3rAjk/img.png&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;436&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;✔️ E2E 테스트에서 &lt;/span&gt;@Transactional&lt;span&gt;&amp;nbsp;을 사용하면 안되는 이유&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이게 쫌 야무진데,&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &lt;u&gt;&quot;간단하게 @Transactional을 사용해서 데이터를 롤백시키면 되는거 아닌가?&quot;&lt;/u&gt;&lt;/span&gt; 라는 생각을 했었지만 결과적으로 아니였습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1523&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6rtEg/btsofOIpV06/M7I2k9gCvJKDU4qAmfAwx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6rtEg/btsofOIpV06/M7I2k9gCvJKDU4qAmfAwx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6rtEg/btsofOIpV06/M7I2k9gCvJKDU4qAmfAwx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6rtEg%2FbtsofOIpV06%2FM7I2k9gCvJKDU4qAmfAwx0%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;1523&quot; height=&quot;263&quot; data-origin-width=&quot;1523&quot; data-origin-height=&quot;263&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;@Transactional 이 안되는 이유를 찾아보니&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SpringBootTest 에서 실행되는 각 메소드의 스레드와 &amp;rarr; RestAssured 로 호출하는 프로덕션 Controller 메소드의 스레드는 별개의 스레드를 사용하기 때문이었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Transactional
class TransactionalTest {

    @DisplayName(&quot;지하철 노선을 생성한다.&quot;)
    @Test
    void createLine() {

        System.out.println(&quot; !!!!!!! 테스트 스레드  : &quot; + Thread.currentThread());

        //given
        Map&amp;lt;String, Object&amp;gt; params = 어떤어떤 파라미터;
       
        
        //when
        ExtractableResponse&amp;lt;Response&amp;gt; 노선등록응답값 = RestAssured
                .given().log().all()
                .body(params)
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .when().post(&quot;/lines&quot;)
                .then().log().all()
                .extract();
                
        //then
        검증코드
    }
}

//Controller
@PostMapping(&quot;/lines&quot;)
ResponseEntity&amp;lt;LineResponse&amp;gt; createStationLine(@RequestBody LineRequest request) {

    System.out.println(&quot; !!!!!!! 프러덕션 스레드  : &quot; + Thread.currentThread());

    LineResponse response = lineFacade.lineCreate(request);

    return ResponseEntity
        .status(HttpStatus.CREATED)
        .body(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;위와같이 각각의 메소드가 실행될 때의 스레드를 확인해보니&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 메소드는 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Test worker, 5, main&lt;/b&gt;&lt;/span&gt; 이라는 스레드에서 동작&lt;/li&gt;
&lt;li&gt;프로덕션 Controller 메소드의 스레드는 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;http-nio-8080-exec-3, 5, main&lt;/b&gt;&lt;/span&gt; 이라는 별도의 스레드에서 동작하는걸 확인할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qkpBf/btsn89m5QoM/Dd87KE0YDtkTZHUz7wKkYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qkpBf/btsn89m5QoM/Dd87KE0YDtkTZHUz7wKkYk/img.png&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;363&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.9&quot; style=&quot;width: 51.292%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qkpBf/btsn89m5QoM/Dd87KE0YDtkTZHUz7wKkYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqkpBf%2Fbtsn89m5QoM%2FDd87KE0YDtkTZHUz7wKkYk%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;509&quot; height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ydkub/btsn9UwtEze/JwK13TokKUt0QYiPgfRs2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ydkub/btsn9UwtEze/JwK13TokKUt0QYiPgfRs2k/img.png&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;447&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.5452%;&quot; data-widthpercent=&quot;48.1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ydkub/btsn9UwtEze/JwK13TokKUt0QYiPgfRs2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fydkub%2Fbtsn9UwtEze%2FJwK13TokKUt0QYiPgfRs2k%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;581&quot; height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;/div&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;  따라서 @Transactinal 을 적용한 End-to-End 테스트 환경에서의 롤백과정은 다음과 같습니다,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트 메서드가 스레드 A 에서 실행된다.&lt;/li&gt;
&lt;li&gt;테스트 메서드 내의 코드에서 컨트롤러의 Post 메서드를 호출한다.&lt;/li&gt;
&lt;li&gt;호출을 받은 컨트롤러 메서드는 스레드 B에서 실행된다.&lt;/li&gt;
&lt;li&gt;테스트 메서드가 완료되면 롤백을 수행한다.&lt;/li&gt;
&lt;li&gt;하지만 트랜잭션의 범위는 스레드 A 내로 한정되므로 스레드 B에는 아무런 영향을 끼치지 못한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YuxHt/btsn7K87n2x/BOg88W5YVHsDlJSE7ZxTUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YuxHt/btsn7K87n2x/BOg88W5YVHsDlJSE7ZxTUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YuxHt/btsn7K87n2x/BOg88W5YVHsDlJSE7ZxTUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYuxHt%2Fbtsn7K87n2x%2FBOg88W5YVHsDlJSE7ZxTUk%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;689&quot; height=&quot;69&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;69&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transacnal 의 공식문서에서는 트랜잭션이 내부에서 시작된 스레드로 전파되지 않는다고 나와있습니다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그렇기때문에&lt;/span&gt; @Transacnal을 사용해서는 통합테스트환경을 격리시킬 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. @SpringBootTest 시 각 테스트마다 컨텍스트 새로올리기&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;어차피 테스트 환경을 격리시켜야한다면, 매번 테스트가 실행될 때 컨텍스트를 새롭게 올려 온전히 새로운 환경에서 돌리는 것도 하나의 방법입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ @DirtiesContext&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@DirtiesContext 어노테이션은 SpringBootTest 로 올라가는 컨텍스트 다시 로드시켜 캐시정보를 아예 삭제시킵니다.&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;@DirtesContext 원리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SpringBootTest 를 돌릴 때 처음에만 &lt;span style=&quot;color: #006dd7;&quot;&gt;빈을 띄우고 캐싱&lt;/span&gt;해서 재사용하여 다음 테스트 케이스는 빠르게 실행이 가능하도록 합니다&lt;/li&gt;
&lt;li&gt;이러한 &lt;u&gt;빈을 재사용하는 조건&lt;/u&gt; 중 하나가 &lt;span style=&quot;color: #006dd7;&quot;&gt;컨텍스트가 오염되지 않았을 때 (상태가 변경되지 않았을 떄)&lt;/span&gt; 인데&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;@DirtiesContext 는 빈을 오염&lt;/span&gt;시켜 캐시기능을 사용하지 않도록하는 설정하는 역할을 수행합니다.&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;@DirtiesContext 단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@DirtiesContext 를 사용하면 편리하지만 매번 새로운 Context를 구성하다보니 시간이 많이 걸리게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqCPxa/btsmr32F36l/Y3R8SLyKu412FkGZm7SJR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqCPxa/btsmr32F36l/Y3R8SLyKu412FkGZm7SJR0/img.png&quot; data-alt=&quot;@DirtiesContext 실행시간&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqCPxa/btsmr32F36l/Y3R8SLyKu412FkGZm7SJR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqCPxa%2Fbtsmr32F36l%2FY3R8SLyKu412FkGZm7SJR0%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;876&quot; height=&quot;355&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;@DirtiesContext 실행시간&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;테스트 9개에 7초정도 걸리는데, 기존의 진행하던 사이드 프로젝트는 단위테스트만 작성하여 104개에 3초가 걸리지 않으니 굉장히 느린 테스트 시간임을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsIOom/btsmr3O8jZQ/nSoiAILkEnNNNjZdLxq3fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsIOom/btsmr3O8jZQ/nSoiAILkEnNNNjZdLxq3fK/img.png&quot; data-alt=&quot;단위테스트 실행시간&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsIOom/btsmr3O8jZQ/nSoiAILkEnNNNjZdLxq3fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsIOom%2Fbtsmr3O8jZQ%2FnSoiAILkEnNNNjZdLxq3fK%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;964&quot; height=&quot;289&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;단위테스트 실행시간&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;✨ 3. 테스트가 시작할 때 마다 데이터 지우기 (with. Truncate)&lt;/h2&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;어차피 계속 문제가되는건 DB! DB를 격리시키자&amp;nbsp;&lt;br /&gt;@DirtiesContext는 편리하지만 굳이 매번 테스트마다 컨텍스트를 리로드할 필요가 없습니다. 중요한건 데이터의 격리이니까요&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;  자 데이터의 격리 방법은 3가지가 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@Sql 혹은 쿼리 수행 (Truncate)&lt;/li&gt;
&lt;li&gt;EntityManager 를 사용 (Truncate)&lt;/li&gt;
&lt;li&gt;테스트 컨테이너의 사용 (이건 안다룰거임 다음 블로그 참고 &amp;rarr; &lt;a href=&quot;https://yeoon.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[사내 TestContainer 적용] Spring boot 통합테스트 도입기&lt;/a&gt; )&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. @Sql 혹은 쿼리 수행 (Truncate)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Sql 어노테이션을 사용해 미리정의해 놓은 쿼리문을 테스트 실행전에 날리는 방법입니다.&lt;/li&gt;
&lt;li&gt;@쿼리를 날려 디비를 클린한 상태로 만들어주기 때문에 각 데이터베이스는 서로 데이터를 침범하지 못합니다.&lt;/li&gt;
&lt;li&gt;@DirtiesContext보다 낮은 비용을 가지지만, 테이블이 늘어날 때 마다 관리해주어야하는 귀찮음이 존재합니다.&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;2. EntityManager 를 사용 (Truncate)&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;1번 방법의 관리포인트를 EntityManager 를 사용하여 알아서 찾도록 바꿨습니다.&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;i&gt;AcceptanceTestConfig.class&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 E2E 테스트에서 공통적인 부분을 추출했습니다.&lt;/li&gt;
&lt;li&gt;DataBaseCleanUp이라는 클래스의 excute 메소드를 매번 테스트 실행전에 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public abstract class AcceptanceTestConfig {
    @Autowired
    private DatabaseCleanup databaseCleanup;

    @BeforeEach
    public void setUp() {
        databaseCleanup.execute();
    }
}&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;i&gt;DatabaseCleanUp.class&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EntityManager 를 사용해 테이블 이름을 추출한 후 trucate 시킵니다.&lt;/li&gt;
&lt;li&gt;truncate 쿼리 실행 후 auto-increament 되었던 id 값도 다시 시작점을 초기화 시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1689752203364&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@TestComponent
public class DatabaseCleanup implements InitializingBean {
    @PersistenceContext
    private EntityManager entityManager;

    private List&amp;lt;String&amp;gt; tableNames;

    @Override
    public void afterPropertiesSet() {
        tableNames = entityManager.getMetamodel().getEntities().stream()
                .filter(entity -&amp;gt; entity.getJavaType().getAnnotation(Entity.class) != null)
                .map(entity -&amp;gt; entity.getName())
                .collect(Collectors.toList());
    }

    @Transactional
    public void execute() {
        entityManager.flush();
        entityManager.createNativeQuery(&quot;SET REFERENTIAL_INTEGRITY FALSE&quot;).executeUpdate();
        for (String tableName : tableNames) {
            entityManager.createNativeQuery(&quot;TRUNCATE TABLE &quot; + tableName).executeUpdate();
            entityManager.createNativeQuery(&quot;ALTER TABLE &quot; + tableName + &quot; ALTER COLUMN ID RESTART WITH 1&quot;).executeUpdate();
        }
        entityManager.createNativeQuery(&quot;SET REFERENTIAL_INTEGRITY TRUE&quot;).executeUpdate();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 수행했을 때 16개의 인수테스트가 3초대로 끊어졌습니다.&lt;/p&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;테이블이 늘어나도 관리포인트가 생기지 않고&lt;/li&gt;
&lt;li&gt;@DirtiesContext 보다 빠르다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNgNiv/btsog0htANT/RHkjELWz8IjQFlc1W6dhOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNgNiv/btsog0htANT/RHkjELWz8IjQFlc1W6dhOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNgNiv/btsog0htANT/RHkjELWz8IjQFlc1W6dhOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNgNiv%2Fbtsog0htANT%2FRHkjELWz8IjQFlc1W6dhOk%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;1058&quot; height=&quot;437&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;통합테스트 데이터베이스 분리 환경 구성 끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Test-Driven Develop</category>
      <category>java</category>
      <category>junit</category>
      <category>SpringBootTest</category>
      <category>test</category>
      <category>test db</category>
      <category>test 환경</category>
      <category>단위테스트</category>
      <category>테스트격리</category>
      <category>테스트분리</category>
      <category>통합테스트</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/453</guid>
      <comments>https://thalals.tistory.com/453#entry453comment</comments>
      <pubDate>Wed, 19 Jul 2023 17:24:31 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Security] 존재하지 않는 API 호출 시 404 대신 401 or 403 을 반환할 때</title>
      <link>https://thalals.tistory.com/452</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;개발환경은 Spring Boot 3.0.x + Spring Security 6.x 입니다&lt;/blockquote&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;/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;Spring Security 에서 404를 반환하지 않는 이유&lt;/li&gt;
&lt;li&gt;문제 해결&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;[결론 요약]&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;error page 설정&lt;/li&gt;
&lt;li&gt;FORWARD_REQUEST_URI - error 처리 경로 (&quot;/error&quot;) spring seurity 에 등록&lt;/li&gt;
&lt;li&gt;AuthenticationEntryPoint 혹은 AccessDeniedHandler 에서 핸들링&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.   문제상황&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 를 적용하니까 이게 6 버전이라 그런건지,, 설정을 놓친건지 클라이언트에서 존재하지 않는 리소스 endpoint 를 호출할 때 404 NOT FOND 를 반환하지 401 UnAuthorization 코드를 반환하는 문제가 있었습니다&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;p data-ke-size=&quot;size16&quot;&gt;그래서 Filter ErrorHandling 도 건드려봤는데 희안하게 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;401&lt;/span&gt; 을 계속 반환하더군요. &lt;br /&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;404 대신 403&lt;/span&gt;을 반환하는 케이스도 존재하는데 그건 아마 Spring Security 설정에&lt;span style=&quot;color: #ee2323;&quot;&gt; HttpBasic&lt;/span&gt; 을 해주었냐 안해주었냐의 차이일겁니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

    httpSecurity
        .csrf().disable();


    return httpSecurity
        .authorizeHttpRequests(
            authorize -&amp;gt; authorize
                .anyRequest().authenticated()

                .and()
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(exceptionHandlerFilter, JwtFilter.class)
        )
        .httpBasic(Customizer.withDefaults())
        .build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Spring Security 에서 404를 반환하지 않는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  일단 제가 이해하기로는 (틀릴수도 있습니다.) HTTP 요청이 URL 로 들어와서 처리되기 까지의 과정을 Spring 서버의 입장에서만 보면&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;background-color: #f6e199;&quot;&gt;[간단한 성공 요청 흐름]&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;내 IP 와 Port 로 특정 URL 처리 요청이 옴&lt;/li&gt;
&lt;li&gt;Filter 에서 정의된 처리들로 먼저 요청에대해 검사함 (Spring Security 라면 인증,인가)&lt;/li&gt;
&lt;li&gt;Filter에서 통과되면 Dispathcer Servlet 이 URL 에 맞는 Handler 를 찾음 (@Controller + @~Mapping 주석이 달린 URL 은 각각의 핸들러가 존재함)&lt;/li&gt;
&lt;li&gt;Handler 가 요청을 처리한 후 HTTP 응답&lt;/li&gt;
&lt;li&gt;다시 필터 거친 후 HTTP 응답&lt;br /&gt;(Spring Boot 의 코드를 까보니까 &lt;span style=&quot;color: #ee2323;&quot;&gt;Filter 가 체이닝메소드 방식으로 구현된 후&amp;nbsp; 마지막 필터에서 &amp;rarr;&amp;nbsp; Servelet 호출&amp;rarr; 필터 역순으로 메소드 종료&lt;/span&gt;)&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이걸 &lt;u&gt;제가 만든 JWT Filter 가 적용된&lt;/u&gt;&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring Security 를 적용했을 때&lt;/span&gt; 가정해서 정리해보면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;내 IP 와 Port 로 특정 URL 처리 요청이 옴&lt;/li&gt;
&lt;li&gt;Filter 에서 정의된 처리들로 먼저 요청에대해 검사함&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Spring Security 의 인증이 필요한 URL 인지 검사함&amp;nbsp;&lt;/li&gt;
&lt;li&gt;인증이 필요한 url 이면 &amp;rarr; 앞단 기본적인 필터들을 통과한 후 JWT Filter 를 호출 함&lt;/li&gt;
&lt;li&gt;jwt filter가 인증절차를 마치면 Spring SecurityContextHolder 에 유저 정보를 저장함&lt;/li&gt;
&lt;li&gt;필터의 가장 마지막단인 AuthentificationFilter 에서 SecurityContextHolder 의 정보를 호출하여 해당 요청에 인가 과정을 거침&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Filter에서 통과되면 Dispathcer Servlet 이 URL 에 맞는 Handler 를 찾음 (@Controller + @~Mapping 주석이 달린 URL 은 각각의 핸들러가 존재함)&lt;/li&gt;
&lt;li&gt;Handler 가 요청을 처리한 후 HTTP 응답&lt;/li&gt;
&lt;li&gt;다시 필터 거친 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;HTTP 응답&lt;br /&gt;(Spring Boot 의 코드를 까보니까&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Filter 가 체이닝메소드 방식으로 구현된 후&amp;nbsp; 마지막 필터에서 &amp;rarr;&amp;nbsp; Servelet 호출&amp;rarr; 필터 역순으로 메소드 종료&lt;/span&gt;)&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wVSm4/btsnwQNPMgG/EtLf557PwPJeU9EcU4etkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wVSm4/btsnwQNPMgG/EtLf557PwPJeU9EcU4etkK/img.png&quot; data-alt=&quot;Filter 들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wVSm4/btsnwQNPMgG/EtLf557PwPJeU9EcU4etkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwVSm4%2FbtsnwQNPMgG%2FEtLf557PwPJeU9EcU4etkK%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;701&quot; height=&quot;308&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Filter 들&lt;/figcaption&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;✔️ exception 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이런 filter 단에서 exception 이 발생하면 인증-인가에 관련된 에러라면 ExceptionTranslationFilter 로 넘어가&amp;nbsp;&lt;br /&gt;AuthenticationEntryPoint 혹은 AccessDeniedHandler 에 위임하여 에러를 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외의 에러처리는 직접 fitler 단에 exception을 핸들링 할 필터를 새롭게 만들어주어야 합니다 &amp;rarr;&lt;a href=&quot;https://thalals.tistory.com/451&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; Filter 단 예외 처리하기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMztbe/btsnD2Ca6lc/kUtoGRzjrHKK3yQ9MNA6P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMztbe/btsnD2Ca6lc/kUtoGRzjrHKK3yQ9MNA6P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMztbe/btsnD2Ca6lc/kUtoGRzjrHKK3yQ9MNA6P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMztbe%2FbtsnD2Ca6lc%2FkUtoGRzjrHKK3yQ9MNA6P1%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;800&quot; height=&quot;409&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 자 그럼 404 Not Found 가 왜 안뜨냐&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 잘못된 url 요청이 들어와도 올바른 jwt 가 HTTP 프로토콜에 담겨있다면 filter 에서는 신경을 쓰지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) filter 처리가 끝난 후 Dispathcer 서블릿에서 적절한 핸들러를 찾지 못했기 때문에 response 에 에러 정보와 404 status 값을 담아 서블릿에서의 처리를 끝냅니다. (여기에는 not found 에 대한 인터셉터 핸들러 작업또한 포함되어 있게죠)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) Dispatcher Servlet 요청이 끝난후 체이닝 메소드로 구성된 filter 단들의 처리가 종료되고 HTTP 응답 프로토콜을 세팅해주어야합니다.&lt;br /&gt;&amp;rarr; 여기서 랜더링 해줄 에러페이지가 있다면 세팅해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwWjbL/btsnFD2wMkR/hfGzNBdQWzFDF939Gw7XMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwWjbL/btsnFD2wMkR/hfGzNBdQWzFDF939Gw7XMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwWjbL/btsnFD2wMkR/hfGzNBdQWzFDF939Gw7XMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwWjbL%2FbtsnFD2wMkR%2FhfGzNBdQWzFDF939Gw7XMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;575&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 저 status 메소드에서 HTTP 상태코드 및 해당 메세지를 처리합니다.&lt;br /&gt;근데 spring security 를 적용했고, 별도의 error page를 지정해 주지 않아 attribute 에 에러다! 정도의 정보를 담는거 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1029&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x0b7S/btsnFft37G6/9FpNWCB1IO2X0YzgAE2kj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x0b7S/btsnFft37G6/9FpNWCB1IO2X0YzgAE2kj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x0b7S/btsnFft37G6/9FpNWCB1IO2X0YzgAE2kj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx0b7S%2FbtsnFft37G6%2F9FpNWCB1IO2X0YzgAE2kj1%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;800&quot; height=&quot;1029&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1029&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 페이지 랜더링에 대한 체크를 위한 메소드에서 response 의 setCommitted(false)를 통해 isCommited() 가 false 값으로 설정되었기 때문에  응답 메세지를 재설정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w6fFS/btsnD4UkL1S/gs5zpbr1k5pgKQxthZW37k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w6fFS/btsnD4UkL1S/gs5zpbr1k5pgKQxthZW37k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w6fFS/btsnD4UkL1S/gs5zpbr1k5pgKQxthZW37k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw6fFS%2FbtsnD4UkL1S%2Fgs5zpbr1k5pgKQxthZW37k%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;750&quot; height=&quot;385&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 여기서 응답 메세지 재설정을 위해 다시 ApplicationDispatcher 을 호출하여 필터를 거쳐 FORWARD_REQUEST_URI 로 요청을 보냅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t82an/btsnD5Z0fex/yA3tky9UVFcYgMReA6KKv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t82an/btsnD5Z0fex/yA3tky9UVFcYgMReA6KKv0/img.png&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;1370&quot; data-is-animation=&quot;false&quot; id=&quot;kEditorPhotosEditingImage-5&quot; width=&quot;800&quot; height=&quot;722&quot; style=&quot;width: 39.8467%; margin-right: 10px;&quot; data-widthpercent=&quot;40.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t82an/btsnD5Z0fex/yA3tky9UVFcYgMReA6KKv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft82an%2FbtsnD5Z0fex%2FyA3tky9UVFcYgMReA6KKv0%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;1518&quot; height=&quot;1370&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oHkop/btsnFeWaZ7j/2LLfrlgz4Kfq3oO3Sm7mWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oHkop/btsnFeWaZ7j/2LLfrlgz4Kfq3oO3Sm7mWk/img.png&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;1090&quot; data-is-animation=&quot;false&quot; style=&quot;width: 58.9905%;&quot; data-widthpercent=&quot;59.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oHkop/btsnFeWaZ7j/2LLfrlgz4Kfq3oO3Sm7mWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoHkop%2FbtsnFeWaZ7j%2F2LLfrlgz4Kfq3oO3Sm7mWk%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;1788&quot; height=&quot;1090&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mxJGf/btsnIKT5zaA/Y87qB2i0wbkzRzpXYa0pH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mxJGf/btsnIKT5zaA/Y87qB2i0wbkzRzpXYa0pH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mxJGf/btsnIKT5zaA/Y87qB2i0wbkzRzpXYa0pH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmxJGf%2FbtsnIKT5zaA%2FY87qB2i0wbkzRzpXYa0pH1%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;1752&quot; height=&quot;1200&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;1200&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;  결과적으로는 필터를 다시 호출하게 되는데 이때는 Request 객체에 에러 url 속성이 존재하기 때문에 jwt filter 가 호출되지 않고 skip 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1055&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lRbY4/btsnw0CHGSq/0sS9tUvfdPbtyKpQeUZLD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lRbY4/btsnw0CHGSq/0sS9tUvfdPbtyKpQeUZLD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lRbY4/btsnw0CHGSq/0sS9tUvfdPbtyKpQeUZLD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlRbY4%2Fbtsnw0CHGSq%2F0sS9tUvfdPbtyKpQeUZLD0%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;1055&quot; height=&quot;491&quot; data-origin-width=&quot;1055&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tthqS/btsnseiiBRK/rPmPkatay4wjfQAVP9ZAXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tthqS/btsnseiiBRK/rPmPkatay4wjfQAVP9ZAXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tthqS/btsnseiiBRK/rPmPkatay4wjfQAVP9ZAXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtthqS%2FbtsnseiiBRK%2FrPmPkatay4wjfQAVP9ZAXK%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;777&quot; height=&quot;138&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-13 오후 5.15.46.jpg&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eJeJMB/btsnwRlwAm7/MmKEgsGwr6HH0kkc5O4zL1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eJeJMB/btsnwRlwAm7/MmKEgsGwr6HH0kkc5O4zL1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eJeJMB/btsnwRlwAm7/MmKEgsGwr6HH0kkc5O4zL1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeJeJMB%2FbtsnwRlwAm7%2FMmKEgsGwr6HH0kkc5O4zL1%2Fimg.jpg&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;890&quot; height=&quot;802&quot; data-filename=&quot;스크린샷 2023-07-13 오후 5.15.46.jpg&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;802&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;(  이게 결론  )&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 그렇기 때문에 마지막 필터단인 AuthorizaitonFilter 에서 SpringContext 에 인증된 사용자 정보가 없기때문에 AccessDeniedException 을 던져버립니다...!!!!!!!!!!!!!&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kBu1O/btsnFbd8KHS/Nhj4Id4XQbdJJjvxch8nY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kBu1O/btsnFbd8KHS/Nhj4Id4XQbdJJjvxch8nY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kBu1O/btsnFbd8KHS/Nhj4Id4XQbdJJjvxch8nY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkBu1O%2FbtsnFbd8KHS%2FNhj4Id4XQbdJJjvxch8nY0%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;1378&quot; height=&quot;654&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;3. 문제 해결 방법 ✨&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  자 원인을 파악하기까지 굉장히 힘들었는데 결론은 SpringSecurity 가 error 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;&lt;b&gt;✔️ 문제 원인을 생각해 보았을 떄 여러가지 해결 방법이 있겠네요&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;error page 설정&lt;/li&gt;
&lt;li&gt;FORWARD_REQUEST_URI - error 처리 경로 (&quot;/error&quot;) spring seurity 에 등록&lt;/li&gt;
&lt;li&gt;AuthenticationEntryPoint 혹은 AccessDeniedHandler 에서 핸들링&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  제가 추천하는 방법은 가장 간단한 2번 방법입니다 !&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;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1938&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qqIc7/btsnF7PQMDT/b9J1trfm4sOwrdiBCCPA50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qqIc7/btsnF7PQMDT/b9J1trfm4sOwrdiBCCPA50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qqIc7/btsnF7PQMDT/b9J1trfm4sOwrdiBCCPA50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqqIc7%2FbtsnF7PQMDT%2Fb9J1trfm4sOwrdiBCCPA50%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;1938&quot; height=&quot;516&quot; data-origin-width=&quot;1938&quot; data-origin-height=&quot;516&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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번 방법도 한번 시도해봐서 내용을 남겨놓겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;authenticationEntryPoint() 와 accessDeniedHandler 메소드를 이용해 default class 대신 적용할 class 들을 주입해주어 핸들링 할 수 있습니다.&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;b&gt;&lt;i&gt;SecurityConfiguration.class&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final JwtFilter jwtFilter;
    private final ExceptionHandlerFilter exceptionHandlerFilter;
    private final HttpRequestEndpointChecker endpointChecker;


    private static final String[] PERMIT_URL_ARRAY = {
    //    &quot;/error&quot;    - 2번 방법은 이거 하나만 추가해주면 됩니당
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
            .csrf().disable()
            .httpBasic().disable()
            .exceptionHandling()
            .authenticationEntryPoint(new MyAuthenticationEntryPoint(endpointChecker))
            .accessDeniedHandler(new MyAccessDeniedHandler(endpointChecker));


        return httpSecurity
            .authorizeHttpRequests(
                authorize -&amp;gt; authorize
                    .requestMatchers(PERMIT_URL_ARRAY).permitAll()
                    .anyRequest().authenticated()

                    .and()
                    .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                    .addFilterBefore(exceptionHandlerFilter, JwtFilter.class)
            )
            .build();
    }

}&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;i&gt;&lt;b&gt;MyAuthenticationEntryPoint.class &amp;amp;&amp;amp; MyAccessDeniedHandler.class&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
public class MyAuthenticationEntryPoint extends Http403ForbiddenEntryPoint {


    private final HttpRequestEndpointChecker endpointChecker;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException authException) throws IOException {
        if (!endpointChecker.isEndpointExist(request)) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, &quot;Resource not found&quot;);
        } else {
            super.commence(request, response, authException);
        }
    }
}

@RequiredArgsConstructor
public  class MyAccessDeniedHandler extends AccessDeniedHandlerImpl {

    private HttpRequestEndpointChecker endpointChecker;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
        AccessDeniedException accessDeniedException) throws IOException, ServletException {

        if (!endpointChecker.isEndpointExist(request)) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, &quot;Resource not found&quot;);
        } else {
            super.handle(request, response, accessDeniedException);
        }
    }
}&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;i&gt;&lt;b&gt;HttpRequestEndpointChecker.class&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;handler 가 존재하는지 안하는지 판단해주기 위한 클래스 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class HttpRequestEndpointChecker {

    private final DispatcherServlet servlet;


    public boolean isEndpointExist(HttpServletRequest request) {

        for (HandlerMapping handlerMapping : servlet.getHandlerMappings()) {
            try {
                HandlerExecutionChain foundHandler = handlerMapping.getHandler(request);
                if (foundHandler != null) {
                    return true;
                }
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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 Security를 설정할 때 꼼꼼하게 고려해가며 설정할걸 후회도 되네요 ㅠㅠ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 파악하고 삽질하는데 굉장히 오래걸렸지만 API 요청이 필터를 거쳐 디스패쳐 서블릿 까지 어떤 흐름으로 이동하는지 어느정도 흐름을 파악해 볼 수 있었던거같아 좋은 경험이었다고 생각합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 오늘도 아디오스!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;[ Spring Security ]&lt;/span&gt;&lt;/h4&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;404 NOT_FOUND 반환하기&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(Spring Filter 요청 처리 흐름)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;======================   Spring Security 시리즈======================&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/436&quot;&gt;Spring Security 가이드 (with. Spring boot 3.0) - 스프링 시큐리티란, 동작 과정, 사용 방법, JWT 발급&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/451&quot;&gt;Spring Security Exception Handling - Filter 단 예외 처리하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://thalals.tistory.com/452&quot;&gt;[Spring Security] 존재하지 않는 API 호출 시 404 대신 401 or 403 을 반환할 때&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; href=&quot;https://thalals.tistory.com/448&quot;&gt;@AuthenticationPrincipal 유닛 테스트 - Custom Mock User 삽입하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>401 instead of 404</category>
      <category>403 isntead of 404</category>
      <category>dont return not found</category>
      <category>java</category>
      <category>security 404</category>
      <category>security error 과정</category>
      <category>security not found</category>
      <category>spring</category>
      <category>springboot</category>
      <category>스프링시큐리티</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/452</guid>
      <comments>https://thalals.tistory.com/452#entry452comment</comments>
      <pubDate>Sat, 15 Jul 2023 18:36:08 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security Exception Handling - Filter 단 예외 처리하기</title>
      <link>https://thalals.tistory.com/451</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;오늘은 Spring Security 를 적용했지만 JWT 가 만료되거나, 잘못된 토큰일 경우 401 코드 뿐만아니라 에러 메세지까지 핸들링 해줄 수 있도록 설정해 주고자 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Spring Security 와 @ControllerAdvice&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 상황은 아래와 같습니다. (&lt;s&gt;깔끔하져?&lt;/s&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lQ9xc/btsmDHSIt0a/GtxD51QRF3jh2sOKRGGFNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lQ9xc/btsmDHSIt0a/GtxD51QRF3jh2sOKRGGFNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lQ9xc/btsmDHSIt0a/GtxD51QRF3jh2sOKRGGFNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlQ9xc%2FbtsmDHSIt0a%2FGtxD51QRF3jh2sOKRGGFNk%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;672&quot; height=&quot;253&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;253&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;일단 저는 Spring Boot 에서 에러 핸들링을 @ControllerAdvice 를 이용한 AOP 방식으로 처리해주었는데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Spring Security 는 Spring Context 의 바깥 쪽, 즉 Filter 단에서 Servlet 에 전달되기 전에 처리됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkp5zk/btsmKzZYmgy/D7oVCKfFd0z0X0zgWGwz0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkp5zk/btsmKzZYmgy/D7oVCKfFd0z0X0zgWGwz0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkp5zk/btsmKzZYmgy/D7oVCKfFd0z0X0zgWGwz0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkp5zk%2FbtsmKzZYmgy%2FD7oVCKfFd0z0X0zgWGwz0K%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;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ControllerAdvice 가 핸들링하는 에러는 Context 내부에서 던져진 Exception이 Controller까지 타고와야 핸들링이 가능해집니다. (&lt;a href=&quot;https://thalals.tistory.com/272&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://thalals.tistory.com/272&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Security는 Context 외부 filter 에서 인증가 인가를 판단해주니, filter 내부에서의 Exception 처리를 해주는 환경을 별도로 구성해 주어야합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Filter Exception Handling 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 filter 에서 핸들링하는 과정자체는 스프링 컨텍스트 내부에서 핸들링해주는거와 유사하게 동작하게 하면됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;A필터에서 에러(Exception)를 던진다.&lt;/li&gt;
&lt;li&gt;A필터보다 앞에있는 B 필터에서 Exception이 체크해주고 핸들링 해준다.&lt;/li&gt;
&lt;li&gt;@ControllerAdvice 에서 Response Body 에 핸들링한 에러 메세지를 담는거처럼 Exception 내용을 Response 에 추가해준다.&lt;/li&gt;
&lt;li&gt;Response 는 필터들을 거쳐 클라이언트에게 전달된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;코드로 보시죠!&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;SecurityConfiguration&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 Spring Security 설정 코드 입니다.&lt;/li&gt;
&lt;li&gt;자세한 코드는 해당 포스팅을 확인해주세요. 거의 유사합니다. &amp;rarr; &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://thalals.tistory.com/436&quot;&gt;Spring Security 가이드 (with. Spring boot 3.0)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;간단하게 설명하자면, 그냥 Custom 하게 만들어준 JWT Filter 내부에 유효성을 검사하고 Exception 을 throw 해주는 로직입니다.&lt;/li&gt;
&lt;li&gt;  이렇게 되면 던져진 Exception 이 던져지기 때문에 해당 Filter 를 벗어나게 됩니다. 그렇기 때문에 401 코드만 반환해주고 에러 메세지는 비게 되는거죠&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final JwtFilter jwtFilter;


    private static final String[] PERMIT_URL_ARRAY = {
        /* 허용해줄 url */
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
            .csrf().disable();

        return httpSecurity
            .authorizeHttpRequests(
                authorize -&amp;gt; authorize
                    .requestMatchers(PERMIT_URL_ARRAY).permitAll()
                    .anyRequest().authenticated()

                    .and()
                    .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
            )
            .httpBasic(Customizer.withDefaults())
            .build();
    }

}


//JwtFilter 내부 Token 유효성 검사 로직
public 핉터 {
	
    ..생략
    
    public boolean validateToken(final String token) {
        try {
            Jwts.parserBuilder().setSigningKey(jwtSecretKey).build().parseClaimsJws(token);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            LogWriteUtils.logInfo(String.format(&quot;exception : %s, message : 잘못된 JWT 서명입니다.&quot;, e.getClass().getName()));
            throw new TokenNotValidateException(&quot;잘못된 JWT 서명입니다.&quot;, e);

        } catch (ExpiredJwtException e) {
            LogWriteUtils.logInfo(String.format(&quot;exception : %s, message : 만료된 JWT 토큰입니다.&quot;, e.getClass().getName()));
            throw new TokenNotValidateException(&quot;만료된 JWT 토큰입니다.&quot;, e);

        } catch (UnsupportedJwtException e) {
            LogWriteUtils.logInfo(String.format(&quot;exception : %s, message : 지원되지 않는 JWT 토큰입니다.&quot;, e.getClass().getName()));
            throw new TokenNotValidateException(&quot;지원되지 않는 JWT 토큰입니다.&quot;, e);

        } catch (IllegalArgumentException e) {
            LogWriteUtils.logInfo(String.format(&quot;exception : %s, message : JWT 토큰이 잘못되었습니다.&quot;, e.getClass().getName()));
            throw new TokenNotValidateException(&quot;JWT 토큰이 잘못되었습니다.&quot;, e);
        }
    }
}

public class TokenNotValidateException extends JwtException {

    public TokenNotValidateException(String message) {
        super(message);
    }

    public TokenNotValidateException(String message, Throwable cause) {
        super(message, cause);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;✔️ SecurityConfiguration 리팩토링&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; JWT Filter 앞에 Response 를 반환해주기 전 Exception Handler Filter를 위치시켜줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final JwtFilter jwtFilter;
    private final ExceptionHandlerFilter exceptionHandlerFilter;


    private static final String[] PERMIT_URL_ARRAY = {
       ..생략
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
            .csrf().disable();

        return httpSecurity
            .authorizeHttpRequests(
                authorize -&amp;gt; authorize
                    .requestMatchers(PERMIT_URL_ARRAY).permitAll()
                    .anyRequest().authenticated()

                    .and()
                    .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                    .addFilterBefore(exceptionHandlerFilter, JwtFilter.class)
            )
            .httpBasic(Customizer.withDefaults())
            .build();
    }

}&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;span&gt; &amp;nbsp;&lt;/span&gt;ExceptionHandlerFilter&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 Filter 에서 만약 핸들링하고자 하는 Exception이 던져졌다면 try-catch 로 핸들링 해줍니다.&lt;/li&gt;
&lt;li&gt;스프링 컨텍스트 내부 처리처럼 단순하게 생각해서, 응답값에 넣고싶은 메세지를 넣어줄 겁니다.&lt;/li&gt;
&lt;li&gt;getWirter() 함수는 문자 텍스트를 응답값에 담을 수 있는 PrintWriter 객체를 반환하고, PrintWriter 에는 write 메소드로 문자열 내용을 담을 수 있습니다.&lt;/li&gt;
&lt;li&gt;즉, Class 객체를 String 으로 변환시켜야합니다. Json 형식이면 좋으므로 ObjectMapper를 사용해 주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class ExceptionHandlerFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
        FilterChain filterChain) throws ServletException, IOException {

        try {
            filterChain.doFilter(request, response);
        } catch (TokenNotValidateException ex) {
            setErrorResponse(HttpStatus.UNAUTHORIZED, request, response, ex);
        } 
    }

    public void setErrorResponse(HttpStatus status, HttpServletRequest request,
        HttpServletResponse response, Throwable ex) throws IOException {

        response.setStatus(status.value());
        response.setContentType(&quot;application/json; charset=UTF-8&quot;);

        response.getWriter().write(
            ErrorResponse.of(
                    HttpStatus.UNAUTHORIZED,
                    ex.getMessage(),
                    request
                )
                .convertToJson()
        );
    }
}

@Getter
@ToString
public class ErrorResponse {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    private final String timestamp;
    private final int status;
    private final String error;
    private final String message;
    private final String path;

    //생성자 및 정적 메소드 생략

    public String convertToJson() throws JsonProcessingException {
        return objectMapper.writeValueAsString(this);
    }

}&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;짜잔~ 그럼 이렇게 이쁘게 Json 타입으로 변환된 에러 응답값이 바디에 담겨서 반환됩니다~~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExceptionHandlerFitler 가 일종의 @RestControllerAdvce + @ExceptionHandler 의 역할을 유사하게 구현하게 되는거죠&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6nVSf/btsmA1EsFIY/5zI79vFN6moyZEMRHLB0U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6nVSf/btsmA1EsFIY/5zI79vFN6moyZEMRHLB0U0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6nVSf/btsmA1EsFIY/5zI79vFN6moyZEMRHLB0U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6nVSf%2FbtsmA1EsFIY%2F5zI79vFN6moyZEMRHLB0U0%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;675&quot; height=&quot;216&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마음에 드네요.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;뭐든 명확한게 좋다고 생각합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이상 끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvEbk1/btsmGlvUSLV/ObE0tB7k1Nu4wK8WcIeaMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvEbk1/btsmGlvUSLV/ObE0tB7k1Nu4wK8WcIeaMk/img.png&quot; data-alt=&quot;귀엽다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvEbk1/btsmGlvUSLV/ObE0tB7k1Nu4wK8WcIeaMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvEbk1%2FbtsmGlvUSLV%2FObE0tB7k1Nu4wK8WcIeaMk%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;458&quot; height=&quot;447&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;귀엽다&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;======================   Spring Security 시리즈======================&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;✔️&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/436&quot;&gt;Spring Security 가이드 (with. Spring boot 3.0) - 스프링 시큐리티란, 동작 과정, 사용 방법, JWT 발급&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt; &lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/451&quot;&gt;Spring Security Exception Handling - Filter 단 예외 처리하기&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span&gt;✔️&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://thalals.tistory.com/452&quot;&gt;[Spring Security] 존재하지 않는 API 호출 시 404 대신 401 or 403 을 반환할 때&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/448&quot;&gt;@AuthenticationPrincipal 유닛 테스트 - Custom Mock User 삽입하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>401 body</category>
      <category>filter</category>
      <category>filter error</category>
      <category>jwt</category>
      <category>security</category>
      <category>security 6</category>
      <category>spring</category>
      <category>spring 3.0</category>
      <category>springboot</category>
      <category>UnAuthorization handler</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/451</guid>
      <comments>https://thalals.tistory.com/451#entry451comment</comments>
      <pubDate>Thu, 6 Jul 2023 23:34:56 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Junit] @SpringBootTest + @Autowired 에러 &amp;quot;Could not autowire. No beans of &amp;quot;</title>
      <link>https://thalals.tistory.com/450</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;때는 바야흐로..&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ATDD 과정 수강 중 각각의 E2E 인수테스트의 데이터베이스를 서로 영향받지않게 격리 시켜주기 위해 &lt;br /&gt;test 패키지에 만든 별도의 클래스를 빈으로 올려 @Autowired 로 주입받고자 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IBJXh/btsmyulOMez/nHj9cb4fVit31aNmlVgzGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IBJXh/btsmyulOMez/nHj9cb4fVit31aNmlVgzGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IBJXh/btsmyulOMez/nHj9cb4fVit31aNmlVgzGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIBJXh%2FbtsmyulOMez%2FnHj9cb4fVit31aNmlVgzGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;291&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;291&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 에러가 뜨는군요!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;DataBaseCleanUp 이라는 Class는 Test 패키지 아래에서만 존재하는 클래스입니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@TestComponent
public class DataBaseCleanUp {
	.. 생략
}&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;@SpringBootConfiguration 의 scan 대상으로는 포함시키지 않기위해 @TestComponent 를 적용해 주었습니다.&lt;/li&gt;
&lt;li&gt;@TestComponent는 Test 를 위한 Component 이지만, javadoc 설명처럼 직접적으로 ComponentScan 을 사용하는 경우 Filter 에 대한 정의를 해주어야하는 유의사항이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt; error code&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;java.lang.IllegalStateException:&amp;nbsp;Unable&amp;nbsp;to&amp;nbsp;find&amp;nbsp;a&amp;nbsp;@SpringBootConfiguration,&amp;nbsp;you&amp;nbsp;need&amp;nbsp;to&amp;nbsp;use&amp;nbsp;@ContextConfiguration&amp;nbsp;or&amp;nbsp;@SpringBootTest(classes=...)&amp;nbsp;with&amp;nbsp;your&amp;nbsp;test&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;@SpringBootTest 빈 컨텍스트 등록 과정&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;@SpringBootTest 어노테이션은 @SpringBootApplication이 있는 Application 을 찾아서 사용하고,&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게 찾은 @SpringBootApplication 안의 @ComponentScan이 스캔한 빈들을 그대로 스프링 컨텍스트에 올려서 사용한다고 하더군요&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;그리고 테스트에서 실행했기 때문에 test 중에서도 hello.core를 포함한 그 하위 패키지는 컴포넌트 스캔의 대상이 된다고 합니다,,,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;에러 해결 방법&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;  지금 깨달아서 해본건데 test 패키지 안에서도 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;'@SpringBootApplication이 있는 Application' 패키지 내부에 존재하는 컴포넌트들&lt;/span&gt;은 스캔의 대상이 됩니다.. &lt;br /&gt;따라서 패키지 구조를 잘 맞춰주면 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/prluj/btsmtDXPupR/PdYzPuzJzb6Sn9nnjVzUEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/prluj/btsmtDXPupR/PdYzPuzJzb6Sn9nnjVzUEk/img.png&quot; data-alt=&quot;이렇게&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/prluj/btsmtDXPupR/PdYzPuzJzb6Sn9nnjVzUEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fprluj%2FbtsmtDXPupR%2FPdYzPuzJzb6Sn9nnjVzUEk%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;396&quot; height=&quot;401&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게&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;  하지만 저 처럼 우매한 실수를 하거나 고집이 쎄서 맞추기 싫다! 하신다면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@ContextConfiguration&lt;/span&gt; 를 사용하시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JPrPL/btsmA3mLgkJ/2Fluz6VPzXm59PmsvXly1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JPrPL/btsmA3mLgkJ/2Fluz6VPzXm59PmsvXly1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JPrPL/btsmA3mLgkJ/2Fluz6VPzXm59PmsvXly1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJPrPL%2FbtsmA3mLgkJ%2F2Fluz6VPzXm59PmsvXly1K%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;393&quot; height=&quot;397&quot; data-origin-width=&quot;393&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;이런 구조였고 아래처럼, 컨택스트를 포함해줄 클래스에 추가해주었습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
@ContextConfiguration(classes= {DataBaseCleanUp.class, SubwayApplication.class})
public abstract class AcceptanceTestConfig {

    @Autowired
    DataBaseCleanUp dataBaseCleanUp;

    @BeforeEach
    void setUp() {
        dataBaseCleanUp.clean();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결론.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;테스트도 패키지 구조를 잘 맞추고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;원리를 이해하고 코딩하자.. ㅠ&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Spring err</category>
      <category>@autowired bean</category>
      <category>autowired not found</category>
      <category>java</category>
      <category>spring</category>
      <category>Spring context</category>
      <category>spring test context</category>
      <category>springboot</category>
      <category>SpringBootTest</category>
      <category>test type</category>
      <category>type found</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/450</guid>
      <comments>https://thalals.tistory.com/450#entry450comment</comments>
      <pubDate>Wed, 5 Jul 2023 17:41:35 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] oneToMany, 일 대 다 관계 조회 성능 테스트 (Jpa, QueryDsl, Java Stream, 단일 DB 조회 쿼리 성능 비교)</title>
      <link>https://thalals.tistory.com/449</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;  오랜만에 포스팅!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;오늘은 Spring 에서 다대다, 일대다 관계를 가지는 RDB 테이블에서의 데이터를 가져올 수 있는 방법과, 어떤 방법이 제일 빨랐는지 기록해볼려 합니다.&lt;/blockquote&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 에서 JPA를 사용하면 oneToMany, manyToMany 의 관계에서 터지는 N+1 문제를 해결하기 위한 방법을 많이 고민하고는 합니다.&lt;br /&gt;저 또한 취준생때 겪었던 문제 중 가장 고민을 많이 고생했던 문제로 포스팅을 남겼던 기억이 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;a href=&quot;https://thalals.tistory.com/295&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Spring] JPA N+1 문제 해결방법(지연로딩 N+1, 2개 이상 ToMany 관계, fetch join, 페이지네이션)&lt;/a&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 이번 포스팅에서는 JPA 의 @OneToMany 기능의 사용을 전혀 고려하지 않고 아래의 방법들에 대해서만 비교를 해보았습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;연관관계를 가지는 테이블마다 조회쿼리 날리기 (4개의 테이블 4개의 쿼리)&lt;/li&gt;
&lt;li&gt;java Stream grouping 후 중복 제거&lt;/li&gt;
&lt;li&gt;queryDsl transform&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;JPA를 사용하지 않은 이유는 지극히 개인적인 생각인데&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;JPA N+1 문제를 신경써줘야한다.&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이전 포스팅과 같이 fetch Join 과 batch size를 조정하여 할 수 있지만, 2개 이상의 연관관걔를 가지는 테이블은 fetch join이 불가능하다.&lt;/li&gt;
&lt;li&gt;데이터 조회를 위해 어플리케이션 단의 설정에서 batch size 제한을 두고 싶지 않았다. (&lt;s&gt;네 한계를 스스로 정하지마....&lt;/s&gt;)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Entity 연관관계 lazy 로딩으로 설정하기 &lt;br /&gt;&amp;rarr; eager 와 lazy가 혼합되고 이거에 의존적이게 되면 추후에 어디서 에러가 터졌는지 파악하기 힘들더라..&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;JPA의 연관관계를 사용하다 보면 신경써줘야할게 많다.&lt;/span&gt;&lt;br /&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;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[결론]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 단일 테이블에 CRUD 쿼리에서는 JPA 가 정말편하고, ORM 의 생산적인 기능을 너무 똑똑하게 해내지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 관계를 가지는 테이블간의 쿼리는 queryDsl 로 직접 제어하는게 &amp;rarr; (지금의 제 생각에는) 조금 더  지속가능한 코드를 만들어 갈 수 있는 방법이 아닌가라는 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;잡설이 길었지만 &lt;br /&gt;이제 Spring + Java 에서 복수개의 일대다 관계를 가지는 테이블을 조회하는 방법에대해서 정리해보겠습니다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;spring boot 3.0.+&lt;/li&gt;
&lt;li&gt;java 17&lt;/li&gt;
&lt;li&gt;hibernate 6.1.7&lt;/li&gt;
&lt;li&gt;Mysql&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;100,000 User&lt;/li&gt;
&lt;li&gt;각 유저당 3개의 이상형&lt;/li&gt;
&lt;li&gt;3개의 관심사&lt;/li&gt;
&lt;li&gt;3개의 프로필 사진을 가짐 &amp;rarr; 단순 계산 : 10만 | 30만 | 30만 | 30만&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjQ6vP/btslS66mgli/IIVJnIui7uAhfjwUrGSm91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjQ6vP/btslS66mgli/IIVJnIui7uAhfjwUrGSm91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjQ6vP/btslS66mgli/IIVJnIui7uAhfjwUrGSm91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjQ6vP%2FbtslS66mgli%2FIIVJnIui7uAhfjwUrGSm91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;483&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;690&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;제 개인적인 프로젝트지만 세상이 흉흉하기에(?) 내부 컬럼은 숨기겠습니다..ㅋㅋㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 불러오고자 하는 결과는, 각 유저당 매핑되는 이상형리스트, 관심사리스트, 프로필 리스트 입니다. response 만 보자면 아래와 같게됩니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public record Response(
    String username,
    String userUuid,
	
   //.. 생략..

    List&amp;lt;IdealTypeResponse&amp;gt; idealTypeResponseList,
    List&amp;lt;InterestResponse&amp;gt; interestResponses,
    List&amp;lt;UserProfilePhotoResponse&amp;gt; userProfilePhotos,

) {}


//이상형
public record IdealTypeResponse(
    Integer idx,
    String name
) {}


// 관심사
public record InterestResponse(
    Integer idx,
    String name
) {}


//프로필 사진
public record UserProfilePhotoResponse(
    String url
) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Simple Query (N번의 select 문)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 방법이고, 회사코드나 주변 지인분들에게 여쭤봤을 때 &lt;br /&gt;복잡하지않은 관계를(1~2개의 연관 테이블) 가지고 데이터 수가 많지 않은 Entity 들은 단순하게 n 개의 select 문을 날리고 있더군요&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;s&gt;귀찮아서&lt;/s&gt;&amp;nbsp;이렇게 할려했으나..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에 연결하고 해지하는, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;&lt;/span&gt; 1 번의 select 비용&lt;/span&gt;이 굉장히 비싸다&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;시나리오는 아래와 같았고, 5번은 테스트하고자 하는 부분이 아니기에 생략했습니다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;10만 유저 중 100 명의 유저를 조회&lt;/li&gt;
&lt;li&gt;100명의 유저의 &lt;u&gt;uuid로 In 조건&lt;/u&gt; 30만 이상형 테이블 중 300건 조회&lt;/li&gt;
&lt;li&gt;100명의 유저의 &lt;u&gt;uuid로 In 조건&lt;/u&gt; 30만 관심사 테이블 중 300건 조회&lt;/li&gt;
&lt;li&gt;100명의 유저의 &lt;u&gt;uuid로 In 조건&lt;/u&gt; 30만 프로필 사진 테이블 중 300건 조회&lt;/li&gt;
&lt;li&gt;4개의 select 로 불러온 4개의 List 를 각 유저에 맞는 데이터로 매칭&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 메소드로 나눌까 하다가, (&lt;s&gt;귀찮아서&lt;/s&gt;) 심플하게 하나의 querydsl로 몰았습니다 ㅎㅎ 테스트니까여~&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
public List&amp;lt;MainScreenUserInfoTransformMapper&amp;gt; find_simple( ) {

    // 1번. select user
    List&amp;lt;User&amp;gt; userList = 대충 100명의 유저

    // 2번. select 프로필 사진
    List&amp;lt;UserProfilePhoto&amp;gt; userProfilePhotos = queryFactory.selectFrom(userProfilePhoto)
        .where(userProfilePhoto.userUuid.in(
            userList.stream().map(User::getUserUuid).toList())
        )
        .fetch();

    // 3번. select 이상형 리스트
    List&amp;lt;UserIdealType&amp;gt; userIdealTypeList = queryFactory.selectFrom(userIdealType)
        .where(userIdealType.userUuid.in(
            userList.stream().map(User::getUserUuid).toList())
        )
        .fetch();

    // 4번. select 관심사 사진
    List&amp;lt;UserInterests&amp;gt; userInterestsList = queryFactory.selectFrom(userInterests)
        .where(userInterests.userUuid.in(
            userList.stream().map(User::getUserUuid).toList())
        )
        .fetch();

    return null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 진짜 놀라웠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 코드로 돌려보았을 때, 4개의 쿼리가 날라가는데만 30초 가까이 걸렸습니다.&lt;/li&gt;
&lt;li&gt;10만 데이터를 테스트하기전에 1만으로도 한번 돌려봤는데 3초가 걸렸습니다.&amp;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-06-30 오전 9.20.09.jpg&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7Phij/btslXoE1IdB/6VQi0spJh6z4qQPZX064d1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7Phij/btslXoE1IdB/6VQi0spJh6z4qQPZX064d1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7Phij/btslXoE1IdB/6VQi0spJh6z4qQPZX064d1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7Phij%2FbtslXoE1IdB%2F6VQi0spJh6z4qQPZX064d1%2Fimg.jpg&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;762&quot; height=&quot;120&quot; data-filename=&quot;스크린샷 2023-06-30 오전 9.20.09.jpg&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론&amp;nbsp; 프러덕션 코드상 실제 날아가는 쿼리 로직은 아래와 같은 상황도 고려해야하지만, 그럼에도 불구하고 굉장히 많은 시간이 걸린게 사실입니다.&lt;/p&gt;
&lt;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;페이징을 위해 커버링 인덱스도 사용함&lt;/li&gt;
&lt;li&gt;user_uuid 가 외래키 이지만 in 조건절에 String 값 100건을 넣어 조회&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 java 매칭 코드까지 생각한다면 끔찍하네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Java Stream grouping 후 중복 제거&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;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;시나리오&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;연관되는 테이블들을 inner join 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 컬럼 1당 - 이상형 3, 관심사 3, 프로필 3 의 행이 매칭되어 27개의 컬럼 결과값이 생성됩니다.&lt;/li&gt;
&lt;li&gt;해당 값들을 dto 객체에 담습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유저 고유 값으로 Stream GroupingBy 을 통해 데이터를 추출하고자 하는 데이터별로 추려냅니다.&lt;/li&gt;
&lt;li&gt;27개의 컬럼 중 중복되는 이상형, 관심사, 프로필 중 중복되는 데이터를 제거합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;시나리오대로 해봅니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 테이블 inner join&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계산대로 라면 100개의 유저를 조회했으니 2700개의 데이터가 리스트안에 들어갈 것 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//queryDsl

@Override
public List&amp;lt;MainScreenUserInfoMapper&amp;gt; find( ) {

	//.. 생략

    return queryFactory.select(
            new QMainScreenUserInfoMapper(
                user.username,
                user.userUuid,
                new QIdealTypeMapper(
                    idealType.idx,
                    idealType.name
                ),
                new QInterestMapper(
                    interest.idx,
                    interest.name
                ),
                new QUserProfilePhotoMapper(
                    userProfilePhoto.idx,
                    userProfilePhoto.userUuid,
                    userProfilePhoto.url
                )
            )
        )
        .from(user)
        .innerJoin(userProfilePhoto)
        .on(user.userUuid.eq(userProfilePhoto.userUuid))
        .innerJoin(userInterests)
        .on(user.userUuid.eq(userInterests.userUuid))
        .innerJoin(interest)
        .on(userInterests.interestIdx.eq(interest.idx))
        .innerJoin(userIdealType)
        .on(user.userUuid.eq(userIdealType.userUuid))
        .innerJoin(idealType)
        .on(userIdealType.idealTypeIdx.eq(idealType.idx))
        .where(
            //100개 추출 커버링 인덱스
            user.idx.in(userDailyFallingIdxList)
        )
        .fetch();
}

//데이터를 담을 Projection DTO
public record MainScreenUserInfoMapper(
    String userUuid,
    IdealTypeMapper idealTypeMapper,
    InterestMapper interestMapper,
    UserProfilePhotoMapper userProfilePhotoMapper
) {

    @QueryProjection
    public MainScreenUserInfoMapper{}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2) Stream GroupingBy&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stream 으로 userUuid 가 같은 데이터들을 추출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private MainScreenResponse getMainScreenResponse(//생략) {
    
    final Map&amp;lt;String, List&amp;lt;MainScreenUserInfoMapper&amp;gt;&amp;gt; listMap = findService.find()
        .stream()
        .collect(Collectors.groupingBy(MainScreenUserInfoMapper::userUuid));
}&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;3) 중복 데이터 제거&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스의 역할을 나누기 위해 Response List 를 가진 일급 컬렉션을 만들어 주었습니다.&lt;/li&gt;
&lt;li&gt;이 일급 컬렉션의 역할은 중복데이터가 제거된 response List 를 가지는 것 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;public record MainScreenUserInfoResponseGroup(
    List&amp;lt;MainScreenUserInfoResponse&amp;gt; responses
) {

    public static MainScreenUserInfoResponseGroup of(
        final Map&amp;lt;String, List&amp;lt;MainScreenUserInfoMapper&amp;gt;&amp;gt; listMap) {

        final List&amp;lt;MainScreenUserInfoResponse&amp;gt; responses = new ArrayList&amp;lt;&amp;gt;();

        for (Map.Entry&amp;lt;String, List&amp;lt;MainScreenUserInfoMapper&amp;gt;&amp;gt; entry : listMap.entrySet()) {
            List&amp;lt;MainScreenUserInfoMapper&amp;gt; mapperList = entry.getValue();

            responses.add(MainScreenUserInfoResponse.of(mapperList));
        }

        return new MainScreenUserInfoResponseGroup(
            responses.stream()
                .sorted(Comparator.comparing(MainScreenUserInfoResponse::userDailyFallingCourserIdx))
                .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;중복된 데이터를 제거하기 위해, 하나의 user_uuid 를 가지는 27개의 컬럼을 받아 HashSet으로 중복데이터를 제거해 주었습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public record MainScreenUserInfoResponse(
    String userUuid,
    HashSet&amp;lt;IdealTypeResponse&amp;gt; idealTypeResponseList,
    HashSet&amp;lt;InterestResponse&amp;gt; interestResponses,
    HashSet&amp;lt;UserProfilePhotoResponse&amp;gt; userProfilePhotos
) {

    public static MainScreenUserInfoResponse of(final List&amp;lt;MainScreenUserInfoMapper&amp;gt; mapperList) {

        if (mapperList.isEmpty()) {
            return null;
        }

        MainScreenUserInfoMapper base = mapperList.get(0);

        return new MainScreenUserInfoResponse(
            base.userUuid(),
            new HashSet&amp;lt;&amp;gt;(mapperList.stream().map(MainScreenUserInfoMapper::idealTypeMapper)
                .map(IdealTypeResponse::of).toList()),
            new HashSet&amp;lt;&amp;gt;(mapperList.stream().map(MainScreenUserInfoMapper::interestMapper)
                .map(InterestResponse::of).toList()),
            new HashSet&amp;lt;&amp;gt;(mapperList.stream().map(MainScreenUserInfoMapper::userProfilePhotoMapper)
                .map(UserProfilePhotoResponse::of).toList())
        );
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;  결과&lt;/h3&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;1만 유저 일 때는 1012ms 정도&lt;/li&gt;
&lt;li&gt;10만 유저 일 때는 1209 ms 정도로 큰차이를 보이지는 않았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ypp8v/btslRoHaD5a/x6IS5K7D7kps1euU7Q1ST1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ypp8v/btslRoHaD5a/x6IS5K7D7kps1euU7Q1ST1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ypp8v/btslRoHaD5a/x6IS5K7D7kps1euU7Q1ST1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fypp8v%2FbtslRoHaD5a%2Fx6IS5K7D7kps1euU7Q1ST1%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;790&quot; height=&quot;103&quot; data-origin-width=&quot;790&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;3. queryDsl transForm 사용하기&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;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;transForm&lt;/span&gt; 은 QueryDsl 에서 결과처리를 커스터마이징 하기위해 제공하는 기능 중 하나입니다.&lt;/li&gt;
&lt;li&gt;.transForm() 집합 함수는 메모리에서 쿼리 결과에 대한 집한 연산을 수행하는&lt;span style=&quot;background-color: #f6e199;&quot;&gt; com.mysema.query.group.GroupBy&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;클래스에서 제공합니다.&lt;/span&gt;&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;i&gt;&lt;b&gt;config&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저, Spring boot 3.0.+ (Hibernate 6.1.+) 버전에서는 평범하게 transForm을 사용할려고하니 아래와 같은 에러가 발생해서 추가적인 설정을 해줘야 했습니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd; color: #ee2323; text-align: start;&quot;&gt;org.hibernate.ScrollableResults.get(int)&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &amp;rarr; 이게 JPA 와 하이버네이트의 버전이 맞지않을때 나는 에러라고 하는데,,, &lt;br /&gt;아래처럼 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;JPQLTemplates.DEFAULT&lt;/span&gt; 설정값을 넣으니 해결되었습니다 ... 솔직히 잘모르겠습니다..!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@RequiredArgsConstructor
public class QueryDslConfig {

    private final EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager);
    }
}&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;b&gt;&lt;i&gt;queryDsl&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고 : &lt;a href=&quot;http://querydsl.com/static/querydsl/3.7.2/reference/ko-KR/html/ch03s02.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://querydsl.com/static/querydsl/3.7.2/reference/ko-KR/html/ch03s02.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;transForm을 사용하면 groupBy로 묶은 key를 기준으로 Map 을 반홥니다. 여기까지는 stream groupinby랑 결과값이 똑같지만&lt;/li&gt;
&lt;li&gt;list 함수를 통해 결과를 모을 수 있습니다.&lt;/li&gt;
&lt;li&gt;조인을 하게되면 결과값이 카다시안 곱으로 중복 생성되기 때문에, 이에 대한 결과값들을 중복제거 해주기 위해 Group.set 으로 묶어 반환해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.querydsl.core.group.GroupBy;

//...

@Override
public List&amp;lt;MainScreenUserInfoTransformMapper&amp;gt; find_atTransform() {

    return queryFactory.selectFrom(user)
        .innerJoin(userProfilePhoto)
        .on(user.userUuid.eq(userProfilePhoto.userUuid))
        .innerJoin(userInterests)
        .on(user.userUuid.eq(userInterests.userUuid))
        .innerJoin(interest)
        .on(userInterests.interestIdx.eq(interest.idx))
        .innerJoin(userIdealType)
        .on(user.userUuid.eq(userIdealType.userUuid))
        .innerJoin(idealType)
        .on(userIdealType.idealTypeIdx.eq(idealType.idx))
        .where(
            user.idx.in(//커버링 인덱스 리스트)
        )
        .transform(GroupBy.groupBy(user.userUuid).list(
            new QMainScreenUserInfoTransformMapper(
                user.username,
                user.userUuid,
                GroupBy.set(
                    new QIdealTypeMapper(
                        idealType.idx,
                        idealType.name
                    )
                ),
                GroupBy.set(
                    new QInterestMapper(
                        interest.idx,
                        interest.name
                    )
                ),
                GroupBy.set(
                    new QUserProfilePhotoMapper(
                        userProfilePhoto.idx,
                        userProfilePhoto.userUuid,
                        userProfilePhoto.url
                    )
                )
            )));
}&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;i&gt;&lt;b&gt;Query Projection Dto&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;당연히 쿼리를 담을 프로젝션 클래스에서 Set으로 설정해야겠죠?&lt;/li&gt;
&lt;li&gt;저는 jdk 17 을 사용해서 record 를 사용했지만 일반 pojo class 를 사용하신다면 @EqualsAndHashCode를 설정해야합니다. (중복 체크를 위해)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public record MainScreenUserInfoMapper(
    String username,
    String userUuid,
    Set&amp;lt;IdealTypeMapper&amp;gt; idealTypeMapper,
    Set&amp;lt;InterestMapper&amp;gt; interestMapper,
    Set&amp;lt;UserProfilePhotoMapper&amp;gt; userProfilePhotoMapper
) {

    @QueryProjection
    public MainScreenUserInfoMapper {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;  결과&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결과입니다. 2번 방법과 로직의 흐름은 같다 하더라도, 하이버네이트가 메모리에서 처리하는게 최적화가 더 잘되어있나 봅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;10만 유저 일 때는 989 ms 라는 결과 나왔고 &amp;rarr; JAVA Stream GroupBy 보다 단순하게 계산해보면 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;약 22%&lt;/span&gt;&lt;/b&gt;&amp;nbsp; 빨랐습니다.&lt;/li&gt;
&lt;li&gt;또한 결과를 제하더라도, 가독성과 의존성 측면에서 데이터를 담아오는 Projection Class 만 List 대신 Set 을 사용하기 때문에 유지보수에 유리하다고 생각됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zApp7/btslZuGp9OD/dNkCHXi51Z2iW51iOqYeH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zApp7/btslZuGp9OD/dNkCHXi51Z2iW51iOqYeH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zApp7/btslZuGp9OD/dNkCHXi51Z2iW51iOqYeH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzApp7%2FbtslZuGp9OD%2FdNkCHXi51Z2iW51iOqYeH1%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;990&quot; height=&quot;113&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재밌는 실험이 끝나네요&lt;br /&gt;이렇게 데이터를 넣어가면서 각 상황에 맞춰 시간을 테스트해본건 처음인데 유의미한결과 도출되어서 다행입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 를 사용하지는 않았지만 Spring 에서 1 대 다의 관계를 가지는 데이터를 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;span style=&quot;color: #006dd7;&quot;&gt; &lt;u&gt;카디널리티 곱에의해 생성되는 데이터가 최대 27개&lt;/u&gt;&lt;/span&gt;의 컬럼으로 &lt;span style=&quot;color: #000000; background-color: #f6e199;&quot;&gt;항상 제한되어있는 상황&lt;/span&gt;이기 때문에,&lt;br /&gt;상황에 따라 결과는 달라질 수 있다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 끝..!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;querydsl 공식 문서 : &lt;a href=&quot;http://querydsl.com/static/querydsl/3.7.2/reference/ko-KR/html/ch03s02.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://querydsl.com/static/querydsl/3.7.2/reference/ko-KR/html/ch03s02.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;group by 예제 코드 : &lt;a href=&quot;https://github.com/querydsl/querydsl/blob/master/querydsl-collections/src/test/java/com/querydsl/collections/GroupByTest.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/querydsl/querydsl/blob/master/querydsl-collections/src/test/java/com/querydsl/collections/GroupByTest.java&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>1:n 조회 방법</category>
      <category>ManyToOne</category>
      <category>n+1 해결방법</category>
      <category>OneToMany</category>
      <category>spring boot</category>
      <category>다대다 조회 방법</category>
      <category>다대일 조회</category>
      <category>스프링</category>
      <category>일대다 조회</category>
      <category>자바</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/449</guid>
      <comments>https://thalals.tistory.com/449#entry449comment</comments>
      <pubDate>Fri, 30 Jun 2023 16:51:30 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Security] @AuthenticationPrincipal 유닛 테스트 - Custom Mock User 삽입하기</title>
      <link>https://thalals.tistory.com/448</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;✨ 이번 포스팅에서는, @AuthenticationPrincipal 유닛 테스트에 대해서 기록해보고자 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트는 JWT + Spring Security를 사용하고 있고, Spring Security 에서 제공해주는 User 객체가 아닌 실제 디비에 저장되어있는 Custom 한 User 정보를 이용해 인증된 유저를 &lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;SecurityContextHolder&lt;/span&gt;에 저장하는 방식으로 구현해놓았습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;참고   &lt;a href=&quot;https://thalals.tistory.com/436&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring Security 가이드 (with. Spring boot 3.0) - 스프링 시큐리티란, 동작 과정, 사용 방법, JWT 발급&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;그렇기 때문에 요청이 들어오는 Rest API 에 담긴 JWT가 정상적으로 인증이 가능한 토큰이라면 &lt;br /&gt;&amp;rarr; 지속적으로 DB에 접근하지 않고도 로그인한 객체 정보를 가져오기 위해 사용하는 @AuthenticationPrincipal 어노테이션을 사용할 수 있습니다.&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;  여기까지는 좋았는데 RestDocs 를 사용해서 Controller 러를 테스트하는데 문제가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✔️ Spring Security Controller Unit Test&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 스프링 시큐리티를 사용하는 Controller 는 @WithMockUser 를 사용하면 moking 유저를 사용해서 인증절차를 통과시킬 수 있습니다.&lt;br /&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;  하지만, @WithMockUser를 사용하면 Spring security.core.userdetails의 User객체가 들어가기 때문에 아래와같이 직접 생성한 특정 User 객체를 이용할 때는 Error가 발생합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@GetMapping(&quot;chat/rooms&quot;)
public ResponseEntity&amp;lt;List&amp;lt;ChatRoomResponse&amp;gt;&amp;gt; findMyChatRoom(@AuthenticationPrincipal final User user) {

    return ResponseEntity.ok(chatFacade.findMyRoom(user.getUserUuid()));
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;core user에는 username, password, role 만이 들어가 있기 때문이기도 하고&lt;/li&gt;
&lt;li&gt;@AuthenticationPrincipal 로 인해 바인딩 되는 과정에서, 내가 주입받고자하는 instance 타입이 아니라면 바인딩이 되지 않기 때문입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m0Pov/btsj5RcGZkp/pv13zRuAjDKPAw2YhxmNk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m0Pov/btsj5RcGZkp/pv13zRuAjDKPAw2YhxmNk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m0Pov/btsj5RcGZkp/pv13zRuAjDKPAw2YhxmNk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm0Pov%2Fbtsj5RcGZkp%2Fpv13zRuAjDKPAw2YhxmNk0%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;919&quot; height=&quot;604&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;604&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; @AuthenticationPrincipal 이 Context에서 로그인한 User 객체를 바인딩하는 과정을 살펴보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 정보를 불러온 User 객체(&lt;span style=&quot;background-color: #f6e199;&quot;&gt;principal.getClss()&lt;/span&gt;)와 바인딩하고자하는 파라미터 객체 (&lt;span style=&quot;background-color: #f6e199;&quot;&gt;parameter.getParameterType()&lt;/span&gt;) 을 비교하여 맞지않으면 null 을 반환해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 Test 실행 시 NullPointException이 발생했습니다 !&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@WebMvcTest(ChatController.class)
class ChatControllerTest extends ControllerTestConfig {

    @Test
    @WithMockUser
    @DisplayName(&quot;이렇게하면 @Authentication 으로 바인딩될 떄 null 이 들어감&quot;)
    void getChatRooms() throws Exception {


        //then
        ResultActions resultActions = mockMvc.perform(
            RestDocumentationRequestBuilders.get(&quot;/rest-api/url&quot;)
                .header(&quot;Authorization&quot;, &quot;Bearer {ACCESS_TOKEN}&quot;)
        ).andDo(
            document(&quot;RestDocs 문서&quot;,
                preprocessRequest(prettyPrint()),
                preprocessResponse(prettyPrint()),
                resource(
                    ResourceSnippetParameters.builder()
                        .requestFields()
                        .responseFields(
                           ...
                          )
                        .responseSchema(Schema.schema(&quot;ChatRoomResponse&quot;))
                        .build()
                )
            ));

        resultActions.andExpect(MockMvcResultMatchers.status().isOk());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJYHS/btsj8WwKT7o/gZSH47x2AJdtONNlU5dFsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJYHS/btsj8WwKT7o/gZSH47x2AJdtONNlU5dFsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJYHS/btsj8WwKT7o/gZSH47x2AJdtONNlU5dFsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJYHS%2Fbtsj8WwKT7o%2FgZSH47x2AJdtONNlU5dFsk%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;994&quot; height=&quot;299&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✔️ @WithMockUser 에 Custom User 객체를 주입하는 방법&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;제가 찾은 가장 우아하고(?) 테스트코드 또한 여기저기 지저분하게 작성하지 않은 방법은 Test 시 사용할 저만의 @WithMockUser 어노테이션을 만드는 것 입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; Custom 한 @WithMockUser 만들기&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;@WithCustomMockUser&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@WithSecurityContext 어노테이션을 사용하면 사용할 SecurityContext를 지정해줄 수 있습니다. 즉 어떤 객체로 바인딩 할 것인지를 직접 만들어서 적용할 수 있습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithCustomMockUserSecurityContextFactory.class)
public @interface WithCustomMockUser {


    String userUuid() default &quot;userUuid&quot;;

    String role() default &quot;NORMAL&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;WithCustomMockUserSecurityContextFactory.class&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용할 SecurityContext를 정의해줄 클래스입니다.&lt;/li&gt;
&lt;li&gt;WithSecuriryContextFactory 제네릭 바운디드 타입이 Annotation이므로 위에서 만들어준 WithMockUser를 타입으로 넣어줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class WithCustomMockUserSecurityContextFactory implements WithSecurityContextFactory&amp;lt;WithCustomMockUser&amp;gt; {

    @Override
    public SecurityContext createSecurityContext(WithCustomMockUser annotation) {
        String userUuid = annotation.userUuid();
        String role = annotation.role();

        //여기서 바인딩되어 반환할 객체를 정의해주면 됩니다
        User user = UserFixture.make(userUuid, role);
        
        UsernamePasswordAuthenticationToken token =
            new UsernamePasswordAuthenticationToken(user, &quot;password&quot;, List.of(new SimpleGrantedAuthority(role)));
        SecurityContext context = SecurityContextHolder.getContext();
        context.setAuthentication(token);
        return context;
    }
}&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;i&gt;ControllerTest.class&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 해서 주입해줄 mok user를 어노테이션을 이용하여 커스텀하게 주입할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@WebMvcTest(ChatController.class)
class ChatControllerTest extends ControllerTestConfig {

    @Test
    @WithCustomMockUser
    @DisplayName(&quot;이렇게하면 @Authentication 으로 바인딩될 떄 null 이 들어감&quot;)
    void getChatRooms() throws Exception {


        //then
        ResultActions resultActions = mockMvc.perform(
            RestDocumentationRequestBuilders.get(&quot;/rest-api/url&quot;)
                .header(&quot;Authorization&quot;, &quot;Bearer {ACCESS_TOKEN}&quot;)
        ).andDo(
            document(&quot;RestDocs 문서&quot;,
                preprocessRequest(prettyPrint()),
                preprocessResponse(prettyPrint()),
                resource(
                    ResourceSnippetParameters.builder()
                        .requestFields()
                        .responseFields(
                           ...
                          )
                        .responseSchema(Schema.schema(&quot;ChatRoomResponse&quot;))
                        .build()
                )
            ));

        resultActions.andExpect(MockMvcResultMatchers.status().isOk());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6Lek6/btsj3mqBBTu/TNu7oLiUnBpLu7OQ9GXgx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6Lek6/btsj3mqBBTu/TNu7oLiUnBpLu7OQ9GXgx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6Lek6/btsj3mqBBTu/TNu7oLiUnBpLu7OQ9GXgx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6Lek6%2Fbtsj3mqBBTu%2FTNu7oLiUnBpLu7OQ9GXgx1%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;991&quot; height=&quot;569&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;만족스러운 스프링 시큐리티 mock user inject 방법이었습니다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;✅ Spring Security&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;컨트롤러 유닛 테스트&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;@AuthenticationPrincipal Mocking 하기&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;======================   Spring Security 시리즈======================&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;✔️&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/436&quot;&gt;Spring Security 가이드 (with. Spring boot 3.0) - 스프링 시큐리티란, 동작 과정, 사용 방법, JWT 발급&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;✔️&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://thalals.tistory.com/451&quot;&gt;Spring Security Exception Handling - Filter 단 예외 처리하기&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span&gt;✔️&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://thalals.tistory.com/452&quot;&gt;[Spring Security] 존재하지 않는 API 호출 시 404 대신 401 or 403 을 반환할 때&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt; &lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #04beb8;&quot; href=&quot;https://thalals.tistory.com/448&quot;&gt;@AuthenticationPrincipal 유닛 테스트 - Custom Mock User 삽입하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Test-Driven Develop</category>
      <category>@WithMockUser</category>
      <category>authentionprincipal mocking</category>
      <category>security controller</category>
      <category>security mock</category>
      <category>spring</category>
      <category>unittest</category>
      <category>스프링</category>
      <category>스프링부트</category>
      <category>스프링시큐리티</category>
      <category>자바</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/448</guid>
      <comments>https://thalals.tistory.com/448#entry448comment</comments>
      <pubDate>Thu, 15 Jun 2023 21:20:46 +0900</pubDate>
    </item>
    <item>
      <title>Spring Data mongoDB + mysql 사용하기 (with. queries)</title>
      <link>https://thalals.tistory.com/447</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;=====&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;채팅서버 구현하기 시리즈&lt;/span&gt;&amp;nbsp; =====&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #212164; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://thalals.tistory.com/442&quot;&gt;[Web-Network] - 채팅 서버 설계를 위한 배경지식 정리 (HTTP, WebSocket, WebRTC)&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;✔️&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://thalals.tistory.com/445&quot;&gt;[Spring/Spring Boot] - Spring WebSocket 공식문서 가이드 살펴보기&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;✔️&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://thalals.tistory.com/446&quot;&gt;[Spring/Spring Boot] - Spring WebSocket STOMP 채팅 서버 구현하기 (with. JWT, Exception Handling)&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #666666;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt; &lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://thalals.tistory.com/447&quot;&gt;[Spring/Spring Boot] - Spring Data mongoDB + mysql 사용하기 (with. queries)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅서버를 구현하면서,&amp;nbsp; 채팅 내역을 저장할 DB로 nosql 을 사용하기로 했고 그 중에 mongoDB를 사용하고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 API 서버에서는 JPA를 이용해 mysql 을 사용하고 있었고, 기존 프로젝트에 mongoDB를 추가 사용해 mysql + mongoDB의 구조를 가져가고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결자체는 생각보다 간단했고, spring-data-mongoDB를 별도로 사용해 jpa 와 충돌할것도 수정할 것도 없었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. spring data mongoDB 설정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;✔️ build.gradle&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;clean&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;//mongo
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'&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;i&gt;&lt;i&gt;✔️&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/i&gt;application.yaml&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring:
    datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql:-
        username: -
        password: -
    data:
        mongodb:
            host: -
            port: 27017
            authentication-database: admin
            username: username
            password: password
            database: my_db&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 추가적으로 날라가는 mongoDB 쿼리가 보고싶다면 아래처럼 로깅을 설정해주시면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;logging:
    level:
        org:
            hibernate:
                SQL: DEBUG
                orm.jdbc.bind: TRACE
            springframework:
                data:
                    mongodb:
                        core:
                            MongoTemplate: DEBUG&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;i&gt;✔️&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; MongoDBConfig&lt;/span&gt;&lt;/i&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실 별도의 config 설정이 필수는 아닙니다. 이거 없어도 잘 저장하고 조회하는데&lt;/li&gt;
&lt;li&gt;저장할때 컬럼에 자동적으로 _class 가 생기는걸 방지하기 위한 설정 파일입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;//_class 컬럼이 자동 생성 방지
@Configuration
@EnableMongoRepositories(&quot;com.example.chatserver.repository&quot;)
@EnableMongoAuditing
public class MongoDBConfig {

    @Bean
    public MappingMongoConverter mappingMongoConverter(
        MongoDatabaseFactory mongoDatabaseFactory,
        MongoMappingContext mongoMappingContext
    ) {
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));
        return converter;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. mongoDB Document Entity&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  JPA 를 사용하는 RDMS는 spring 에서 @Entity 를 사용하지만 nosql인 mongoDB는 @Document를 사용해야 합니다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Data MongoDB에서도 JPA Auditting 기능을 사용하고 싶다면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@EnalbeMongoAuditing&lt;/span&gt; 어노테이션을 Application 클래스나 Bean으로 등록되는 Config 파일에 명시해 주면 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;간혹 블로그를 보다보면 QClass 생성을위해 &lt;u&gt;&quot;@Document 와 @Entity를 같이 사용&quot;&lt;/u&gt; 하는 글이 있는데, 이는 &lt;u&gt;권장되지 않는 방법&lt;/u&gt;입니다.&lt;/li&gt;
&lt;li&gt;만약 document 의 idx를 mongoDB의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;ObjectID&lt;/span&gt;를 이용했다면(default) &lt;u&gt;@Field 어노테이션의 targetType 속성&lt;/u&gt;을 사용하여 spring data mongoDB 사용시 objectID로 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@Document(collection = &quot;collection_name&quot;)
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class TestDocument {

    @Id
    @Field(value = &quot;_id&quot;, targetType = FieldType.OBJECT_ID)
    private String id;

    @Field(&quot;room_idx&quot;)
    private Long roomIdx;
    
    @Field(&quot;sender_name&quot;)
    private String senderName;

    @Field(&quot;msg&quot;)
    private String msg;

    @Field(&quot;img_url&quot;)
    private String imgUrl;

    @Field(&quot;created_at&quot;)
    @CreatedDate
    private LocalDateTime createdAt;

    @Field(&quot;updated_at&quot;)
    @LastModifiedDate
    private LocalDateTime updatedAt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. MongoDB Repository&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JpaRepository처럼 기본적인 CRUD는 MongoRepository를 이용하여 사용할 수 있습니다.&lt;br /&gt;하지만 spring data mongoDB 는 queryDsl 을 사용하지 않기 때문에 Custom한 mongo query를 날리는 부분이 jpa 와 다릅니다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저는 ObjectID를 사용하기때문에 명시적으로 조건절에 new ObjectId 객체를 생성하여 파라미터로 넘겨주었지만,&lt;/li&gt;
&lt;li&gt;Entity 설정에 targetType = OBJECT_ID를 했다면 생략하셔도 무방합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
public interface ChatRepository extends MongoRepository&amp;lt;ChatHistory, Long&amp;gt;, ChatCustomRepository {

}

public interface ChatCustomRepository {

    List&amp;lt;ChatHistory&amp;gt; findAllCursorPagingBy(final long chatRoomId, final String chatIdx,
        final int size);
}

@RequiredArgsConstructor
public class ChatCustomRepositoryImpl implements ChatCustomRepository{

    private final MongoTemplate mongoTemplate;

    @Override
    public List&amp;lt;ChatHistory&amp;gt; findAllCursorPagingBy(final long chatRoomId, final String chatIdx,
        final int size) {

        Query query = new Query();

        query.addCriteria(Criteria.where(&quot;room_idx&quot;).is(chatRoomId));

        if(Objects.nonNull(chatIdx)) {
            query.addCriteria(Criteria.where(&quot;_id&quot;).lt(new ObjectId(chatIdx)));
        }

        query.with(Sort.by(Direction.DESC, &quot;_id&quot;)).limit(size);

        return mongoTemplate.find(query, TestDocument.class);

    }
}&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;  검색 시 다양한 방법이 나왔지만, 별도의 QClass 를 만들어주지 않아도 되고 명시적으로 쿼리를 작성할 수 있는 위의 방법이 저는 좋아보였습니다. &lt;br /&gt;&lt;i&gt;(즉, 이게 정답은 아니니 더 권장되는 방법이 있다면 저에게 알려주세요 ㅎㅎㅎ)&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  아래처럼 save() 를 바로 사용해도 되고, custom 한 mongo query 를 마치 querydsl 처럼 사용할 수 도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@Transactional
@RequiredArgsConstructor
public class ChatService {

    private final ChatRepository chatRepository;


    public ChatResponse recordHistory(final String chatRoomNo, final ChatRequest request) {

        final ChatHistory save = chatRepository.save(
             ChatHistory.of(Long.parseLong(chatRoomNo), request.sender(), request.senderUuid(),
                 request.msg(), request.imgUrl())
        );

        return ChatResponse.of(save);
    }
    
    public List&amp;lt;ChatHistory&amp;gt; readHistory(final long chatRoomId, final String chatIdx,
        final int size) {

        return chatRepository.findAllCursorPagingBy(chatRoomId, chatIdx, size);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;그럼 이런 쿼리가 날아감&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ExTCy/btsjZEXq7yq/vv3mTL8DcIpFSjvmiTlFK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ExTCy/btsjZEXq7yq/vv3mTL8DcIpFSjvmiTlFK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ExTCy/btsjZEXq7yq/vv3mTL8DcIpFSjvmiTlFK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FExTCy%2FbtsjZEXq7yq%2Fvv3mTL8DcIpFSjvmiTlFK1%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;1456&quot; height=&quot;56&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;spring mongo custom query :&lt;a href=&quot;https://www.baeldung.com/queries-in-spring-data-mongodb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.baeldung.com/queries-in-spring-data-mongodb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;spring data mongoDB Reference : &lt;a href=&quot;https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#repositories.custom-implementations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#repositories.custom-implementations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot</category>
      <category>java</category>
      <category>jpa</category>
      <category>mongo</category>
      <category>mongodb</category>
      <category>mongoDB queries</category>
      <category>mysql</category>
      <category>Queries</category>
      <category>spring</category>
      <category>springboot</category>
      <category>몽고</category>
      <author>민돌v</author>
      <guid isPermaLink="true">https://thalals.tistory.com/447</guid>
      <comments>https://thalals.tistory.com/447#entry447comment</comments>
      <pubDate>Wed, 14 Jun 2023 18:10:38 +0900</pubDate>
    </item>
  </channel>
</rss>