<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기록, 한 걸음씩 확실하게</title>
    <link>https://hoonsb.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 8 Aug 2026 17:54:18 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>sope</managingEditor>
    <image>
      <title>기록, 한 걸음씩 확실하게</title>
      <url>https://tistory1.daumcdn.net/tistory/5940396/attach/521b5272d2dc43b1863e60de52e3589d</url>
      <link>https://hoonsb.tistory.com</link>
    </image>
    <item>
      <title>[MSSQL] 저장 프로시저(Stored Procedure)와 파라미터 스니핑</title>
      <link>https://hoonsb.tistory.com/107</link>
      <description>&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 저장 프로시저(Stored Procedure)란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;복잡한 SQL 쿼리문들을 하나의 함수처럼 묶어서 &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;8&quot;&gt;데이터베이스에 미리 컴파일하여 저장해 둔 객체&lt;/b&gt;를 말한다. 필요할 때마다 긴 쿼리를 다시 짜는 게 아니라, 이름만 불러서 실행(EXEC 프로시저명)할 수 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size20&quot;&gt;  함수(FUNCTION)와 다른 점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;9&quot;&gt;&lt;b&gt;FUNCTION(함수)&lt;/b&gt;: 반드시 하나의 값을 반환해야 하며, SELECT나 WHERE 절 내부에서 호출된다. 오직 데이터를 읽고 계산하는 용도로만 쓰이며, 내부에서 INSERT/UPDATE 같은 데이터 변경이 엄격히 금지된다.&lt;/li&gt;
&lt;li data-path-to-node=&quot;9&quot;&gt;&lt;b&gt;PROCEDURE(프로시저)&lt;/b&gt;: 독립적으로 EXEC 명령어로 실행되며, 반환값이 없어도 된다. 내부에서 데이터를 자유롭게 수정/삭제할 수 있고 복잡한 트랜잭션 제어가 가능하다.&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;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 현대 웹 개발에서는 많이 보이지 않는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;13&quot;&gt;&lt;b&gt;SQL Mapper과 ORM의 사용&lt;/b&gt;: 과거에는 자바 코드에 긴 SQL을 안 섞으려고 프로시저를 주로 사용했다. SQL Mapper는 SQL과 동적 쿼리 로직을 깔끔하게 분리할 수 있기에 굳이 DB에서 로직을 처리할 이유가 사라졌다.&lt;/li&gt;
&lt;li data-path-to-node=&quot;13&quot;&gt;&lt;b&gt;비즈니스 로직은 서버에서 처리&lt;/b&gt;: 현대 아키텍처 철학에서는 DB는 저장/조회에 충실하고, 비즈니스 로직에 대한 처리는 서버에서 하도록 하여 역할을 확실히 분리하고자 한다.&lt;/li&gt;
&lt;li data-path-to-node=&quot;13&quot;&gt;&lt;b&gt;형상 관리와 배포의 용이성&lt;/b&gt;: SQL Mapper에서 작성된 소스는 형상 관리 Tool을 통해 커밋 이력이 완벽히 관리되지만, DB 프로시저는 버전 관리가 까다로워 유지보수가 쉽지 않은 단점이 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-path-to-node=&quot;9&quot;&gt;  프로시저는 그럼 사용되지 않는가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대형 금융권, 대기업 물류처럼 수십 년간 축적된 복잡한 로직이 DB에 묶여있는 곳이나, 수천만 건의 데이터를 네트워크 이동 없이 한 번에 처리해야 하는 대량의 배치(Batch) 작업에서는 여전히 프로시저가 핵심 동력원인 경우가 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 파라미터 스니핑 (Parameter Sniffing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSSQL은 프로시저가 최초로 실행될 때 들어온 파라미터의 값을 읽고, 그 값에 딱 맞춘 최적의 실행 계획을 짜서 메모리(캐시)에 저장한다. 이후 호출부터는 이 계획을 무조건 재사용하도록 되어 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;여기까지만 보면 실행할 때마다 새로운 계획을 짜지 않아도 돼서 빠르겠다 싶을 수 있다. 문제는 첫 번째 파라미터에는 최고였던 쿼리 실행 계획이 두 번째 파라미터에는 최악의 계획이 될 수 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size20&quot;&gt;파라미터 스니핑 예시&lt;/h4&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;주문 내역을 조회하는 프로시저가 있다고 가정하고, 여기에는 두 가지 유형의 고객사가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;22&quot;&gt;&lt;b&gt;신생 소형 고객사 (ID: 999)&lt;/b&gt;: 이번 달 주문 데이터 단 5건&lt;/li&gt;
&lt;li data-path-to-node=&quot;22&quot;&gt;&lt;b&gt;초대형 대기업 고객사 (ID: 111)&lt;/b&gt;: 이번 달 주문 데이터 30만 건&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1780672483739&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE PROCEDURE GetOrderDashboard
    @CustomerID INT,
    @StartDate DATETIME
AS
BEGIN
    SELECT OrderID, CustomerID, OrderDate, TotalAmount, OrderStatus
    FROM Orders
    WHERE CustomerID = @CustomerID AND OrderDate &amp;gt;= @StartDate;
END;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;새벽 4:00:&lt;/b&gt; 배치 프로그램이 소형 고객사(ID=999)의 데이터를 조회한다. MSSQL은 적은 건수의 데이터 분포를 확인하고, Index Seek 방식으로 계획을 짠 후 메모리(캐시)에 실행계획을 올린다. 데이터 조회는 빠르게 진행된다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0&quot;&gt;오전 9:05:&lt;/b&gt; 출근한 대기업 담당자가 로그인하여 대기업 고객사(ID=111)의 대시보드를 켜고, 주문 내역을 조회한다. 30만 건을 가져와야 하는데, MSSQL은 새벽에 만들어둔 Index Seek 계획을 무지성으로 재사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,2,0&quot;&gt;결과:&lt;/b&gt; 30만 건을 찾으려고 인덱스 루프를 돌며 디스크 I/O를 무자비하게 낭비하여 성능이 떨어진다. 결국 30초 타임아웃과 함께 DB CPU 점유율이 100%를 찍으며 웹 서버 전체가 먹통이 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;*Index Seek&lt;/b&gt;: 색인을 보고 원하는 데이터를 찾아가는 방법, 소량의 데이터 조회에 최적&lt;br /&gt;&lt;b&gt;*Table Scan&lt;/b&gt;: 데이터 밀도가 높아서 테이블의 모든 내용을 조회하여 찾는 방법, 대량의 데이터 조회에 최적&lt;br /&gt;&lt;b&gt;*Index Scan&lt;/b&gt;: 테이블 데이터는 보지 않고, 색인 자체를 처음부터 끝까지 훑는 방식&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 파라미터 스니핑 예방법&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) OPTIMIZE FOR UNKNOWN&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSSQL이 최초 파라미터 값에 치중된 계획을 짜도록 하지 않고 &lt;b&gt;통계 정보를 바탕으로 가장 평균적이고 무난한 실행 계획을 세우도록 강제&lt;/b&gt;하는 힌트를 주는 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780673006888&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER PROCEDURE GetOrderDashboard
    @CustomerID INT,
    @StartDate DATETIME
AS
BEGIN
    SELECT OrderID, CustomerID, OrderDate, TotalAmount, OrderStatus
    FROM Orders
    WHERE CustomerID = @CustomerID AND OrderDate &amp;gt;= @StartDate
    OPTION (OPTIMIZE FOR UNKNOWN); -- 쿼리 맨 밑에 힌트를 추가한다
END;&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;(2) 로컬 변수(Local Variable) 우회&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터를 내부 변수에 한 번 복사해서 사용하는 방식이다. MSSQL은 컴파일 시점에 로컬 변수에 무슨 값이 담길지 예측할 수 없기 때문에, 자동으로 무난하고 안전한 평균치 계획을 수립합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1780673092106&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER PROCEDURE GetOrderDashboard
    @CustomerID INT,
    @StartDate DATETIME
AS
BEGIN
    -- 로컬 변수에 대입 (컴파일 시점에 스니핑 차단)
    DECLARE @LocalID INT = @CustomerID;
    DECLARE @LocalKey DATETIME = @StartDate;

    SELECT OrderID, CustomerID, OrderDate, TotalAmount, OrderStatus
    FROM Orders
    WHERE CustomerID = @LocalID AND OrderDate &amp;gt;= @LocalKey;
END;&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) WITH RECOMPILE 옵션 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 실행 계획을 새로 짤 수 있도록 강제하는 WITH RECOMPILE 옵션을 주어 해결할 수도 있다. 하지만 &lt;b&gt;호출할 때마다 컴파일&lt;/b&gt;이 되기 때문에 프로시저를 사용하는 장점을 얻을 수 없고, 동시 접속자가 몰릴 때 DB CPU를 폭발시키는 주범이 될 수 있어 많이 사용되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1780673278948&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE PROCEDURE GetOrderDashboard
    @CustomerID INT,
    @StartDate DATETIME
WITH RECOMPILE -- 옵션 선언
AS
BEGIN
    SELECT OrderID, CustomerID, OrderDate, TotalAmount, OrderStatus
    FROM Orders
    WHERE CustomerID = @CustomerID AND OrderDate &amp;gt;= @StartDate;
END;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 파라미터 스니핑 발생 시 처치 방법&lt;/b&gt;&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;어떤 프로시저가 파라미터 스니핑에 걸려 DB CPU를 잡아먹고 있는지 찾아야 한다. SSMS(SQL Server Management Studio)에서 아래 스크립트를 실행하여 현재 가장 느린 프로시저를 색출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780673589171&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 현재 캐시에 있는 프로시저 중 평균 CPU 사용량이 가장 높은 상위 5개 조회
SELECT TOP 5
    Result.text AS [ProcedureName],
    Stats.total_worker_time / Stats.execution_count AS [Avg_CPU_Time],
    Stats.execution_count AS [Execution_Count],
    Stats.total_elapsed_time / Stats.execution_count AS [Avg_Total_Time]
FROM sys.dm_exec_procedure_stats AS Stats
CROSS APPLY sys.dm_exec_sql_text(Stats.sql_handle) AS Result
ORDER BY [Avg_CPU_Time] DESC;&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;(2) 범인 프로시저의 실행 계획 제거&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장 전체 DB 서버를 껐다 켤 수는 없으므로 메모리에 올라가 있는 해당 프로시저의 잘못된 실행 계획만 강제로 지운다.&lt;/p&gt;
&lt;pre id=&quot;code_1780673678637&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 특정 프로시저의 ID를 찾아 캐시에서 제거
DECLARE @PlanHandle VARBINARY(64);

SELECT @PlanHandle = plan_handle 
FROM sys.dm_exec_procedure_stats 
WHERE object_id = OBJECT_ID('GetOrderDashboard');

IF @PlanHandle IS NOT NULL
    FREEPROCCACHE (@PlanHandle); -- 해당 계획만 제거&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;이렇게 병목 되어 있는 프로시저의 실행 계획을 제거하여 다음 해당 프로시저의 요청이 오면 새로운 계획을 짜서 메모리에 저장한다. 하지만 다른 요청이 이어서 들어오면 결국 같은 상황이 발생되기 때문에 반드시 해당 프로시저에 OPTIMIZE FOR UNKNOWN을 넣어 예방할 수 있도록 조치를 해야 한다.&lt;/p&gt;</description>
      <category>  Computer Science/데이터베이스</category>
      <category>파라미터 스니핑</category>
      <category>프로시저</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/107</guid>
      <comments>https://hoonsb.tistory.com/107#entry107comment</comments>
      <pubDate>Sat, 6 Jun 2026 00:42:52 +0900</pubDate>
    </item>
    <item>
      <title>[OCR] 영수증 텍스트 추출 구현 (Tesseract -&amp;gt; PaddleOCR)</title>
      <link>https://hoonsb.tistory.com/106</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-filename=&quot;blob&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgT2Es/dJMcadvm8Kr/5mJ2q5dSafMcnpEnd5lXA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgT2Es/dJMcadvm8Kr/5mJ2q5dSafMcnpEnd5lXA1/img.png&quot; data-alt=&quot;OCR 테스트를 위한 영수증 이미지 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgT2Es/dJMcadvm8Kr/5mJ2q5dSafMcnpEnd5lXA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgT2Es%2FdJMcadvm8Kr%2F5mJ2q5dSafMcnpEnd5lXA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;253&quot; height=&quot;295&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OCR 테스트를 위한 영수증 이미지 파일&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;프로젝트에서 영수증(매출전표) 이미지를 분석해 상호명, 승인번호, 카드번호, 금액 등을 자동으로 구조화하는 OCR 기능을 검토하게 되었습니다. &quot;비용을 들이지 않는 오픈소스 형태&quot;라는 제약 조건 속에서, 인식률과 속도라는 두 마리 토끼를 잡기 위해 진행했던 최적화 과정을 기록으로 남겨봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 자바 내장 Tesseract의 한계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 스프링 부트(Springboot) 내부에 Tess4J(Tesseract)를 직접 내장하여 OCR을 시도했습니다. &lt;b&gt;가장 간단하게 구현할 수 있는 선택지이기도 했고, 외부 서버와의 통신이 없기 때문에 아키텍처가 단순하다는 장점&lt;/b&gt;이 있었습니다. 직접 구현하여 테스트를 진행해 보니 매출전표나 영수증을 다운로드한 이미지는 꽤나 잘 인식을 했는데, 사용자가 직접 촬영한 영수증 이미지에서는 굉장히 만족스럽지 못한 결과를 얻었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;: 영어와 정자체 한글은 그런대로 읽었지만, 실제 영수증처럼 약간 기울어지거나 서체가 특이하고 여백이 불규칙한 한글 이미지는 글자가 완전히 깨졌습니다. (롯데마스터승인 -&amp;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;: 상용 수준의 영수증 파싱을 위해서는 한국어 영수증 데이터셋 학습이 잘 되어 있는 현대적인 딥러닝 OCR 엔진이 필수적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. [1차 개선] Spring Boot + Python FastAPI 하이브리드 설계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글 영수증 인식률이 좋다는 평가가 많았던 PaddleOCR(PP-OCRv5)로 변경해 보기로 결정했습니다. 하지만 PaddleOCR은 Python 기반의 딥러닝 프레임워크인 PaddlePaddle 위에서 동작하므로 자바 웹 서버에 직접 얹을 수 없었습니다. 그래서 서버를 분리하여 서로 통신하는 하이브리드 아키텍처를 구축했습니다.&lt;/p&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개의 서버를 실행하여 테스트해야 하는 번거로움이 있어서 메인이 되는 Springboot 서버만 실행해도 전체 시스템이 유기적으로 실행될 수 있도록 ServerRunner를 구현하여 Python 서버의 생명주기를 자동으로 관리하도록 설계하여 불편함을 줄였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. [1차 개선 문제점] 로컬 CPU 성능의 한계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PaddleOCR은 GPU 환경에서는 빠르게 동작하는 가벼운 모델이지만, 일반 개발 환경인 로컬 CPU에서는 영수증 사진 1장을 스캔하는데, 40~50초가 넘는 엄청난 지연 시간을 보여주었습니다. 추출 결과물은 Tesseract와 비교했을 때 만족스럽게 나왔지만, 40~50초의 시간은 사용자가 기다리기에 너무나도 많은 시간이었습니다.&lt;/p&gt;
&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; PaddlePaddle&amp;nbsp;프레임워크&amp;nbsp;자체가&amp;nbsp;CPU&amp;nbsp;인프라&amp;nbsp;최적화&amp;nbsp;연산에&amp;nbsp;최적화되어&amp;nbsp;있지&amp;nbsp;않아&amp;nbsp;로컬에서&amp;nbsp;간단한&amp;nbsp;테스트를&amp;nbsp;돌리기조차&amp;nbsp;어려웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. [2차 개선] ONNX&amp;nbsp;Runtime&amp;nbsp;(RapidOCR)&amp;nbsp;도입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 최적화 명령어가 포함된 ONNX 런타임으로 경량화하여 추론 과정을 거칠 수 있도록 변경 작업을 진행했습니다. 경량 한글 모델이었던 PP-OCRv3 Mobile 모델을 적용하여 속도는 3초 미만으로 비약적으로 빨라졌지만, 특수기호(*), 숫자 쉼표(,) 등에서 오타가 다수 발생되었습니다. 정확도를 높이기 위해서 PP-OCRv5 ONNX 정밀 모델로 변경작업을 진행한 후 다시 테스트했을 때 12초 정도의 시간이 소요되었고, 추출 정확도는 이용할 수 있는 값들에 대해서는 95% 이상 정확하게 추출되는 것을 확인해 볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[첫 번째 추출 결과물]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어 설정이 누락되어 기본 중국어/영어 모델이 강제 적용되면서 한자로 글자가 심하게 깨진 상태를 얻었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1780290717948&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TID:10****50-[6681]
[LTPayPass]
[号Y同]：青晨
5137-92**-****-3625
22:26/03/2008:15:59
13.6362
早：
1.364
15.000
66028074[EC]
9214239451/107-14-56234
号（TEL：0234390333）
B20克
KI 
VANKEY:0079051297591783
J4307649-6681-SM99
[0004]&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 설정으로 한글은 읽기 시작했으나 모바일 경량 모델의 한계로 카드 번호(5127...), 부가세(1니원), 승인번호(Aaagn74...) 등에서 크고 작은 오타와 글자 뭉개짐이 다수 발생했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1780290790566&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;로데마스터습인
TID: 10***5O- [6681]
[가맹점용]
[LTPayPass]
5127--92+-  ie -SH25
거래일시:26/0312008:15:59
금액
13630원
부가세
1니원
합계
15000원
승인번호
Aaagn74 led]
매입사명[010-DESC]
롯데카드

... 생략 ...&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카드 번호(5137-92**-****-3625), 부가세 및 합계 쉼표 문자(13,636원, 15,000원), 승인번호(66028074[EC])까지 95% 이상 무결점으로 복원된 상태의 추출 결과물을 얻었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1780290898247&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;롯데마스터
승인
TID:10****50-[6681]
[가맹점용]
[LTPayPass]
[Y]
5137-92**-****-3625
거래일시:26/03/20 08:15:59
금  액:
13,636원
부가세:
1 364원
합계:
15,000원
승인번호
66028074[EC]
매입사명[010-DESC ]
롯데카드

... 생략 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 결론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 최적화를 마친 뒤에 선택에 따라서 Option A와 Option B라는 유연한 선택지를 남겨두었습니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Option A (초고정밀도 모드):&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;* PP-OCRv5&lt;span&gt;&amp;nbsp;모델 사용&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 로컬 CPU 속도: 약 12초 (Native PaddleOCR CPU의 30초 대기 대비 2배 이상 개선)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 인식률: 95% 이상 (필요한 데이터에 한함)&lt;/p&gt;
&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;Option&amp;nbsp;B&amp;nbsp;(고속&amp;nbsp;모드):&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* PP-OCRv3 Mobile 모델 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 로컬 CPU 속도: 약 4초 (속도는 압도적으로 빠름)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 인식률: 90% 내외 (특수기호, 숫자 쉼표 등에서 오타 발생)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 실제 서비스에 붙여서 사용하기 위해서는 4초의 속도도 부족할 수 있습니다. 정확도와 인식 속도 모두 잡아야 하기 때문에 다른 방법을 고려해야 할 수도 있습니다. 다만 이번 테스트 구현은 회사에서 필요할 수도 있는 기능을 비용이 들지 않는 방향에서 검토하다 보니 정확도와 속도 모두 잡는 방법은 더 많은 시간 투자가 필요할 것으로 여겨집니다. 사내에서 해당 서비스 구축에 대해서 비용을 지불할 수 있는 상황이라면 CLOVA OCR과 같은 서비스도 있기 때문에 고려해 보면 좋을 것 같습니다.&lt;/p&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> &amp;zwj;  Dev/SpringBoot</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/106</guid>
      <comments>https://hoonsb.tistory.com/106#entry106comment</comments>
      <pubDate>Mon, 1 Jun 2026 19:20:34 +0900</pubDate>
    </item>
    <item>
      <title>2024년 1회차 정보처리기사 필기/실기 합격 후기 공부방법, 느낀점</title>
      <link>https://hoonsb.tistory.com/104</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 2024년도 1회 차 필기, 실기를 응시하고 자격을 취득했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coJrAe/btsIR2T0gkK/rOXo24mKMfs8bgvZ2D7l7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coJrAe/btsIR2T0gkK/rOXo24mKMfs8bgvZ2D7l7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coJrAe/btsIR2T0gkK/rOXo24mKMfs8bgvZ2D7l7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoJrAe%2FbtsIR2T0gkK%2FrOXo24mKMfs8bgvZ2D7l7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;221&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;221&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;제가 전공을 바꾸고 주변에서 가장 많이 들었던 말이 &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;span style=&quot;color: #ee2323;&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;254&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bILhHg/btsIPZLf0FP/xTpnqmmQuJ8ZWhnky25wA1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bILhHg/btsIPZLf0FP/xTpnqmmQuJ8ZWhnky25wA1/img.jpg&quot; data-alt=&quot;뒤 늦게 깨닫게 된 저의 표정과 비슷하네요.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bILhHg/btsIPZLf0FP/xTpnqmmQuJ8ZWhnky25wA1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbILhHg%2FbtsIPZLf0FP%2FxTpnqmmQuJ8ZWhnky25wA1%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;276&quot; height=&quot;216&quot; data-origin-width=&quot;254&quot; data-origin-height=&quot;199&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 필기는 CBT, CBT, CBT 그리고 또 CBT&lt;/b&gt;&lt;/h3&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;하지만 책에 나오는 모든 내용을 다 정독하면서 암기하기에는 정보처리기사에만 쓸 수 있는 시간은 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실기는 필기 붙은 다음에 생각하자는 마인드로 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;1주일 동안은 &lt;a href=&quot;https://www.comcbt.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CBT 사이트&lt;/a&gt;에서 2020년도부터 2022년도까지 문제를 풀었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BJauF/btsIPukJBq7/4SlcNlEyBYN1WTrLRNQkv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BJauF/btsIPukJBq7/4SlcNlEyBYN1WTrLRNQkv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BJauF/btsIPukJBq7/4SlcNlEyBYN1WTrLRNQkv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBJauF%2FbtsIPukJBq7%2F4SlcNlEyBYN1WTrLRNQkv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;415&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;496&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;하루에 하나씩만 풀어도 8일이면 다 풀 수 있습니다. 요약본을 읽고 나서 푸는 것이 안 본 것보다는 도움이 많이 되지만, 그냥 풀어도 상관없습니다.&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1주일 동안 문제를 풀었다면, 이제는 1주일 동안 선택 연도 말고, 아래 &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;이렇게 2주만 CBT를 돌려보고, 필기를 봐도 무난하게 통과하실 수 있으실 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 실기는 공부, 공부, 공부&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실기는 사실 요령이라고 하면서 말씀드릴 수 있는 부분이 없을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필기처럼 CBT 문제를 계속 돌려볼 수도 없고, 기출문제가 있다고 한들 그대로 나오지 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 사용했던 교재는 수제비입니다. 1, 2권으로 나누어져 있어서 따로 들고 다니기도 편했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/117206088&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.yes24.com/Product/Goods/117206088&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722249113035&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;2023 수제비 정보처리기사 실기 1권+2권 합본세트 - 예스24&quot; data-og-description=&quot;비전공자를 위한 최고의 수험서!!- 2023년 합격을 위한 NCS 기반 모의고사 수록- 궁극의 암기비법(두음쌤)과 학습 Point 수록- 최적의 맞춤 학습을 위한 커뮤니티 운영- 암기비법 PDF 제공(커뮤니티 내&quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;https://www.yes24.com/Product/Goods/117206088&quot; data-og-url=&quot;https://www.yes24.com/Product/Goods/117206088&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/V01Lm/hyWGUaASrk/o0TDq5Nf8ho1yL5wAzMwP0/img.jpg?width=1163&amp;amp;height=1200&amp;amp;face=0_0_1163_1200,https://scrap.kakaocdn.net/dn/cHURKy/hyWG2zIDy3/klKtQmtnPaWkZYMsj55KNk/img.jpg?width=1163&amp;amp;height=1200&amp;amp;face=0_0_1163_1200,https://scrap.kakaocdn.net/dn/HiCdh/hyWGYYoldJ/YYEbcKKoKrofscAGE2KnZ0/img.jpg?width=1163&amp;amp;height=1200&amp;amp;face=0_0_1163_1200&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/117206088&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.yes24.com/Product/Goods/117206088&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/V01Lm/hyWGUaASrk/o0TDq5Nf8ho1yL5wAzMwP0/img.jpg?width=1163&amp;amp;height=1200&amp;amp;face=0_0_1163_1200,https://scrap.kakaocdn.net/dn/cHURKy/hyWG2zIDy3/klKtQmtnPaWkZYMsj55KNk/img.jpg?width=1163&amp;amp;height=1200&amp;amp;face=0_0_1163_1200,https://scrap.kakaocdn.net/dn/HiCdh/hyWGYYoldJ/YYEbcKKoKrofscAGE2KnZ0/img.jpg?width=1163&amp;amp;height=1200&amp;amp;face=0_0_1163_1200');&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;2023 수제비 정보처리기사 실기 1권+2권 합본세트 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;비전공자를 위한 최고의 수험서!!- 2023년 합격을 위한 NCS 기반 모의고사 수록- 궁극의 암기비법(두음쌤)과 학습 Point 수록- 최적의 맞춤 학습을 위한 커뮤니티 운영- 암기비법 PDF 제공(커뮤니티 내&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;위 교재를 보면 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;span style=&quot;color: #0593d3;&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;/li&gt;
&lt;li&gt;자주 나오는 유형에서 비어있는 점수를 채우자.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 간단하지만, 아주 괜찮은 전략이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 비전공자이긴 하지만, 프로젝트와 타 전공 수업을 들으면서 C, Java, Python을 모두 다루어 봤고, 데이터베이스 전공 수업도 신청해서 들었었기 때문에 코딩과 데이터베이스를 틀리면 안 되는 파트라고 설정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 제가 봤을 때, 자주 나왔던 부분은 OO 하는 보안방법은?, OO하는 해킹방법은? 이런 문제들이 많이 나오는 것을 확인했습니다.&lt;/p&gt;
&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: #f9f9c8;&quot;&gt;모든 코딩 문제와 데이터베이스 그리고 추가로 2~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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://starrykss.tistory.com/1856&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://starrykss.tistory.com/1856&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722249747318&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;[정보처리기사 실기] 단원별 정리 &amp;amp; 예상 문제 &amp;amp; 기출 문제&quot; data-og-description=&quot;정보처리기사 단원별 정리 &amp;amp; 예상 문제 &amp;amp; 기출 문제정보처리기사 실기 시험을 준비하면서 블로그에 올렸었던 글들을 한 페이지에 정리해본다.&amp;nbsp;개념 정리 2020년 NCS 개편 후의 내용들수험서, 인터&quot; data-og-host=&quot;starrykss.tistory.com&quot; data-og-source-url=&quot;https://starrykss.tistory.com/1856&quot; data-og-url=&quot;https://starrykss.tistory.com/1856&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/6m3LX/hyWGYD5Xsn/uLAmaplbVNONjSlDnhEr9k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/jaPWb/hyWGVN77rD/j1TmFQ6yHd8KBq0W3Ly5d0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://starrykss.tistory.com/1856&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://starrykss.tistory.com/1856&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/6m3LX/hyWGYD5Xsn/uLAmaplbVNONjSlDnhEr9k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/jaPWb/hyWGVN77rD/j1TmFQ6yHd8KBq0W3Ly5d0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;[정보처리기사 실기] 단원별 정리 &amp;amp; 예상 문제 &amp;amp; 기출 문제&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사 단원별 정리 &amp;amp; 예상 문제 &amp;amp; 기출 문제정보처리기사 실기 시험을 준비하면서 블로그에 올렸었던 글들을 한 페이지에 정리해본다.&amp;nbsp;개념 정리 2020년 NCS 개편 후의 내용들수험서, 인터&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;starrykss.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;span style=&quot;background-color: #f9f9c8;&quot;&gt;Java, Python, C에 대한 기본 문법을 다지고 아래 있는 블로그에 기출, 예상 문제들을 계속 풀면서 준비&lt;/span&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://starrykss.tistory.com/1797&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://starrykss.tistory.com/1797&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722249860023&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;[정보처리기사 실기] 프로그래밍 기출 문제 정리 (2017년~2022년)&quot; data-og-description=&quot;프로그래밍 기출 문제 정리 (2017년~2022년) 정보처리기사 실기 기출 문제 중에서 프로그래밍(C, Java, Python)과 관련된 문제를 정리해 본다. 이 문제들은 복원을 한 것으로, 실제 출제된 문제와 다를 &quot; data-og-host=&quot;starrykss.tistory.com&quot; data-og-source-url=&quot;https://starrykss.tistory.com/1797&quot; data-og-url=&quot;https://starrykss.tistory.com/1797&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pYZsy/hyWG3k7rIU/CBWV2SPyUivftzlGf9TW8K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bO24Qy/hyWGNvNyTF/fnfm61HLXWY0BKuxAVnCdk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://starrykss.tistory.com/1797&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://starrykss.tistory.com/1797&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pYZsy/hyWG3k7rIU/CBWV2SPyUivftzlGf9TW8K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bO24Qy/hyWGNvNyTF/fnfm61HLXWY0BKuxAVnCdk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;[정보처리기사 실기] 프로그래밍 기출 문제 정리 (2017년~2022년)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프로그래밍 기출 문제 정리 (2017년~2022년) 정보처리기사 실기 기출 문제 중에서 프로그래밍(C, Java, Python)과 관련된 문제를 정리해 본다. 이 문제들은 복원을 한 것으로, 실제 출제된 문제와 다를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;starrykss.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 문제 연습해 보기 정말 좋습니다. 3~4일 동안은 퇴근 후 코딩문제에 올인을 하면서 보냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;코딩 문제를 한 바퀴 돌렸다면 바로 다음 파트 &quot;데이터베이스&quot; 기출문제를&lt;/span&gt; 풀었습니다.&lt;/p&gt;
&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://starrykss.tistory.com/1840&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://starrykss.tistory.com/1840&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722249982570&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;[정보처리기사 실기] 데이터베이스 기출 문제 정리 (2017년~2022년)&quot; data-og-description=&quot;데이터베이스 기출 문제 정리 (2017년~2022년) 정보처리기사 실기 기출 문제 중에서 데이터베이스(Database)와 관련된 문제를 정리해 본다. SQL(Structed Query Language) 이 문제들은 복원을 한 것으로, 실제 &quot; data-og-host=&quot;starrykss.tistory.com&quot; data-og-source-url=&quot;https://starrykss.tistory.com/1840&quot; data-og-url=&quot;https://starrykss.tistory.com/1840&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cf688c/hyWG2mcvfs/ilyknvMUkwvkkxkk9kcQTK/img.png?width=481&amp;amp;height=371&amp;amp;face=0_0_481_371,https://scrap.kakaocdn.net/dn/SUsoz/hyWGVAyRZ9/1zI1AnKZ61BRV8myx6SqfK/img.png?width=481&amp;amp;height=371&amp;amp;face=0_0_481_371,https://scrap.kakaocdn.net/dn/ehIvDZ/hyWG2GvlLt/Zk5QwiRGbpycZPkvUgl2P1/img.png?width=481&amp;amp;height=371&amp;amp;face=0_0_481_371&quot;&gt;&lt;a href=&quot;https://starrykss.tistory.com/1840&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://starrykss.tistory.com/1840&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cf688c/hyWG2mcvfs/ilyknvMUkwvkkxkk9kcQTK/img.png?width=481&amp;amp;height=371&amp;amp;face=0_0_481_371,https://scrap.kakaocdn.net/dn/SUsoz/hyWGVAyRZ9/1zI1AnKZ61BRV8myx6SqfK/img.png?width=481&amp;amp;height=371&amp;amp;face=0_0_481_371,https://scrap.kakaocdn.net/dn/ehIvDZ/hyWG2GvlLt/Zk5QwiRGbpycZPkvUgl2P1/img.png?width=481&amp;amp;height=371&amp;amp;face=0_0_481_371');&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;[정보처리기사 실기] 데이터베이스 기출 문제 정리 (2017년~2022년)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스 기출 문제 정리 (2017년~2022년) 정보처리기사 실기 기출 문제 중에서 데이터베이스(Database)와 관련된 문제를 정리해 본다. SQL(Structed Query Language) 이 문제들은 복원을 한 것으로, 실제&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;starrykss.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 1~2주 정도 빡세게 잡고 해 보시면 코딩 문제, 데이터베이스를 한 바퀴 돌려볼 수 있을 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시험 1주 전부터는 코딩문제, 데이터베이스 보면서 틀리거나 헷갈렸던 부분들을 다시 보고 계속 틀리는 부분은 어디 적어두면 좋습니다. (시험 당일날 도움이 많이 됩니다.)&lt;/p&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~3문제를 맞히기로 선택한 파트를 노려줍니다. 여기는 그냥 암기.. 암기이기 때문에 어쩔 수 없이 외워줍니다.&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;744&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEorWz/btsIQ0QasWj/y7YYS3nTt8DRdE8Cc9gWrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEorWz/btsIQ0QasWj/y7YYS3nTt8DRdE8Cc9gWrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEorWz/btsIQ0QasWj/y7YYS3nTt8DRdE8Cc9gWrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEorWz%2FbtsIQ0QasWj%2Fy7YYS3nTt8DRdE8Cc9gWrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;744&quot; height=&quot;178&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;178&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;이렇게 준비를 해서 시험을 봤고, 필단형 72점을 받고 합격했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세웠던 전략이 통해서 시험 보고 나오는 날에도 합격한 것 같은 기분이 들면서 나왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시험은 예상했던 대로 코딩과 데이터베이스 문제의 비중에 굉장히 컸습니다. 또한 특정 기술, 보안에 대한 문제도 적지 않게 나와서 합격할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;035&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/035.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/035.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev</category>
      <category>정보처리기사</category>
      <category>정보처리기사 비전공</category>
      <category>정보처리기사 실기</category>
      <category>정보처리기사 필기</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/104</guid>
      <comments>https://hoonsb.tistory.com/104#entry104comment</comments>
      <pubDate>Mon, 29 Jul 2024 20:02:53 +0900</pubDate>
    </item>
    <item>
      <title>해시(Hash)와 암호화(Encryption)의 차이점?</title>
      <link>https://hoonsb.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;결론을 먼저 말해보면 둘 다 암호화 기법이지만, &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;해시(Hash)는 단방향 암호화 기법이며, 암호화(Encryption)는 양방향 암호화 기법&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;즉, Hash는 평문 데이터를 암호화된 데이터로 바꿀 수 있는 단방향 암호화 기법이고, Encryption은 평문을 암호화된 데이터로 바꾸는 암호화와 암호화된 데이터를 평문 데이터로 바꿀 수 있는 복호화가 존재합니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Hash&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&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;li&gt;해시 입력 값은 다르지만, 해시 결과 값은 동일할 수 있습니다. (여기서 중복이 적게 생길수록 좋은 해시 함수입니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kH6nQ/btsD4V8iv7W/iKHcxyfKArP5kwcDXaCrKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kH6nQ/btsD4V8iv7W/iKHcxyfKArP5kwcDXaCrKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kH6nQ/btsD4V8iv7W/iKHcxyfKArP5kwcDXaCrKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkH6nQ%2FbtsD4V8iv7W%2FiKHcxyfKArP5kwcDXaCrKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;552&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;해시만 적용하는 것으로 완벽한 보안이 될까?&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 어떻게 보안을 해도 어떤 천재 해커나 집요한 해커들에게 뚫릴 가능성이 Zero는 아니기 때문에 더 튼튼한 보안을 결정해서 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 평문 데이터를 단순히 해시함수 한 번만 거쳐 암호화를 했다고 완벽한 보안이 될 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보통 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;해시 함수를 여러 번 돌리거나 Salt를 넣어서 사용&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;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 어떻게 막 해시 함수에 임의의 값을 막 넣다보니 해커가 평문 데이터를 알아냈다고 했을 때, 이 평문 데이터는 임의의 값인 Salt가 섞여있는 데이터이기 때문에 해커는 이 Salt 값까지 알아내야 실제 데이터를 얻어낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 해시 함수에는 MD5, bcrypt, SHA가 존재합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/da5RJn/btsEbBnc3ua/VKw3K1uL7waHdbcJTYZmC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/da5RJn/btsEbBnc3ua/VKw3K1uL7waHdbcJTYZmC1/img.png&quot; data-alt=&quot;해시 함수별 보안과 속도 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/da5RJn/btsEbBnc3ua/VKw3K1uL7waHdbcJTYZmC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fda5RJn%2FbtsEbBnc3ua%2FVKw3K1uL7waHdbcJTYZmC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;487&quot; height=&quot;111&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;228&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;b&gt;2. Encryption&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;양쪽에서 같은 암호화 키를 가지고 있는 암호화 방식을 &lt;b&gt;&quot;대칭키 방식&quot;&lt;/b&gt;이라고 합니다.&lt;/li&gt;
&lt;li&gt;한쪽은 개인키(private key)를 가지고, 다른 한쪽은 개인키와 한 쌍인 공개키(public key)를 가지고 암호화 복호화하는 방식을 &lt;b&gt;&quot;비대칭키 방식&quot;&lt;/b&gt;이라고 합니다.&lt;/li&gt;
&lt;li&gt;&quot;대칭키&quot;와 &quot;비대칭키&quot;를 둘 다 사용하는 혼합 암호화 방식도 존재합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;암호화 방식은 해시와는 다르게 양방향성&lt;/span&gt;을 가지게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1780&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rpydH/btsEdU7lXUn/oOl1Dvn4qtLzZNkX9reqbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rpydH/btsEdU7lXUn/oOl1Dvn4qtLzZNkX9reqbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rpydH/btsEdU7lXUn/oOl1Dvn4qtLzZNkX9reqbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrpydH%2FbtsEdU7lXUn%2FoOl1Dvn4qtLzZNkX9reqbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1780&quot; height=&quot;552&quot; data-origin-width=&quot;1780&quot; data-origin-height=&quot;552&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;파이팅짤.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;665&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biLk6u/btsEcaW8Yia/g3bYP2gFklx5sJokBQxWpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biLk6u/btsEcaW8Yia/g3bYP2gFklx5sJokBQxWpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biLk6u/btsEcaW8Yia/g3bYP2gFklx5sJokBQxWpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiLk6u%2FbtsEcaW8Yia%2Fg3bYP2gFklx5sJokBQxWpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;241&quot; height=&quot;229&quot; data-filename=&quot;파이팅짤.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;665&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;</description>
      <category>  Computer Science</category>
      <category>대칭키</category>
      <category>비대칭키</category>
      <category>암호화</category>
      <category>해시</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/102</guid>
      <comments>https://hoonsb.tistory.com/102#entry102comment</comments>
      <pubDate>Tue, 30 Jan 2024 15:11:04 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] @Async 이용하여 비동기 구현하기</title>
      <link>https://hoonsb.tistory.com/101</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;비동기 구현을 하려는 이유&lt;/b&gt;&lt;/h2&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;프로젝트에서 서버에서 발생하는 5xx Exception을 모니터링하기 위해서 Slack WebHook을 사용해서 구현했습니다.&lt;/p&gt;
&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: #f9f9c8;&quot;&gt;5xx 예외가 발생 -&amp;gt; Slack 메시지 전송 -&amp;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;여기서 사용하고 있는 Slack 서버는 제가 제어할 수 없는 부분이기 때문에 만약 Slack 서버에 지연이 생겨 응답을 늦게 준다면 어떻게 될지 생각을 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack 메시지 전송이 지연되어 정작 더 중요한 다음 과정인 &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;실제로 Slack 메시지를 보내는 push() 메서드에서 5초의 지연 시간을 주고 테스트 해보니 클라이언트에게 응답이 가는 시간도 그만큼 지연이 되는 것을 확인했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2206&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YrLQz/btsDGp2VJpr/uysWG8YdGHKL5jiml1KtdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YrLQz/btsDGp2VJpr/uysWG8YdGHKL5jiml1KtdK/img.png&quot; data-alt=&quot;요청이 들어온 시간과 클라이언트에게 응답되는 시간 사이의 5초간의 지연 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YrLQz/btsDGp2VJpr/uysWG8YdGHKL5jiml1KtdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYrLQz%2FbtsDGp2VJpr%2FuysWG8YdGHKL5jiml1KtdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2206&quot; height=&quot;348&quot; data-origin-width=&quot;2206&quot; data-origin-height=&quot;348&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;못마땅짤.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GBG2b/btsDJYpfFVp/PSJhPbXmDFDz07kQK1mhO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GBG2b/btsDJYpfFVp/PSJhPbXmDFDz07kQK1mhO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GBG2b/btsDJYpfFVp/PSJhPbXmDFDz07kQK1mhO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGBG2b%2FbtsDJYpfFVp%2FPSJhPbXmDFDz07kQK1mhO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;217&quot; height=&quot;214&quot; data-filename=&quot;못마땅짤.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;790&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: #f9f9c8;&quot;&gt;Slack으로 메시지를 보내는 부분을 비동기로 처리하여 클라이언트에게 응답을 제공하는 부분에 지연이 생기지 않도록 구현&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Spring의 @Async&lt;/b&gt;&lt;/h2&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;구현을 하기 전에 @Async를 조금 알아보면, 비동기 처리를 간단하게 할 수 있도록 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;사용 방법은 @Async를 사용하겠다고 @EnableAsyc를 붙여주고, 비동기로 처리할 메서드 위에 @Async를 붙여주면 됩니다.&lt;/p&gt;
&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: #f9f9c8;&quot;&gt;@Async를 사용할 때는 주의 해야할 점이 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;private 메서드에 붙여서는 안 됩니다. (컴파일 에러로 확인 가능)&lt;/li&gt;
&lt;li&gt;같은 클래스 내부에서 스스로 호출(self-invocation)을 하면 안 된다. (컴파일 에러로 확인 불가)&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번은 private 메서드에 @Async를 붙여보면 IntelliJ에서 컴파일 에러를 확인해 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VrVxB/btsDKelivg7/7OBngSZXdCmWJ4mo6SNuBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VrVxB/btsDKelivg7/7OBngSZXdCmWJ4mo6SNuBk/img.png&quot; data-alt=&quot;private 메서드에 @Async 붙였을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VrVxB/btsDKelivg7/7OBngSZXdCmWJ4mo6SNuBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVrVxB%2FbtsDKelivg7%2F7OBngSZXdCmWJ4mo6SNuBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;292&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;private 메서드에 @Async 붙였을 때&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;2번은 아래와 같이 컴파일로 확인이 되지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tJlid/btsDKPlfIN9/qHvlxkMTVtebWz9BcreDa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tJlid/btsDKPlfIN9/qHvlxkMTVtebWz9BcreDa0/img.png&quot; data-alt=&quot;같은 클래스 내부에 있는 test() 메서드를 self invocation&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tJlid/btsDKPlfIN9/qHvlxkMTVtebWz9BcreDa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtJlid%2FbtsDKPlfIN9%2FqHvlxkMTVtebWz9BcreDa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;332&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;같은 클래스 내부에 있는 test() 메서드를 self invocation&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;이제 외부에서 MonitoringProvider의 push() 메서드를 호출 했을 때, 내부에 있는 test()가 당연히 비동기 처리 되겠지 하고 생각하고 놔두면 이제 디버깅 지옥을 맛볼 수 있습니다.&lt;/p&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() 메서드를 여러 번 요청해서 비동기 처리가 되는지 확인해보면 다음과 같이 순차적으로 동기적으로 처리되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vpLOj/btsDGrGsNO4/r1i3KkUqTHRx3j2ZuDzUG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vpLOj/btsDGrGsNO4/r1i3KkUqTHRx3j2ZuDzUG0/img.png&quot; data-alt=&quot;순차적으로 동기적으로 처리된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vpLOj/btsDGrGsNO4/r1i3KkUqTHRx3j2ZuDzUG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvpLOj%2FbtsDGrGsNO4%2Fr1i3KkUqTHRx3j2ZuDzUG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1928&quot; height=&quot;622&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;622&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: #f9f9c8;&quot;&gt;Spring 컨테이너에 등록되어 있는 스프링 빈의 메서드가 호출되었을 때, @Async가 붙어있다면 AOP로 처리되어 스프링이 메서드 요청을 가로채서 다른 Thread Pool에서 실행시켜주는 구조&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;그래서 @Async를 private에 붙이거나 내부 호출을 하게 되면 스프링이 가로채서 프록시 처리를 할 수 없기 때문에 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;&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;b&gt;✨ 적용하기&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 주의점을 확인하고, 기존 로직에 비동기 처리를 진행해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, @Async를 사용할 수 있도록 @EnableAsync를 붙여줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705740163838&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EnableAsync
@SpringBootApplication
public class PraisePushApplication {
    public static void main(String[] args) {
        SpringApplication.run(PraisePushApplication.class, args);
    }
}&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;비동기 처리를 원했던 메시지를 보내는 push() 메서드에 @Async를 붙여줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705740233390&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SlackMonitoringProvider implements MonitoringProvider {

    @Value(&quot;${error.webhook.url}&quot;)
    private String webHookUrl;

    @Async
    @Override
    public void push(final Exception exception, final HttpServletRequest request) {
        SlackApi slackApi = new SlackApi(webHookUrl);
        slackApi.call(createSlackMessage(exception, request));
    }
    
    ... 생략 ...
}&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;요청 시간 로그를 설정하고, push 로직에서 5초의 지연을 두고 요청을 확인해 보면 메시지를 보내는 로직이 비동기적으로 처리되는 것을 확인할 수 있습니다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bymOEt/btsDIx6X6jb/EbTqzWfSs5K2pmfz8fxDNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bymOEt/btsDIx6X6jb/EbTqzWfSs5K2pmfz8fxDNk/img.png&quot; data-alt=&quot;Slack으로 메시지 보내는 로직이 비동기로 처리되어 클라이언트에게 응답되는 시간이 먼저 처리되는 것을 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bymOEt/btsDIx6X6jb/EbTqzWfSs5K2pmfz8fxDNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbymOEt%2FbtsDIx6X6jb%2FEbTqzWfSs5K2pmfz8fxDNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1942&quot; height=&quot;428&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Slack으로 메시지 보내는 로직이 비동기로 처리되어 클라이언트에게 응답되는 시간이 먼저 처리되는 것을 확인&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;</description>
      <category> &amp;zwj;  Dev/SpringBoot</category>
      <category>@Async</category>
      <category>Spring Async</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/101</guid>
      <comments>https://hoonsb.tistory.com/101#entry101comment</comments>
      <pubDate>Sat, 20 Jan 2024 17:52:20 +0900</pubDate>
    </item>
    <item>
      <title>[Jenkins] ERROR: script returned exit code 255</title>
      <link>https://hoonsb.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 시작한 프로젝트에서 Jenkins를 사용해서 CI/CD 파이프라인을 구성하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;develop 브랜치에서 작업을 하고, push를 하면 Jenkins Pipeline이 동작하면서 자동화 배포 작업이 진행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 프로젝트에서는 다음과 같은 Pipeline이 동작해서 배포를 진행합니다.&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;Git Clone&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Github Repository의 develop 브랜치에 변화가 발생되면 Jenkins 동작이 시작됩니다.&lt;/li&gt;
&lt;li&gt;develop 브랜치를 Jenkins 서버로 clone 해서 가져옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DEV-Build&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이 과정에서 프로젝트의 Build와 Test를 진행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Deploy&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Jenkins 서버에서 Develop 서버로 SSH 통신을 통해 배포 스크립트를 실행합니다.&lt;/li&gt;
&lt;li&gt;이 글에서는 이 &quot;Deploy&quot; 과정에서 스크립트 실행 후 발생한 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&quot;ERROR: script returned exit code 255&quot;&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-origin-width=&quot;1196&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvvnDw/btsDj2lBdsX/glJWyMKPMyQ2y2eSbEEy11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvvnDw/btsDj2lBdsX/glJWyMKPMyQ2y2eSbEEy11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvvnDw/btsDj2lBdsX/glJWyMKPMyQ2y2eSbEEy11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvvnDw%2FbtsDj2lBdsX%2FglJWyMKPMyQ2y2eSbEEy11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;259&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;474&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;1200&quot; data-origin-height=&quot;1040&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWTTFn/btsDhTJjicD/SFatpoRU0oaKR3vQIYLWc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWTTFn/btsDhTJjicD/SFatpoRU0oaKR3vQIYLWc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWTTFn/btsDhTJjicD/SFatpoRU0oaKR3vQIYLWc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWTTFn%2FbtsDhTJjicD%2FSFatpoRU0oaKR3vQIYLWc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;351&quot; height=&quot;304&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1040&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 기존 스크립트&lt;/b&gt;&lt;/h2&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;pre id=&quot;code_1704881673904&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash
source ~/.bashrc

REPOSITORY=/home/praise-push
echo &quot;### check running application... ###&quot;
CURRENT_PID=$(pgrep -f praise-push)
echo &quot;### current pid = $CURRENT_PID&quot;

if [[ -z ${CURRENT_PID} ]]; then
    echo &quot;### Not found running application ###&quot;
else
    echo &quot;### Found application!! try to kill process $CURRENT_PID ###&quot;
    kill -15 $CURRENT_PID
    sleep 3
fi
echo &quot;### Ready to deploy ###&quot;

JAR_NAME=$(ls $REPOSITORY | grep 'praise-push' | tail -n 1)

echo &quot;### Source file Name: $JAR_NAME&quot;
chmod +x $REPOSITORY/$JAR_NAME
nohup /usr/bin/java -jar $REPOSITORY/$JAR_NAME &amp;gt;&amp;gt; $REPOSITORY/nohup.out 2&amp;gt;&amp;amp;1 &amp;amp;
echo &quot;success&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;b&gt;[ 실행 중인 프로세스가 있는지 확인 ]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 설명하면 현재 실행 중인 java application의 PID를 찾아서 CURRENT_PID에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 실행중인 프로세스가 있다면 kill 명령어를 통해 중지시키고, 없다면 그냥 넘어가도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[ 새로운 프로세스를 실행합니다. ]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins 서버에서 Build, Test 과정이 끝난 jar 파일을 Develop 서버로 복사해서 가져오게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 jar 파일을 nohup을 이용해서 서버를 실행합니다.&lt;/p&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에서 Deploy Pipeline은 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704882424956&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;stage('Deploy') {
    steps {
        sshagent(credentials: ['dev_server']) {
            sh &quot;ssh -o StrictHostKeyChecking=no -p 9001 root@123.11.11.11 'uptime'&quot;
            sh &quot;scp -P 9001 /var/jenkins_home/workspace/praise-push-dev/build/libs/praise-push-0.0.1-SNAPSHOT.jar root@123.11.11.11:/home/praise-push&quot;
            sh &quot;ssh -t -p 9001 root@123.11.11.11 'cd /home/praise-push &amp;amp;&amp;amp; ./deploy.sh'&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;ssh으로 uptime을 요청하여 첫 SSH 연결을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scp를 이용하여 Jenkins 서버에서 Build, Test 과정을 마치고 생성된 jar 파일을 Develop 서버로 복사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh를 이용하여 원격으로 &quot;/home/praise-push/deploy.sh&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;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;먼저 script에서 어떻게 해야 255 exit code가 발생되는지 찾아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Remote(원격) 서버에 SSH가 설치되어 있지 않거나, 올바른 Connection이 아닌 경우&lt;/b&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;처음에는 SSH 연결이 잘 안 되고 있구나라고 판단하여 이쪽으로 찾아보기 시작했었습니다.&lt;/p&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)에서 Key Pair를 생성하여 공개키(pub)를 원격서버(Develop)로 보내서 서버의 &quot;authrized_keys&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;SSH 요청 시 클라이언트와 서버 사이의 세션키(대칭키) 생성을 위한 키 교환이 시작되는데, 이때 &quot;authrized_keys&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;그래서 저는 SSH 연결이 잘 못 된 것은 아닌지 Jenkins 서버와 Develop 서버를 다시 연결을 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 결과는 달라지지 않았습니다. 다시 한번 확인해 보니 위 스크립트에서 이 부분 이전까지는 스크립트가 실행되는 것을 확인했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704882578024&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;echo &quot;### Ready to deploy ###&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;이 echo 명령어전까지는 실행이 되었다는 것은 Jenkins -&amp;gt; Develop의 SSH 연결에서 발생한 문제는 아니라는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 SSH 연결을 통해 &quot;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;/home/praise-push/deploy.sh&quot;&lt;span&gt; 명령까지 들어와서 실행을 했지만, 어떤 이유 때문에 중간에 script가 비정상적으로 종료되어 script가 exit 255 code를 리턴했던 것이라는 판단을 하게 되었습니다.&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; text-align: start;&quot;&gt;&lt;span&gt;즉, 문제는 deploy.sh 스크립트에 있다!&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;deploy.sh를 하나씩 실행하며 디버깅을 해봤습니다.&lt;/b&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;1. 첫 번째 시도&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704882799928&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash
source ~/.bashrc
REPOSITORY=/home/praise-push&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;당연히 문제는 없겠지만, 이틀 넘게 SSH가 원인인 줄 알고 삽질을 해서 아주 꼼꼼하게... 진행해 보기 위해서 스크립트의 시작 부분부터 진행했습니다.&lt;/p&gt;
&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;&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;pre id=&quot;code_1704882921138&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash
source ~/.bashrc
REPOSITORY=/home/praise-push
echo &quot;### check running application... ###&quot;
CURRENT_PID=$(pgrep -f praise-push)
echo &quot;### current pid = $CURRENT_PID&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;/p&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. 세 번째 시도&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704882963884&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash
source ~/.bashrc
REPOSITORY=/home/praise-push
echo &quot;### check running application... ###&quot;
CURRENT_PID=$(pgrep -f praise-push)
echo &quot;### current pid = $CURRENT_PID&quot;

if [[ -z ${CURRENT_PID} ]]; then
    echo &quot;### Not found running application ###&quot;
else
    echo &quot;### Found application!! try to kill process $CURRENT_PID ###&quot;
    kill -15 $CURRENT_PID
    sleep 3
fi&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;예상했던대로 세 번째 시도에서 실패했습니다. 그럼 이 if 블록 내부에 문제가 있다는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법적인 오류는 아닌 것으로 보였고, &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;의심이 갔던 부분은 &quot;kill -15 $CURRENT_PID&quot; 부분&lt;/span&gt;이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcLNuw/btsDdETY9FX/UWYO24Hpw3QKxu8DqXdubk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcLNuw/btsDdETY9FX/UWYO24Hpw3QKxu8DqXdubk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcLNuw/btsDdETY9FX/UWYO24Hpw3QKxu8DqXdubk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcLNuw%2FbtsDdETY9FX%2FUWYO24Hpw3QKxu8DqXdubk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;365&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;608&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;b&gt;4. 네 번째 시도&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704883159089&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash
source ~/.bashrc
REPOSITORY=/home/praise-push
echo &quot;### check running application... ###&quot;
CURRENT_PID=$(pgrep -f praise-push)
echo &quot;### current pid = $CURRENT_PID&quot;

if [[ -z ${CURRENT_PID} ]]; then
    echo &quot;### Not found running application ###&quot;
else
    echo &quot;### Found application!! try to kill process $CURRENT_PID ###&quot;
    sleep 3
fi&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;의심이 들었던 kill 명령어를 제거한 후 다시 시도해봤는데, Deploy Pipeline이 성공했습니다.&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;1194&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daGxhJ/btsDfMK3zZQ/OxVnOOa6NHnWWZj8AxXKkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daGxhJ/btsDfMK3zZQ/OxVnOOa6NHnWWZj8AxXKkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daGxhJ/btsDfMK3zZQ/OxVnOOa6NHnWWZj8AxXKkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaGxhJ%2FbtsDfMK3zZQ%2FOxVnOOa6NHnWWZj8AxXKkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;254&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;476&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 원인 분석&lt;/b&gt;&lt;/h2&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;kill 명령어로 CURRENT_PID를 종료하고 있었기 때문에 Deploy Pipeline을 실행시켜 두고, Develop 서버에서 계속 &quot;ps&amp;nbsp;-ef&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;praise-push&quot;을 실행하며 콘솔을 확인해 봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704883456926&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@praise-push-dev:/home/praise-push# ps -ef | grep praise-push
root     22623     1  3 19:01 pts/0    00:00:16 /usr/bin/java -jar /home/praise-push/praise-push-0.0.1-SNAPSHOT.jar
root     23741 16549  0 19:09 pts/0    00:00:00 grep --color=auto praise-push
root@praise-push-dev:/home/praise-push# ps -ef | grep praise-push
root     22623     1  3 19:01 pts/0    00:00:16 /usr/bin/java -jar /home/praise-push/praise-push-0.0.1-SNAPSHOT.jar
root     23789 23720  0 19:09 ?        00:00:00 bash -c                              cd /home/praise-push                             ./deploy.sh
root     23794 16549  0 19:09 pts/0    00:00:00 grep --color=auto praise-push&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;이상한 점을 확인했습니다. Deploy Pipeline이 실행되는 중간에 &quot;praise-push&quot;가 포함된 새로운 프로세스가 하나 실행되는 것을 보았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704883542299&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root     23789 23720  0 19:09 ?        00:00:00 bash -c                              cd /home/praise-push                             ./deploy.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;span style=&quot;background-color: #f9f9c8;&quot;&gt;Deploy Pipeline에서 실행했던 &quot;cd /home/priase-push&quot; 명령어에서 시작되었던 프로세스가 kill의 대상인 &quot;pgrep -f praise-push&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;/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;Jenkins에서 SSH를 통해 Develop 서버의 cd /home/praise-push/deploy.sh를 실행 &lt;span style=&quot;color: #f89009;&quot;&gt;(PID 1)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;deploy.sh 스크립트에서 현재 실행 중인 praise-push-0.0.1.jar&lt;span style=&quot;color: #ee2323;&quot;&gt;(PID 2)&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;&quot;pgrep -f praise-push&quot;을 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&quot;pgrep -f praise-push&quot;의 결과를 CURRENT_PID 변수에 저장합니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;여기서 CURRENT_PID에는 의도대로라면 &lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;(PID 2)&lt;/span&gt;만 있어야 합니다.&lt;/li&gt;
&lt;li&gt;하지만, 결과에는 &lt;span style=&quot;color: #f89009; text-align: left;&quot;&gt;(PID 1)&lt;/span&gt;와 &lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;(PID 2)&lt;/span&gt;가 들어가게 됩니다.&lt;/li&gt;
&lt;li&gt;이유는 &quot;cd /home/&lt;span style=&quot;color: #006dd7;&quot;&gt;praise-push&lt;/span&gt;/deploy.sh&quot;에 praise-push가 포함되어 탐색 대상이 되었던 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&quot;kill -15 $CURRENT_PID&quot;에서 &lt;span style=&quot;color: #f89009; text-align: left;&quot;&gt;(PID 1)&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;(PID 2)&lt;/span&gt;를 모두 죽이게 됩니다.&lt;/li&gt;
&lt;li&gt;여기서 deploy.sh의 비정상적인 종료가 되고, exit code 255를 리턴하게 됩니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 해결&lt;/b&gt;&lt;/h2&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;&quot;pgrep -f praise-push&quot; 스크립트를 통해 찾으려는 대상은 &quot;praise-push-0.0.1.jar&quot;인 jar 프로세스이기 때문에 아래와 같이 변경했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704885891195&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CURRENT_PID=$(pgrep -f praise-push-*.jar)&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;이 부분만 이렇게 수정한 후 Jenkins CI/CD Pipeline을 실행시켜 보니 모든 stage가 통과되었고, 정상적으로 배포에 성공했습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/carzX3/btsDkFDyDzp/wsSAQCzOGZGKgu5DsgWmDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/carzX3/btsDkFDyDzp/wsSAQCzOGZGKgu5DsgWmDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/carzX3/btsDkFDyDzp/wsSAQCzOGZGKgu5DsgWmDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcarzX3%2FbtsDkFDyDzp%2FwsSAQCzOGZGKgu5DsgWmDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;306&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;512&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;생각보다 굉장히 사소한 실수로 시작된 문제인데, 꽤나 오랜 시간 잡고 있었어서 기록해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category> &amp;zwj;  Dev/Infra</category>
      <category>Jenkins exit code 255</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/100</guid>
      <comments>https://hoonsb.tistory.com/100#entry100comment</comments>
      <pubDate>Wed, 10 Jan 2024 20:27:35 +0900</pubDate>
    </item>
    <item>
      <title>[Team Ginger]  크리스마스에도 코딩을 하는 사람이 있다?</title>
      <link>https://hoonsb.tistory.com/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저는 어드벤트 캘린더 형식의 크리스마스 익명 편지 서비스인 &lt;span style=&quot;background-color: #f8e1e2;&quot;&gt;&lt;b&gt;&quot;Ginger Hotel  &quot;&lt;/b&gt;&lt;/span&gt;의 2023년 이야기를 적어볼까 합니다.&lt;/p&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;b&gt; ️ 팀진저의 백엔드에서는 이런 기술 스택을 선택했습니다.&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 백엔드 개발을 맡아서 진행하게 되었습니다. 가장 먼저 했던 것은 기술 스택을 선택하는 일이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;988&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tPUjV/btsDfmdGT1f/kgZJKSWUqidXfQZpdkQFH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tPUjV/btsDfmdGT1f/kgZJKSWUqidXfQZpdkQFH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tPUjV/btsDfmdGT1f/kgZJKSWUqidXfQZpdkQFH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtPUjV%2FbtsDfmdGT1f%2FkgZJKSWUqidXfQZpdkQFH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;483&quot; height=&quot;214&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;438&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: #f9f9c8;&quot;&gt;Nest.js, Typescript, PostgreSQL&lt;/span&gt;이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;Java, Spring으로 공부하고 계신데 왜 Node 기반의 기술 스택을 선택하게 되었나요?&quot;&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;저에게도 큰 도전이었던 것 같습니다. 팀원 한분이 풀스택으로 참여를 희망하셨어서 Front-End 개발팀에서 선택했던 언어인 Typescript에 맞추기 위함이 가장 큰 이유였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 처음 사용해 보는 기술 스택들이었기 때문에 신중하게 선택을 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Typescript&lt;/b&gt;의 경우에는 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;타입의 안정성을 고려하며 개발&lt;/span&gt;할 수 있어서 언어 선택에 있어서는 주저함이 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크는 &lt;b&gt;Nest.js&lt;/b&gt;를 선택했던 이유는 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;모듈 단위로 나누어 개발을 할 수 있고, 확장성에 있어서도 부족함이 없다고 생각&lt;/span&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 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;그리고 개발을 하며 느꼈던 점은 npm에 정말 유용한 라이브러리가 많아서 부족함 없이 개발을 했던 것 같습니다.&lt;/p&gt;
&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;PostgreSQL&lt;/b&gt;을 선택했습니다. 가장 큰 이유는 진저호텔에서는 Member -&amp;gt; Hotel -&amp;gt; HotelWindow -&amp;gt; Letter -&amp;gt; Reply와 같이 굉장히 깊은 연관 관계의 Depth가 있었습니다.&lt;/p&gt;
&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: #f9f9c8;&quot;&gt;Join과 같은 연산 처리 능력이 뛰어난 데이터베이스&lt;/span&gt;를 고려해야 했습니다. 또한 2022년 진저호텔(편지 약 650만 개)이 가지고 있는 데이터를 무시할 수는 없었기 때문에 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;대용량 데이터에서 성능이 좋은 데이터베이스&lt;/span&gt;를 찾다 보니 Oracle과 PostgreSQL을 찾게 되었습니다.&lt;/p&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를 사용하기로 결정이 된 상태이기 때문에 라이센스 비용 또한 고려해야 했기 때문에 라이센스 비용이 무료였던 PostgreSQL을 최종적으로 선택하게 되었습니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;☁️ 팀진저의 클라우드 이야기&lt;/b&gt;&lt;/h2&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;b&gt;&quot;클라우드 플랫폼&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qQWI6/btsC9m6GqVA/BPyk3KkMEOK5gqROtyzkSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qQWI6/btsC9m6GqVA/BPyk3KkMEOK5gqROtyzkSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qQWI6/btsC9m6GqVA/BPyk3KkMEOK5gqROtyzkSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqQWI6%2FbtsC9m6GqVA%2FBPyk3KkMEOK5gqROtyzkSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1648&quot; height=&quot;834&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;834&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;color: #9d9d9d;&quot;&gt;(AWS는 맨날 EC2 만 써봐서...)&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;Application Load Balancer는 부하 분산을 위해 EC2를 증설할 계획을 가지고 있었는데, 그래서 미리 적용해 두게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Cloud Front는 본래 용도는 CDN 서버로 자주 사용되는 데이터를 캐시 하여 비용을 줄이는 용도로 많이 쓰인다고 합니다.&lt;/p&gt;
&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: #f9f9c8;&quot;&gt;지금 현재 아키텍처에서는 Cloud Front가 캐시의 역할이 아닌 그냥 AWS Certificate Manager와의 상호작용을 통해 SSL 인증서를 확인해 주는 역할&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;이대로 서비스에 나간다면 Cloud Front를 거치는 비용이 계속 나갈 것으로 판단되었고, 멋쟁이 사자처럼 대학 동아리에서 인연이 있었던 높으신 분께 현재 상황에 대한 피드백을 요청했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Cloud Front 제거&lt;/b&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;얻은 피드백에서는 Load Balancer에서도 AWS Certificate Manager와 상호작용할 수 있는 방법이 있다는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pajamacoder.tistory.com/68&quot;&gt;[aws] ELB Application Load Balancer를 이용한 SSL 설정&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704859176239&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;[aws] ELB Application Load Balancer를 이용한 SSL 설정&quot; data-og-description=&quot;이번 포스트에서는 AWS ELB application load balancer를 이용해 SSL을 적용 하는 방법을 정리한다. 즉 이 포스트의 목적은 아래 Fig 1과 같은 architecture 로 서버 운영 구조를 완성하는 것이다. 이 구조를 간&quot; data-og-host=&quot;pajamacoder.tistory.com&quot; data-og-source-url=&quot;https://pajamacoder.tistory.com/68&quot; data-og-url=&quot;https://pajamacoder.tistory.com/68&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EoFiz/hyU2jJ5XiV/wSkJDfMf348B8fhAuHgykk/img.png?width=791&amp;amp;height=360&amp;amp;face=0_0_791_360,https://scrap.kakaocdn.net/dn/fwWKy/hyU2qWLsCt/66GuJaRmsQQ2SSLmqmIHU1/img.png?width=791&amp;amp;height=360&amp;amp;face=0_0_791_360,https://scrap.kakaocdn.net/dn/Useqv/hyU2qJbZNK/8huHVY7Go45MOqh7kvk46k/img.png?width=1581&amp;amp;height=747&amp;amp;face=0_0_1581_747&quot;&gt;&lt;a href=&quot;https://pajamacoder.tistory.com/68&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pajamacoder.tistory.com/68&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EoFiz/hyU2jJ5XiV/wSkJDfMf348B8fhAuHgykk/img.png?width=791&amp;amp;height=360&amp;amp;face=0_0_791_360,https://scrap.kakaocdn.net/dn/fwWKy/hyU2qWLsCt/66GuJaRmsQQ2SSLmqmIHU1/img.png?width=791&amp;amp;height=360&amp;amp;face=0_0_791_360,https://scrap.kakaocdn.net/dn/Useqv/hyU2qJbZNK/8huHVY7Go45MOqh7kvk46k/img.png?width=1581&amp;amp;height=747&amp;amp;face=0_0_1581_747');&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;[aws] ELB Application Load Balancer를 이용한 SSL 설정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 AWS ELB application load balancer를 이용해 SSL을 적용 하는 방법을 정리한다. 즉 이 포스트의 목적은 아래 Fig 1과 같은 architecture 로 서버 운영 구조를 완성하는 것이다. 이 구조를 간&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pajamacoder.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;이렇게 되면 기존 Cloud Front를 떼어내서 아키텍처가 가벼워지고, Cloud Front에서 과금될 비용들을 다른 곳에 쓸 수 있게 되기 때문에 바로 적용하게 되었습니다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpnmIk/btsDfnRmpUq/vAdEB7S6jgu0xWLiGQTslk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpnmIk/btsDfnRmpUq/vAdEB7S6jgu0xWLiGQTslk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpnmIk/btsDfnRmpUq/vAdEB7S6jgu0xWLiGQTslk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpnmIk%2FbtsDfnRmpUq%2FvAdEB7S6jgu0xWLiGQTslk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1566&quot; height=&quot;914&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;914&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;Cloud Front를 떼어내고 난 후의 아키텍처입니다. 생각보다 빠르게 떼어낼 수 있었습니다.&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;CI/CD 파이프라인에 Docker 적용&lt;/b&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;기존에는 CI/CD 과정에서 Github Actions로 EC2에 접근하여 미리 적어둔 스크립트를 실행시키도록 해두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, EC2에서 프로젝트 build 작업까지 하다 보니 인스턴스에 부하가 모이게 되는 것 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Docker Image를 이용하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련된 내용은 따로 글에 정리를 잘해뒀습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hoonsb.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hoonsb.tistory.com/98&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704865256994&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;[Github Actions x Docker] 자동화 배포 환경 도커로 변경하기&quot; data-og-description=&quot;1. 기존 아키텍처와 배포 스크립트 분석 프로젝트 진행 과정에서 초반에 간단한 인프라 설계를 위처럼 진행했습니다. 먼저 EC2(Ubuntu) 내부에 Node 16 버전을 포함해서 필요한 것들을 모두 수동으로 &quot; data-og-host=&quot;hoonsb.tistory.com&quot; data-og-source-url=&quot;https://hoonsb.tistory.com/98&quot; data-og-url=&quot;https://hoonsb.tistory.com/98&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qZD3p/hyU2gT99z7/fKoky4Is5HHWjE6hLFhmSk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bRZMvE/hyU2sG4Knq/F2EC0ZumtlPzes35UsDc60/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/br40mJ/hyU2nMwmVt/Ktjmu2B4Kz0rBc6bie5qb0/img.png?width=1371&amp;amp;height=727&amp;amp;face=0_0_1371_727&quot;&gt;&lt;a href=&quot;https://hoonsb.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hoonsb.tistory.com/98&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qZD3p/hyU2gT99z7/fKoky4Is5HHWjE6hLFhmSk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bRZMvE/hyU2sG4Knq/F2EC0ZumtlPzes35UsDc60/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/br40mJ/hyU2nMwmVt/Ktjmu2B4Kz0rBc6bie5qb0/img.png?width=1371&amp;amp;height=727&amp;amp;face=0_0_1371_727');&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 Actions x Docker] 자동화 배포 환경 도커로 변경하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 기존 아키텍처와 배포 스크립트 분석 프로젝트 진행 과정에서 초반에 간단한 인프라 설계를 위처럼 진행했습니다. 먼저 EC2(Ubuntu) 내부에 Node 16 버전을 포함해서 필요한 것들을 모두 수동으로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hoonsb.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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;EC2 인스턴스 추가, RDS Multi-AZ 다중화 배포 적용&lt;/b&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;진저호텔 서비스를 운영하는 데 있어서 가장 중요하게 여겼던 것은 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;span style=&quot;background-color: #f9f9c8;&quot;&gt;편지를 안전하게 보관&lt;/span&gt;하자!&lt;/li&gt;
&lt;li&gt;사용자들이 서비스를 이용하는 과정에서 끊기지 않도록 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;중단 없이 확장할 수 있는 서버&lt;/span&gt;를 만들자!&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 서버 중단 없이 기능을 확장하고, hotfix를 처리하기 위해서 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;EC2 인스턴스를 추가하여 Load Balancer의 대상 그룹에 추가&lt;/span&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구성하고, 하나의 서버에 배포 변경 사항을 적용하고 Health Check가 통과되었을 때, 다른 하나의 서버를 배포하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 진저호텔은 12월 한 달간 한 번의 서버 중단 없이 서비스를 유지할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음은 데이터 백업입니다. 기존 RDS는 Single-AZ로 하나의 Availability Zone을 사용했기 때문에 데이터 백업 중에는 데이터베이스가 중단된다는 문제점이 있었습니다. 중단 없이 백업할 수 있는 방법은 없을까 방법을 찾다가 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;AWS RDS에서는 &quot;Multi-AZ 다중화 배포&quot; 옵션을 제공&lt;/span&gt;했고, master DB와 standby DB가 있어서 백업의 경우에는 standby에서 진행되어 master DB는 계속 중단 없이 사용할 수 있게 됩니다. 또한 master DB에 문제가 생겼을 때, standby DB가 바로 master로 승격하게 되면 장애 대응에도 신속한 특징을 가지고 있어 운영 환경에서는 적용하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 변경한 후의 아키텍처는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIt0QF/btsDfiiLctR/LQ7WIF017pa6Ca4WVNSGXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIt0QF/btsDfiiLctR/LQ7WIF017pa6Ca4WVNSGXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIt0QF/btsDfiiLctR/LQ7WIF017pa6Ca4WVNSGXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIt0QF%2FbtsDfiiLctR%2FLQ7WIF017pa6Ca4WVNSGXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;970&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;970&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;서버 인스턴스의 상태를 모니터링하자.&lt;/b&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;background-color: #f9f9c8;&quot;&gt;Discord의 Web Hook과 AWS의 CloudWatch를 연동하여 사용&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;서버 모니터링이 중요했던 이유는 RDS의 경우 만약 쿼리 병목이나 슬로우 쿼리가 발견되어 과도한 부하가 걸리게 되거나, 요청에 대한 처리 속도가 늦어지게 되는 경우를 조기에 발견하고 대응해야 된다고 생각되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서  CloudWatch에 EC2, ALB, RDS에서 감지할 옵션들의 임계값을 설정하고, 임계값을 넘어선 경우에 AWS SNS를 통해 AWS Lambda에서 Event를 받아 Discord로 알림을 바로 줄 수 있도록 구성했습니다.&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;870&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xuWfG/btsDfib0wb9/RFYbkfWmBp8VfRyKnieRt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xuWfG/btsDfib0wb9/RFYbkfWmBp8VfRyKnieRt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xuWfG/btsDfib0wb9/RFYbkfWmBp8VfRyKnieRt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxuWfG%2FbtsDfib0wb9%2FRFYbkfWmBp8VfRyKnieRt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;338&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;534&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;이렇게 서버 인스턴스 모니터링 과정을 거치게 되면 다음과 같은 메시지(경보)를 Discord에서 확인하고, 바로 대응할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbtLgu/btsDiu95Pm3/JrKoaNjPKw5HngNW99knS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbtLgu/btsDiu95Pm3/JrKoaNjPKw5HngNW99knS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbtLgu/btsDiu95Pm3/JrKoaNjPKw5HngNW99knS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbtLgu%2FbtsDiu95Pm3%2FJrKoaNjPKw5HngNW99knS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1864&quot; height=&quot;666&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;666&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;제가 무적 짱짱맨이어서 24시간 안 자고, 계속 서버만 모니터링할 수 있다면 좋았을 텐데 저는... 무적 짱짱맨이 아니기 때문에 다른 팀원들의 눈과 귀를 빌리고자 Discord를 적극적으로 사용하게 되었습니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;✉️ 편지, 답장 차단 프로세스에 대한 고민&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 편지 차단에 대한 피드백이 굉장히 많았었습니다. 그래서 올해는 편지 차단 기능에 힘을 많이 쓰게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차단 기능에 있어서 익명 편지 서비스이다보니 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;편지를 보낸 사람이 누구인지 특정할 수 없도록 하는데 고민&lt;/span&gt;을 많이 했었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJWsl/btsDxe1hN7I/10iWPln1yERD6HbOfpF1Wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJWsl/btsDxe1hN7I/10iWPln1yERD6HbOfpF1Wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJWsl/btsDxe1hN7I/10iWPln1yERD6HbOfpF1Wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJWsl%2FbtsDxe1hN7I%2F10iWPln1yERD6HbOfpF1Wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1222&quot; height=&quot;354&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민 했었던 부분은 한 명이 여러 닉네임으로 편지를 보냈을 때의 상황이었습니다. 위 예시를 보면 &quot;헤르미온느&quot;와 &quot;론&quot;으로부터 받은 편지가 있습니다. &quot;헤르미온느&quot;와 &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;만약 기존에 생각하고 있던 프로세스로 편지를 받은 철수가 훈섭을 차단했을 때, block_history 테이블에 차단한 사람은 철수, 차단당한 사람은 훈섭으로 하게 되었을 때 아래와 같은 고민이 있었습니다.&lt;/p&gt;
&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: #f9f9c8;&quot;&gt;철수가 &quot;헤르미온느&quot;를 차단했는데, &quot;론&quot;에게 받은 편지도 차단된다면, 철수는 &quot;헤르미온느&quot;와 &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;각 편지는 별개로 차단을 할 수 있고, 발신자에 대한 차단을 횟수로 관리하여 편지 차단을 진행합니다. &quot;헤르미온느&quot;에게 온 편지를 차단하면 차단한 사람은 철수, 차단당한 사람은 훈섭 그리고 차단 횟수가 1이 됩니다. 이렇게 되면 훈섭은 철수에게 편지를 보낼 수 없고, &quot;헤르미온느&quot;에게 온 편지를 차단했을 때, &quot;론&quot;에게 온 편지에는 어떠한 영향도 주지 않기 때문에 &quot;헤르미온느&quot;와 &quot;론&quot;이 같은 발신자라는 것을 유추할 수 없게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cg3wa5/btsDxTCeT4j/wsubtSiFsZuxLHV8KFk8nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cg3wa5/btsDxTCeT4j/wsubtSiFsZuxLHV8KFk8nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cg3wa5/btsDxTCeT4j/wsubtSiFsZuxLHV8KFk8nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcg3wa5%2FbtsDxTCeT4j%2FwsubtSiFsZuxLHV8KFk8nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1203&quot; height=&quot;280&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 차단을 해제하게 되면 차단 횟수는 감소하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1972&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxNiwz/btsDCR5tLNF/LSiXSW7ueKvMRefsCS7ORk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxNiwz/btsDCR5tLNF/LSiXSW7ueKvMRefsCS7ORk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxNiwz/btsDCR5tLNF/LSiXSW7ueKvMRefsCS7ORk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxNiwz%2FbtsDCR5tLNF%2FLSiXSW7ueKvMRefsCS7ORk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1972&quot; height=&quot;600&quot; data-origin-width=&quot;1972&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;론&quot;에게 받은 편지에 대한 차단을 해제했을 때는 아직 훈섭(헤르미온느)에게 받은 편지를 차단한 상태이기 때문에 훈섭에게는 편지와 답장을 받을 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1986&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw3j2A/btsDARkyZL4/w6l4Kz6OTt33N3ztcmTlIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw3j2A/btsDARkyZL4/w6l4Kz6OTt33N3ztcmTlIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw3j2A/btsDARkyZL4/w6l4Kz6OTt33N3ztcmTlIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw3j2A%2FbtsDARkyZL4%2Fw6l4Kz6OTt33N3ztcmTlIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1986&quot; height=&quot;760&quot; data-origin-width=&quot;1986&quot; data-origin-height=&quot;760&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;&quot;헤르미온느&quot;에게 받은 편지까지 차단 해제를 하면 철수가 훈섭을 차단했던 횟수가 0이 되기 때문에 해당 데이터는 사라지게 됩니다. 이제는 훈섭이 철수에게 편지를 보낼 수 있게 됩니다.&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;그냥신난짤.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/davJMC/btsDRIgrjYa/1LHPgJlTM12tKa1007Z6z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/davJMC/btsDRIgrjYa/1LHPgJlTM12tKa1007Z6z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/davJMC/btsDRIgrjYa/1LHPgJlTM12tKa1007Z6z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdavJMC%2FbtsDRIgrjYa%2F1LHPgJlTM12tKa1007Z6z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;329&quot; data-filename=&quot;그냥신난짤.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;930&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;대부분 인프라 관련된 클라우드 이야기였지만, 제가 프로젝트를 진행하며 느꼈던 내용과 왜 이런 결정을 했는지에 대해서 기록해두고 싶어서 정리하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 2023년 진저호텔의 백엔드 개발 이야기를 풀어봤습니다.  &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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3624&quot; data-origin-height=&quot;2164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvk5BS/btsDhh4MxjB/6RdMp8IrLROVqUO8LtgUMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvk5BS/btsDhh4MxjB/6RdMp8IrLROVqUO8LtgUMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvk5BS/btsDhh4MxjB/6RdMp8IrLROVqUO8LtgUMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvk5BS%2FbtsDhh4MxjB%2F6RdMp8IrLROVqUO8LtgUMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3624&quot; height=&quot;2164&quot; data-origin-width=&quot;3624&quot; data-origin-height=&quot;2164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Activity/팀진저</category>
      <category>백엔드</category>
      <category>진저호텔</category>
      <category>팀진저</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/99</guid>
      <comments>https://hoonsb.tistory.com/99#entry99comment</comments>
      <pubDate>Wed, 10 Jan 2024 12:12:56 +0900</pubDate>
    </item>
    <item>
      <title>[Github Actions x Docker] 자동화 배포 환경 도커로 변경하기</title>
      <link>https://hoonsb.tistory.com/98</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 기존 아키텍처와 배포 스크립트 분석&lt;/b&gt;&lt;/h2&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-origin-width=&quot;1016&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6HzNk/btsC6UPl3sU/WGS4gHuyMxIPKtGKr6tjl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6HzNk/btsC6UPl3sU/WGS4gHuyMxIPKtGKr6tjl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6HzNk/btsC6UPl3sU/WGS4gHuyMxIPKtGKr6tjl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6HzNk%2FbtsC6UPl3sU%2FWGS4gHuyMxIPKtGKr6tjl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;791&quot; height=&quot;456&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;586&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;span style=&quot;background-color: #f9f9c8;&quot;&gt;EC2(Ubuntu) 내부에 Node 16 버전을 포함해서 필요한 것들을 모두 수동으로 설치했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 deploy.sh라는 스크립트 파일을 만들어 배포 스크립트를 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Github Actions에서 SSH 연결을 통해 EC2에 접근하여 deploy.sh를 실행하여 자동화 배포가 되도록 설정하여 완료했습니다.&lt;/p&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 저장소에서 프로젝트 최근 파일을 가져온 후 추가된 npm 라이브러리를 확인하고 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 build 하고, 빌드가 성공하면 현재 실행 중인 프로세스를 확인하고 중지시켜 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 백그라운드 실행을 위해서 nohup을 통해서 application을 실행시켜 줬습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689136122&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# deploy.sh

echo &quot;1. project root로 이동&quot;
cd ~/server/ginger-hotel-server

echo &quot;2. git pull&quot;
git pull origin develop

echo &quot;3. 추가된 라이브러리 설치&quot;
npm install

echo &quot;4. project build&quot;
npm run build

if [ $? -eq 0 ]; then
  # 빌드 성공 시
  echo &quot;5. 실행중인 프로세스 확인&quot;
  CURRENT_PID=$(pgrep -f node)

  echo &quot;6. 실행중인 프로세스 중지&quot;
  sudo kill -15 $CURRENT_PID

  echo &quot;7. 홈 경로 이동 후 프로젝트 재실행&quot;
  nohup npm run start:prod &amp;gt; ~/nohup.out 2&amp;gt; ~/nohup.err &amp;lt; /dev/null &amp;amp;
else
  echo &quot;프로젝트 Build 실패&quot;
  exit 1
fi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태로도 자동화 배포환경에는 문제가 없었고, 개발 단계에서 크게 불편한 점은 못 느꼈었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이 거의 마무리될 때쯤 두 가지 의문점이 들었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서버(EC2)가 증설되었을 때, EC2에 필요한 환경(특정 버전 Node)을 모두 수동으로 설치해줘야 하는 건가? Node 말고, 환경이 추가된다면?&lt;br /&gt;&lt;br /&gt;실제 서비스를 운영중일 때, Application이 실행되고 있는 서버(EC2)에서 Git Pull을 하고, Build를 하는 과정이 부하를 주어 서버 성능에 영향을 주지는 않을까?&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;background-color: #f9f9c8;&quot;&gt;&quot;도커&quot;를 활용하여 필요한 환경까지 함께 이미지로 만들고, Build 환경을 운영 서버와 분리해 보자는&lt;/span&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Ubuntu에 도커 설치하기&lt;/b&gt;&lt;/h2&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;1. ubuntu 패키지를 먼저 업데이트합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689685097&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&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Docker 설치에 필요한 패키지를 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689719802&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Docker 공식 GPG 키를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689747975&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -&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;4. Docker 공식 apt 저장소를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689767146&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&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;5. 시스템 패키지를 다시 업데이트합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689784130&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&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;6. Docker를 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689797408&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get install docker-ce docker-ce-cli containerd.io -y&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;7. Docker의 실행 상태를 확인합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689811672&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl status docker&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;8. 도커 허브 로그인을 진행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704689829377&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo docker login&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;a href=&quot;https://hub.docker.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker Hub&lt;/a&gt;에서 가입한 이메일과 비밀번호로 로그인을 진행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Docker Hub는 무료로 계정 1개당 1개의 Private 저장소를 사용할 수 있도록 제공해 줍니다.&lt;/li&gt;
&lt;li&gt;저는 이전에 Private 저장소를 쓴 적이 없기 때문에 이번 프로젝트에서 Private 저장소를 만들어 사용했습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 프로젝트의 Root 경로에 DockerFile 생성&lt;/b&gt;&lt;/h2&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;여기서 DockerFile은 도커에 존재하는 이미지를 기반으로 하여 내가 작성한 스크립트 파일을 통해 나만의 이미지를 생성할 수 있는 파일입니다. 즉, 저는 여기에 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;필요한 Docker 이미지를 골라 담아서 제가 만들고 있는 서버에 필요한 이미지를 생성하는 DockerFile을 생성&lt;/span&gt;했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704690169418&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Node Base Image
FROM node:16-alpine

# RUN mkdir -p /app
WORKDIR /app

# Current Local . to /app/
ADD . /app/

# install Library
RUN npm install

# Build
RUN npm run build

# PORT
EXPOSE 8080

# Start
ENTRYPOINT npm run start:prod&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;먼저 저는 Node 16 버전을 Base로 해서 Application을 실행시키고 있기 때문에 Base 이미지를 Node의 16 버전을 사용합니다.&lt;/li&gt;
&lt;li&gt;RUN, CMD, COPY 등이 실행되는 기본 경로를 WORKDIR로 &quot;/app&quot;으로 설정합니다.&lt;/li&gt;
&lt;li&gt;ADD 명령어를 통해 현재 프로젝트 경로(.)에 있는 소스파일을 WORKDIR로 설정한 도커의 경로(/app/)로 복사합니다.&lt;/li&gt;
&lt;li&gt;이제 순서대로 npm 라이브러리를 설치하고, build를 진행합니다.&lt;/li&gt;
&lt;li&gt;EXPOSE를 통해서 도커 컨테이너가 실행되었을 때, 요청을 기다리고 있을 Listen 포트를 8080으로 지정합니다.&lt;/li&gt;
&lt;li&gt;마지막으로 ENTRYPOINT 명령어를 통해서 컨테이너가 생성되고, 최초로 실행될 때 수행하는 명령어를 설정하고 마무리합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. EC2 서버에서 실행할 스크립트 파일 생성&lt;/b&gt;&lt;/h2&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;기존에 작성했던 배포 스크립트에서는 Git을 통해 EC2로 직접 Pull 하여 코드를 통합하고, Build 하는 과정도 모두 EC2에서 진행했었습니다. 하지만 이제 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;코드 통합과 Build는 DockerFile에 설정된 대로 Github Actions에서 진행하여 Docker Hub의 Private 저장소로 저장&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;즉, 이제 EC2에서는 이 Private Docker Hub 저장소에 있는 이미지를 가져와서 실행만 시켜 Deploy 과정을 마무리하는 스크립트를 실행하면 완료됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704690933604&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# start-docker.sh

echo &quot;1. Pull Docker Image&quot;
sudo docker pull khsrla9806/ginger-hotel:lastest

echo &quot;2. Stop and Remove Container / Remove &amp;lt;none&amp;gt; Image&quot;
sudo docker stop ginger-hotel
sudo docker rm -f $(sudo docker ps -qa -f status=exited)
sudo docker rmi -f $(sudo docker images -qa --filter &quot;dangling=true&quot;)

echo &quot;3. 새로운 이미지 실행&quot;
sudo docker run -d --name ginger-hotel -p 80:8080 -v ./logs:/app/logs khsrla9806/ginger-hotel:lastest&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;Github Actions에서 코드 통합, Build를 거친 새로운 Docker Image를 가져옵니다.&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;&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;b&gt;5. Github Actions WorkFlow 설정&lt;/b&gt;&lt;/h2&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;이제 DockerFile까지 설정을 완료했기 때문에 Github Actions를 통해서 제가 설정한 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;DockerFile대로 이미지를 만들어 제 Private Docker Hub 저장소에 이미지를 올리고, SSH로 EC2에 접속하여 새롭게 작성한 start-docker.sh 스크립트 파일을 실행&lt;/span&gt;할 수 있도록 Github Actions WorkFlow를 설정해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704691135014&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: GINGER PRODUCTION SERVER DEPLOY

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v3

      - name: Nodejs 16
        uses: actions/setup-node@v3
        with:
          node-version: 16.20.2
          cache: 'npm'

      - name: Setting Production Env
        run: echo &quot;${{ secrets.PRODUCTION_ENV }}&quot; &amp;gt;&amp;gt; .env.prod

      - name: Login Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v4.0.0
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ secrets.DOCKERHUB_REPO }}/ginger-hotel:lastest

      - name: SSH Remote Command
        uses: appleboy/ssh-action@v0.1.4
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          timeout: 40s

          script: ./start-docker.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;위에서 ${{ secrets.USERNAME }} 이런 부분들은 Github Actions에서 사용할 수 있는 Secret 변수들을 설정해 둘 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhhFxt/btsDaydwKMt/4HvkjLfnkB86mLlDwMDdUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhhFxt/btsDaydwKMt/4HvkjLfnkB86mLlDwMDdUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhhFxt/btsDaydwKMt/4HvkjLfnkB86mLlDwMDdUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhhFxt%2FbtsDaydwKMt%2F4HvkjLfnkB86mLlDwMDdUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;707&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;707&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 동작 확인&lt;/b&gt;&lt;/h2&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;이제 main 브랜치에 Merge가 되는 순간 작성해 둔 WorkFlow대로 Github Actions가 실행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;925&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btaqD2/btsC4xmT3YK/IMSRbbYHxkQac1DhRyvkp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btaqD2/btsC4xmT3YK/IMSRbbYHxkQac1DhRyvkp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btaqD2/btsC4xmT3YK/IMSRbbYHxkQac1DhRyvkp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtaqD2%2FbtsC4xmT3YK%2FIMSRbbYHxkQac1DhRyvkp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1513&quot; height=&quot;925&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;925&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;모든 Flow가 완료되면 자동화 배포 작업이 완료됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1371&quot; data-origin-height=&quot;727&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkNH6O/btsC6xGFMh2/YVkjKl1a8e981dodt44s3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkNH6O/btsC6xGFMh2/YVkjKl1a8e981dodt44s3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkNH6O/btsC6xGFMh2/YVkjKl1a8e981dodt44s3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkNH6O%2FbtsC6xGFMh2%2FYVkjKl1a8e981dodt44s3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1371&quot; height=&quot;727&quot; data-origin-width=&quot;1371&quot; data-origin-height=&quot;727&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 변경한 후의 아키텍처는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHk5cq/btsC8Rq7uzK/6xcfgCIrGN6m4fUl2zlngk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHk5cq/btsC8Rq7uzK/6xcfgCIrGN6m4fUl2zlngk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHk5cq/btsC8Rq7uzK/6xcfgCIrGN6m4fUl2zlngk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHk5cq%2FbtsC8Rq7uzK%2F6xcfgCIrGN6m4fUl2zlngk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;913&quot; height=&quot;523&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;523&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;지금까지 프로젝트의 CI/CD를 할 때, 처음에 소개했던 것 처럼 EC2 서버에게 너무 많은 책임을 부여했던 것 같아서 늘 도커로 해보자라고 생각을 했었는데 드디어 해볼 수 있게 되어 값진 경험을 가져간 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;074&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/074.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/074.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/Infra</category>
      <category>CI/CD</category>
      <category>docker</category>
      <category>EC2</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/98</guid>
      <comments>https://hoonsb.tistory.com/98#entry98comment</comments>
      <pubDate>Mon, 8 Jan 2024 14:28:42 +0900</pubDate>
    </item>
    <item>
      <title>[디프만 x 인프런] CS 완전 정복 스터디</title>
      <link>https://hoonsb.tistory.com/97</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스터디를 시작한 이유&lt;/b&gt;&lt;/h2&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;스터디를 시작했었던 가장 큰 이유는 CS 공부를 하는 데 있어서 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;어느 정도의 강제성이 필요&lt;/span&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;다른 재미있는 강의들도 많았는데, 그중에서 지금 당장 저에게 유용하고 공부를 해도 해도 항상 부족하다고 느껴졌던 CS 스터디였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CS 강의는 인프런에 있는 개발남노씨님의 &quot;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%A0%84%EA%B3%B5%EB%A9%B4%EC%A0%91-cs-%EC%99%84%EC%A0%84%EC%A0%95%EB%B3%B5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;기출로 대비하는 개발자 전공면접 [CS 완전정복]&lt;/a&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;커리큘럼을 봤을 때, 자료구조, 운영체제, 데이터베이스, 네트워크의 핵심적인 부분들을 깔끔한 예시 영상을 통해서 정리해 주시는 거 같았습니다. &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;12월 31일까지 계획된 스터디에서 1주에 1개의 과목씩 해결&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;쿠폰이 50%까지 할인이 되었는데, 할인받아서 강의를 구매했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1984&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG8S1W/btsC4v12aNK/oLyGVmbTjjdsdVDH9QIAmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG8S1W/btsC4v12aNK/oLyGVmbTjjdsdVDH9QIAmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG8S1W/btsC4v12aNK/oLyGVmbTjjdsdVDH9QIAmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG8S1W%2FbtsC4v12aNK%2FoLyGVmbTjjdsdVDH9QIAmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;712&quot; height=&quot;245&quot; data-origin-width=&quot;1984&quot; data-origin-height=&quot;684&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스터디 방식은?&lt;/b&gt;&lt;/h2&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;스터디원은 총 7명이 모였고, OT를 진행하며 스터디 방식, 규칙에 대해서 정하는 시간을 가졌습니다.&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;span style=&quot;background-color: #f9f9c8;&quot;&gt;강의를 듣고 개인적으로 정리해 보기.&lt;/span&gt;&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해당 과목에 대한 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;예상 질문 만들기&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;최대한 다른 사람의 질문과 겹치지 않도록 하고, 최소한 2개씩은 만들어오기로 정했습니다.&lt;/li&gt;
&lt;li&gt;스터디를 매주 월요일 오후 9시 비대면으로 진행되었고, 내용 정리 + 예상 질문은 매주 목요일 오후 10시까지 만들기로 정했습니다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(만들어진 예상 질문을 스터디원 중 한 분이 매우 공정한 방법(사다리 타기)을 이용해서 담당자를 분배했습니다.)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;여기서 어느 정도의 강제성을 주기 위해서 인당 40,000원씩 모아서 매주 정한 규칙을 지키지 않았을 때 10,000원씩 차감하기로 규칙을 정했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매주 월요일 이전까지 질문 답변 담당자는 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;자신이 맡은 질문에 대한 대답을 작성&lt;/span&gt;합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매주 월요일 오후 9시에 비대면으로 게더 타운에서 모여 스터디를 진행합니다.&lt;/b&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;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 과정으로 스터디를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스터디 후기&lt;/b&gt;&lt;/h2&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;1주차에는 서로 어색한 분위기를 풀고, 서로의 소소한 스몰토크(?)도 하면서 분위기를 풀고 스터디 규칙을 만들었습니다.&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;1474&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm9ge4/btsCVJAKHjG/JtTb7GU26SoTZ2wKhD25Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm9ge4/btsCVJAKHjG/JtTb7GU26SoTZ2wKhD25Zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm9ge4/btsCVJAKHjG/JtTb7GU26SoTZ2wKhD25Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm9ge4%2FbtsCVJAKHjG%2FJtTb7GU26SoTZ2wKhD25Zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;429&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자 주차에 정해진 강의를 듣고, 강의 내용을 정리하는 과정에서 궁금했던 부분도 많이 찾아볼 수 있었고 알았던 부분은 다시 한번 리마인드 해서 좋았고, 가볍게 알고 넘어갔던 부분들은 보충하는 시간을 가질 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1970&quot; data-origin-height=&quot;1382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mbs7R/btsCZ9Z0181/kILnwcJ1k0LrVVIfYPqLQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mbs7R/btsCZ9Z0181/kILnwcJ1k0LrVVIfYPqLQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mbs7R/btsCZ9Z0181/kILnwcJ1k0LrVVIfYPqLQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmbs7R%2FbtsCZ9Z0181%2FkILnwcJ1k0LrVVIfYPqLQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1970&quot; height=&quot;1382&quot; data-origin-width=&quot;1970&quot; data-origin-height=&quot;1382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1970&quot; data-origin-height=&quot;1126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DnEL3/btsC4GP3ZlI/762cpqOMXr4ii3yzFq1TQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DnEL3/btsC4GP3ZlI/762cpqOMXr4ii3yzFq1TQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DnEL3/btsC4GP3ZlI/762cpqOMXr4ii3yzFq1TQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDnEL3%2FbtsC4GP3ZlI%2F762cpqOMXr4ii3yzFq1TQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1970&quot; height=&quot;1126&quot; data-origin-width=&quot;1970&quot; data-origin-height=&quot;1126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lHTbP/btsCZju5ur3/iK5DCtOyOY0o3gmjT8K9k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lHTbP/btsCZju5ur3/iK5DCtOyOY0o3gmjT8K9k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lHTbP/btsCZju5ur3/iK5DCtOyOY0o3gmjT8K9k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlHTbP%2FbtsCZju5ur3%2FiK5DCtOyOY0o3gmjT8K9k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1962&quot; height=&quot;1138&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1956&quot; data-origin-height=&quot;1132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZf1Rr/btsC4cn72O3/K1mXRBLIRAWTbGsk3Vx4g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZf1Rr/btsC4cn72O3/K1mXRBLIRAWTbGsk3Vx4g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZf1Rr/btsC4cn72O3/K1mXRBLIRAWTbGsk3Vx4g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZf1Rr%2FbtsC4cn72O3%2FK1mXRBLIRAWTbGsk3Vx4g1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1956&quot; height=&quot;1132&quot; data-origin-width=&quot;1956&quot; data-origin-height=&quot;1132&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;1014&quot; data-origin-height=&quot;1324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5cP5A/btsCTyT0agM/GMHJJsKLqhJH5oge2kE7kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5cP5A/btsCTyT0agM/GMHJJsKLqhJH5oge2kE7kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5cP5A/btsCTyT0agM/GMHJJsKLqhJH5oge2kE7kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5cP5A%2FbtsCTyT0agM%2FGMHJJsKLqhJH5oge2kE7kK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;485&quot; height=&quot;633&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1014&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;p data-ke-size=&quot;size16&quot;&gt;서로 준비해 온 질문에 대한 &quot;질문자&quot;와 &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 팀원분들이 너무 적극적으로 참여해 주셨고, 서로 말하지 않아도 꼬꼬무 질문을 스스로 만들어서 준비해 왔던 것이 너무 좋았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7LuUj/btsCZ9FGKs8/dhfcVmA31Ly8aFFrRPnwmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7LuUj/btsCZ9FGKs8/dhfcVmA31Ly8aFFrRPnwmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7LuUj/btsCZ9FGKs8/dhfcVmA31Ly8aFFrRPnwmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7LuUj%2FbtsCZ9FGKs8%2FdhfcVmA31Ly8aFFrRPnwmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;492&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1416&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;1944&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E9zTe/btsC4vVl4K6/xKsVLXWZij1nykzsxK3sT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E9zTe/btsC4vVl4K6/xKsVLXWZij1nykzsxK3sT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E9zTe/btsC4vVl4K6/xKsVLXWZij1nykzsxK3sT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE9zTe%2FbtsC4vVl4K6%2FxKsVLXWZij1nykzsxK3sT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;352&quot; data-origin-width=&quot;1944&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 꼬리까지 물어가며 적극적으로 참여를 했고, 네트워크를 마지막으로 2024년 1월 2일에 스터디를 종료했습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 벌금으로 모았던 돈으로 추후 대면 일정이 있을 때, 커피나 밥을 먹기로 하고, 이제 2월까지 마무리해야 하는 각자 팀의 프로젝트가 있기 때문에 거기에 집중하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 강의에서 제공해 주는 질문들도 충분히 도움이 되지만, 서로 공부하고 고민하면서 모았던 질문들도 소중한 자산이 된 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 계속해서 공부해야겠지만, 앞으로 할 공부에 큰 도움이 되지 않았을까 하는 마음으로 스터디를 마무리하게 되었습니다.&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;1604&quot; data-origin-height=&quot;1264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TvYoB/btsCZ6bc2Hh/UFbsDpYKg0Z5UxBOxi95xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TvYoB/btsCZ6bc2Hh/UFbsDpYKg0Z5UxBOxi95xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TvYoB/btsCZ6bc2Hh/UFbsDpYKg0Z5UxBOxi95xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTvYoB%2FbtsCZ6bc2Hh%2FUFbsDpYKg0Z5UxBOxi95xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;539&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Activity/디프만</category>
      <category>디프만</category>
      <category>디프만 인프런 스터디</category>
      <category>인프런</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/97</guid>
      <comments>https://hoonsb.tistory.com/97#entry97comment</comments>
      <pubDate>Thu, 4 Jan 2024 13:31:30 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 20210번 파일 탐색기 (Java)</title>
      <link>https://hoonsb.tistory.com/96</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/20210&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;20210번 파일 탐색기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 유형을 혼내주고 있는 도중 내가 혼나버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 골랐는데, 항상 정답률을 보고 적당한 문제들을 골랐었는데 하하 왜 안 보고 골랐을까.&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2384&quot; data-origin-height=&quot;1142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPy43x/btsB7OuAXIf/gb7xncyeEW0fUJP04VUkQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPy43x/btsB7OuAXIf/gb7xncyeEW0fUJP04VUkQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPy43x/btsB7OuAXIf/gb7xncyeEW0fUJP04VUkQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPy43x%2FbtsB7OuAXIf%2Fgb7xncyeEW0fUJP04VUkQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2384&quot; height=&quot;1142&quot; data-origin-width=&quot;2384&quot; data-origin-height=&quot;1142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2406&quot; data-origin-height=&quot;1212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wO09l/btsB6nYSpi5/zprsnFd1dYl2eIiPbBKkhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wO09l/btsB6nYSpi5/zprsnFd1dYl2eIiPbBKkhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wO09l/btsB6nYSpi5/zprsnFd1dYl2eIiPbBKkhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwO09l%2FbtsB6nYSpi5%2FzprsnFd1dYl2eIiPbBKkhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2406&quot; height=&quot;1212&quot; data-origin-width=&quot;2406&quot; data-origin-height=&quot;1212&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;b&gt;1. 문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 처음 보고나서 조건이 되게 많다는 것을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼핏 보기에는 뭔가 간단하게 그냥 조건 따라서 정렬하면 되는 문제라고 판단됐다. 그래서 선택했을지도...&lt;/p&gt;
&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;둘 다 숫자가 아닌 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 이렇게 3가지 경우로 나눌 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;둘 다 숫자라면?&lt;/b&gt;
&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;만약 두 숫자의 크기가 같다면, 앞에 0의 개수가 적은 것이 우선순위가 더 높다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;두 숫자의 크기도 같고, 앞에 0의 개수도 동일하다면 우선순위가 동일&lt;/span&gt;하다. &lt;span style=&quot;color: #dddddd;&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: 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;b&gt;둘 다 문자라면?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;문자의 우선순위는 다음과 같은 우선순위를 갖는다.&lt;/li&gt;
&lt;li&gt;두 문자가 같으면(A와 A) 우선순위가 동일하다.&lt;/li&gt;
&lt;li&gt;두 문자의 종류가 같으면(A와 a) 대문자가 우선순위가 높다.&lt;/li&gt;
&lt;li&gt;두 문자의 종류가 다르면(A와 c) &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;AaBbCcDd....XxYyZz&lt;/span&gt;와 같은 우선순위를 갖는다. &lt;span style=&quot;color: #dddddd;&quot;&gt;(문제 제대로 안 읽으면 여기서 고생)&lt;/span&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;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;2^63을 초과할 수 있기 때문에 일반적인 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;Long 타입은 사용할 수 없다&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;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;문제를 다 풀고 나서 다른 분들의 답을 봤는데, 반복문과 Index를 아주 매력적으로 사용해서 푸신 분들을 많이 봤는데... 존경스럽다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;나는 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;아주 큰 수도 저장할 수 있는 Java의 BigDecimal을 사용&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;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;왜냐! BigDecimal은 Comparable 인터페이스를 구현하고 있어서 compareTo를 사용할 수 있기 때문에 사용해 봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KW12q/btsB5DOo95F/rpiyZEuGqO6MH5hxOOpDGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KW12q/btsB5DOo95F/rpiyZEuGqO6MH5hxOOpDGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KW12q/btsB5DOo95F/rpiyZEuGqO6MH5hxOOpDGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKW12q%2FbtsB5DOo95F%2FrpiyZEuGqO6MH5hxOOpDGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1232&quot; height=&quot;344&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;344&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 소스코드&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1702659131908&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(reader.readLine());
        PriorityQueue&amp;lt;Value&amp;gt; queue = new PriorityQueue&amp;lt;&amp;gt;();

        for (int i = 0; i &amp;lt; N; i++) {
            queue.add(new Value(reader.readLine()));
        }

        StringBuilder builder = new StringBuilder();
        while (!queue.isEmpty()) {
            builder.append(queue.poll()).append(&quot;\n&quot;);
        }
        System.out.println(builder);
    }
}

class Value implements Comparable&amp;lt;Value&amp;gt; {
    String value;
    ArrayList&amp;lt;String&amp;gt; seperatedValue;

    public Value(String value) {
        this.value = value;
        setSeperatedValue(value);
    }

    private void setSeperatedValue(String value) {
        this.seperatedValue = new ArrayList&amp;lt;&amp;gt;();
        StringBuilder tempBuilder = new StringBuilder();

        for (int i = 0; i &amp;lt; value.length(); i++) {
            char temp = value.charAt(i);

            if (Character.isDigit(temp)) {
                tempBuilder.append(temp);
                continue;
            } else if (tempBuilder.length() &amp;gt; 0) { // 숫자 아니고, 빌더에 값이 있는 경우
                seperatedValue.add(tempBuilder.toString());
                tempBuilder = new StringBuilder();
            }
            seperatedValue.add(String.valueOf(temp));
        }

        if (tempBuilder.length() &amp;gt; 0) { // 다 돌고 빌더에 남아 있는 것이 있으면 마저 넣어줌
            seperatedValue.add(tempBuilder.toString());
        }
    }

    @Override
    public int compareTo(Value other) {
        ArrayList&amp;lt;String&amp;gt; seperatedValueOfOther = other.seperatedValue;
        int smallSize = Math.min(seperatedValueOfOther.size(), this.seperatedValue.size());
        for (int i = 0; i &amp;lt; smallSize; i++) {
            String valueOfThis = this.seperatedValue.get(i);
            String valueOfOther = seperatedValueOfOther.get(i);

            boolean isNumberOfThis = isNumber(valueOfThis);
            boolean isNumberOfOther = isNumber(valueOfOther);

            // 둘 다 숫자인 경우
            if (isNumberOfThis &amp;amp;&amp;amp; isNumberOfOther) {
                BigDecimal decimalOfThis = convertToDecimal(valueOfThis);
                BigDecimal decimalOfOther = convertToDecimal(valueOfOther);

                if (decimalOfThis.equals(decimalOfOther)) {
                    // 값도 같고, 0의 개수도 같은 경우
                    if (valueOfThis.length() == valueOfOther.length()) {
                        continue;
                    }
                    return valueOfThis.length() - valueOfOther.length();
                }
                return decimalOfThis.compareTo(decimalOfOther);
            }

            // 둘 중 하나만 숫자인 경우
            if (isNumberOfThis || isNumberOfOther) {
                return isNumberOfThis ? -1 : 1;
            }

            // 둘 다 문자인 경우
            int comparedValue = compare(valueOfThis, valueOfOther);

            if (comparedValue == 0) {
                continue;
            }

            return comparedValue;
        }

        return this.value.length() - other.value.length();
    }

    private int compare(String value1, String value2) {
        if (value1.equals(value2)) {
            return 0;
        }
        if (value1.equalsIgnoreCase(value2)) {
            if (value1.toUpperCase().compareTo(value1) == 0) {
                return -1;
            }
            return 1;
        }

        return value1.compareToIgnoreCase(value2);
    }

    private boolean isNumber(String value) {
        return Character.isDigit(value.charAt(0));
    }

    private BigDecimal convertToDecimal(String value) {
        return new BigDecimal(value);
    }

    @Override
    public String toString() {
        return value;
    }
}&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;처음에 BigDecimal의 생성자를 무자비하게 그냥 사용했었는데, 속도가 많이 떨어지는 것을 느꼈다.&lt;/p&gt;
&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: #f9f9c8;&quot;&gt;isNumber()라는 메서드를 분리해서 해당 문자열이 숫자인지 확인한 후에 숫자인 경우에만 BigDecimal을 사용하도록 변경했더니 속도가 어느 정도 개선&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;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2304&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b748Cf/btsB1KgG9yO/LBLHQcSBKNwHoNZJQizzd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b748Cf/btsB1KgG9yO/LBLHQcSBKNwHoNZJQizzd1/img.png&quot; data-alt=&quot;집념의 흔적....isNumber()를 적용한 후에 1848ms에서 1384ms까지 속도가 올라가는 것을 확인할 수 있었다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b748Cf/btsB1KgG9yO/LBLHQcSBKNwHoNZJQizzd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb748Cf%2FbtsB1KgG9yO%2FLBLHQcSBKNwHoNZJQizzd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2304&quot; height=&quot;744&quot; data-origin-width=&quot;2304&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;집념의 흔적....isNumber()를 적용한 후에 1848ms에서 1384ms까지 속도가 올라가는 것을 확인할 수 있었다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/알고리즘</category>
      <category>BigDecimal</category>
      <category>백준 20210번 자바</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/96</guid>
      <comments>https://hoonsb.tistory.com/96#entry96comment</comments>
      <pubDate>Sat, 16 Dec 2023 01:57:22 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 16934번 게임 닉네임 (Java)</title>
      <link>https://hoonsb.tistory.com/95</link>
      <description>&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;코딩 테스트를 몇 번 보면서 느낀점이 있었다. &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;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://www.acmicpc.net/problem/16934&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;16934번 게임 닉네임&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2316&quot; data-origin-height=&quot;1186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTFkHB/btsBQt6NBMk/ogmsxjbRd6UsPojVjnscY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTFkHB/btsBQt6NBMk/ogmsxjbRd6UsPojVjnscY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTFkHB/btsBQt6NBMk/ogmsxjbRd6UsPojVjnscY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTFkHB%2FbtsBQt6NBMk%2FogmsxjbRd6UsPojVjnscY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2316&quot; height=&quot;1186&quot; data-origin-width=&quot;2316&quot; data-origin-height=&quot;1186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2306&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJTlp/btsBQsGNcPw/tSBr5Dm8Iu6XpaW5VvGlN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJTlp/btsBQsGNcPw/tSBr5Dm8Iu6XpaW5VvGlN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJTlp/btsBQsGNcPw/tSBr5Dm8Iu6XpaW5VvGlN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJTlp%2FbtsBQsGNcPw%2FtSBr5Dm8Iu6XpaW5VvGlN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2306&quot; height=&quot;1012&quot; data-origin-width=&quot;2306&quot; data-origin-height=&quot;1012&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;b&gt;1. 문제 분석&lt;/b&gt;&lt;/h2&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;문자열의 Prefix를 확인해야 하고, 이미 포함되어 있는지에 대한 여부도 확인하면서 문제에 접근해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 생각났던 알고리즘은 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&quot;트라이&quot; 알고리즘&lt;/span&gt;이다. &lt;span style=&quot;color: #dddddd;&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;왜 &quot;트라이&quot;인가? 새롭게 추가되는 문자열을 이미 추가되어 있는 문자열의 prefix에 해당하는지 확인을 해야 한다.&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;span style=&quot;background-color: #f9f9c8;&quot;&gt;생성 시간 복잡도는 주어지는 닉네임 개수(N, 최대 100,000)와 최대 문자열 길이(L, 10)를 고려해 보면 O(N*L)&lt;/span&gt;의 시간복잡도로 트라이를 만들 수 있고, &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;탐색에서는 O(L)&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 소스코드&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1702444899141&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    private static final String NEW_LINE = &quot;\n&quot;;
    private static final String BLANK = &quot;&quot;;

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        Trie trie = new Trie();
        NicknameCounts nicknameCounts = new NicknameCounts();
        StringBuilder answer = new StringBuilder();

        int N = Integer.parseInt(reader.readLine());
        for (int i = 0; i &amp;lt; N; i++) {
            String nickname = reader.readLine();
            nicknameCounts.insert(nickname);

            int matchedLastIndex = trie.searchMatchLastIndex(nickname);
            if (matchedLastIndex == nickname.length() - 1) { // nickname 전체가 prefix로 존재
                int x = nicknameCounts.getCount(nickname);
                answer.append(nickname).append(x &amp;gt; 1 ? x : BLANK).append(NEW_LINE);
            } else if (matchedLastIndex &amp;gt; -1) { // trie 안에 겹치는 prefix 존재
                answer.append(nickname, 0, matchedLastIndex + 2).append(NEW_LINE);
            } else if (matchedLastIndex == -1) { // trie 안에 겹치는 prefix 없음
                answer.append(nickname.charAt(0)).append(NEW_LINE);
            }

            trie.insert(nickname);
        }
        System.out.println(answer);
    }
}

class TrieNode {
    Map&amp;lt;Character, TrieNode&amp;gt; childNodes;
    boolean isTerminated;

    public TrieNode() {
        this.childNodes = new HashMap&amp;lt;&amp;gt;();
        this.isTerminated = false;
    }
}

class Trie {
    TrieNode rootNode;

    public Trie() {
        this.rootNode = new TrieNode();
    }

    public void insert(String word) {
        TrieNode node = rootNode;
        for (char ch : word.toCharArray()) {
            node = node.childNodes.computeIfAbsent(ch, key -&amp;gt; new TrieNode());
        }
        node.isTerminated = true;
    }

    public int searchMatchLastIndex(String word) {
        int index = -1;
        TrieNode node = rootNode;
        for (char ch : word.toCharArray()) {
            node = node.childNodes.getOrDefault(ch, null);

            if (node == null) {
                break;
            }
            index++;
        }
        return index;
    }
}

class NicknameCounts {
    private final Map&amp;lt;String, Integer&amp;gt; nicknameCounts;

    public NicknameCounts() {
        this.nicknameCounts = new HashMap&amp;lt;&amp;gt;();
    }

    public void insert(String nickname) {
        if (nicknameCounts.containsKey(nickname)) {
            nicknameCounts.put(nickname, nicknameCounts.get(nickname) + 1);
            return;
        }
        nicknameCounts.put(nickname, 1);
    }

    public int getCount(String nickname) {
        return nicknameCounts.getOrDefault(nickname, 0);
    }
}&lt;/code&gt;&lt;/pre&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; 입력된 nickname 전체가 이전에 등록된 닉네임의 prefix로 존재하는 경우&lt;/li&gt;
&lt;li&gt;입력된 nickname 일부가 이전에 등록된 닉네임의 prefix로 존재하는 경우&lt;/li&gt;
&lt;li&gt;입력된 nickname이 이전에 등록된 닉네임의 prefix로 존재하지 않는 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 3개의 경우로 나눠서 코드를 작성해봤습니다.&lt;/p&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) 번의 경우 입력된 nickname 전체가 이미 다른 닉네임의 prefix로 존재하기 때문에 동일한 nickname의 개수가 있는지 확인해 보고, 만약 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;동일한 nickname의 개수가 1보다 큰 경우 &quot;nickname{개수}&quot;를 붙여서 별칭으로 지정해 줍니다.&lt;/span&gt; 만약 1보다 작다면 그냥 본인의 닉네임 자체를 별칭으로 사용합니다.&lt;/p&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) 번의 경우 입력된 nickname의 일부가 이미 다른 닉네임의 prefix로 존재하기 때문에 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;matchedLastIndex를 사용해서 substring을 통해 가장 짧은 prefix&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;예를 들어 &quot;abcd&quot;가 이미지 저장되어 있고, &quot;abdef&quot;가 입력된 닉네임이라면 matchedLastIndex는 1이 될 것입니다. 그럼 여기서 가장 짧은 prefix를 잘라내려면 &quot;abd&quot;가 되어야 하는데, substring(0, 1 + 2)으로 잘라낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 번의 경우 nickname이 이전에 등록된 어떤 닉네임의 prefix와도 겹치지 않기 때문에 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;그냥 자신의 nickname을 그대로 별칭으로 사용&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;&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;트라이를 최근에 동아리에서 CS 스터디하면서 알게 되었는데, 그때 검색에서 많이 사용되는 알고리즘이라고 해서 한번 문제에 직접 사용해보고 싶었는데, 마침 문자열 문제에 사용할 수 있게 돼서 재미(?) 있었습니다.&lt;/p&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그로밋01.png&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brBIig/btsBQSkZe7O/bIJ41NvhMj3IbBKEu8R631/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brBIig/btsBQSkZe7O/bIJ41NvhMj3IbBKEu8R631/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brBIig/btsBQSkZe7O/bIJ41NvhMj3IbBKEu8R631/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrBIig%2FbtsBQSkZe7O%2FbIJ41NvhMj3IbBKEu8R631%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;304&quot; height=&quot;296&quot; data-filename=&quot;그로밋01.png&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;296&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;</description>
      <category> &amp;zwj;  Dev/알고리즘</category>
      <category>Trie</category>
      <category>백준 게임 닉네임</category>
      <category>트라이</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/95</guid>
      <comments>https://hoonsb.tistory.com/95#entry95comment</comments>
      <pubDate>Wed, 13 Dec 2023 14:34:59 +0900</pubDate>
    </item>
    <item>
      <title>[Postgresql] Enum 타입에 요소 추가하기</title>
      <link>https://hoonsb.tistory.com/94</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Enum 타입 조회하기&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1701860875373&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select enum_range(null::membership_enum);&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;membership_enum이라는 enum 타입을 만들어서 &quot;FREE&quot;, &quot;STANDARD&quot;, &quot;DELUXE&quot;를 저장해 놨었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 만약 &quot;VIP&quot;라는 membership_enum 타입이 추가된다면 어떻게 요소를 추가할 수 있을까요?&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Enum 타입에 Value 추가하기&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1701861023833&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;alter type membership_enum add value 'VIP';&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;위와 같은 SQL문으로 membership_enum에 &quot;VIP&quot;라는 요소를 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 만약 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;VIP가 사라지고, VVIP가 추가되었다면&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Enum 타입 Value 이름 변경하기&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1701863116195&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;alter type membership_enum rename value 'VIP' to 'VVIP';&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;먼저 간단하게 위와 같은 명령어로 &quot;VIP&quot;를 &quot;VVIP&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;여기서 만약 member 테이블에서 만약 membership_enum을 필드 타입으로 가지고 있을 때는 &quot;VIP&quot;를 이미 값으로 가지고 있는 데이터가 있을 때는 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdoLHq/btsBvUC4lq2/vYHDkJpq4e4Qlz2ZPtbBZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdoLHq/btsBvUC4lq2/vYHDkJpq4e4Qlz2ZPtbBZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdoLHq/btsBvUC4lq2/vYHDkJpq4e4Qlz2ZPtbBZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdoLHq%2FbtsBvUC4lq2%2FvYHDkJpq4e4Qlz2ZPtbBZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;428&quot; height=&quot;89&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 보면 park가 아직 membership에 VIP를 가지고 있습니다. 여기서 만약 위 SQL 명령어로 &quot;VIP&quot;를 &quot;VVIP&quot;로 이름을 바꾸면 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyIFyY/btsBx7viS16/rfGYQOGwc7GnIP1aEH3Ks0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyIFyY/btsBx7viS16/rfGYQOGwc7GnIP1aEH3Ks0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyIFyY/btsBx7viS16/rfGYQOGwc7GnIP1aEH3Ks0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyIFyY%2FbtsBx7viS16%2FrfGYQOGwc7GnIP1aEH3Ks0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;253&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;membership_enum은 아무런 오류를 발생시키지 않고, 변경되어 버립니다. 이제 변경되었으면 &quot;VIP&quot;라는 값을 갖는 member인 &quot;park&quot;의 membership이 변경되었을까요?&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;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckF5kT/btsBzLSy5MP/Kr135MhZIoar7SRNlNGK61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckF5kT/btsBzLSy5MP/Kr135MhZIoar7SRNlNGK61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckF5kT/btsBzLSy5MP/Kr135MhZIoar7SRNlNGK61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckF5kT%2FbtsBzLSy5MP%2FKr135MhZIoar7SRNlNGK61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;415&quot; height=&quot;93&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;park가 가지고 있던 VVIP가 membership이 &quot;VIP&quot;에서 &quot;VVIP&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;이제 만약 &quot;VVIP&quot;를 없애야 한다면 어떻게 해야 할까요? 기존에 &quot;VVIP&quot;인 고객들을 &quot;DELUXE&quot; 등급으로 변경하고, &quot;VVIP&quot;를 membership_enum에서 제거해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alter table membership_enum drop value 'VIP'가 될까요? 되지 않습니다. Postgresql에서는 Enum의 value 추가는 가능한데, 삭제가 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 불편함이 없다면 기존의 membership_enum을 사용하고 &quot;VVIP&quot;만 사용하지 않으면 됩니다. 하지만 쓰이지도 않는 &quot;VVIP&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;span style=&quot;background-color: #f9f9c8;&quot;&gt;새로운 enum 타입을 만들어서 기존의 member.membership 타입을 변경해 주면&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Enum 타입 생성하여 변경하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1701864330740&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create type renew_membership_enum as enum('FREE', 'STANDARD', 'DELUXE');&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;새로 변경할 Enum 타입을 생성해 줍니다. 이때 우리는 &quot;VVIP&quot;는 뺄 것이기 때문에 &quot;FREE&quot;, &quot;STANDARD&quot;, &quot;DELUXE&quot;만 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701864398599&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;alter table member
alter column membership drop default,
alter column membership drop not null;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 기존 member 테이블의 membership 필드에 NOT NULL 제약조건과 DEFAULT 조건을 넣어두었기 때문에 이것을 제거해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701864485069&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;alter table member 
alter column membership type renew_membership_enum
using membership::text::renew_membership_enum,
alter column membership set default 'FREE'::renew_membership_enum,
alter column membership set not null;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 member 테이블에서 membership 필드의 타입을 새로 생성한 renew_membership_enum으로 변경을 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 using 하고 &quot;membership::text::renew_membership_enum&quot;이라는 부분이 있는데, 이것은 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;기존의 membership 타입의 필드를 문자열(text) 값으로 변경하고, 그 문자열을 renew_membership_enum 타입으로 형변환&lt;/span&gt;을 해주라는 것입니다. 그리고 이어서 바로 새로운 NOT NULL 제약조건과 DEFAULT 조건을 추가해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 쿼리를 실행을 해보면 이렇게 오류가 뜹니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vQxnS/btsBylNGs9O/IfKSTMP2wf1CkgGPwSDRk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vQxnS/btsBylNGs9O/IfKSTMP2wf1CkgGPwSDRk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vQxnS/btsBylNGs9O/IfKSTMP2wf1CkgGPwSDRk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvQxnS%2FbtsBylNGs9O%2FIfKSTMP2wf1CkgGPwSDRk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;771&quot; height=&quot;173&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 오류가 떴는지 보면 입력된 &quot;VVIP&quot;는 renew_membership_enum 타입의 value가 아니라는 오류입니다. 이는 아직 우리의 member 테이블에 있는 park라는 회원이 membership 값으로&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;VVIP&quot;을 가지고 있기 때문입니다. &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Case When을 써서 분기를 주기도 하는 거 같은데, 저는 UPDATE 벌크 연산으로 처리를 해주겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701864966184&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;update member set membership = 'DELUXE' where membership = 'VVIP';&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;이제 alter table 쿼리를 실행해 보면 정상적으로 바뀐 것을 확인할 수 있습니다. 이제 사용하지 않는 기존의 membership_enum은 제거해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701865027437&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;drop type membership_enum;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경이 완료되었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;009&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/009.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/009.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/Database</category>
      <category>alter type</category>
      <category>postgresql enum</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/94</guid>
      <comments>https://hoonsb.tistory.com/94#entry94comment</comments>
      <pubDate>Wed, 6 Dec 2023 21:18:42 +0900</pubDate>
    </item>
    <item>
      <title>[디프만] 새로운 도전 디프만 14기 서버 개발 합격</title>
      <link>https://hoonsb.tistory.com/93</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;디프만을 선택한 이유&lt;/b&gt;&lt;/h2&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13기 때 디프만을 알게 되었었는데, 그때는 사실 &quot;넣어도 떨어지겠지..?&quot;라는 생각으로 도전 조차 못해봤었습니다. 백엔드 부트캠프에서 Java, Spring을 학습하고, 프로젝트도 참여해 보면서 사실 자신감도 많이 얻었던 것 같습니다. 부트캠프에서도 열정적인 분들과 만나서 치열하게 고민하고, 프로젝트를 했던 경험이 너무 의미 있었습니다. 그래서 지금까지 배운 지식들을 가지고, 새로운 &lt;span style=&quot;background-color: #f9f9c8;&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 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;b&gt;서류 접수&lt;/b&gt;&lt;/h2&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;서류 접수는 10월 2일부터 8일까지 진행되었습니다. 저는 13기 때 디프만을 알게 된 후로 멀리서 쳐다보고 있어야지 하는 생각으로 디프만 인스타 공식계정을 팔로우하고 있다가 14기 모집 소식을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문항은 총 8개 정도였는데, 마지막 2개는 깃허브 링크와 포트폴리오 링크여서 작성해야 하는 문항은 6개 정도입니다. 저는 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;span style=&quot;background-color: #f9f9c8;&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;그렇게 서류 마감 후 10일 정도 있다가 메일이 하나 왔습니다. 메일 제목은 &quot;디프만 14기 서류 결과 안내&quot;였습니다.&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;1559&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eK93En/btszMdKEFce/BkikM89IXRylxsTPtRg901/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eK93En/btszMdKEFce/BkikM89IXRylxsTPtRg901/img.png&quot; data-alt=&quot;서류 합격 메일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eK93En/btszMdKEFce/BkikM89IXRylxsTPtRg901/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeK93En%2FbtszMdKEFce%2FBkikM89IXRylxsTPtRg901%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;216&quot; data-origin-width=&quot;1559&quot; data-origin-height=&quot;420&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;서류 합격 메일을 받고, 바로 4일 뒤인 22일 날 면접을 봐야 했었기 때문에 조급해하면서 여기저기 막 구글을 찾아 나서기 시작했습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;온라인 면접 준비&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭘 물어보실지 모르기 때문에 면접은 항상 긴장되고, 무서운 것 같습니다. 서류 결과가 나오고 하루 정도 있다가 면접과 관련된 메일이 하나 도착했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1433&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ico7Z/btszONx6J5e/LHWNHVbN7EDzrYXrqoDxH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ico7Z/btszONx6J5e/LHWNHVbN7EDzrYXrqoDxH1/img.png&quot; data-alt=&quot;면접 안내 메일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ico7Z/btszONx6J5e/LHWNHVbN7EDzrYXrqoDxH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIco7Z%2FbtszONx6J5e%2FLHWNHVbN7EDzrYXrqoDxH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;819&quot; height=&quot;295&quot; data-origin-width=&quot;1433&quot; data-origin-height=&quot;516&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;면접은 Zep이라는 가상 공간에서 진행됐습니다. 면접 날짜는 서류 접수 당시에 선택할 수 있었던 거 같습니다. 저는 일요일이 모든 시간이 널널했기 때문에 22일을 선택했었습니다. 저는 13시 30분에 면접을 30분 동안 봐야 하는 사람이 되었습니다. 덜덜덜  &lt;/p&gt;
&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;저는 4일이라는 기간동안 어떻게 준비해야 할까 하다가 서류에 작성했던 내용을 집중적으로 먼저 확인을 하자는 목표를 세웠습니다. 후기들을 많이 참고하긴 했는데, 기술 질문 관련 내용들은 후기마다 많이 달랐던 것 같습니다. 그래서 &lt;span style=&quot;background-color: #f9f9c8;&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;그래서 관련해서 적었던 기술들, 프로젝트에서 썼던 기술들을 위주로 준비를 했던 것 같습니다. 제가 서류에 자신 있는 기술에 QueryDSL을 썼었습니다. 가장 최근에 마무리했던 프로젝트에서 복잡한 쿼리를 많이 다뤘어서 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;그래서 프로젝트에서 사용했었던&amp;nbsp;&lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;DB(MySQL), JPA, QueryDSL, Spring, Java, AWS, CI/CD&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;&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;b&gt;온라인 면접 당일&lt;/b&gt;&lt;/h2&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;일요일날 아침 7시 40분에 일어나서... 어제 보다가 잠든 질문들을 한번 다시 쓱 봤습니다. 면접은 다대다 면접이었고, 같은 파트 사람들끼리 면접을 진행합니다. 같이 보는 인원은 2명에서 3명이라고 되어있었습니다.&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 자기소개를 시작으로 해서 각자 위치에 맞는 질문을 해주시는 것 같았습니다. 저는 취준을 하고 있었기 때문에 취준과 병행이 가능한지에 대해서 여쭤보셨었습니다. 아무래도 팀을 이뤄서 2월까지 프로젝트를 마무리하는 동아리이기 때문에 &lt;span style=&quot;background-color: #f9f9c8;&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;이제 대망의 기술 질문이 시작되고, 심장이 요동쳤었는데 커피챗한다는 느낌으로 참여해 주시면 좋을 거 같다고 말씀을 하시고 시작했습니다. 기술 질문은 공통 질문이 없었고, 각자 사용해 보거나 자신 있다고 적었던 기술 스택 위주로 질문을 주셨습니다. 저는 QueryDSL을 적었어서 관련된 질문을 많이 받았습니다. 그 외에도 DB, JPA 위주의 질문을 해주셨습니다. 준비한 것 외에서 나온 것도 몇 개 있었는데 프로젝트하면서 알게 된 내용 위주로 답을 했습니다. 사실 뭐라고 대답을 했는지도 긴장하고 있어서 기억이 안 났습니다. 기술 질문에서 제가 느꼈던 것은 &lt;span style=&quot;background-color: #f9f9c8;&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;그렇게 30분을 순삭하고, 나와서 유부 초밥을 왕창 먹었습니다. 디프만 사이트에는 최종 합격은 10월 31일 날 나온다고 쓰여 있어서 이제 기다림의 시간만이 남았습니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최종 발표&lt;/b&gt;&lt;/h2&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;최종 발표 메일은 10월 29일날 왔습니다. 제가 언제부터인가 메일을 거의 카톡처럼 확인하는 습관이 생겨서 계속 수시로 보다가 저녁에 메일을 확인하게 되었습니다.&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;1587&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AeaIn/btszKIkucJ5/joK5hsDOjHJQj3k7vOdYPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AeaIn/btszKIkucJ5/joK5hsDOjHJQj3k7vOdYPK/img.png&quot; data-alt=&quot;최종 합격 메일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AeaIn/btszKIkucJ5/joK5hsDOjHJQj3k7vOdYPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAeaIn%2FbtszKIkucJ5%2FjoK5hsDOjHJQj3k7vOdYPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;251&quot; data-origin-width=&quot;1587&quot; data-origin-height=&quot;492&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;진짜 확인하고 나서 진짜 붙은건가 진짜인가 이러면서 확인을 했었습니다. 디프만 13기 때 쳐다만 보다가 14기에 이렇게 붙으니 진짜 기분이 묘했습니다. 개발직군 경쟁률은 10.7대 1이라고 인스타그램에 나와있어서 기분이 뭔가 좋았습니다.  &lt;/p&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;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;1906&quot; data-origin-height=&quot;1294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9mceJ/btszQBYASHb/xwOF4vebmM0XcZfDvEYOKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9mceJ/btszQBYASHb/xwOF4vebmM0XcZfDvEYOKK/img.png&quot; data-alt=&quot;OT가서 소심하게 한장 찍었습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9mceJ/btszQBYASHb/xwOF4vebmM0XcZfDvEYOKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9mceJ%2FbtszQBYASHb%2FxwOF4vebmM0XcZfDvEYOKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;539&quot; height=&quot;366&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;1294&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OT가서 소심하게 한장 찍었습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;055&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/055.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/055.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Activity/디프만</category>
      <category>디프만 14기 면접</category>
      <category>디프만 14기 서류</category>
      <category>디프만 14기 서버</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/93</guid>
      <comments>https://hoonsb.tistory.com/93#entry93comment</comments>
      <pubDate>Mon, 6 Nov 2023 01:23:11 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Application Load Balancer에 Cloud Front의 요청만 허가하기</title>
      <link>https://hoonsb.tistory.com/92</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl6HeQ/btszDCdftds/CmRIr7V76KEUYaLKTkI3X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl6HeQ/btszDCdftds/CmRIr7V76KEUYaLKTkI3X0/img.png&quot; data-alt=&quot;Client에서 EC2까지 가는 아키텍처 (RDS, S3 등 제외)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl6HeQ/btszDCdftds/CmRIr7V76KEUYaLKTkI3X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl6HeQ%2FbtszDCdftds%2FCmRIr7V76KEUYaLKTkI3X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;341&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Client에서 EC2까지 가는 아키텍처 (RDS, S3 등 제외)&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;최근 아키텍처를 설계해야 하는 일이 있어서 위와 같이 Cloud Front를 앞단에 두고, ELB에 요청을 전달해서 EC2로 가는 방향으로 설계를 했습니다.&lt;/p&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;span style=&quot;background-color: #f9f9c8;&quot;&gt;ELB의 DNS 서버로도 아무나 접근이 가능&lt;/span&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beG9oR/btszCkdoJDt/1vwYSDc4tJxh7IQC8KwYrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beG9oR/btszCkdoJDt/1vwYSDc4tJxh7IQC8KwYrK/img.png&quot; data-alt=&quot;누구나 ELB의 DNS를 알면 접근이 가능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beG9oR/btszCkdoJDt/1vwYSDc4tJxh7IQC8KwYrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeG9oR%2FbtszCkdoJDt%2F1vwYSDc4tJxh7IQC8KwYrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;442&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;누구나 ELB의 DNS를 알면 접근이 가능&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: #f9f9c8;&quot;&gt;ELB(Origin)에서는 내가 배포한 Cloud Front에서 오는 요청만 받고, 나머지 요청은 모두 403 Forbbiden으로 처리&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Cloud Front에 등록된 Origin(ELB)로 보내는 요청에 헤더 추가하기&lt;/b&gt;&lt;/h2&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-origin-width=&quot;2812&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BLQ6E/btszGUcRP4S/yXjNZ0Hhf8KXMrLynSFLKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BLQ6E/btszGUcRP4S/yXjNZ0Hhf8KXMrLynSFLKK/img.png&quot; data-alt=&quot;Cloud Front 콘솔 -&amp;amp;gt; 배포 -&amp;amp;gt; 원본 -&amp;amp;gt; 내 ELB 체크 -&amp;amp;gt; 편집&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BLQ6E/btszGUcRP4S/yXjNZ0Hhf8KXMrLynSFLKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBLQ6E%2FbtszGUcRP4S%2FyXjNZ0Hhf8KXMrLynSFLKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;258&quot; data-origin-width=&quot;2812&quot; data-origin-height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cloud Front 콘솔 -&amp;gt; 배포 -&amp;gt; 원본 -&amp;gt; 내 ELB 체크 -&amp;gt; 편집&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;1312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buRiu1/btszCIri58v/IXeOKQKvLMvOylmW9kkpr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buRiu1/btszCIri58v/IXeOKQKvLMvOylmW9kkpr1/img.png&quot; data-alt=&quot;헤더 추가 -&amp;amp;gt; 헤더 이름, 값 입력 -&amp;amp;gt; 변경 사항 저장&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buRiu1/btszCIri58v/IXeOKQKvLMvOylmW9kkpr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuRiu1%2FbtszCIri58v%2FIXeOKQKvLMvOylmW9kkpr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;588&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;1312&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;헤더 추가 -&amp;gt; 헤더 이름, 값 입력 -&amp;gt; 변경 사항 저장&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서는 헤더 이름을 &quot;X-Custom-Access-Token&quot;이라고 했지만, &lt;span style=&quot;background-color: #f9f9c8;&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;&lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;값도 본인이 원하는 값&lt;/span&gt;으로 입력합니다. 값을 정하기 어렵다면 &lt;a href=&quot;https://randomkeygen.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 눌러서 한번 Random한 Key 값을 만들어보세요.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. ELB(Elastic Load Balancer) 리스너 및 규칙 추가하기&lt;/b&gt;&lt;/h2&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-origin-width=&quot;2802&quot; data-origin-height=&quot;1338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xpbpG/btszCrpRrZw/MZfpFYG92KkDBzWBwkTBh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xpbpG/btszCrpRrZw/MZfpFYG92KkDBzWBwkTBh1/img.png&quot; data-alt=&quot;EC2 -&amp;amp;gt; 로드밸런서 -&amp;amp;gt; 내 로드 밸런서 선택 -&amp;amp;gt; HTTP:80 또는 본인이 설정한 규칙 선택 -&amp;amp;gt; 규칙 편집&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xpbpG/btszCrpRrZw/MZfpFYG92KkDBzWBwkTBh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxpbpG%2FbtszCrpRrZw%2FMZfpFYG92KkDBzWBwkTBh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;363&quot; data-origin-width=&quot;2802&quot; data-origin-height=&quot;1338&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;EC2 -&amp;gt; 로드밸런서 -&amp;gt; 내 로드 밸런서 선택 -&amp;gt; HTTP:80 또는 본인이 설정한 규칙 선택 -&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 data-ke-size=&quot;size16&quot;&gt;이제 Cloud Front에서 보내는 커스텀 헤더를 받아서 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;내 Cloud Front에서 온 요청이구나를 알 수 있도록 로드 밸런서(ELB)의 리스너 규칙을 편집&lt;/span&gt;해줘야 합니다. 위에 &lt;span style=&quot;color: #006dd7;&quot;&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-origin-width=&quot;1770&quot; data-origin-height=&quot;990&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pg4MF/btszCmPBC2H/2I583tIpfamSRyHHBVcuIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pg4MF/btszCmPBC2H/2I583tIpfamSRyHHBVcuIK/img.png&quot; data-alt=&quot;들어와서 아래로 내린 후 &amp;quot;규칙 추가&amp;quot;를 눌러줍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pg4MF/btszCmPBC2H/2I583tIpfamSRyHHBVcuIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPg4MF%2FbtszCmPBC2H%2F2I583tIpfamSRyHHBVcuIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;765&quot; height=&quot;428&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;990&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;들어와서 아래로 내린 후 &quot;규칙 추가&quot;를 눌러줍니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCU5fm/btszF2bwO5R/wg3DYTkzsvIBX9zK0AQff1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCU5fm/btszF2bwO5R/wg3DYTkzsvIBX9zK0AQff1/img.png&quot; data-alt=&quot;규칙의 이름을 입력해줍니다. 아무 거나 입력하셔도 됩니다. Ex) &amp;quot;Listener Rule&amp;quot;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCU5fm/btszF2bwO5R/wg3DYTkzsvIBX9zK0AQff1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCU5fm%2FbtszF2bwO5R%2Fwg3DYTkzsvIBX9zK0AQff1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;770&quot; height=&quot;382&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;규칙의 이름을 입력해줍니다. 아무 거나 입력하셔도 됩니다. Ex) &quot;Listener Rule&quot;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ofwZo/btszHruPfOz/YZnzpbJwthOkwPjNajxeW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ofwZo/btszHruPfOz/YZnzpbJwthOkwPjNajxeW1/img.png&quot; data-alt=&quot;&amp;quot;조건 추가&amp;quot;를 클릭해서 조건을 추가해줍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ofwZo/btszHruPfOz/YZnzpbJwthOkwPjNajxeW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FofwZo%2FbtszHruPfOz%2FYZnzpbJwthOkwPjNajxeW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;763&quot; height=&quot;399&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&quot;조건 추가&quot;를 클릭해서 조건을 추가해줍니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDv2nz/btszC3hI5mJ/BkU6StRF1eM6jNwHqIjOtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDv2nz/btszC3hI5mJ/BkU6StRF1eM6jNwHqIjOtK/img.png&quot; data-alt=&quot;저희는 HTTP 헤더를 이용할 것이기 때문에 HTTP 헤더를 선택해줍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDv2nz/btszC3hI5mJ/BkU6StRF1eM6jNwHqIjOtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDv2nz%2FbtszC3hI5mJ%2FBkU6StRF1eM6jNwHqIjOtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;392&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;저희는 HTTP 헤더를 이용할 것이기 때문에 HTTP 헤더를 선택해줍니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;1210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uBcbp/btszF23FcWB/NyLzSDS9SAzPqipaI9Q551/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uBcbp/btszF23FcWB/NyLzSDS9SAzPqipaI9Q551/img.png&quot; data-alt=&quot;헤더의 이름과 헤더의 값을 입력해줍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uBcbp/btszF23FcWB/NyLzSDS9SAzPqipaI9Q551/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuBcbp%2FbtszF23FcWB%2FNyLzSDS9SAzPqipaI9Q551%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;758&quot; height=&quot;550&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;1210&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: #f9f9c8;&quot;&gt;1번에서 Cloud Front 원본(Origin)에 설정했던 헤더의 이름과 값을 입력&lt;/span&gt;해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 확인을 누르고 쭉쭉쭉 넘겨서 대상 그룹(EC2 인스턴스 그룹)을 선택하고, 규칙을 저장합니다.&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;1844&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2FfHi/btszCFahtXy/daCQLOhYUrC91yWurDFLek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2FfHi/btszCFahtXy/daCQLOhYUrC91yWurDFLek/img.png&quot; data-alt=&quot;규칙을 저장을 한 후의 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2FfHi/btszCFahtXy/daCQLOhYUrC91yWurDFLek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2FfHi%2FbtszCFahtXy%2FdaCQLOhYUrC91yWurDFLek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;797&quot; height=&quot;316&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;730&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 ELB를 만들면 &quot;기본값&quot;으로 규칙이 하나 추가됩니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 &lt;span style=&quot;background-color: #f9f9c8;&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;1540&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A9KTJ/btszCkYTbMP/1zUzH2ahdsHIYvwxm0VG20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A9KTJ/btszCkYTbMP/1zUzH2ahdsHIYvwxm0VG20/img.png&quot; data-alt=&quot;기본값 규칙 체크 -&amp;amp;gt; 규칙 편집 클릭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A9KTJ/btszCkYTbMP/1zUzH2ahdsHIYvwxm0VG20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA9KTJ%2FbtszCkYTbMP%2F1zUzH2ahdsHIYvwxm0VG20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;277&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기본값 규칙 체크 -&amp;gt; 규칙 편집 클릭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fq0Ev/btszDCYL3Jz/OiKeEvb11K3vikk7lRJHZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fq0Ev/btszDCYL3Jz/OiKeEvb11K3vikk7lRJHZ1/img.png&quot; data-alt=&quot;고정 응답 반환 선택 -&amp;amp;gt; 응답 코드 입력 -&amp;amp;gt; 응답 본문 입력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fq0Ev/btszDCYL3Jz/OiKeEvb11K3vikk7lRJHZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFq0Ev%2FbtszDCYL3Jz%2FOiKeEvb11K3vikk7lRJHZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;753&quot; height=&quot;429&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;figcaption&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 data-ke-size=&quot;size16&quot;&gt;기존에 &quot;대상 그룹으로 전달&quot;로 되어 있는 선택지를 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&quot;고정 응답 반환&quot;&lt;/span&gt;으로 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 코드는 어떤 걸로 입력해도 상관없습니다. 저는 Forbidden으로 사용하기 위해서 403을 입력해 줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 본문은 선택 내용입니다. 저는 &quot;Access denied&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;p data-ke-size=&quot;size16&quot;&gt;이제 서버 도메인으로 한번 접근을 해보겠습니다. (&lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;Cloud Front을 통해서 접근&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;1700&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mMfrD/btszD0rx22l/zPVCJWPMNVWAurpImNL7w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mMfrD/btszD0rx22l/zPVCJWPMNVWAurpImNL7w0/img.png&quot; data-alt=&quot;200 OK를 응답하면서 접근에 성공했습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mMfrD/btszD0rx22l/zPVCJWPMNVWAurpImNL7w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmMfrD%2FbtszD0rx22l%2FzPVCJWPMNVWAurpImNL7w0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;319&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;200 OK를 응답하면서 접근에 성공했습니다.&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;이 부분도 사실 추후에 Client가 완성되면 Cors에 등록해서 외부 접근은 막아 주는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ELB의 DNS로 접근을 한번 해보겠습니다. (&lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;Elastic Load Balancer에 직접 접근&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;1608&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lI6g5/btszGNLG8NF/NJh7ddqkFh4b5sSFaGLXZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lI6g5/btszGNLG8NF/NJh7ddqkFh4b5sSFaGLXZk/img.png&quot; data-alt=&quot;의도했던 대로 403 에러를 발생하며, &amp;quot;Access denied&amp;quot;라는 문구가 출력됩니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lI6g5/btszGNLG8NF/NJh7ddqkFh4b5sSFaGLXZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlI6g5%2FbtszGNLG8NF%2FNJh7ddqkFh4b5sSFaGLXZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;353&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;의도했던 대로 403 에러를 발생하며, &quot;Access denied&quot;라는 문구가 출력됩니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;008&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/008.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/008.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/Infra</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/92</guid>
      <comments>https://hoonsb.tistory.com/92#entry92comment</comments>
      <pubDate>Thu, 2 Nov 2023 17:10:28 +0900</pubDate>
    </item>
    <item>
      <title>[NestJS] nest start --watch와 node dist/main의 차이</title>
      <link>https://hoonsb.tistory.com/91</link>
      <description>&lt;pre id=&quot;code_1698817036415&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
    ...
    &quot;start:dev&quot;: &quot;cross-env NODE_ENV=dev nest start --watch&quot;,
    &quot;start:prod&quot;: &quot;cross-env NODE_ENV=prod node dist/main&quot;,
    ...
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경을 dev와 prod로 나누기 위해서 실행 명령어를 확인하던 도중 둘 다 실행을 하는 것이지만, &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&lt;b&gt;start:dev&lt;/b&gt;는 nest start --watch&lt;/span&gt;라는 명령어로 실행을 하고, &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&lt;b&gt;start:prod&lt;/b&gt;는 node dist/main&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;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;b&gt;1. nest start --watch&lt;/b&gt;&lt;/h2&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-origin-width=&quot;811&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0kKp8/btszyEwfW2s/d3DOl4PoKh4IB79S2f5Xz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0kKp8/btszyEwfW2s/d3DOl4PoKh4IB79S2f5Xz0/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://docs.nestjs.com/cli/scripts&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0kKp8/btszyEwfW2s/d3DOl4PoKh4IB79S2f5Xz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0kKp8%2FbtszyEwfW2s%2Fd3DOl4PoKh4IB79S2f5Xz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;784&quot; height=&quot;151&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://docs.nestjs.com/cli/scripts&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nest 공식 문서에 들어가 보면 nest start 명령어에 대한 설명이 존재합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&quot;nest start&quot; 명령어는 프로젝트를 빌드하고, 실행하는 데 사용되는 명령어입니다.&lt;/li&gt;
&lt;li&gt;&quot;nest start&quot;는 &quot;nest build&quot;와 유사한 빌드 프로세스를 수행하고, &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&lt;b&gt;빌드된 Application을 실행&lt;/b&gt;&lt;/span&gt;합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &quot;nest start&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rR1bM/btszzDi1gbT/OAAxf2zUWd0qpVJttTknA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rR1bM/btszzDi1gbT/OAAxf2zUWd0qpVJttTknA1/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://docs.nestjs.com/cli/usages#nest-build&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rR1bM/btszzDi1gbT/OAAxf2zUWd0qpVJttTknA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrR1bM%2FbtszzDi1gbT%2FOAAxf2zUWd0qpVJttTknA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;797&quot; height=&quot;113&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://docs.nestjs.com/cli/usages#nest-build&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 Nest 공식문서를 더 살펴보면 nest start의 --watch 옵션은 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&lt;b&gt;Watch 모드로 실행을 해서 Live Reload&lt;/b&gt;&lt;/span&gt;를 해주는 옵션이라고 합니다. 그래서 로컬에서 npm run start:dev로 실행을 한 후 코드에 변경이 생기면 자동으로 변경을 적용해 주는 것이 이 옵션 덕분입니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. node dist/main&lt;/b&gt;&lt;/h2&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;NestJS는 javascript로 컴파일되어 Node.js 환경에서 실행이 됩니다. npm run build를 실행하면 nest build 명령어가 실행되며 NestJS 프로젝트가 빌드됩니다. 이때 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&lt;b&gt;루트 경로에 dist라는 폴더&lt;/b&gt;&lt;/span&gt;를 만들게 됩니다. 빌드되고 번들링 된 파일들이 이 dist 폴더 안에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트는 보통 main이라는 곳을 시작점으로 해서 Application을 실행하게 됩니다. Nest JS도 이 main.js에서부터 Application이 실행됩니다. &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&lt;b&gt;node dist/main은 빌드된 파일 중 Application을 시작하기 위한 main.js를 실행하는 명령어입니다.&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;즉, node dist/main을 사용하기 위해서는 nest build와 같은 빌드 명령어가 선행되어야 합니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 결론&lt;/b&gt;&lt;/h2&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;코드 변경을 감지하고, Live Reload를 통해서 개발 효율을 높이기 위해서 nest start --watch를 사용할 수 있습니다. 코드 변경에 대해서 직접 build를 하고, 다시 시작하면서 코드를 테스트하지 않아도 됩니다. 그래서 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&lt;b&gt;start:dev에서는 nest start --watch를 사용&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;운영 환경에서는 Live Reload가 필요하지 않습니다. 그리고 직접 build를 하는 경우가 대부분이기 때문에 빌드된 파일인 dist/main을 실행만 해주면 됩니다. 그렇기 때문에 &lt;span style=&quot;background-color: #f9f9c8;&quot;&gt;&lt;b&gt;start:prod에서는 node dist/main을 사용&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;start:prod에서도 nest start를 사용한다면 프로젝트 실행을 할 수는 있겠지만, build 과정을 두 번 거치는 경우가 발생할 수 있습니다. 보통 서버에 배포해서 build를 진행하고, build에 실패하면 Application을 실행하면 안 됩니다. 그렇기 때문에 의도에 맞게 적절하게 사용해야 할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;063&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/063.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/063.png&quot; width=&quot;80&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Reference&lt;/b&gt;&lt;/h2&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;a href=&quot;https://docs.nestjs.com/cli/usages#nest-start&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.nestjs.com/cli/usages#nest-start&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1698817461999&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;Documentation | NestJS - A progressive Node.js framework&quot; data-og-description=&quot;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&quot; data-og-host=&quot;docs.nestjs.com&quot; data-og-source-url=&quot;https://docs.nestjs.com/cli/usages#nest-start&quot; data-og-url=&quot;https://docs.nestjs.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yoKng/hyUnPP3GdF/eCPQCOVpF3PF6Pi8mgdlEk/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/cli/usages#nest-start&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.nestjs.com/cli/usages#nest-start&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yoKng/hyUnPP3GdF/eCPQCOVpF3PF6Pi8mgdlEk/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429');&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;Documentation | NestJS - A progressive Node.js framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.nestjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/cli/scripts&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.nestjs.com/cli/scripts&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1698817469071&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;Documentation | NestJS - A progressive Node.js framework&quot; data-og-description=&quot;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&quot; data-og-host=&quot;docs.nestjs.com&quot; data-og-source-url=&quot;https://docs.nestjs.com/cli/scripts&quot; data-og-url=&quot;https://docs.nestjs.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/YL7vy/hyUnNdDNLv/vnTl0Udh0F00k2Mz5f5ekk/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/cli/scripts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.nestjs.com/cli/scripts&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/YL7vy/hyUnNdDNLv/vnTl0Udh0F00k2Mz5f5ekk/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429');&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;Documentation | NestJS - A progressive Node.js framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.nestjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/Nest JS</category>
      <category>nestjs</category>
      <category>NestJS 실행</category>
      <category>npm run start:dev</category>
      <category>npm run start:prod</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/91</guid>
      <comments>https://hoonsb.tistory.com/91#entry91comment</comments>
      <pubDate>Wed, 1 Nov 2023 15:11:09 +0900</pubDate>
    </item>
    <item>
      <title>[NestJS] AWS S3, Multer로 이미지 업로드 구현하기</title>
      <link>https://hoonsb.tistory.com/90</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. S3 버킷 생성&lt;/b&gt;&lt;/h2&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;S3 서비스를 이용하려면 &lt;a href=&quot;https://aws.amazon.com/ko/free/?trk=fa2d6ba3-df80-4d24-a453-bf30ad163af9&amp;amp;sc_channel=ps&amp;amp;ef_id=Cj0KCQjwy4KqBhD0ARIsAEbCt6gAJNoN3ZkPvkq2_6N8sKnNwcBxnH5M67V26esdf4pq2BJVIsKVWjEaAs_nEALw_wcB:G:s&amp;amp;s_kwcid=AL!4422!3!563761819834!e!!g!!aws!15286221779!129400439466&amp;amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc&amp;amp;awsf.Free%20Tier%20Types=*all&amp;amp;awsf.Free%20Tier%20Categories=*all&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS 사이트&lt;/a&gt;에 들어가서 S3 버킷을 생성해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKI3X9/btszyFHzsYO/4DH1QUhyrcyvFjK5nlmkH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKI3X9/btszyFHzsYO/4DH1QUhyrcyvFjK5nlmkH0/img.png&quot; data-alt=&quot;S3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKI3X9/btszyFHzsYO/4DH1QUhyrcyvFjK5nlmkH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKI3X9%2FbtszyFHzsYO%2F4DH1QUhyrcyvFjK5nlmkH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;321&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;S3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 사이트에 로그인한 후 위에 검색창에 S3라고 검색하면, S3를 시작할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2134&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y6AsV/btszyG0LWy9/TL3p1WY3MitvdtMmpw2ZpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y6AsV/btszyG0LWy9/TL3p1WY3MitvdtMmpw2ZpK/img.png&quot; data-alt=&quot;버킷 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y6AsV/btszyG0LWy9/TL3p1WY3MitvdtMmpw2ZpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY6AsV%2FbtszyG0LWy9%2FTL3p1WY3MitvdtMmpw2ZpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;184&quot; data-origin-width=&quot;2134&quot; data-origin-height=&quot;656&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;노란색 버튼 &quot;버킷 만들기&quot;를 클릭해서 버킷 생성을 시작합니다.&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;1656&quot; data-origin-height=&quot;917&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdLWdv/btszy5Thrwu/tkl524yafSKyXZIwm2CX11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdLWdv/btszy5Thrwu/tkl524yafSKyXZIwm2CX11/img.png&quot; data-alt=&quot;버킷 이름, AWS 리전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdLWdv/btszy5Thrwu/tkl524yafSKyXZIwm2CX11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdLWdv%2Fbtszy5Thrwu%2Ftkl524yafSKyXZIwm2CX11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;313&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1656&quot; data-origin-height=&quot;917&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;버킷 이름, AWS 리전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버킷 이름을 먼저 작성해 줍니다. 버킷 이름에는 지켜야 하는 규칙이 있습니다. &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html?icmpid=docs_amazons3_console&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&quot;버킷 이름 지정 규칙 보기&quot;&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;1642&quot; data-origin-height=&quot;1314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buES4O/btszuFWcTHx/VBUW57La9wqaDJ4meSeZy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buES4O/btszuFWcTHx/VBUW57La9wqaDJ4meSeZy0/img.png&quot; data-alt=&quot;객체 소유권, 퍼블릭 엑세스 차단 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buES4O/btszuFWcTHx/VBUW57La9wqaDJ4meSeZy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuES4O%2FbtszuFWcTHx%2FVBUW57La9wqaDJ4meSeZy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;613&quot; height=&quot;491&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;1314&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;객체 소유권은 권장되는 방향인 &quot;ACL 비활성화됨&quot;을 선택했습니다. 저는 역할별로 IAM 사용자를 만들어서 사용할 예정입니다.&lt;/p&gt;
&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; 기본적으로 선택되어 있는 대로 하고&lt;/span&gt;&lt;/span&gt;&amp;nbsp;넘어갔습니다. 나중에 버킷에 가서 권한에 대한 설정은 변경할 수 있습니다. 저는 버킷에 대한 정책을 직접 작성해서 사용할 예정이지만, 여기서는 권장되는 방법으로 설정하고 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 선택 사항들은 그대로 놔두고 버킷을 생성하면 버킷 생성이 완료됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2180&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPJm4g/btszvc7wqtb/cBQrRK2EP4dnotR5kpXFkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPJm4g/btszvc7wqtb/cBQrRK2EP4dnotR5kpXFkK/img.png&quot; data-alt=&quot;버킷 폴더 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPJm4g/btszvc7wqtb/cBQrRK2EP4dnotR5kpXFkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPJm4g%2Fbtszvc7wqtb%2FcBQrRK2EP4dnotR5kpXFkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;184&quot; data-origin-width=&quot;2180&quot; data-origin-height=&quot;604&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;생성된 버킷에 들어가 보면 처음에는 비어있는데, &quot;폴더 만들기&quot;를 눌러서 원하는 폴더명을 입력해서 만들어줍니다. 저는 &quot;images&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. IAM 사용자 생성 &amp;amp; 정책 적용&lt;/b&gt;&lt;/h2&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;S3라고 치고 들어갔던 검색창에 &quot;IAM&quot;이라고 검색하면 아래 화면으로 이동할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2780&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BcQO1/btszvgu36j3/lOfhOYU7AHMNnDGF0bKYk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BcQO1/btszvgu36j3/lOfhOYU7AHMNnDGF0bKYk1/img.png&quot; data-alt=&quot;IAM 사용자 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BcQO1/btszvgu36j3/lOfhOYU7AHMNnDGF0bKYk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBcQO1%2Fbtszvgu36j3%2FlOfhOYU7AHMNnDGF0bKYk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;234&quot; data-origin-width=&quot;2780&quot; data-origin-height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IAM 사용자 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버킷 생성을 끝냈으면 이제 IAM 사용자를 생성합니다. &quot;사용자 생성&quot; 버튼을 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2076&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NyOJ3/btszyDC1yuP/1yUGwWdYkk2yCkG4kzVvR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NyOJ3/btszyDC1yuP/1yUGwWdYkk2yCkG4kzVvR0/img.png&quot; data-alt=&quot;IAM 사용자 이름&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NyOJ3/btszyDC1yuP/1yUGwWdYkk2yCkG4kzVvR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNyOJ3%2FbtszyDC1yuP%2F1yUGwWdYkk2yCkG4kzVvR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;299&quot; data-origin-width=&quot;2076&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IAM 사용자 이름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 사용자의 이름을 설정합니다. 이 이름은 나중에 AWS Console에 IAM 사용자로 로그인할 때 사용되는 정보입니다. 너무 어렵고 길게 만들면... 의미 있게 작성해 주시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qV3QS/btszzJvHyKs/Fophk2WkIyREXzcYmcSAsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qV3QS/btszzJvHyKs/Fophk2WkIyREXzcYmcSAsK/img.png&quot; data-alt=&quot;권한 설정, 정책 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qV3QS/btszzJvHyKs/Fophk2WkIyREXzcYmcSAsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqV3QS%2FbtszzJvHyKs%2FFophk2WkIyREXzcYmcSAsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;696&quot; height=&quot;267&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;784&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;다음으로 넘어가면 권한 옵션을 설정합니다. 저는 직접 정책을 생성해서 사용할 것이기 때문에 &quot;직접 정책 연결&quot;을 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 사용자를 만든 후에 정책을 따로 만들어도 되지만, 저는 이 페이지에서 바로 사용할 정책을 생성해서 등록하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;정책 생성&quot; 버튼을 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2050&quot; data-origin-height=&quot;1118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHTdsR/btszy3OKe8l/l87KKcnSKXzuHKKUwQTXAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHTdsR/btszy3OKe8l/l87KKcnSKXzuHKKUwQTXAk/img.png&quot; data-alt=&quot;정책 편집기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHTdsR/btszy3OKe8l/l87KKcnSKXzuHKKUwQTXAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHTdsR%2Fbtszy3OKe8l%2Fl87KKcnSKXzuHKKUwQTXAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;673&quot; height=&quot;367&quot; data-origin-width=&quot;2050&quot; data-origin-height=&quot;1118&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;정책 생성으로 들어와서 JSON을 선택해 주면 정책을 작성할 수 있는 정책 편집기가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 이제 정책을 작성해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1698739461658&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;AllowToAllBuckets&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;s3:GetBucketLocation&quot;,
                &quot;s3:ListAllMyBuckets&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Sid&quot;: &quot;AllowToAllObjects&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &quot;s3:ListBucket&quot;,
            &quot;Resource&quot;: &quot;arn:aws:s3:::my-bucket-name&quot;
        },
        {
            &quot;Sid&quot;: &quot;AllowToGetPutDeleteObjects&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;s3:PutObject&quot;,
                &quot;s3:GetObject&quot;,
                &quot;s3:DeleteObject&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:s3:::my-bucket-name/*&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sid가 있는 객체 하나하나를 Statemen라고 합니다. Sid는 해당 Statement를 식별하는 id 값입니다. 여러 개의 Statement를 입력해서 목적에 맞게 정책을 여러 개 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 AllowToAllBuckets는 모든 버킷에 접근할 수 있도록 허용한 것이고, AllowToAllObjects는 객체 리스트에 접근할 수 있고, AllowToGetPutDeleteObjects는 객체 조회, 수정, 삭제에 대한 정책입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Resource에는 해당 Statement가 적용되는 대상을 적어줘야 합니다. &quot;*&quot;는 모든 대상을 의미하고, &quot;arn:aws:s3:::my-bucket-name&quot;은 제가 이번 포스트에서 생성한 &quot;my-bucket-name&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;이렇게 정책 생성을 완료하고, 다시 IAM 생성 과정으로 넘어가서 만들었던 정책을 검색해서 선택한 후 &lt;b&gt;내가 만들고 있는 IAM 사용자에게 내가 만든 정책을 적용&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;2086&quot; data-origin-height=&quot;1098&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r209m/btszwguFCY9/hVKeLrS0qz1kVecgLjv3kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r209m/btszwguFCY9/hVKeLrS0qz1kVecgLjv3kK/img.png&quot; data-alt=&quot;IAM 사용자 검토, 정책 등록 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r209m/btszwguFCY9/hVKeLrS0qz1kVecgLjv3kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr209m%2FbtszwguFCY9%2FhVKeLrS0qz1kVecgLjv3kK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;339&quot; data-origin-width=&quot;2086&quot; data-origin-height=&quot;1098&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IAM 사용자 검토, 정책 등록 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음으로 넘어가면 최종적으로 지금까지 만들었던 IAM 사용자의 내용을 검토할 수 있습니다. 여기서 내가 생성해서 적용해 준 정책이 정상적으로 등록되었는지 확인합니다. 검토가 완료되었다면 &quot;사용자 생성&quot;을 통해서 IAM 사용자 생성을 완료합니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Access Key 발급&lt;/b&gt;&lt;/h2&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;이제 IAM 사용자를 이용해서 Nest 프로젝트에 적용하기 위해서 필요한 Access Key를 발급해야 합니다.&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;2182&quot; data-origin-height=&quot;1322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxIaCq/btszw57D2kC/DpRZk3onYPwdq0a3Pc7XZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxIaCq/btszw57D2kC/DpRZk3onYPwdq0a3Pc7XZk/img.png&quot; data-alt=&quot;IAM &amp;amp;gt; 사용자 &amp;amp;gt; 보안 자격 증명&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxIaCq/btszw57D2kC/DpRZk3onYPwdq0a3Pc7XZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxIaCq%2Fbtszw57D2kC%2FDpRZk3onYPwdq0a3Pc7XZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;362&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;1322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IAM &amp;gt; 사용자 &amp;gt; 보안 자격 증명&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 생성한 IAM 사용자를 누르고 들어가서 밑에 &quot;보안 자격 증명&quot;이라는 버튼을 클릭합니다. 그리고 쭉 밑으로 내려봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2076&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HQ0K1/btszyKorZrH/ZlMy5XWLKonNlvkiIZqBdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HQ0K1/btszyKorZrH/ZlMy5XWLKonNlvkiIZqBdK/img.png&quot; data-alt=&quot;엑세스 키 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HQ0K1/btszyKorZrH/ZlMy5XWLKonNlvkiIZqBdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHQ0K1%2FbtszyKorZrH%2FZlMy5XWLKonNlvkiIZqBdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;254&quot; data-origin-width=&quot;2076&quot; data-origin-height=&quot;882&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;밑에 내려보면 &quot;액세스 키 만들기&quot; 버튼이 있습니다. 눌러서 엑세스 키를 만들어줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;1184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UZI49/btszvbOkC1j/9ps3CDEchngHpYjgacAOQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UZI49/btszvbOkC1j/9ps3CDEchngHpYjgacAOQ1/img.png&quot; data-alt=&quot;사용 사례&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UZI49/btszvbOkC1j/9ps3CDEchngHpYjgacAOQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUZI49%2FbtszvbOkC1j%2F9ps3CDEchngHpYjgacAOQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;425&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;1184&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;사용하시는 목적에 맞게 선택해 주시면 됩니다. 저는 CLI로 선택을 하고 넘어가겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xgPLj/btszuxxjZe2/WUFrvprKpS22lvxhnE0K91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xgPLj/btszuxxjZe2/WUFrvprKpS22lvxhnE0K91/img.png&quot; data-alt=&quot;설명 태그 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xgPLj/btszuxxjZe2/WUFrvprKpS22lvxhnE0K91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxgPLj%2FbtszuxxjZe2%2FWUFrvprKpS22lvxhnE0K91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;216&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;628&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;선택 사항이기 때문에 작성하든 말든 상관없습니다. 저는 간단하게 &quot;aws-s3-access-key&quot;라고 적은 후 &quot;액세스 키 만들기&quot; 버튼을 눌러 생성을 완료하겠습니다. 생성 완료 후 &lt;b&gt;&quot;Access key ID&quot;와 &quot;Secret access key&quot;&lt;/b&gt;를 확인할 수 있는데, CSV 파일을 저장해서 보관하시기 바랍니다. 나중에 Nest 프로젝트에 연결하기 위해서 필요한 정보입니다. 해당 정보는 노출이 절대 절대 안 되게 잘 관리해 주시길 바랍니다. Github에도 올라가지 않도록요!&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Nest 프로젝트에 적용&lt;/b&gt;&lt;/h2&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;이제 Nest 프로젝트에 적용하기 위해서 필요한 라이브러리를 설치해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1698740750201&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install multer-s3 @aws-sdk/client-s3&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;.env 파일에 S3 관련 설정 정보를 입력합니다. (dotenv가 설치되어있다는 가정하에 진행합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1698740856817&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# AWS S3 Properties
AWS_S3_BUCKET_NAME=my-bucket-name
AWS_REGION=ap-northeast-2
AWS_ACCESS_KEY_ID={엑세스 토큰 발급 시 받은 ACCESS KEY ID)
AWS_SECRET_ACCESS_KEY={엑세스 토큰 발급 시 받은 SECRET ACCESS KEY}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY의 이름&lt;/span&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;이제 Nest 프로젝트에서 S3의 이미지 저장 로직을 처리할 코드를 작성합니다. 저는 해당 이미지 저장 로직을 여러 곳에서 사용할 것이기 때문에 core 패키지의 utils에 만들도록 하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1698741073875&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { BadRequestException, Injectable } from &quot;@nestjs/common&quot;;
import * as AWS from &quot;aws-sdk&quot;;
import { PromiseResult } from 'aws-sdk/lib/request';
import * as path from &quot;path&quot;;

@Injectable()
export class S3Service {
  private readonly s3: AWS.S3;
  private readonly MAXIMUM_IMAGE_SIZE: number;
  private readonly ACCEPTABLE_MIME_TYPES: string[];
  public readonly S3_BUCKET_NAME: string;

  constructor() {
    this.s3 = new AWS.S3({
      region: process.env.AWS_REGION
    })
    this.MAXIMUM_IMAGE_SIZE = 3000000; // 이미지 용량 3MB 제한
    this.ACCEPTABLE_MIME_TYPES = ['image/jpg', 'image/png', 'image/jpeg']; // 이미지 확장자 제한
    this.S3_BUCKET_NAME = process.env.AWS_S3_BUCKET_NAME
  }

  async uploadToS3(file: Express.Multer.File): Promise&amp;lt;S3UploadResponse&amp;gt; {
    try {
      if (!this.ACCEPTABLE_MIME_TYPES.includes(file.mimetype)) {
        throw new BadRequestException('이미지 파일 확장자는 jpg, png, jpeg만 가능합니다.');
      }
      if (file.size &amp;gt; this.MAXIMUM_IMAGE_SIZE) {
        throw new BadRequestException('업로드 가능한 이미지 최대 용량은 3MB입니다.');
      }

      const key = `images/${Date.now()}_${path.basename(file.originalname,)}`
        .replace(/ /g, ''); // 공백을 제거하기 위한 regex입니다.

      const s3Object = await this.s3
        .putObject({
          Bucket: this.S3_BUCKET_NAME,
          Key: key,
          Body: file.buffer,
          ContentType: file.mimetype
        })
        .promise();

      const imgUrl = `https://${this.S3_BUCKET_NAME}.s3.amazonaws.com/${key}`;

      return {
        key: key,
        s3Object: s3Object,
        contentType: file.mimetype,
        url: imgUrl
      }
      
    } catch (error) {
      throw error;
    }
  }
}

export type S3UploadResponse = {
  key: string;
  s3Object: PromiseResult&amp;lt;AWS.S3.PutObjectOutput, AWS.AWSError&amp;gt;;
  contentType: string;
  url: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3에 이미지를 업로드하는데 필요한 정보들을 S3Service에서 처리하고, 이미지를 업로드하는 메서드인 uploadToS3를 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 가능한 이미지의 최대 용량은 3MB로 제한을 두고, 이미지 확장자는 png, jpg, jpge로 제한을 두었습니다.&lt;/p&gt;
&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;text-align: start;&quot;&gt;AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY에 대한 설정을 해주지 않는 것을 볼 수 있습니다. 실제로 accessKeyId라고 코드를 쳐보면 deprecated 되었다고 합니다. &lt;b&gt;env 파일에 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY라는 이름으로 만들어 두면, S3에 요청할 때 알아서 확인&lt;/b&gt;을 한다고 합니다. (싱기방기)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1698741276396&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { S3Service } from './utils/s3.service';

@Module({
  providers: [S3Service],
  exports: [S3Service]
})
export class CoreModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 S3Service를 CoreModule의 providers와 exports에 등록을 하고, 필요한 곳에서 CoreModule을 import 받아 사용할 수 있습니다. CoreModule을 @Global()로 만들어도 되지만, 일단은 직접 import로 주입하는 방법을 이용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;064&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/064.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/064.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;</description>
      <category> &amp;zwj;  Dev/Nest JS</category>
      <category>Multer와 S3 이용하여 이미지 업로드</category>
      <category>NestJS S3</category>
      <category>NestJS 이미지 업로드</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/90</guid>
      <comments>https://hoonsb.tistory.com/90#entry90comment</comments>
      <pubDate>Tue, 31 Oct 2023 17:35:52 +0900</pubDate>
    </item>
    <item>
      <title>[NestJS] FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory</title>
      <link>https://hoonsb.tistory.com/89</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 상황&lt;/b&gt;&lt;/h2&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;자동화 배포 과정에서 Nest 애플리케이션을 빌드하는데, 빌드 실패가 나면서 이런 오류 메시지가 출력됐습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1698735398506&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 할당된 Javascript 힙 메모리를 넘어섰다는 것입니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 문제 해결 방법&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1698735479906&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;node -e 'console.log(v8.getHeapStatistics().heap_size_limit/(1024*1024))'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어를 터미널에 쳐보면 현재 설정되어 있는 힙 메모리를 확인할 수 있습니다. 확인해 보니 저는 495라고 떴습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서는 동작이 됐으니 로컬에서 확인한 값과 동일한 수준까지 올려주기로 했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1698735579177&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export NODE_OPTIONS=--max_old_space_size=2096&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2096까지 올려서 다시 첫 번째 명령어로 495에서 2096으로 변경된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하고 수동으로 서버에 작성해뒀던 ./deploy.sh을 실행해서 빌드 성공 후 배포에도 성공을 했습니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 터미널을 재시작하면 설정이 초기화되는 문제&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;됐겠지 하고 작업 후에 다시 자동화 배포를 하는 과정에서 빌드 시 동일한 문제가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니 터미널에서 설정해줬던 &quot;max_old_space_size=2096&quot;이 &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;그래서 프로젝트 규모가 커지면서 점점 더 많은 힙 메모리를 요구하게 되었지만, 기본적으로 설정된 힙 메모리는 512MB입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Github Actions에서 SSH를 통해 서버에 접근하여./deploy.sh를 실행&lt;/b&gt;하도록 설정해뒀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 과정을 진행할 때마다 터미널에 새로 접근을 해서 2096MB이 아닌 512MB로 적용되어서 동일하게 빌드가 실패했습니다.&lt;/p&gt;
&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;배포 스크립트인 deploy.sh에 들어가서 프로젝트 빌드를 시작하기 전에 &quot;export NODE_OPTIONS=--max_old_space_size=2096&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;근본적인 문제 해결은 메모리 누수를 확인해서 메모리가 누수되는 부분을 분석하여 찾아내야 한다고 합니다. 하지만, 지금은 메모리 누수를 확인할 만큼의 프로젝트 규모가 되지 않았다고 생각을 해서 일단은 NodeJS의 힙 메모리 할당량을 늘리자는 판단을 했습니다.&lt;/p&gt;
&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;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;045&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/045.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/045.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;</description>
      <category> &amp;zwj;  Dev/Nest JS</category>
      <category>JavaScript heap out of memory</category>
      <category>nestjs</category>
      <category>힙 메모리 할당</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/89</guid>
      <comments>https://hoonsb.tistory.com/89#entry89comment</comments>
      <pubDate>Tue, 31 Oct 2023 16:26:23 +0900</pubDate>
    </item>
    <item>
      <title>[NestJS] winston 라이브러리를 이용하여 파일에 로그 남기기</title>
      <link>https://hoonsb.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Nest로 진행하고 있던 프로젝트가 있었습니다. InternalServerError(500)가 터지면 디버깅을 하거나, 오류에 대응할 때 효율을 높일 수 있도록 로그를 파일에 남기려고 적용하고자 했습니다. Java에서는 LogBack으로 로그를 관리하는 것 같은데, Nest에서는 &lt;b&gt;Winston&lt;/b&gt;이라는 라이브러리를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/winstonjs/winston&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Winston Github&lt;/a&gt;에 가서 Winston이 제공하는 기능, 사용법을 확인해 볼 수 있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1698325151892&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 - winstonjs/winston: A logger for just about everything.&quot; data-og-description=&quot;A logger for just about everything. Contribute to winstonjs/winston development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/winstonjs/winston&quot; data-og-url=&quot;https://github.com/winstonjs/winston&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/winstonjs/winston&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/winstonjs/winston&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - winstonjs/winston: A logger for just about everything.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A logger for just about everything. Contribute to winstonjs/winston 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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Winston 라이브러리 설치&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1698325290043&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install winston nest-winston&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GlobalExceptionFilter에서 Winston 사용하여 파일에 로그 남기기&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1698325402334&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { LocalDateTime } from &quot;@js-joda/core&quot;;
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from &quot;@nestjs/common&quot;;
import { HttpArgumentsHost } from &quot;@nestjs/common/interfaces&quot;;
import { Response } from &quot;express&quot;;
import * as winston from 'winston';

const { simple } = winston.format;

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  private readonly apiLogger;
  private readonly basicLogger;

  constructor() {
    this.apiLogger = winston.createLogger({
      transports: [
        new winston.transports.File({
          level: 'error',
          filename: 'api.error.log',
          dirname: 'logs',
          format: simple()
        })
    ]});

    this.basicLogger = winston.createLogger({
      transports: [
        new winston.transports.File({
          level: 'error',
          filename: 'basic.error.log',
          dirname: 'logs',
          format: simple()
        })
    ]});
  }

  catch(exception: any, host: ArgumentsHost) {
    const httpArgumentHosts: HttpArgumentsHost = host.switchToHttp();
    const response: Response = httpArgumentHosts.getResponse();
    const requestUrl: string = host.getArgs()[0].url;

    if (exception instanceof HttpException) {
      let status: number = exception.getStatus();

      if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
        this.apiLogger.error(`[${LocalDateTime.now()}][${requestUrl}] ${exception}`);
      }

      response
        .status(status)
        .json({
          success: false,
          error: exception.message
        });
      
      return;
    }

    this.basicLogger.error(`[${LocalDateTime.now()}][${requestUrl}] ${exception}`);

    response
      .status(HttpStatus.INTERNAL_SERVER_ERROR)
      .json({
        success: false,
        error: exception.message
      });
  }
}&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;Service에서 발생 시키는 예외들을 한 곳으로 모아서 처리하기 위해서 ExceptionFilter를 구현하는 GlobalExceptionFilter를 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InternalServerError(500)로서 error 로그를 찍는 경우를 아래와 같이 2가지로 정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;throw new InternalServerException()&lt;/b&gt;으로 던져져서 Status 코드가 500인 경우&lt;/li&gt;
&lt;li&gt;예상하지 못한 곳에서 예외가 터져서 &lt;b&gt;핸들링하지 못한 Exception&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번의 경우에는 의도적으로 InternalServerException을 이용해서 던지는 경우이기 때문에 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;api.error.log&lt;/b&gt;&lt;/span&gt; 파일에 로그를 저장했습니다. 반대로 2번의 경우에는 의도하지 않은 예외를 잡은 경우이기 때문에 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;basic.error.log&lt;/b&gt;&lt;/span&gt; 파일에 로그를 저장하도록 했습니다. 추가로 디버깅이 편리하도록 &lt;b&gt;어느 API 요청에서 발생했던 예외인지 확인을 위해 Request URL도 함께&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;포맷에 combine()을 이용해서 timestamp를 찍는 방법도 있었는데, js-joda의 LocalDateTime.now()가 더 편하고, 코드로 봤을 때 직관적인 것 같아서 &lt;b&gt;simple() 포맷을 사용&lt;/b&gt;해서 로그를 남겼습니다. Winston Github 링크에 가보면 다양한 포맷에 대한 설명이 있습니다. 의도에 맞게 사용하시는 것을 추천합니다.&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;510&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/llX9C/btszgm2gcLh/vpk8rOaJ7F4Ka5M9XKHrq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/llX9C/btszgm2gcLh/vpk8rOaJ7F4Ka5M9XKHrq1/img.png&quot; data-alt=&quot;logs 디렉터리 내부에 생긴 log 파일 2개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/llX9C/btszgm2gcLh/vpk8rOaJ7F4Ka5M9XKHrq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FllX9C%2Fbtszgm2gcLh%2Fvpk8rOaJ7F4Ka5M9XKHrq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;374&quot; height=&quot;104&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;logs 디렉터리 내부에 생긴 log 파일 2개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하고 재시작을 하면 logs/api.error.log와 logs/basic.error.log 파일이 생깁니다. 이제 해당 필터를 거치게할 Controller에 &lt;b&gt;@UseFilters()&lt;/b&gt;를 붙여서 테스트를 해봐야 합니다. 500 에러가 발생하도록 해서 로그가 파일에 정상적으로 찍히는지 확인해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1698381976717&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@UseFilters(GlobalExceptionFilter)
@Controller('letters')
export class LettersController {
  // 코드 생략
}&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;로그 확인하기&lt;/b&gt;&lt;/h2&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-origin-width=&quot;1462&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1MMC/btszfZsMEzA/um7PCWOXV0SDZeR1jKkueK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1MMC/btszfZsMEzA/um7PCWOXV0SDZeR1jKkueK/img.png&quot; data-alt=&quot;500 Internal Server Error 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1MMC/btszfZsMEzA/um7PCWOXV0SDZeR1jKkueK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1MMC%2FbtszfZsMEzA%2Fum7PCWOXV0SDZeR1jKkueK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;157&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;500 Internal Server Error 발생&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 Postman을 통해 확인한 Internal Server Error입니다. 이는 throw new InternalServerException을 통해 의도된 에러였기 때문에 api.error.log에 저장됩니다. 정상적으로 저장이 됐는지 확인해 봅니다.&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;1622&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCqLbC/btszhoMaiel/twS1ZTRRgrhCzjBVsSfWx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCqLbC/btszhoMaiel/twS1ZTRRgrhCzjBVsSfWx1/img.png&quot; data-alt=&quot;logs/api.error.log 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCqLbC/btszhoMaiel/twS1ZTRRgrhCzjBVsSfWx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCqLbC%2FbtszhoMaiel%2FtwS1ZTRRgrhCzjBVsSfWx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;105&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;logs/api.error.log 파일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 저장됐습니다. 의도했던 error 로그가 파일에 찍혀서 저장된 것을 확인할 수 있습니다.&lt;/p&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;b&gt;참고&lt;/b&gt;&lt;/h2&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;a href=&quot;https://docs.nestjs.com/techniques/logger#use-external-logger&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.nestjs.com/techniques/logger#use-external-logger&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1698327757898&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;Documentation | NestJS - A progressive Node.js framework&quot; data-og-description=&quot;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&quot; data-og-host=&quot;docs.nestjs.com&quot; data-og-source-url=&quot;https://docs.nestjs.com/techniques/logger#use-external-logger&quot; data-og-url=&quot;https://docs.nestjs.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxMihm/hyUkebxABe/054b2GKi0nZkVKoUIidd2K/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/techniques/logger#use-external-logger&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.nestjs.com/techniques/logger#use-external-logger&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxMihm/hyUkebxABe/054b2GKi0nZkVKoUIidd2K/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429');&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;Documentation | NestJS - A progressive Node.js framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.nestjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/Nest JS</category>
      <category>nestjs logger</category>
      <category>nestjs winston</category>
      <category>nestjs 파일에 로그 남기기</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/87</guid>
      <comments>https://hoonsb.tistory.com/87#entry87comment</comments>
      <pubDate>Thu, 26 Oct 2023 22:21:19 +0900</pubDate>
    </item>
    <item>
      <title>[패스트캠퍼스] 패스트캠퍼스 백엔드 개발 부트캠프 5기 프로젝트 회고</title>
      <link>https://hoonsb.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;3월부터 시작해서 벌써 7개월이 지나 마지막 날이 되었습니다. 짧다고 하면 짧고, 길다면 길 수 있는 시간이 지났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;10월이 되면 조금 성장해 있을까?&quot;라는 생각을 하면서 부트캠프에 참여했던 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7개월 동안 부트캠프에서 진행했었던 프로젝트의 과정을 기록해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;첫 번째 프로젝트&lt;/b&gt;&lt;/h2&gt;
&lt;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;b&gt;Java만을 이용&lt;/b&gt;하여 콘솔로 입력을 받고, 콘솔에 출력을 하는 &quot;스마트 스토어&quot;를 만드는 프로젝트였습니다. 이 프로젝트에서는 List, Map, Set과 같은 컬렉션 프레임워크의 사용이 금지되었습니다. 즉, 사용할 거면 직접 구현해서 사용했던 프로젝트입니다. 평소에 편리하게 그냥 가져다 쓰던 List, Map, Set 등 편리한 컬렉션들이 어떻게 작동되는지 느껴볼 수 있었던 프로젝트였습니다.&lt;/p&gt;
&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;패스트캠퍼스에서는 Java 언어 수강 기간이 꽤나 큰 비중을 차지했었습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;두 번째 프로젝트&lt;/b&gt;&lt;/h2&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;color: #006dd7;&quot;&gt;&lt;b&gt;&quot;야구관리 프로그램&quot;&lt;/b&gt;&lt;/span&gt;이었습니다. Java와 &lt;b&gt;JDBC 그리고 Database(MySQL)&lt;/b&gt;를 이용하는 토이 프로젝트였습니다. 데이터베이스 설계에 대한 이론 강의를 진행한 후에 프로젝트가 진행되어 직접 요구사항에 대한 데이터베이스 설계를 해 볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경기장, 팀, 선수, 퇴출 선수에 대한 &lt;b&gt;테이블을 설계&lt;/b&gt;했고, 요구사항에 맞게 비즈니스 로직을 Service에 구현했습니다. 두 번째 프로젝트에서는 두 명이서 팀을 이뤄서 진행한 첫 번째 팀 프로젝트였기 때문에 &lt;b&gt;Github Commit Message Convetion&lt;/b&gt;을 정하고, &lt;b&gt;Pull Request를 이용해서 Rebase Merge&lt;/b&gt;를 하는 방식으로 프로젝트를 진행했습니다. 야구 관리 프로그램을 진행할 때, 패키지 설계에 대한 고민도 했었습니다. 이때는 기능을 기준으로 db, dto, model, service, view 이렇게 패키지를 분리해서 작업을 했었습니다.&lt;/p&gt;
&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: #1b711d;&quot;&gt;&lt;a style=&quot;color: #1b711d;&quot; href=&quot;https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;최주호 강사님&lt;/a&gt;&lt;/span&gt;이 데이터베이스 수업을 맡아주셨었는데 수업을 하실 때 적절한 비유를 통해서 이해를 쉽게 할 수 있도록 도와주셨던 것 같습니다. Index에 대한 개념, Index는 언제 사용하면 좋을지, OneToOne, OneToMany, ManyToOne, ManyToMany 등 여러 가지 의존 관계에 대해서 배울 수 있었습니다. 지금까지도 유용하게 잘 사용하고 있는 지식들입니다.&lt;/p&gt;
&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://github.com/khsrla9806/fastcampus-toyproject2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/khsrla9806/fastcampus-toyproject2&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1696862192770&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 - khsrla9806/fastcampus-toyproject2:  ️ JDK11 + Gradle + JDBC + MySQL&quot; data-og-description=&quot; ️ JDK11 + Gradle + JDBC + MySQL. Contribute to khsrla9806/fastcampus-toyproject2 development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/khsrla9806/fastcampus-toyproject2&quot; data-og-url=&quot;https://github.com/khsrla9806/fastcampus-toyproject2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cmDczc/hyT9x921jy/RLVB7dFe8nPyFv2745xCr1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/khsrla9806/fastcampus-toyproject2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/khsrla9806/fastcampus-toyproject2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cmDczc/hyT9x921jy/RLVB7dFe8nPyFv2745xCr1/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 - khsrla9806/fastcampus-toyproject2:  ️ JDK11 + Gradle + JDBC + MySQL&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; ️ JDK11 + Gradle + JDBC + MySQL. Contribute to khsrla9806/fastcampus-toyproject2 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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;세 번째 프로젝트&lt;/b&gt;&lt;/h2&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;color: #006dd7;&quot;&gt;&lt;b&gt;&quot;게시판&quot;&lt;/b&gt;&lt;/span&gt; 프로젝트입니다. 드디어 &lt;b&gt;Springboot&lt;/b&gt;를 사용하기 시작한 토이 프로젝트입니다. Spring Data JPA와 MyBatis에 대한 이론 강의를 진행한 후 프로젝트가 진행되어 요구 사항에서는 &lt;b&gt;사용자 서버는 Spring Data JPA를 이용하고, 관리자 서버는 MyBatis&lt;/b&gt;를 사용하라는 요구 사항이 있었습니다. 백엔드 팀원끼리 진행했던 프로젝트였기 때문에 View는 &lt;b&gt;Thymeleaf와 Bootstrap&lt;/b&gt;를 이용하여 Server Side Rendering을 했습니다.&lt;/p&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 통신을 할지 아니면 멀티 모듈로 구성해서 Entity들을 공유하여 사용할지 결정해야 했었습니다. 관리자의 기능은 사용자 기능이 완료된 후에 함께 구현하기로 했었기 때문에 프로젝트의 남은 기한을 고려했을 때, 새로운 저장소를 만들어 서버 자체를 분리하는 데는 무리가 있다고 판단하여 &lt;b&gt;멀티 모듈&lt;/b&gt;로 사용자는 8080 포트, 관리자는 8081 포트로 분리하기로 결정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 프로젝트에서 유용하게 사용했었던 Github Convention과 Pull Request &amp;amp; Rebase Merge 전략을 그대로 가져와서 새로운 팀원들과 살을 붙여 이용했습니다. 추가로 &lt;b&gt;작업 내용을 Issue화 해서 협업을 진행&lt;/b&gt;했습니다. Commit Message에 Issue 번호를 적어서 작성하면 어떤 작업에서 발생된 Commit 내용인지 빠르게 파악할 수 있어서 좋았습니다.&lt;/p&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 data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/khsrla9806/board-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/khsrla9806/board-project&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1696862166638&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 - khsrla9806/board-project:  ️ Spring Boot (2.7.13) + Gradle + JDK11 + Spring Data JPA(Board) + MyBatis(Admin) + MyS&quot; data-og-description=&quot; ️ Spring Boot (2.7.13) + Gradle + JDK11 + Spring Data JPA(Board) + MyBatis(Admin) + MySQL - GitHub - khsrla9806/board-project:  ️ Spring Boot (2.7.13) + Gradle + JDK11 + Spring Data JPA(Board) + M...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/khsrla9806/board-project&quot; data-og-url=&quot;https://github.com/khsrla9806/board-project&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJFdCG/hyT9K9knte/Izo9Pn9vt7Eh5mar8GxWNk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/khsrla9806/board-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/khsrla9806/board-project&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJFdCG/hyT9K9knte/Izo9Pn9vt7Eh5mar8GxWNk/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 - khsrla9806/board-project:  ️ Spring Boot (2.7.13) + Gradle + JDK11 + Spring Data JPA(Board) + MyBatis(Admin) + MyS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; ️ Spring Boot (2.7.13) + Gradle + JDK11 + Spring Data JPA(Board) + MyBatis(Admin) + MySQL - GitHub - khsrla9806/board-project:  ️ Spring Boot (2.7.13) + Gradle + JDK11 + Spring Data JPA(Board) + M...&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;네 번째 프로젝트&lt;/b&gt;&lt;/h2&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;color: #006dd7;&quot;&gt;&lt;b&gt;&quot;연차 &amp;amp; 당직 프로젝트&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;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;와이어프레임을 작성한 후 백엔드 팀원끼리 모여 ERD 설계를 진행하고, &lt;b&gt;API 명세서&lt;/b&gt;도 프런트 엔드 팀원분들과 함께 작성했습니다. 둘 다 처음 하는 협업이어서 어떤 Request를 받고, 어떤 Response를 줘야 할지에 대해서 의논했습니다. 처음 했던 협업 치고는 순탄하게 진행되었던 거 같습니다.&lt;/p&gt;
&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;AWS Lightsail과 FileZilla&lt;/b&gt;를 가지고, 서버를 실제로 배포해 보는 경험을 해볼 수 있었습니다. 그리고 클라이언트 서버(HTTPS)와의 통신을 위해서는 SSL 인증서를 발급받아 도메인을 구매한 후 연결을 했어야 했습니다. 도메인은 &lt;b&gt;가비아&lt;/b&gt;에서 저렴하게 구입했고, &lt;b&gt;LetsEncrypt를 이용&lt;/b&gt;하여 인증서를 발급하고, application.yml에 설정을 추가해 줌으로써 프로젝트를 진행할 수 있었습니다.&lt;/p&gt;
&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://github.com/khsrla9806/mini-annual-work-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/khsrla9806/mini-annual-work-project&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1696862510316&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 - khsrla9806/mini-annual-work-project:  ️ Spring Boot (2.7.14) + Gradle + JDK11 + Spring Data JPA + MySQL + AWS&quot; data-og-description=&quot; ️ Spring Boot (2.7.14) + Gradle + JDK11 + Spring Data JPA + MySQL + AWS - GitHub - khsrla9806/mini-annual-work-project:  ️ Spring Boot (2.7.14) + Gradle + JDK11 + Spring Data JPA + MySQL + AWS&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/khsrla9806/mini-annual-work-project&quot; data-og-url=&quot;https://github.com/khsrla9806/mini-annual-work-project&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Bxgav/hyT9KIhXqe/vUjZ9pX3CKOZboyx9Psunk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/khsrla9806/mini-annual-work-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/khsrla9806/mini-annual-work-project&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Bxgav/hyT9KIhXqe/vUjZ9pX3CKOZboyx9Psunk/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 - khsrla9806/mini-annual-work-project:  ️ Spring Boot (2.7.14) + Gradle + JDK11 + Spring Data JPA + MySQL + AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; ️ Spring Boot (2.7.14) + Gradle + JDK11 + Spring Data JPA + MySQL + AWS - GitHub - khsrla9806/mini-annual-work-project:  ️ Spring Boot (2.7.14) + Gradle + JDK11 + Spring Data JPA + MySQL + AWS&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다섯 번째 프로젝트&lt;/b&gt;&lt;/h2&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;color: #006dd7;&quot;&gt;&lt;b&gt;&quot;기업연계&quot;&lt;/b&gt;&lt;/span&gt; 프로젝트였습니다. 지금까지 했던 4개의 프로젝트는 이 파이널 프로젝트를 위한 발판이라고 할 수 있습니다. 진행되는 과정은 연계된 기업의 대표분들이 직접 어떤 설루션을 해결해줬으면 하는지, 어떤 프로젝트를 진행하고 싶은지를 말하는 시간을 갖고 나서 1 지망, 2 지망, 3 지망 선택했습니다. 같은 기업을 선택한 사람들끼리 랜덤으로 팀이 구성되었습니다. 거의 모든 사람이 1 지망에 선택했던 프로젝트를 진행하셨던 거 같습니다. 기업연계 프로젝트는 &lt;b&gt;PM, UX/UI도 합류&lt;/b&gt;를 하기 때문에 소통이 매우 중요했던 프로젝트였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패스트캠퍼스의 모든 프로젝트를 진행하며 조금 아쉽다고 생각되었던 부분이 &lt;b&gt;팀을 랜덤으로 정해준다는 것&lt;/b&gt;이었습니다. 참여 점수, 성적 등을 고려하여 팀을 배정해 준다고는 하지만, 5~6개월이 지나는 동안 어느 정도 함께 해보고 싶은 사람들이 있다고 생각이 됩니다. 뭐 누구를 만나든 최선을 다해야 하는 것은 맞지만, 열정이 비슷한 사람들끼리 팀을 이루면 그 시너지 효과가 더욱 클 것이라고 생각되었기 때문입니다.&lt;/p&gt;
&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;배포 자동화(CI/CD) 환경 구성, 테스트 커버리지 50% 이상 유지&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;실제로 프로젝트에서 Github Actions와 SSH를 이용하여 배포 자동화 환경을 구성했고, 테스트 커버리지는 57%를 달성했습니다. 그리고 새롭게 &lt;b&gt;Java Code Convention&lt;/b&gt;도 정했습니다. 이때 나온 컨벤션이 거의 15개... 모두 다 지키지는 못했지만, 컨벤션을 정하는 과정에서도 배움이 있었습니다. 추가로 이번에는 &lt;b&gt;Gitflow 전략&lt;/b&gt;을 도입하고, Rebase Merge 전략이 아닌 &lt;b&gt;Squash Merge&lt;/b&gt; 전략을 사용했습니다. Squash Merge 전략을 사용한 이유는 하나의 작업에서 발생하는 Commit을 세분화하다 보니 너무 많은 Commit이 생겼고, 오히려 해당 작업을 추적하기가 어려워졌었습니다. 그래서 Squash Merge를 사용해서 세분화된 Commit들을 하나의 Commit으로 합쳐서 효과적으로 커밋을 관리하고, 가독성을 높일 수 있을 것이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/livable-final/server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/livable-final/server&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1697254291425&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 - livable-final/server: 오피스 혁신을 위한 통합 플랫폼 &amp;lsquo;오피스너&amp;rsquo;의 유저 가입자수와 WAU를 &quot; data-og-description=&quot;오피스 혁신을 위한 통합 플랫폼 &amp;lsquo;오피스너&amp;rsquo;의 유저 가입자수와 WAU를 높이기 위한 프로젝트 - GitHub - livable-final/server: 오피스 혁신을 위한 통합 플랫폼 &amp;lsquo;오피스너&amp;rsquo;의 유저 가입자수와 WAU를 &quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/livable-final/server&quot; data-og-url=&quot;https://github.com/livable-final/server&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bg3c0K/hyT9BFMbIU/ULXFYov8iilk6kdMkpKKr1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/livable-final/server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/livable-final/server&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bg3c0K/hyT9BFMbIU/ULXFYov8iilk6kdMkpKKr1/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 - livable-final/server: 오피스 혁신을 위한 통합 플랫폼 &amp;lsquo;오피스너&amp;rsquo;의 유저 가입자수와 WAU를&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오피스 혁신을 위한 통합 플랫폼 &amp;lsquo;오피스너&amp;rsquo;의 유저 가입자수와 WAU를 높이기 위한 프로젝트 - GitHub - livable-final/server: 오피스 혁신을 위한 통합 플랫폼 &amp;lsquo;오피스너&amp;rsquo;의 유저 가입자수와 WAU를&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리 회고&lt;/b&gt;&lt;/h2&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;처음에는 Github를 Push, Pull 정도만 이해하고 사용했었습니다. 프로젝트를 거치면서 Pull Request도 사용해 보고, 충돌을 해결하기 위해서 Rebase도 사용해 볼 수 있었습니다. 더 나아가 Issue를 적극적으로 사용하게 되었고, 마지막에는 Organization을 생성하여 Client와 Server를 함께 관리하고, 상황에 적절한 Gitflow 전략과 Merge 전략을 선택해서 사용할 수 있을 정도로 성장을 했습니다.&lt;/p&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 강의, Spring 강의, JPA, QueryDSL, MVC 모두 구매해서 수강하고, 바로 진행하고 있던 프로젝트에 활용해 보며 학습했습니다.&lt;/p&gt;
&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;Docker를 사용해서 배포환경을 구성&lt;/b&gt;해보고 싶습니다. 그리고 파이널 프로젝트에서 잠깐 사용했었던 &lt;b&gt;JMeter를 조금 심오하게 사용해서 성능 개선&lt;/b&gt;도 해보고 싶다는 목표가 생겼습니다. 지금까지 프로젝트에서 문서화를 할 때 접근하기 좋았던 Notion을 주로 사용했지만, &lt;b&gt;Swagger를 사용하여 API 문서화&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;결국에는 스스로 하지 않고 가만히 있으면 가마니가 됩니다. 어느 곳을 가던 하는 만큼 얻어갈 수 있다고 생각합니다. 제가 열심히 하고, 열정적으로 참여하면 주변 사람들도 그런 사람들로 채워졌던 거 같습니다. 비전공자로 개발을 시작했지만, 10개월 동안 좋은 사람들과 함께 진행할 수 있어서 좋았던 활동이었다고 말할 수 있을 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;043&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/043.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/043.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;</description>
      <category>  Activity/패스트캠퍼스</category>
      <category>패스트 캠퍼스 부트캠프</category>
      <category>패스트캠퍼스 백엔드 부트캠프</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/86</guid>
      <comments>https://hoonsb.tistory.com/86#entry86comment</comments>
      <pubDate>Tue, 10 Oct 2023 00:17:00 +0900</pubDate>
    </item>
    <item>
      <title>JackSon DTO 역직렬화 파헤치기 (JackSon과 Getter)</title>
      <link>https://hoonsb.tistory.com/85</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; ️ 사용한 기술 버전 확인&lt;/b&gt;&lt;/h3&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;b&gt;SpringBoot&lt;/b&gt;: 2.7.15&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JackSon&lt;/b&gt;: 2.13.5&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Lombok&lt;/b&gt;: 1.18.28&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  분석을 해보기로 한 이유&lt;/b&gt;&lt;/h3&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;Controller에서 Json에서 DTO로 역직렬화를 할 때, @NoArgsConstructor + @Setter가 있으면 역직렬화가 되는지 알고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JackSon의 내부 코드를 모두 까보지 않으면 모르지만, 개발하며 마주한 몇 가지 생각지 못한 케이스가 있어서 다뤄보려고 합니다.&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;추가적인 말이 없을 때까지는 아래의 HTTP 요청과 Controller를 사용했을 때의 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695454472879&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST http://localhost:8080/test
Content-Type: application/json

{
  &quot;testString&quot;: &quot;테스트&quot;,
  &quot;testInteger&quot;: 100
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695454781454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/test&quot;)
public String test01(@RequestBody TestDTO dto) {
    System.out.println(&quot;&amp;gt;&amp;gt;&amp;gt; &quot; + dto);

    return &quot;ok&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. @NoArgsConstructor&lt;/b&gt;&lt;/h3&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;pre id=&quot;code_1695454655873&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
public static class TestDTO {
    private String testString;
    private Integer testInteger;

    public String toString() {
        return &quot;testString: &quot; + this.testString + &quot;, testInteger=&quot; + this.testInteger;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toString()은 값이 제대로 바인딩되었는지 콘솔에서 확인하기 위한 용도입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695454707452&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; testString: null, testInteger=null&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;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. @NoArgsConstructor + @Getter&lt;/b&gt;&lt;/h3&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;두 번째는 기본 생성자에 Getter를 붙여봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695454918573&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
public static class TestDTO {
    private String testString;
    private Integer testInteger;

    public String toString() {
        return &quot;testString: &quot; + this.testString + &quot;, testInteger=&quot; + this.testInteger;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695454947572&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; testString: 테스트, testInteger=100&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;&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. @NoArgsConstructor + @Setter&lt;/b&gt;&lt;/h3&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;세 번째는 Setter와 생성자의 조합입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695455039874&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Setter
@NoArgsConstructor
public static class TestDTO {
    private String testString;
    private Integer testInteger;

    public String toString() {
        return &quot;testString: &quot; + this.testString + &quot;, testInteger=&quot; + this.testInteger;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695455069070&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; testString: 테스트, testInteger=100&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중간으로 확인해야 할 것이 있습니다. 기본 생성자만 있을 때는 바인딩이 안 됐었는데, Getter나 Setter가 있으면 바인딩을 해줍니다. 한 가지 특별 케이스를 만들어봅시다.&lt;/p&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. @NoArgsConstructor + 커스텀 Getter&lt;/b&gt;&lt;/h3&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;만약 Getter의 기능을 하는 다른 이름의 메서드를 만들고 싶어 졌다고 해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;나는 get이라는 걸 안 쓰고 find를 써서 새롭게 만들어볼래.&quot; &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;lt;-- 똑같은 건 싫어 싫어 개발자&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1695455236077&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
public static class TestDTO {
    private String testString;
    private Integer testInteger;

    public String findTestString() {
        return testString;
    }

    public Integer findTestInteger() {
        return testInteger;
    }

    public String toString() {
        return &quot;testString: &quot; + this.testString + &quot;, testInteger=&quot; + this.testInteger;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695455324457&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; testString: null, testInteger=null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;???... Getter와 똑같은 기능을 하는 메서드를 만들었는데 데이터가 정상적으로 바인딩되지 않았다. 그럼 여기서 또 궁금한 건 못 참는 피곤한 사람(나)들은 get이라는 이름으로 커스텀 메서드를 만들어봅니다. ㅎ&lt;/p&gt;
&lt;pre id=&quot;code_1695455412595&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
public static class TestDTO {
    private String testString;
    private Integer testInteger;

    public String getTestString() {
        return testString;
    }

    public Integer getTestInteger() {
        return testInteger;
    }

    public String toString() {
        return &quot;testString: &quot; + this.testString + &quot;, testInteger=&quot; + this.testInteger;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695455445424&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; testString: 테스트, testInteger=100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;!!!? 바인딩이 되었습니다. 더 궁금한걸 못 참는 분들은 이미 해보시고 있겠지만, Setter도 동일합니다. bindTestInteger() 이런 식으로 Setter를 만들면 바인딩이 제대로 되지 않습니다.&lt;/p&gt;
&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;첫 번째 결론은 Getter 또는 Setter가 필요한데, 이름이 get 어쩌고, set 어쩌고여야 한다&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;a href=&quot;https://jenkov.com/tutorials/java-json/jackson-objectmapper.html#how-jackson-objectmapper-matches-json-fields-to-java-fields&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JackSon 홈페이지&lt;/a&gt;에 가보면 알 수 있습니다. 영어를 잘하는 분들은 이런 노가다짓은 안 하고 계시겠죠..? 흑 하지만... 재밌는 걸 어떡하죠.  &lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;To read Java objects from JSON with Jackson properly, it is important to know how Jackson maps the fields of a JSON object to the fields of a Java object, so I will explain how Jackson does that.By default Jackson maps the fields of a JSON object to fields in a Java object by matching the names of the JSON field to the getter and setter methods in the Java object. &lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;Jackson removes the &quot;get&quot; and &quot;set&quot; part of the names of the getter and setter methods, and converts the first character of the remaining name to lowercase. &lt;/b&gt;&lt;/span&gt;For instance, the JSON field named brand matches the Java getter and setter methods called getBrand() and setBrand(). The JSON field named engineNumber would match the getter and setter named getEngineNumber() and setEngineNumber().If you need to match JSON object fields to Java object fields in a different way, you need to either use a custom serializer and deserializer, or use some of the many Jackson Annotations.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jackson에서 필드를 바인딩할 때 getter, setter 메서드에서 get, set 부분을 제거하고 첫 문자를 소문자로 변경하는 방식으로 해당 필드가 있는지 확인한다고 합니다. 즉, 해당 변수를 찾을 때 변수명을 확인하는 게 아니고 getter와 setter를 보고 해당 필드가 있는지 확인을 하고, 매칭한다는 것을 의미합니다.&lt;/p&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Getter 테스트 (제일 재밌는 부분  )&lt;/b&gt;&lt;/h3&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;Jackson에서는 getter, setter에서 get, set 부분을 제거하고, 첫 문자를 소문자로 변경하는 방식으로 해당 필드를 찾는다고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금한 건 못 참는 두 번째 테스트를 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5.1 get + 소문자 시작&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1695648484759&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
public static class TestDTO {
    private String testString;
    private Integer testInteger;

    public void gettestString() {
        return testString;
    }

    public void gettestInteger() {
        return testInteger;
    }

    public String toString() {
        return &quot;testString: &quot; + this.testString + &quot;, testInteger=&quot; + this.testInteger;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CamelCase로 작성하기 때문에 이렇게는 안 적겠지만, 궁금해서 테스트해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;get을 제외하면 testString이고, 여기서 첫 문자를 소문자로 바꾸면 testString 그대로 이기 때문에 바인딩이 될 것이라고 예상했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695648577772&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; testString: null, testInteger=null&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5.2 get + 대문자 시작 + 소문자(두 번째 단어) (1)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 첫 문자는 CamelCase대로 대문자로 시작하고, 두 번째 단어를 소문자로 변경해 봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695649011955&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
public static class TestDTO {
    private String testString;
    private Integer testInteger;

    public String getTeststring() {
        return testString;
    }

    public Integer getTestinteger() {
        return testInteger;
    }

    public String toString() {
        return &quot;testString: &quot; + this.testString + &quot;, testInteger=&quot; + this.testInteger;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695649027559&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; testString: null, testInteger=null&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;JackSon에서 말하는 첫 문자를 소문자로 변경한다는 것이 어디까지를 말하는 것일지 궁금해서 더 테스트해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5.3 get + 대문자 시작 + 소문자 시작(두 번째 단어) (2)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 조건은 5.2와 동일하게 가져가고, 이번에는 변수 이름을 변경해 봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695649548404&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
public static class TestDTO {
    private String teststring;
    private Integer testinteger;

    public String getTeststring() {
        return teststring;
    }

    public Integer getTestinteger() {
        return testinteger;
    }

    public String toString() {
        return &quot;teststring: &quot; + this.teststring + &quot;, testinteger=&quot; + this.testinteger;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695649565134&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; teststring: 테스트, testinteger=100&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5.4 get + 대문자 여러 개 + 소문자&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1695649710525&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
public static class TestDTO {
    private String aBCdef;
    private Integer ghijk;

    public String getABCdef() {
        return aBCdef;
    }

    public Integer getGHIjk() {
        return ghijk;
    }

    public String toString() {
        return &quot;aBCdef: &quot; + this.aBCdef + &quot;, ghijk=&quot; + this.ghijk;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 getABCdef()에서 get을 제외하면 ABCdef가 남고, 첫 문자를 소문자로 변경한다고 되어 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상하는 건 &lt;span style=&quot;color: #006dd7;&quot;&gt;&quot;aBCdef라는 변수가 있으면 바인딩이 제대로 될 것이다.&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;아래 있는 getGHIjk()는 get을 제외하면 GHIjk에서 처음으로 만나는 소문자 j 전까지를 한 문자로 본다고 생각하고, 적어봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상이 맞다면 &lt;span style=&quot;color: #006dd7;&quot;&gt;ghijk라는 변수에 바인딩이 정상적으로 될 것입니다. &lt;/span&gt;둘 중에 하나는... 맞겠지  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1695650006894&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; aBCdef: null, ghijk=100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두두둥! 두 번째 예상이 맞은 거 같습니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;get을 제외하고, 그다음에 나오는 단어에서 첫 번째로 만나는 소문자 전까지의 대문자를 모두 소문자로 변경&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5.5 진짜 정말 마지막 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상한 것이 맞는지 이해한 대로 마지막 테스트를 진행해 봅니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;getter에서 get을 제외합니다.&lt;/li&gt;
&lt;li&gt;나머지 단어에서 첫 번째 소문자가 나오기 전까지의 대문자는 모두 소문자로 변경합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1695650255959&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
public static class TestDTO {
    private String abcdEFG;
    private Integer abcDEFG;

    public String getABcdEFG() {
        return abcdEFG;
    }

    public Integer getabcDEFG() {
        return abcDEFG;
    }

    public String toString() {
        return &quot;abcdEFG: &quot; + this.abcdEFG + &quot;, abcDEFG=&quot; + this.abcDEFG;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 변수를 제가 생각한 대로 변경해 보겠습니다. getABcdEFG에서 get을 제거하면 ABcdEFG가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 처음으로 만나는 소문자(c) 전까지의 모든 대문자를 소문자로 변경하면 &lt;b&gt;abcdEFG&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;두 번째 변수를 제가 생각한 대로 변경해 보겠습니다. getabcDEFG에서 get을 제거하면 abcDEFG가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 처음으로 만나는 소문자(a) 이전에는 대문자가 존재하지 않기 때문에 그대로 &lt;b&gt;abcDEFG&lt;/b&gt;가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1695650415512&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[결과]
&amp;gt;&amp;gt;&amp;gt; abcdEFG: 테스트, abcDEFG=100&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;정상적으로 바인딩이 되었습니다. 다음에는 @Builder, @AllArgsConstructor과 JackSon을 사용해 보고, 이번글에서 상세하게 정리해보지 못한 Setter 관련해서도 적어보려고 합니다. 시간이... 된다면?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;048&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/048.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/048.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;</description>
      <category> &amp;zwj;  Dev/SpringBoot</category>
      <category>DTO 역직렬화</category>
      <category>Jackson</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/85</guid>
      <comments>https://hoonsb.tistory.com/85#entry85comment</comments>
      <pubDate>Mon, 25 Sep 2023 23:03:15 +0900</pubDate>
    </item>
    <item>
      <title>[Spring | LetsEncrypt] Http 서버를 Https로 바꿔보자</title>
      <link>https://hoonsb.tistory.com/83</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&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;프론트엔드 팀과 협업을 위해서 API 서버를 개발하고, AWS LightSail을 사용해서 서버를 배포했습니다.&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;span style=&quot;color: #ee2323;&quot;&gt;클라이언트 서버는 https로 배포가 되어있고, 개발 서버는 http로 배포가 되어 있어 두 서버 사이의 통신에 문제가 발생&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;1008&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXmdGz/btsquCMJHAl/Qx7k5cYvFV1AvRLBo8xf10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXmdGz/btsquCMJHAl/Qx7k5cYvFV1AvRLBo8xf10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXmdGz/btsquCMJHAl/Qx7k5cYvFV1AvRLBo8xf10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXmdGz%2FbtsquCMJHAl%2FQx7k5cYvFV1AvRLBo8xf10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1008&quot; height=&quot;88&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;88&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 해결&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1)  SSL 인증서가 필요하다!&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로 HTTPS로 배포를 하기 위해서는 CA의 인증을 받은 SSL 인증서가 필요합니다.&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;무료로 SSL 인증서를 발급 받을 수 있는 &lt;a href=&quot;https://letsencrypt.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Let's Encrypt&lt;/a&gt;를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료라는 특징도 있지만 3개월(90일)마다 인증서를 갱신해줘야 한다는 특징도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 도메인 구매하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;letsencrypt에서 SSL 인증서를 받기 위해서는 &quot;이메일&quot;과 &quot;도메인&quot;이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 도메인이라는 것을 구매해야하는데, &lt;a href=&quot;https://www.gabia.com/&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;2270&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VIMrz/btsquCsoX1X/d0t2WtyPHpOU4fSH3Ck0E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VIMrz/btsquCsoX1X/d0t2WtyPHpOU4fSH3Ck0E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VIMrz/btsquCsoX1X/d0t2WtyPHpOU4fSH3Ck0E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVIMrz%2FbtsquCsoX1X%2Fd0t2WtyPHpOU4fSH3Ck0E0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;934&quot; height=&quot;139&quot; data-origin-width=&quot;2270&quot; data-origin-height=&quot;338&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rqz90/btsp91noWU0/KTY07wrDfcbTlCVQ2Tp8a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rqz90/btsp91noWU0/KTY07wrDfcbTlCVQ2Tp8a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rqz90/btsp91noWU0/KTY07wrDfcbTlCVQ2Tp8a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frqz90%2Fbtsp91noWU0%2FKTY07wrDfcbTlCVQ2Tp8a1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;366&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 뜨면 원하는걸 골라서 선택한 후 신청하기를 진행하고, 결제를 하면 이제 제 도메인이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에는 20,000원짜리로 예시가 되어있는데 잘 찾아보면 500원짜리도 존재하고, 가격은 다양합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신이 적당하다고 생각되는 가격으로 도메인을 하나 구매해 주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이렇게 도메인을 구매했으면 &lt;a href=&quot;https://customer.gabia.com/manual/domain/287/1201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 참고해서 미리 준비되어 있는 서버(AWS)의 Public IP를 DNS로 등록해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) My 가비아 &amp;gt; 도메인으로 접속&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인을 구매하고, 조금 있으면 가비아 홈페이지의 오른쪽 상단에 My가비아에 들어가 보면 도메인을 관리할 수 있는 홈페이지로 갈 수 있습니다.&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;897&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cripzt/btsvoYZEWms/Tj9IecCCpaMMWcekTfMdsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cripzt/btsvoYZEWms/Tj9IecCCpaMMWcekTfMdsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cripzt/btsvoYZEWms/Tj9IecCCpaMMWcekTfMdsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcripzt%2FbtsvoYZEWms%2FTj9IecCCpaMMWcekTfMdsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;528&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;897&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 빨간색 박스에 있는 &lt;b&gt;&quot;도메인&quot;&lt;/b&gt;을 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biGqgj/btsvvFrYtr3/A9JUhlL3t3fxTALpmyzo2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biGqgj/btsvvFrYtr3/A9JUhlL3t3fxTALpmyzo2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biGqgj/btsvvFrYtr3/A9JUhlL3t3fxTALpmyzo2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiGqgj%2FbtsvvFrYtr3%2FA9JUhlL3t3fxTALpmyzo2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;765&quot; height=&quot;234&quot; data-origin-width=&quot;1280&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;들어가면 내가 구매한 도메인을 확인할 수 있고, &lt;b&gt;&quot;관리&quot;&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;1168&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l6FEp/btsvNOnsII3/lL5yX5yljIa6oUnKL4k8bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l6FEp/btsvNOnsII3/lL5yX5yljIa6oUnKL4k8bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l6FEp/btsvNOnsII3/lL5yX5yljIa6oUnKL4k8bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl6FEp%2FbtsvNOnsII3%2FlL5yX5yljIa6oUnKL4k8bK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;368&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쭉 내리다 보면 오른쪽 아래 DNS 레코드 설정으로 갈 수 있는 버튼이 있습니다. &lt;b&gt;&quot;설정&quot;&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;1280&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G7pfH/btsvM0hp86x/UxLC3P2oo6rfXdyJYQWVO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G7pfH/btsvM0hp86x/UxLC3P2oo6rfXdyJYQWVO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G7pfH/btsvM0hp86x/UxLC3P2oo6rfXdyJYQWVO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG7pfH%2FbtsvM0hp86x%2FUxLC3P2oo6rfXdyJYQWVO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;869&quot; height=&quot;194&quot; data-origin-width=&quot;1280&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;내가 구매한 도메인을 확인하고, 체크하고 &lt;b&gt;&quot;DNS 설정&quot;&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;1280&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAkecG/btsvlzsptjQ/GOclDNkkkF2YkpdeB2SHM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAkecG/btsvlzsptjQ/GOclDNkkkF2YkpdeB2SHM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAkecG/btsvlzsptjQ/GOclDNkkkF2YkpdeB2SHM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAkecG%2FbtsvlzsptjQ%2FGOclDNkkkF2YkpdeB2SHM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;943&quot; height=&quot;273&quot; data-origin-width=&quot;1280&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;이제 &lt;b&gt;&quot;레코드 추가&quot;&lt;/b&gt;를 누르고 호스트에 @와 www를 추가해줘야 합니다. 값/위치에는 AWS EC2 또는 다른 클라우드 서비스를 통해 띄운 서버의 IP를 입력해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 수정 또는 확인 버튼을 눌러주고, &lt;b&gt;&quot;저장&quot;&lt;/b&gt;을 눌러주면 DNS 서버에 내가 구매한 도메인과 내 서버의 IP가 연결이 완료되었습니다.&lt;/p&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://poppleserver.store:8080 에 접속해 보면 http://{ubuntu ip}:8080에 접근했을 때와 같은 결과 페이지가 나오면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;DNS 서버의 쿼리가 퍼지기까지는 약간의 시간이 소요되고, 도메인 등록 업체에서는 12시간 정도를 얘기하고 있습니다. 하지만 실제로는 30분 정도 기다리면 사용하는데 문제가 없다고 합니다. 연결될 때까지 기다려 봅시다.&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;이렇게 연결이 되었으면 어떻게 확인해 볼까? 터미널을 켜고 nslookup 명령어로 확인해볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdABwb/btsvMES7MW5/M2gn5OXBAZDwERCEpNvEdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdABwb/btsvMES7MW5/M2gn5OXBAZDwERCEpNvEdk/img.png&quot; data-alt=&quot;nslookup 명령어 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdABwb/btsvMES7MW5/M2gn5OXBAZDwERCEpNvEdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdABwb%2FbtsvMES7MW5%2FM2gn5OXBAZDwERCEpNvEdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;192&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nslookup 명령어 사용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NXDOMAIN 에러가 뜨면 DNS 서버에 제대로 등록될 때까지 조금만 여유를 가지고 기다려보시길 바랍니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 해결이 안 된다면 방법을 찾아야 하겠지만, 제대로 된 IP에 등록하셨다면 될 겁니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(4) Certbot 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ubuntu 서버로 접속한 후에 Certbot을 설치해줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695653136642&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 install certbot&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Ubuntu로 서버를 만들어서 apt-get을 사용했지만, 만약 Amazon Linux를 사용해서 서버를 띄우셨다면 yum을 사용해서 조금 복잡하게 설치를 했던 거 같습니다. (구글링 해보시면 좋을 거 같습니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(5) 인증서 발급받기&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1695653224945&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo certbot certonly --standalone&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;standalone 옵션 말고 다른 옵션들도 있는데, 아래 링크에 첨부를 해뒀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 설정을 모두 끝마치고 서버를 다시 재시작할 것이기 때문에 standalone 옵션을 사용해서 발급했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어를 치면 이메일을 치라고 하고, 몇 가지 동의 사항을 받습니다. 본인의 이메일을 입력하고, yes를 입력해 주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러고 나서 마지막에 도메인을 입력하라고 합니다. 이때 저희가 구매한 도메인을 입력해 줍니다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(DNS 서버에 등록된 상태여야 합니다.)&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;입력하라고 하는 것을 모두 입력해 주면 LetsEncrypt에서 우리 도메인에 어떤 요청을 보내고, 제대로 연결된 도메인이 맞는지 확인하는 작업을 한 후 아래와 같은 문구가 뜹니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695653466575&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/subbak2.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/subbak2.com/privkey.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  만약 계속해서 certification failed이 뜬다면 몇 가지를 확인해 보자!&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;(1) 우리 서버가 이미 80번 포트를 사용하고 있지는 않은가?&lt;/b&gt;&lt;br /&gt;: LetsEncrypt는 80번 포트(http)로 GET 요청을 보내서 어떤 처리를 하고 나서 응답되는 결과가 기대한 결과여야 성공적으로 인증서를 발급해 준다. 하지만 80번 포트를 이미 누군가 쓰고 있으면 안 된다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;(2) 도메인이 DNS 서버에 잘 등록이 되었는가?&lt;/b&gt;&lt;br /&gt;: LetsEncrypt가 이 도메인이 우리 서버와 잘 연결되었는지 확인하기 때문에 필수 조건이다. 이건 터미널에서도 확인이 가능하다. nslookup {도메인} 명령어를 사용해 보자.&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;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;(6) 인증을 통해 받은 파일로 Springboot 프로젝트로 가져갈 keystore 파일 생성하기&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1695653700771&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd /etc/letsencrypt/live/subbak2.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 경로로 이동을 해봅시다. 위 경로는 인증 성공했을 때 나왔던 메시지의 경로입니다. 인증을 통해 만들어진 pem 파일이 있는 곳입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 위 경로로 이동하려고 했을 때, live 파일에 대한 접근 권한이 없었습니다. 그래서 접근 권한을 풀어준 후 접근했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695653790352&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo chmod 777 live&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위 경로로 이동했다면 발급받은 pem 파일을 통해서 keystore 파일을 만들어 내야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695653823989&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out keystore.p12 -name tomcat -CAfile chain.pem -caname root&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 길지만 그냥 Certbot을 통해서 발급받은 fullchain.pem과 privkey.pem을 이용해서 지지고 볶고 해서 Springboot에서 사용할 암호화된 &lt;b&gt;keystore.p12&lt;/b&gt; 파일을 만든다는 명령어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어를 입력하면 &lt;b&gt;비밀번호&lt;/b&gt;를 치라고 하는 게 나오는데, 이때 치는 비밀번호는 나중에 application.yml에 넣어줘야 하는 비밀번호입니다. 아무거나 입력해도 되는데 기억하고 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 keystore.p12를 Spring 프로젝트의 /src/main/resources로 가져가야 합니다. 이때는 리눅스의 mv 명령어로 가져가봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keystore.p12의 파일 권한이 없다면 권한을 허용하고, 이동을 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1695654041982&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo chmod 777 keystore.p12
$ mv keystore.p12 {/src/main/resoureces 경로}&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(7) application.yml 설정&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1691574761298&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#SSL
server:
  ssl:
    enabled: true
    key-store: classpath:keystore.p12
    key-store-type: PKCS12
    key-store-password: {keystore.p12 파일 만들 때 입력한 비밀번호}
    key-alias: tomcat

security:
  require-ssl: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;classpath:는 모두 알겠지만 /src/main/resources 경로를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 있는 security 설정은 Spring Security 때문에 해둔 설정인데, 추가하지 않아도... 되는 거 같은데 더 알아봐야 할 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(8) 포트 포워딩 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 https://poppleserver.store로 요청을 하면 Spring 내장 톰캣의 8080으로 연결되도록 하고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 443 포트(HTTPS)로 들어온 요청을 8080 포트(톰캣)로 포트 포워딩을 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 https://poppleserver.store로 요청을 보내면 8080 포트로 포트 포워딩이 되어 실행됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695654316926&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8080&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(9) https://{구매한 도메인}에 들어가서 API 요청 보내서 확인&lt;/b&gt;&lt;/h4&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;100&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AFMnW/btsvlxap1oU/5sLGL9zByfw5C5pfhpDLV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AFMnW/btsvlxap1oU/5sLGL9zByfw5C5pfhpDLV1/img.png&quot; data-alt=&quot;주소창 자물쇠 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AFMnW/btsvlxap1oU/5sLGL9zByfw5C5pfhpDLV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAFMnW%2Fbtsvlxap1oU%2F5sLGL9zByfw5C5pfhpDLV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;133&quot; height=&quot;53&quot; data-origin-width=&quot;100&quot; data-origin-height=&quot;40&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;LetsEncrypt는 무료이지만, 단점이라고 하면 90일마다 인증서를 재발급해줘야 하는 귀찮음이 있습니다. 이 부분도 찾아보니 자동화를 하는 방법이 있었는데, 필요해질 때 적용해 보면 될 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;045&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/045.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/045.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/SpringBoot</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/83</guid>
      <comments>https://hoonsb.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 7 Aug 2023 22:23:01 +0900</pubDate>
    </item>
    <item>
      <title>[Spring | AWS EC2] AWS EC2에 @Schedule 기능 적용 시 시간 이슈</title>
      <link>https://hoonsb.tistory.com/82</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h3&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;매일 00시 00분마다 작동되는 스케쥴러를 구현하기 위해서 다음과 같이 코드를 작성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1691142672648&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Scheduled(cron = &quot;0 0 0 * * *&quot;, zone = &quot;Asia/Seoul&quot;)
@Transactional
public void surveyScheduling() {
    log.info(&quot;[{}] 스케쥴러 작동 설문조사 데이터 정리&quot;, LocalDateTime.now());

    LocalDate now = LocalDate.now();
    List&amp;lt;Survey&amp;gt; surveys = surveyRepository.findAllByStatusIsNot(SurveyStatus.REVERT);

    for (Survey survey : surveys) {
        if (isEnd(survey.getEndDate().toLocalDateTime(), now)) {
            survey.setStatus(SurveyStatus.REVERT);
        }
        if (isStart(survey.getStartDate().toLocalDateTime(), now)) {
            survey.setStatus(SurveyStatus.IN_PROGRESS);
        }
    }
}

private boolean isEnd(LocalDateTime endDate, LocalDate now) {
    return endDate.toLocalDate().isBefore(now);
}

private boolean isStart(LocalDateTime startDate, LocalDate now) {
    return startDate.toLocalDate().isEqual(now);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 매일 00시 00분 정각에 작동되는 것을 테스트했습니다. (완전 무식하게 정각까지 안 자고 기다리고 있다가 확인했다.  )&lt;/p&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 서버에 변경된 코드를 적용해서 배포했습니다.&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;1928&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIcaVO/btsp7JZUdLd/jSKdKhKKET1PNZcedpAG4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIcaVO/btsp7JZUdLd/jSKdKhKKET1PNZcedpAG4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIcaVO/btsp7JZUdLd/jSKdKhKKET1PNZcedpAG4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIcaVO%2Fbtsp7JZUdLd%2FjSKdKhKKET1PNZcedpAG4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1928&quot; height=&quot;232&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음..? 저 빨간색 네모 안에 있는 status 값이 분명 WAIT에서 IN_PROGRESS로 변경되었어야 하는데... 왜 변하지 않았을까...?&lt;/p&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 서버에 접근해서 로그 기록을 확인해 봤습니다. 분명 스케쥴러가 작동했다면 INFO 레벨의 로그가 찍혔을 것이기 때문에...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2010&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB19g5/btsp35vOu1p/ojr86s9ob4FQeJgGek4RD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB19g5/btsp35vOu1p/ojr86s9ob4FQeJgGek4RD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB19g5/btsp35vOu1p/ojr86s9ob4FQeJgGek4RD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB19g5%2Fbtsp35vOu1p%2Fojr86s9ob4FQeJgGek4RD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2010&quot; height=&quot;386&quot; data-origin-width=&quot;2010&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 INFO 레벨의 지정해 둔 로그가 찍혔다. 그럼 정상적으로 스케쥴러가 작동했다는 것을 의미한다. 근데 &lt;span style=&quot;color: #ef5369;&quot;&gt;제가 의도한 시간이었던 2023년 8월 4일 00:00:00이 아닌 2023년 8월 3일 15:00:00에 동작&lt;/span&gt;을 하는 문제가 발견되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 문제 분석&lt;/b&gt;&lt;/h3&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;처음에 AWS EC2 인스턴스를 만들 때 Asia/Seoul로 선택해서 만들어서 당연히 동작될 줄 알았습니다. 그래서 일단 AWS 서버에 접근해서 date 명령어로 현재 서버의 시간을 한번 확인해 봤습니다.&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;1086&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC7Vbr/btsp8a32c9B/5p9jTX8YtyyPV6iPK1opV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC7Vbr/btsp8a32c9B/5p9jTX8YtyyPV6iPK1opV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC7Vbr/btsp8a32c9B/5p9jTX8YtyyPV6iPK1opV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC7Vbr%2Fbtsp8a32c9B%2F5p9jTX8YtyyPV6iPK1opV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1086&quot; height=&quot;170&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 제 컴퓨터에서는 시간이 2023년 8월 4일 19:05인데, 서버에서는 2023년 8월 4일 10:04로 찍혔습니다. AWS 서버와 현재 제 로컬의 시간이 다르다는 것을 확인했습니다.&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;UTC라는 세계 표준 시간을 사용해서 현재 한국의 시간에서 9시간을 빼면 AWS EC2의 서버 시간이 된다고 합니다. 허허허....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 생각을 하니깐 위에서 8월 3일 15:00에 작동한 게 이해가 됐습니다. 한국 시간으로는 8월 4일 00:00에서 9시간을 빼면 8월 3일 15:00가 나오기 때문이었습니다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 해결 과정&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 서버 시간에서 그럼 9시간을 더해서 재설정해두면 되지 않을까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;되게 단순한 발상인데, 나름 그럴싸한 해결방법 같아 보입니다. 바로 실행에 옮기기 전에 항상 먼저 구글링을 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(터미널에서 뭐 잘못 건드렸다가 노트북 싹 다 날려버렸던 경험이 있어서.... 항상 제대로 알고 나서 실행하는 편...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691144423266&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt; sudo date -s &quot;2023-08-04 19:20:00&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;위에 있는 명령어로 시간을 재설정하게 되면 EC2 타임존이 기본적으로 UTC로 되어 있어서 &lt;span style=&quot;color: #f89009;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) AWS 서버의 타임존을 한국으로 바꿔보자.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 방법처럼 날짜만 한국에 맞추는 것이 아니고, 근본적인 타임존을 한국으로 한번 설정해 봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1691144905445&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt; sudo cat /etc/localtime&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;664&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clSNMp/btsp626CLLH/W55KxzlDb2PkU0zXq20guK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clSNMp/btsp626CLLH/W55KxzlDb2PkU0zXq20guK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clSNMp/btsp626CLLH/W55KxzlDb2PkU0zXq20guK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclSNMp%2Fbtsp626CLLH%2FW55KxzlDb2PkU0zXq20guK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;172&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어로 한번 확인을 해보면 현재는 UTC0로 쓰여있습니다. 즉, 현재는 UTC 기준으로 작동된다는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 이제 한국의 타임존을 쓸 수 있도록 수정해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691145050250&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt; sudo rm /etc/localtime
&amp;gt;&amp;gt; sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 있는 UTC 타임존 파일을 제거하고, 서울 기준의 타임존 파일을 새롭게 적용하는 명령어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어로 재설정을 하고, 다시 date 명령어로 서버의 시간을 확인해 봅시다.&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;578&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QErqb/btsqtvs8cMZ/EmxUPn1BVa68JFLN3Bu0wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QErqb/btsqtvs8cMZ/EmxUPn1BVa68JFLN3Bu0wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QErqb/btsqtvs8cMZ/EmxUPn1BVa68JFLN3Bu0wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQErqb%2Fbtsqtvs8cMZ%2FEmxUPn1BVa68JFLN3Bu0wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;326&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 시간이 의도한 대로 한국 시간으로 설정이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 데이터베이스의 startDate를 2023년 8월 5일로 바꾸고, 하루를 기다려보겠습니다.&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;1946&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccxnnA/btsp7AozFCw/ouPjNBHaTAvuhLSo5yIzA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccxnnA/btsp7AozFCw/ouPjNBHaTAvuhLSo5yIzA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccxnnA/btsp7AozFCw/ouPjNBHaTAvuhLSo5yIzA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccxnnA%2Fbtsp7AozFCw%2FouPjNBHaTAvuhLSo5yIzA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1946&quot; height=&quot;220&quot; data-origin-width=&quot;1946&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 2023년 8월 5일 00:00이 되면 저 id 1번의 데이터에서 status 값이 WAIT에서 IN_PROGRESS가 되어야 합니다. 제발 돼라...&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;2028&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wcJM8/btsp7T3Jfdi/fvlRO6zkkYkPeYZaRJyH1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wcJM8/btsp7T3Jfdi/fvlRO6zkkYkPeYZaRJyH1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wcJM8/btsp7T3Jfdi/fvlRO6zkkYkPeYZaRJyH1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwcJM8%2Fbtsp7T3Jfdi%2FfvlRO6zkkYkPeYZaRJyH1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2028&quot; height=&quot;242&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 확인해보니 정상적으로 스케쥴러가 2023년 8월 5일 00:00에 동작을 한 거 같네요!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 AWS 서버에도 INFO 로그가 찍혔는지도 한번 확인해보겠습니다.&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;2006&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl7EgT/btsp8a5fUxm/shKZcR2Kkgmq0uMKZYyMY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl7EgT/btsp8a5fUxm/shKZcR2Kkgmq0uMKZYyMY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl7EgT/btsp8a5fUxm/shKZcR2Kkgmq0uMKZYyMY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl7EgT%2Fbtsp8a5fUxm%2FshKZcR2Kkgmq0uMKZYyMY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2006&quot; height=&quot;670&quot; data-origin-width=&quot;2006&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음...동작은 해서 update 쿼리가 실행되어 WAIT 상태를 IN_PROGRESS로 변경시키기는 했지만 여전히 로그에 찍힌 시간을 보면 UTC 기준으로 15:00:00이라고 찍히는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 프로젝트 기본 TimeZone 변경하기 + 로그에 찍히는 시간도 변경&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1691384121830&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
public class ServerApplication {

    public static void main(String[] args) {

        SpringApplication.run(ServerApplication.class, args);
    }

    @PostConstruct
    public void init() {
        TimeZone.setDefault(TimeZone.getTimeZone(&quot;Asia/Seoul&quot;));
        System.out.println(&quot;&amp;gt;&amp;gt;&amp;gt; 현재 시간은 &quot; + LocalDateTime.now());
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@PostConstruct를 사용해서 Spring 프로젝트의 기본 TimeZone 세팅을 &quot;Asia/Seoul&quot;로 지정하도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 제대로 동작하는지 확인하기 위해서 LocalDateTime.now()를 사용해서 콘솔에 흔적을 남기도록 하고 한번 동작해 봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nnZBN/btsp8coJaTF/3wHBNpvGjO4PZBaaemQmI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nnZBN/btsp8coJaTF/3wHBNpvGjO4PZBaaemQmI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nnZBN/btsp8coJaTF/3wHBNpvGjO4PZBaaemQmI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnnZBN%2Fbtsp8coJaTF%2F3wHBNpvGjO4PZBaaemQmI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;421&quot; height=&quot;117&quot; data-origin-width=&quot;532&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;빨간색을 보면 정상적으로 &quot;Asia/Seoul&quot;의 시간이 찍힌 것을 확인할 수 있었습니다. (현재 노트북 시간과 동일)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 파란색 부분을 보면 로그로 찍히는 시간은 여전히 UTC 기준으로 찍히는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분도 한번 바꿔봅시다! 간단하게 application.yml 파일을 설정해 주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1691385237899&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logging:
  pattern:
    dateformat: yyyy-MM-dd HH:mm:ss.SSSz,Asia/Seoul&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Log에 찍히는 시간의 dateformat을 설정해 줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Timezone은 당연히 Asia/Seoul로 해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 서버에 jar 파일을 올려서 실행시켜서 확인해 봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsyg3B/btsp9179DsQ/oG4CiUWvEk5Kvq8Ya14kXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsyg3B/btsp9179DsQ/oG4CiUWvEk5Kvq8Ya14kXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsyg3B/btsp9179DsQ/oG4CiUWvEk5Kvq8Ya14kXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsyg3B%2Fbtsp9179DsQ%2FoG4CiUWvEk5Kvq8Ya14kXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;438&quot; height=&quot;70&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 완벽하게 LocalDateTime.now()도 현재 시간으로 뜨고, 로그에 찍히는 시간도 현재 시간으로 찍히는 것을 확인할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아래와 같이 스케쥴러 코드를 살짝만 수정해서 10초가 될 때마다 작동되는 스케쥴러를 작동시켜 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1691385570701&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Scheduled(cron = &quot;10 * * * * *&quot;, zone = &quot;Asia/Seoul&quot;)
@Transactional
public void surveyScheduling() {
    log.info(&quot;[{}] 스케쥴러 작동 설문조사 데이터 정리&quot;, LocalDateTime.now());

    LocalDate now = LocalDate.now();
    List&amp;lt;Survey&amp;gt; surveys = surveyRepository.findAllByStatusIsNot(SurveyStatus.REVERT);

    for (Survey survey : surveys) {
        if (isEnd(survey.getEndDate().toLocalDateTime(), now)) {
            survey.setStatus(SurveyStatus.REVERT);
        }
        if (isStart(survey.getStartDate().toLocalDateTime(), now)) {
            survey.setStatus(SurveyStatus.IN_PROGRESS);
        }
    }
}&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;2108&quot; data-origin-height=&quot;1098&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vwfjv/btsqs8ZaeP8/8DXae9jLdLidQ5pdZhExVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vwfjv/btsqs8ZaeP8/8DXae9jLdLidQ5pdZhExVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vwfjv/btsqs8ZaeP8/8DXae9jLdLidQ5pdZhExVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVwfjv%2Fbtsqs8ZaeP8%2F8DXae9jLdLidQ5pdZhExVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2108&quot; height=&quot;1098&quot; data-origin-width=&quot;2108&quot; data-origin-height=&quot;1098&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과를 확인해 보면 로그에 찍히는 시간과 작동할 때 LocalDateTime.now()로 찍은 시간이 동일한 것을 확인했습니다.  &amp;zwj;♂️&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;</description>
      <category> &amp;zwj;  Dev/오류해결</category>
      <category>@Schedule</category>
      <category>AWS EC2</category>
      <category>AWS 서버 시간</category>
      <category>UTC</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/82</guid>
      <comments>https://hoonsb.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 4 Aug 2023 19:34:34 +0900</pubDate>
    </item>
    <item>
      <title>[네트워크] HTTPS와 SSL/TLS</title>
      <link>https://hoonsb.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS는 애플리케이션 계층과 전송 계층 사이에 신뢰 계층인&amp;nbsp; SSL/TLS 계층을 넣은 신뢰할 수 있는 HTTP 요청을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. SSL/TLS (Secure Socket Layer, Transport Layer Security Protocol)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSL은 1 버전부터 시작해서 2, 3 그리고 TLS 1.0, TLS 1.3까지 버전이 업그레이드되며 마지막으로 TLS로 명칭이 변경되었지만, 보통 SSL/TLS로 불립니다.&lt;/li&gt;
&lt;li&gt;SSL/TLS는 전송 계층에서 보안을 제공하는 프로토콜입니다. 클라이언트와 서버가 통신할 때 이 프로토콜을 통해서 제3자가 메시지에 접근할 수 없도록 해줍니다. (인터셉터 방지)&lt;/li&gt;
&lt;li&gt;SSL/TLS는 &lt;span style=&quot;color: #1a5490;&quot;&gt;보안 세션을 기반으로 데이터를 암호화&lt;/span&gt;하며 보안 세션이 만들어질 때 인증 메커니즘, 키 교환 암호화 알고리즘, 해싱 알고리즘이 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;보안 세션&lt;/b&gt;&lt;br /&gt;보안이 시작되고 끝나는 동안 유지되는 세션을 의미합니다. SSL/TLS는 Handshake를 통해서 보안 세션을 생성하고, 이를 기반으로 상태 정보 등을 공유합니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. HTTPS를 사용하는 이유&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 프로토콜은 암호화되지 않은 평문의 데이터를 전송하는 프로토콜이기 때문에 내가 서버로 보낸 정보(id, password)를 중간에 누군가 훔쳐볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;HTTPS를 사용하게 되면 &lt;span style=&quot;color: #1a5490;&quot;&gt;클라이언트와 서버만 알고 있는 형태로 암호화되어 데이터를 전송&lt;/span&gt;하기 때문에 중간에 훔쳐보더라도 어떤 정보인지 알아내기 힘듭니다.&lt;/li&gt;
&lt;li&gt;그리고 &lt;span style=&quot;color: #1a5490;&quot;&gt;접속한 사이트가 신뢰할 수 있는 곳인지를 사용자에게 알려줍니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;즉 HTTP에서 보안적인 부분까지 포함된 것이 HTTPS이기 때문에 대부분의 서비스에서는 현재 HTTPS 프로토콜을 사용합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 대칭키와 비대칭키&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTPS의 보안 방식을 알아보기 위해서는 대칭키와 비대칭키에 대해서 알고 있어야 합니다.&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) 대칭키&lt;/b&gt;&lt;/h4&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;span style=&quot;color: #1a5490;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 비대칭키 (공개키)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;개인키와 공개키를 한 쌍으로 사용&lt;/span&gt;하는 암호화/복호화 방법입니다.&lt;/li&gt;
&lt;li&gt;서버와 클라이언트가 있으면 서버에서는 개인키(나만 알고 있는 키)를 가지고 있고, 클라이언트들은 공개키(모두가 볼 수 있는 키)를 가질 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;내 개인키로 암호화한 데이터는 같은 쌍의 공개키로만 복호화를 할 수 있고, 반대로 공개키로 암호화한 데이터는 같은 쌍의 개인키로만 복호화를 할 수 있습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;이 방법을 사용하면 대칭키의 한계를 해결할 수 있습니다. 클라이언트가 데이터(id, password)를 공개키로 암호화해서 보내더라도 훔쳐보는 사람이 같은 쌍의 개인키를 가지고 있지 않으면 절대 내용을 볼 수 없습니다.&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;1039&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkyKSQ/btsk0fCeXkC/eKEAWugW2z9BBGxQwxpGW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkyKSQ/btsk0fCeXkC/eKEAWugW2z9BBGxQwxpGW0/img.png&quot; data-alt=&quot;신뢰할 수 있는 사이트인지 알 수 있는 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkyKSQ/btsk0fCeXkC/eKEAWugW2z9BBGxQwxpGW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkyKSQ%2Fbtsk0fCeXkC%2FeKEAWugW2z9BBGxQwxpGW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1039&quot; height=&quot;554&quot; data-origin-width=&quot;1039&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 클라이언트와 서버의 HTTPS를 통한 데이터 전달&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(3.1) 클라이언트는 처음에 서버를 신뢰하지 못하기 때문에 둘 사이에 Handshake를 거칩니다.&lt;br /&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;클라이언트가 랜덤 데이터를 생성해서 서버로 보냅니다.&lt;/li&gt;
&lt;li&gt;서버는 클라이언트에게 전달받은 데이터에 추가로 서버가 랜덤으로 생성한 어떤 데이터와 해당 &lt;span style=&quot;color: #1a5490;&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;894&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsrOKu/btskZYHvdog/CDhBmZgX5evVrjEhXrCKIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsrOKu/btskZYHvdog/CDhBmZgX5evVrjEhXrCKIk/img.png&quot; data-alt=&quot;1번 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsrOKu/btskZYHvdog/CDhBmZgX5evVrjEhXrCKIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsrOKu%2FbtskZYHvdog%2FCDhBmZgX5evVrjEhXrCKIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;323&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1번 과정&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;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;(3.2) 클라이언트는 서버에서 받은 데이터(인증서)를 &lt;span style=&quot;color: #1a5490;&quot;&gt;비대칭키 방식을 사용&lt;/span&gt;해서 복호화합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 전달받은 데이터(인증서)를 브라우저에 내장되어 있는 CA를 통해서 정보를 확인하게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;CA의 인증을 받은 인증서는 CA의 개인키로 암호화&lt;/span&gt;가 되어 있습니다. 즉, 클라이언트는 서버 쪽에서 보내준 인증서가 브라우저에 내장된 CA가 인증한 신뢰할 수 있는 인증서인지를 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;클라이언트는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;브라우저에 저장된 CA의 공개키&lt;/span&gt;를 가지고 해당 인증서를 복호화해 볼 수 있습니다. 복호화가 된다면 신뢰할 수 있는 서버가 보낸 데이터이고, CA 리스트에 있는 모든 공개키로 인증서가 복호화되지 않는다면 신뢰할 수 없는 서버에서 보낸 데이터입니다.&lt;/li&gt;
&lt;li&gt;신뢰할 수 있는 서버에서 보낸 인증서를 CA의 공개키로 복호화해 보면 그 안에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;해당 서버의 공개키가 포함&lt;/span&gt;되어 있습니다.&lt;b&gt;&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-origin-width=&quot;855&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pXYi2/btskVMhkZ0S/ZWfj1Ykfr6R2rods3ArUZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pXYi2/btskVMhkZ0S/ZWfj1Ykfr6R2rods3ArUZk/img.png&quot; data-alt=&quot;2번 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pXYi2/btskVMhkZ0S/ZWfj1Ykfr6R2rods3ArUZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpXYi2%2FbtskVMhkZ0S%2FZWfj1Ykfr6R2rods3ArUZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;331&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2번 과정&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;size18&quot;&gt;&lt;b&gt;(3.3) 클라이언트와 서버는 &lt;span style=&quot;color: #1a5490;&quot;&gt;대칭키 방식&lt;/span&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;주고받는 다량의 데이터들을 계속 비대칭키 방식으로 암호화, 복호화하는 것은 컴퓨터에 큰 무리가 될 수 있는 작업입니다.&lt;/li&gt;
&lt;li&gt;그래서 1번 과정인 Handshake 과정에서 주고받은 &lt;span style=&quot;color: #1a5490;&quot;&gt;클라이언트가 생성한 랜덤한 데이터와 서버가 생성한 랜덤한 데이터&lt;/span&gt;를 가지고 클라이언트는 임시키를 만들어냅니다.&lt;/li&gt;
&lt;li&gt;이렇게 만들어낸 임시키를 2번 과정에서 얻어낸 해당 서버의 공개키를 사용해서 암호화하여 서버로 보낸 후 양쪽(클라이언트, 서버)에서 일련의 과정을 거쳐서 &lt;span style=&quot;color: #1a5490;&quot;&gt;둘만 가지고 있는 동일한 대칭키를 만들어냅니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;이때부터는 이제 이 대칭키를 사용해서 데이터를 암호화하고 복호화해서 전송해도 제3자가 이 내용을 알아볼 수 있는 방법은 없습니다.&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;822&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWMMi0/btskZZsTWIa/pLLPw11KlgRIzHEFIFkz1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWMMi0/btskZZsTWIa/pLLPw11KlgRIzHEFIFkz1K/img.png&quot; data-alt=&quot;클라이언트 -&amp;amp;gt; 서버로 임시키 전달&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWMMi0/btskZZsTWIa/pLLPw11KlgRIzHEFIFkz1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWMMi0%2FbtskZZsTWIa%2FpLLPw11KlgRIzHEFIFkz1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;316&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;클라이언트 -&amp;gt; 서버로 임시키 전달&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v4QDf/btskYKwLAMg/VgLXGzQtcJ2ccw9HdYfnJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v4QDf/btskYKwLAMg/VgLXGzQtcJ2ccw9HdYfnJ0/img.png&quot; data-alt=&quot;일련의 작업을 거쳐 동일 대칭키를 클라이언트와 서버가 갖게됨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v4QDf/btskYKwLAMg/VgLXGzQtcJ2ccw9HdYfnJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv4QDf%2FbtskYKwLAMg%2FVgLXGzQtcJ2ccw9HdYfnJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;305&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;일련의 작업을 거쳐 동일 대칭키를 클라이언트와 서버가 갖게됨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;CA (Certificate Authorites)&lt;/b&gt;&lt;br /&gt;공인된 민간 인증 기관들을 말합니다. CA는 신뢰성이 엄격하게 공인된 기업들만 참여할 수 있습니다. 클라이언트가 사용하는 크롬, 사파리, 엣지 등 브라우저에는 이 CA들의 목록이 내장되어 있습니다. 대표적인 CA는 Comodo, GoDaddy, GlobalSign, 아마존이 있습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. HTTP/3&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP 위에서 돌아가는 HTTP/2와는 다르게 &lt;span style=&quot;color: #1a5490;&quot;&gt;HTTP/3는 UDP 기반의 QUIC(Quick UDP Internet Connection) 프로토콜을 사용&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;HTTP/2에서 HTTP/1.1의 문제점을 개선하기 위해 만들었던 &lt;span style=&quot;color: #1a5490;&quot;&gt;멀티플렉싱&lt;/span&gt;을 HTTP/3에서 모두 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;QUIC는 TCP를 사용하지 않기 때문에 통신이 시작될 때 진행하는 3-way-handshake 과정을 거치지 않아도 되기 때문에&amp;nbsp;&lt;span style=&quot;color: #1a5490;&quot;&gt;초기 연결 설정 시에 지연 시간이 감소&lt;/span&gt;되는 장점을 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;QUIC는 첫 연결 설정에서 클라이언트가 서버에 어떤 신호를 한 번 주고, 서버도 거기에 응답하기만 하면 바로 통신을 시작할 수 있습니다.&lt;/li&gt;
&lt;li&gt;추가로 QUIC는 &lt;span style=&quot;color: #1a5490;&quot;&gt;순방향 오류 수정(Forward Error Correction) 메커니즘을&lt;/span&gt; 가지고 있기 때문에 전송한 패킷이 손실되면 수신 측에서 에러를 검출하여 수정하는 방식을 사용해서 패킷 손실률을 많이 낮추고 있습니다. (UDP의 단점 보완)&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;670&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QFMkV/btskT2ZgBP5/uEpnccypDi9ukl6kRX6i1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QFMkV/btskT2ZgBP5/uEpnccypDi9ukl6kRX6i1k/img.png&quot; data-alt=&quot;HTTP/2와 HTTP/3의 계층 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QFMkV/btskT2ZgBP5/uEpnccypDi9ukl6kRX6i1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQFMkV%2FbtskT2ZgBP5%2FuEpnccypDi9ukl6kRX6i1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;588&quot; height=&quot;300&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HTTP/2와 HTTP/3의 계층 비교&lt;/figcaption&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;QUIC에는 &lt;span style=&quot;color: #1a5490;&quot;&gt;TLS 1.3 기반의 내장 암호화&lt;/span&gt; 프로토콜이 포함되어 있어서 보안 통신을 제공하고, 제3자가 인터넷 트래픽을 가로채고 조작하는 것을 어렵게 만들어줍니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  나올 수 있는 면접 질문&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;암호화방식 중 대칭키 방식과 비대칭키 방식에 대해서 설명해 보세요.&lt;/li&gt;
&lt;li&gt;HTTP 통신 방식의 문제점에 대해서 설명해 보세요.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>  Computer Science/네트워크</category>
      <category>HTTP/3</category>
      <category>https</category>
      <category>QUIC</category>
      <category>대칭키</category>
      <category>비대칭키</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/80</guid>
      <comments>https://hoonsb.tistory.com/80#entry80comment</comments>
      <pubDate>Thu, 22 Jun 2023 17:35:16 +0900</pubDate>
    </item>
    <item>
      <title>[패스트캠퍼스] 패스트캠퍼스 백엔드 개발 14주차 회고</title>
      <link>https://hoonsb.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 9주 차 회고를 작성했었는데, 벌써 5주가 지나서 14주 차가 되었습니다. 허허 가볍게 회고록을 작성해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;zwj;♂️ Database/Spring 실시간 강의 시작&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 Java 수업이 끝나고 이제 Database와 Spring 실시간 강의를 시작했습니다. 혼자서 Spring 공부해 볼 때 최주호 강사님이 운영하는 메타코딩이라는 유튜브 채널에서 공부를 한 적이 있었는데, 이번 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Database, JDBC, MyBatis 강의를 지나서 지금은 Hibernate와 JPA에 대해서 강의를 진행중인데, 다음 주에는 두 번째 토이 프로젝트가 예정이 되어 있습니다. 간단한 프로젝트이긴 한데 이번에는 2인 1조로 프로젝트가 진행돼서 약간 기대반 설렘반입니다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  백준 골드 달성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 회고 글에서 다음 회고 글에서는 골드가 되어있었으면 좋겠다고 했었는데, 골드를 달성했습니다. 알고리즘 문제는 빠짐없이 매일 1문제씩 백준에서 풀고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RVekp/btskJiUOAfD/BxwjsouCVHEKNWjWUCoxK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RVekp/btskJiUOAfD/BxwjsouCVHEKNWjWUCoxK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RVekp/btskJiUOAfD/BxwjsouCVHEKNWjWUCoxK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRVekp%2FbtskJiUOAfD%2FBxwjsouCVHEKNWjWUCoxK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;716&quot; height=&quot;196&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;골드 구간으로 넘어오고 나서 요즘은 삼성 기출 코딩테스트 구현 문제를 하나씩 건들고 있는데, 확실히 난이도가 높아지고 있는 것을 느끼고 있습니다. 다음 회고 글에서는 플래티넘이 되었으면 좋겠다고 쓰고 싶지만 경험치도 엄청 천천히 오르고 문제는 어려워져서 힘들 거 같습니다. 하지만 목표로 잡은 플래티넘이 되는 날까지 꾸준히 풀어갈 계획입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;zwj;  사이드 프로젝트 계획&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 정규 커리큘럼은 아니지만 팀원 분들과 의견이 맞아서 계획 중에 있습니다. 처음에는 Spring MVC를 공부하면서 Springboot와 Thymeleaf로 백엔드 팀원들끼리만 프로젝트를 진행해보려고 했었는데, 계획을 하다보니 프론트 팀원분들을 모집하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성도 높은 프로젝트를 해보면 좋겠지만, 지금은 앞으로 있을 미니 프로젝트와 파이널 프로젝트를 대비하기 위한 간단한 연습정도로 생각하고 참여해볼 생각입니다. 그렇다고 느슨하게 할 생각은 없습니다. 프로젝트는 프론트 팀원분들이 확정되면 기획을 마무리하고 바로 개발에 들어가 볼 계획을 가지고 있습니다. 다음 회고글에서는 사이드 프로젝트 결과에 대해서 다루고 있을 수도 있겠네요.&lt;/p&gt;</description>
      <category>  Activity/패스트캠퍼스</category>
      <category>백엔드 개발</category>
      <category>패스트캠퍼스</category>
      <category>패스트캠퍼스 백엔드 5기</category>
      <category>패스트캠퍼스부트캠프</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/79</guid>
      <comments>https://hoonsb.tistory.com/79#entry79comment</comments>
      <pubDate>Tue, 20 Jun 2023 23:55:03 +0900</pubDate>
    </item>
    <item>
      <title>Github에 잘못 올라간 .idea 폴더 지우기</title>
      <link>https://hoonsb.tistory.com/78</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rqhxm/btskKEQj6sU/kkP8zGkp7LcpX2cx8k2w60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rqhxm/btskKEQj6sU/kkP8zGkp7LcpX2cx8k2w60/img.png&quot; data-alt=&quot;잘못 올라간 .idea 폴더가 존재&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rqhxm/btskKEQj6sU/kkP8zGkp7LcpX2cx8k2w60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRqhxm%2FbtskKEQj6sU%2FkkP8zGkp7LcpX2cx8k2w60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1662&quot; height=&quot;338&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;잘못 올라간 .idea 폴더가 존재&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 .gitignore 설정을 잘못해 줘서 쓸모없는 .idea&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(1) 터미널에 명령어 작성&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1687271320719&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git rm --cached -r .idea&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;원격 저장소(깃허브)에 저장되어 있는 .idea를 삭제할 수 있는 명령어입니다.&lt;/li&gt;
&lt;li&gt;로컬 저장소에 있는 .idea은 삭제되지 않습니다.&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 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(2) 삭제 상태 확인해보기&lt;/b&gt;&lt;/h3&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-origin-width=&quot;841&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uvFxm/btskJigcCwy/To2K9wC8Svf7I1QkJV0adk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uvFxm/btskJigcCwy/To2K9wC8Svf7I1QkJV0adk/img.png&quot; data-alt=&quot;git rm --cached -r .idea 명령어 후에 git status 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uvFxm/btskJigcCwy/To2K9wC8Svf7I1QkJV0adk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuvFxm%2FbtskJigcCwy%2FTo2K9wC8Svf7I1QkJV0adk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;841&quot; height=&quot;307&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;git rm --cached -r .idea 명령어 후에 git status 결과&lt;/figcaption&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;원격 저장소에서 삭제를 원하는 .idea을 삭제했습니다.&lt;/li&gt;
&lt;li&gt;이제 이 변경 사항을 Commit으로 작성해서 Push 해주면 됩니다.&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 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(3) Commit 작성 후 Push&lt;/b&gt;&lt;/h3&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-origin-width=&quot;910&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2JYPS/btskIYvwz8L/LyHVC2vDQ5AkpXd0ex4Ur1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2JYPS/btskIYvwz8L/LyHVC2vDQ5AkpXd0ex4Ur1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2JYPS/btskIYvwz8L/LyHVC2vDQ5AkpXd0ex4Ur1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2JYPS%2FbtskIYvwz8L%2FLyHVC2vDQ5AkpXd0ex4Ur1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;910&quot; height=&quot;236&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;236&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;git commit 명령어를 통해서 커밋 메시지를 작성한 후 origin 저장소에 push 했습니다.&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 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(4) 원격 저장소 확인&lt;/b&gt;&lt;/h3&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-origin-width=&quot;1668&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9k7YN/btskMlvXWwX/yrxwICxmpzYlHttpM1T0OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9k7YN/btskMlvXWwX/yrxwICxmpzYlHttpM1T0OK/img.png&quot; data-alt=&quot;.idea 폴더가 원격 저장소에서 사라진 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9k7YN/btskMlvXWwX/yrxwICxmpzYlHttpM1T0OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9k7YN%2FbtskMlvXWwX%2FyrxwICxmpzYlHttpM1T0OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1668&quot; height=&quot;274&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;.idea 폴더가 원격 저장소에서 사라진 모습&lt;/figcaption&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;.idea 폴더가 정상적으로 삭제된 것을 확인해 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category> &amp;zwj;  Dev/여러가지 팁</category>
      <category>.idea 삭제</category>
      <category>github 파일 삭제</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/78</guid>
      <comments>https://hoonsb.tistory.com/78#entry78comment</comments>
      <pubDate>Tue, 20 Jun 2023 23:35:00 +0900</pubDate>
    </item>
    <item>
      <title>@DataJpaTest Rollback 후 Id 초기화 안됨</title>
      <link>https://hoonsb.tistory.com/77</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&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;Hibernate를 사용해서 간단하게 Customer 객체를 디비에 저장하는 save()와 모든 유저를 조회하는 findAll() 그리고, 단일 조회할 수 있는 findById()를 만들어서 테스트 중에 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1687183130262&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Import(CustomerRepository.class)
@DataJpaTest
class CustomerRepositoryTest {

    @Autowired
    private CustomerRepository customerRepository;

    @Test
    public void findById_test() {
        // Given
        Customer customer = Customer.builder()
                .name(&quot;코스&quot;)
                .tel(&quot;0101111&quot;)
                .build();
        customerRepository.save(customer);
        Long id = 1L;

        // When
        Customer customerPS = customerRepository.findById(id);

        // Then
        assertThat(customerPS.getName()).isEqualTo(&quot;코스&quot;);
    }

    @Test
    public void save_test() {
        // Given
        Customer customer1 = Customer.builder()
                .name(&quot;코스&quot;)
                .tel(&quot;0101111&quot;)
                .build();
        Customer customer2 = Customer.builder()
                .name(&quot;홍길동&quot;)
                .tel(&quot;0102222&quot;)
                .build();

        // When
        customerRepository.save(customer1);
        customerRepository.save(customer2);

        // Then
        Customer customerPS = customerRepository.findById(1L);
        System.out.println(customerPS);
        assertThat(customerPS.getName()).isEqualTo(&quot;코스&quot;);
    }

    @Test
    public void findAll() {
        // Given
        Customer customer1 = Customer.builder()
                .name(&quot;코스&quot;)
                .tel(&quot;0101111&quot;)
                .build();
        Customer customer2 = Customer.builder()
                .name(&quot;임꺽정&quot;)
                .tel(&quot;0101111&quot;)
                .build();
        customerRepository.save(customer1);
        customerRepository.save(customer2);

        // When
        List&amp;lt;Customer&amp;gt; result = customerRepository.findAll();

        // Then
        assertThat(result.size()).isEqualTo(2);
        assertThat(result.get(1).getName()).isEqualTo(&quot;임꺽정&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;내가 처음에 생각한 로직은 다음과 같다.&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;테스트 메서드가 하나씩 실행되고 끝날 때 Transaction이 종료되고, Rollback을 해준다고 알고 있다. 이건 @DataJpaTest에 @Transactional이 붙어있기 때문이다.&lt;/li&gt;
&lt;li&gt;그럼 각 메소드가 종료될 때 Rollback이 되니깐 Id(PK)도 당연히 다시 1부터 시작된다고 생각해서 모든 테스트를 같이 돌려도 완벽히 격리된 테스트를 할 수 있을 거라고 생각했다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 분석&lt;/b&gt;&lt;/h2&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;궁금해서 @AfterEach와 @BeforeEach를 사용해서 각 테스트의 시작 전과 끝난 후에 findAll() 결과를 출력해 봤다.&lt;/p&gt;
&lt;pre id=&quot;code_1687183314660&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@BeforeEach
public void beforeCheck() {
    System.out.println(&quot;===== Before Check =====&quot;);
    check();
    System.out.println(&quot;=======================&quot;);
}

@AfterEach
public void afterCheck() {
    System.out.println(&quot;===== After Check =====&quot;);
    check();
    System.out.println(&quot;=======================&quot;);
}

public void check() {
    List&amp;lt;Customer&amp;gt; result = customerRepository.findAll();
    if (result.isEmpty()) {
        System.out.println(&quot;비어있습니다.&quot;);
    } else {
        result.forEach(customer -&amp;gt; {
            System.out.println(&quot;&amp;gt;&amp;gt;&amp;gt; &quot; + customer.getId() + &quot; &quot; + customer.getName() + &quot; &quot; + customer.getTel());
        });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1687183467606&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# findById 테스트

===== Before check =====
비어있습니다.
=======================

===== After check =====
&amp;gt;&amp;gt;&amp;gt; 1 코스 0101111
=======================


# findAll 테스트

===== Before check =====
비어있습니다.
=======================

===== After check =====
&amp;gt;&amp;gt;&amp;gt; 2 코스 0101111
&amp;gt;&amp;gt;&amp;gt; 3 임꺽정 0101111
=======================


# save 테스트

===== Before check =====
비어있습니다.
=======================

===== After Check =====
&amp;gt;&amp;gt;&amp;gt; 4 코스 0101111
&amp;gt;&amp;gt;&amp;gt; 5 홍길동 0102222
=======================&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과를 보면 다음 테스트가 시작되기 전에 이전 데이터는 Rollback이 되는 게 맞다. 근데 보면 Id 값은 1로 초기화되지 않는 것을 확인해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 현재 h2 데이터베이스를 사용하고 있어서 그런 건가 하고 MySQL로 연동해서 동일하게 실행해 보니 동일한 결과가 나왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 조금 궁금해져서 MySQL을 실행해서 직접 테이블을 하나 만들고, 트랜잭션을 만들어서 Insert 후 동일하게 Rollback을 해봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687186577200&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 테이블 생성
create table member (
	id bigint not null auto_increment,
    name varchar(20),
    primary key(id)
}

# 트랜잭션 시작
start transaction;

insert into member(name) values('오징어');
insert into member(name) values('김징어');&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;560&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnsI9F/btskAYoyPp7/UArAOp3osxVVc3DJOL1BxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnsI9F/btskAYoyPp7/UArAOp3osxVVc3DJOL1BxK/img.png&quot; data-alt=&quot;Rollback 전 Select&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnsI9F/btskAYoyPp7/UArAOp3osxVVc3DJOL1BxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnsI9F%2FbtskAYoyPp7%2FUArAOp3osxVVc3DJOL1BxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;375&quot; height=&quot;138&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Rollback 전 Select&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1687186657759&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 롤백 후 커밋
rollback;
commit;&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;572&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FKj1v/btskAZ10jyZ/LUICW7LXW5Y7PfjuAzChqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FKj1v/btskAZ10jyZ/LUICW7LXW5Y7PfjuAzChqK/img.png&quot; data-alt=&quot;Rollback 후 Select&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FKj1v/btskAZ10jyZ/LUICW7LXW5Y7PfjuAzChqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFKj1v%2FbtskAZ10jyZ%2FLUICW7LXW5Y7PfjuAzChqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;402&quot; height=&quot;66&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Rollback 후 Select&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1687186714125&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 다시 데이터 삽입
insert into member(name) values('이징어');&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;660&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btktZ2/btskAZgEdgw/4mMXoD5eRR5gWAGvqRFliK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btktZ2/btskAZgEdgw/4mMXoD5eRR5gWAGvqRFliK/img.png&quot; data-alt=&quot;데이터 추가 삽입 후 Select&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btktZ2/btskAZgEdgw/4mMXoD5eRR5gWAGvqRFliK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtktZ2%2FbtskAZgEdgw%2F4mMXoD5eRR5gWAGvqRFliK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;416&quot; height=&quot;116&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터 추가 삽입 후 Select&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 상황과 동일하게 상황을 구성해서 직접 쿼리를 작성해봤는데, 롤백 후에도 Id가 1부터 시작되는 것이 아닌 3부터 시작돼서 저장되는 것을 확인해 볼 수 있었습니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 해결 방법&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;@AfterEach에서 각 테스트가 끝나면 Id의 시작점을 RESTART 해준다.&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1687183644286&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AfterEach
public void check() {
    em.createNativeQuery(&quot;ALTER TABLE customer ALTER COLUMN id RESTART&quot;)
            .executeUpdate();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 솔직히 좋은 방법은 아니라고 생각합니다. NativeQuery를 이렇게 사용해서 한다는 것은 특정 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;span style=&quot;color: #1a5490;&quot;&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-origin-width=&quot;494&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsXBsd/btskCqLkiJG/MsH1Bpekj04Oj4k4ZJdiyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsXBsd/btskCqLkiJG/MsH1Bpekj04Oj4k4ZJdiyK/img.png&quot; data-alt=&quot;전체 테스트 실행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsXBsd/btskCqLkiJG/MsH1Bpekj04Oj4k4ZJdiyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsXBsd%2FbtskCqLkiJG%2FMsH1Bpekj04Oj4k4ZJdiyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;193&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;193&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 느낀 점&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 결과는 얻었지만, 좋지 않은 테스트라는 것은 분명하기 때문에 더 좋은 방법을 찾아봐야 할 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 할 때는 Id 값을 저렇게 Magic Number를 사용해서 하는 방법도 좋지 않은 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에 관해서는 더 찾아보고, 학습해야될 요소라고 생각됩니다. 단순히 궁금점이 조금 생겨서 기초부터 파고 들어봤던 거 같습니다.&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/오류해결</category>
      <category>DataJpaTest</category>
      <category>Hibernate</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/77</guid>
      <comments>https://hoonsb.tistory.com/77#entry77comment</comments>
      <pubDate>Mon, 19 Jun 2023 23:12:57 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] 프로세스와 스레드</title>
      <link>https://hoonsb.tistory.com/76</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 프로세스 (Process)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제로 부터 자원을 할당받은 &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 프로그램과 프로세스&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;프로그램은 윈도우에서는 exe파일, 맥북에서는 dmg 파일을 의미합니다.&lt;/li&gt;
&lt;li&gt;프로그램은 프로그래밍 언어(C, Java 등)로 코드를 작성하면 개발할 수 있습니다.&lt;/li&gt;
&lt;li&gt;즉, &lt;span style=&quot;color: #1a5490;&quot;&gt;프로그램은 말그래도 코드 덩어리&lt;/span&gt;를 말합니다.&lt;/li&gt;
&lt;li&gt;이런 코드 덩어리(프로그램)를 실행시키면 프로세스가 된다.&lt;/li&gt;
&lt;li&gt;정리해 보면 &lt;span style=&quot;color: #1a5490;&quot;&gt;프로세스는 프로그램이 작동되고 있는 상태&lt;/span&gt;를 말합니다.&lt;/li&gt;
&lt;li&gt;프로그램이 실행되기 위해서는 운영체제(OS)가 메모리를 할당해줘야 합니다.&lt;/li&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 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 프로세스의 상태&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 13.1977%; height: 18px; text-align: center;&quot;&gt;상태&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%; height: 18px; text-align: center;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 13.1977%; height: 18px; text-align: center;&quot;&gt;생성 (new)&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%; height: 18px;&quot;&gt;프로세스가 생성되고 아직 준비되어 있지 않은 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 13.1977%; height: 18px; text-align: center;&quot;&gt;준비 (ready)&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%; height: 18px;&quot;&gt;프로세스가 실행을 위해 기다리고 있는 상태 &lt;br /&gt;(CPU를 할당받을 수 있는 상태)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 13.1977%; height: 18px; text-align: center;&quot;&gt;실행 (running)&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%; height: 18px;&quot;&gt;프로세스가 CPU를 할당받아 실행되고 있는 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 13.1977%; height: 18px; text-align: center;&quot;&gt;대기 (waiting)&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%; height: 18px;&quot;&gt;프로세스가 특정 이벤트(IO)를 받아 기다리는 상태 &lt;br /&gt;(이벤트가 발생하여 다시 ready 상태가 될 때까지 대기)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.1977%; text-align: center; height: 20px;&quot;&gt;종료 (terminated)&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%; height: 20px;&quot;&gt;프로세스가 실행을 완료하고 종료된 상태 &lt;br /&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;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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 스레드 (Thread)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스가 할당받은 자원을 이용하는 &lt;span style=&quot;color: #1a5490;&quot;&gt;흐름의 단위&lt;/span&gt;를 말합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 프로세스와 스레드&lt;/b&gt;&lt;/h4&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;a href=&quot;https://hoonsb.tistory.com/60&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 참고해보시기 바랍니다.&lt;/li&gt;
&lt;li&gt;하나의 프로세스에서 &lt;span style=&quot;color: #1a5490;&quot;&gt;스레드가 생성될 때는 프로세스의 메모리 구조에서 스택 영역만 할당받아서 복사&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;나머지 &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 스레드의 상태&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 90px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 24.8837%; height: 18px; text-align: center;&quot;&gt;상태&lt;/td&gt;
&lt;td style=&quot;width: 75.1163%; height: 18px; text-align: center;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 24.8837%; height: 18px; text-align: center;&quot;&gt;NEW&lt;/td&gt;
&lt;td style=&quot;width: 75.1163%; height: 18px;&quot;&gt;스레드가 생성되고 아직 호출되지 않은 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 24.8837%; height: 18px; text-align: center;&quot;&gt;RUNNABLE&lt;/td&gt;
&lt;td style=&quot;width: 75.1163%; height: 18px;&quot;&gt;스레드가 실행되기 위해 기다리는 상태 &lt;br /&gt;(CPU를 할당받을 수 있는 상태)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 24.8837%; height: 18px; text-align: center;&quot;&gt;BLOCKED&lt;/td&gt;
&lt;td style=&quot;width: 75.1163%; height: 18px;&quot;&gt;스레드가 특정 이벤트(IO)를 받아 기다리는 상태&lt;br /&gt;(이벤트가 발생하여 다시 RUNNABLE 상태가 될 때까지 대기)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 24.8837%; height: 18px; text-align: center;&quot;&gt;TERMINATED&lt;/td&gt;
&lt;td style=&quot;width: 75.1163%; height: 18px;&quot;&gt;스레드가 실행을 완료하고 종료된 상태&lt;br /&gt;(메모리에서 제거된 상태)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;</description>
      <category>  Computer Science/운영체제</category>
      <category>스레드</category>
      <category>프로그램</category>
      <category>프로세스</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/76</guid>
      <comments>https://hoonsb.tistory.com/76#entry76comment</comments>
      <pubDate>Fri, 16 Jun 2023 00:16:34 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 1918번 후위 표기식 (Java)</title>
      <link>https://hoonsb.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1918&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1918 후위 표기식&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;2334&quot; data-origin-height=&quot;1426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BM9kY/btsjdBOTVMb/q3nqFB0rdSRx4ZpRBAw4qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BM9kY/btsjdBOTVMb/q3nqFB0rdSRx4ZpRBAw4qK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BM9kY/btsjdBOTVMb/q3nqFB0rdSRx4ZpRBAw4qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBM9kY%2FbtsjdBOTVMb%2Fq3nqFB0rdSRx4ZpRBAw4qK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2334&quot; height=&quot;1426&quot; data-origin-width=&quot;2334&quot; data-origin-height=&quot;1426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;주의해야 할 점은 A + B + C의 경우에는 앞에 있는 +가 우선순위가 더 높기 때문에 ABC++가 아닌 &lt;span style=&quot;color: #1a5490;&quot;&gt;(A + B) + C로 묶어서 AB+C+&lt;/span&gt;가 나와야 합니다.&lt;/li&gt;
&lt;li&gt;그리고 추가로 &lt;span style=&quot;color: #1a5490;&quot;&gt;괄호로 묶여서 제공되는 연산자의 경우에는 가장 우선순위가 높기 때문에&lt;/span&gt; 가장 먼저 처리해줘야 합니다.&lt;/li&gt;
&lt;li&gt;알파벳에 해당하는 (A ~ Z)는 바로 문자열에 추가하고, 연산자(+, -, *, /)는 Stack에 저장해야 합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 핵심 아이디어&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stack에는 연산자 들이 저장됩니다. 즉 먼저 들어온 연산자들은 아래쪽에 쌓이게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;첫 번째 아이디어&lt;/b&gt;는 &quot;(&quot;가 스택에 들어왔을 때입니다.&lt;br /&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&quot;(&quot;는 일단 스택에 넣어줍니다. 그다음 연산자를 받아주고, 닫는 괄호 &quot;)&quot;가 들어왔을 때, 괄호 내부에 있는 연산자를 뽑아주는 작업을 해야 합니다.&lt;/li&gt;
&lt;li&gt;그 이유는 &lt;span style=&quot;color: #1a5490;&quot;&gt;괄호 안에 있는 연산자가 어떤 연산자인지 상관없이 우선순위가 높기 때문&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;이때는 while에 조건을 넣어서 &quot;(&quot;가 나올 때까지 pop을 해서 뽑아내서 정답 문자열 뒤에 붙여줍니다. 이때 &quot;(&quot;는 정답에 포함되면 안 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;두 번째 아이디어&lt;/b&gt;는 이전에 스택에 들어온 연산자 중에서 나보다 우선순위가 높은 연산자가 있는지 확인해서 추출해야 합니다.&lt;br /&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;A + B * C 라면 우선순위가 높은 순서로 ABC*+ 이렇게 붙게 됩니다.&lt;/li&gt;
&lt;li&gt;그래서 &lt;span style=&quot;color: #1a5490;&quot;&gt;이미 스택에 들어온 연산자들 중에서 내가 현재 가지고 있는 연산자 보다 높은 우선순위가 존재하면 안 됩니다&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;세 번째 아이디어&lt;/b&gt;는 수식의 모든 순회를 끝냈을 때, Stack에 남아있는 연산자들을 순서대로 추출해서 정답 문자열에 더해줘야 합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 코드 작성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1686286189742&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String notation = br.readLine();
        Stack&amp;lt;Character&amp;gt; operator = new Stack&amp;lt;&amp;gt;();
        StringBuilder builder = new StringBuilder();

        for (int i = 0; i &amp;lt; notation.length(); i++) {
            char value = notation.charAt(i);
            if (value &amp;gt;= 'A' &amp;amp;&amp;amp; value &amp;lt;= 'Z') {
                builder.append(value);
            } else if (value == '(') {
                operator.add(value);
            } else if (value == ')') {
                // 닫는 괄호가 나오면 여는 괄호가 나올 때까지 빼서 연산자만 추출한다. (괄호안에 연산자가 우선순위가 있기 떄문에)
                while (!operator.isEmpty()) {
                    char popVal = operator.pop();
                    if (popVal == '(') {
                        break;
                    }
                    builder.append(popVal);
                }
            } else {
                // 괄호가 아닌 경우에는 이전 연산자가 현재 연산자보다 우선순위가 높은지 확인 후 추출
                while (!operator.isEmpty() &amp;amp;&amp;amp; checkPriority(operator.peek(), value)) {
                    builder.append(operator.pop());
                }
                operator.add(value);
            }
        }
        // 남아있을 수 있는 부분이 있어서 나머지 모두 추출해서 뒤에 붙여줌
        while (!operator.isEmpty()) {
            builder.append(operator.pop());
        }
        System.out.println(builder);
    }

    public static boolean checkPriority(char peekVal, char val) {
        int peekValWeight = setWeight(peekVal);
        int valWeight = setWeight(val);
        // 같은 순위의 연산자라면 먼저 나온 것이 우선순위가 크기 때문에 등호가 필수
        return peekValWeight &amp;gt;= valWeight;
    }

    public static int setWeight(char val) {
        if (val == '(') return 0;
        return val == '+' || val == '-' ? 1 : 2;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category> &amp;zwj;  Dev/알고리즘</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/75</guid>
      <comments>https://hoonsb.tistory.com/75#entry75comment</comments>
      <pubDate>Fri, 9 Jun 2023 13:51:14 +0900</pubDate>
    </item>
    <item>
      <title>Mustache 사용해서 html 파일 읽을 때 한글 깨짐</title>
      <link>https://hoonsb.tistory.com/74</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 코드 구성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1686227845820&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        MustacheViewResolver resolver = new MustacheViewResolver();
        resolver.setPrefix(&quot;classpath:/templates/&quot;);
        resolver.setSuffix(&quot;.html&quot;);
        registry.viewResolver(resolver);
    }
}&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;Mustache 템플릿 엔진을 사용해서 html 파일을 읽도록 설정을 해줬습니다.&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_1686227889582&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;로그인 페이지입니다.&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&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;login.html 페이지는 이렇게 구성했습니다.&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_1686228004372&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/login&quot;)
public String login() {
    return &quot;login&quot;;
}&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;Controller 메서드는 다음과 같이 구성했습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 문제 발생&lt;/b&gt;&lt;/h2&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-origin-width=&quot;1344&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfliA3/btsjcaJZWuB/15cK9igURIBk646P8e9RHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfliA3/btsjcaJZWuB/15cK9igURIBk646P8e9RHK/img.png&quot; data-alt=&quot;localhost:8080/login 요청 시 랜더링 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfliA3/btsjcaJZWuB/15cK9igURIBk646P8e9RHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfliA3%2FbtsjcaJZWuB%2F15cK9igURIBk646P8e9RHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1344&quot; height=&quot;604&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;localhost:8080/login 요청 시 랜더링 결과&lt;/figcaption&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;한글이 ?로 깨져서 나오는 것을 확인했습니다.&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;1290&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu2C6N/btsjctP6wOp/KZkmxuBOD44eycRelwKOAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu2C6N/btsjctP6wOp/KZkmxuBOD44eycRelwKOAK/img.png&quot; data-alt=&quot;개발자 도구 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu2C6N/btsjctP6wOp/KZkmxuBOD44eycRelwKOAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu2C6N%2FbtsjctP6wOp%2FKZkmxuBOD44eycRelwKOAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1290&quot; height=&quot;610&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발자 도구 확인&lt;/figcaption&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;개발자 도구에 들어가서 확인해보니 UTF-8로 되어있는 것이 아니라 ISO-8859-1로 되어있었습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아마도 MustacheViewResolver에서 html 파일을 읽게하는 부분에서 charset, Content-Type을 따로 설정해주지 않은 것이 문제로 보였습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 문제 해결&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1686228233556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        MustacheViewResolver resolver = new MustacheViewResolver();
        resolver.setPrefix(&quot;classpath:/templates/&quot;);
        resolver.setSuffix(&quot;.html&quot;);
        resolver.setCharset(&quot;UTF-8&quot;);
        resolver.setContentType(&quot;text/html;charset=utf-8&quot;);
        registry.viewResolver(resolver);
    }
}&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;charset과 ContentType에 대한 설정 코드를 추가해줬습니다.&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;2376&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eEJGF7/btsjcrxZuQz/oK16H8DQoKYy7LjK4ZNl21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eEJGF7/btsjcrxZuQz/oK16H8DQoKYy7LjK4ZNl21/img.png&quot; data-alt=&quot;한글 설정 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eEJGF7/btsjcrxZuQz/oK16H8DQoKYy7LjK4ZNl21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeEJGF7%2FbtsjcrxZuQz%2FoK16H8DQoKYy7LjK4ZNl21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2376&quot; height=&quot;1032&quot; data-origin-width=&quot;2376&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;한글 설정 완료&lt;/figcaption&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;개발자 도구에서도 Content-Type이 변경된 것을 확인해볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category> &amp;zwj;  Dev/오류해결</category>
      <category>mustache 한글 깨짐</category>
      <category>mustacheViewResolver</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/74</guid>
      <comments>https://hoonsb.tistory.com/74#entry74comment</comments>
      <pubDate>Thu, 8 Jun 2023 21:46:19 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 2206번 벽 부수고 이동하기 (Java)</title>
      <link>https://hoonsb.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2206&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2206번 벽 부수고 이동하기&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;1165&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba3fhK/btsi9WSs5oY/gjFaxKizELIk58XwfKnBA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba3fhK/btsi9WSs5oY/gjFaxKizELIk58XwfKnBA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba3fhK/btsi9WSs5oY/gjFaxKizELIk58XwfKnBA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba3fhK%2Fbtsi9WSs5oY%2FgjFaxKizELIk58XwfKnBA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1165&quot; height=&quot;608&quot; data-origin-width=&quot;1165&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0은 이동할 수 있는 칸, 1은 벽이 있어서 이동할 수 없는 칸입니다.&lt;/li&gt;
&lt;li&gt;(1, 1)에서 시작해서 (N, M)까지 가는데 &lt;span style=&quot;color: #1a5490;&quot;&gt;최단 경로&lt;/span&gt;를 구해야 합니다.&lt;/li&gt;
&lt;li&gt;이동은 상, 하, 좌, 우로 가능하고, 이때 &lt;span style=&quot;color: #1a5490;&quot;&gt;벽(1)을 최소 한번 부수고 지나갈 수 있습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;문제에서 주어진 사항을 보고, BFS로 접근해야겠다는 생각을 했습니다.&lt;/li&gt;
&lt;li&gt;방향이 존재하기 때문에 재귀적으로 구현을 했고, 벽을 1번 이상 부순 경우는 더 이상 나아가지 못하도록 막는 방법을 생각했습니다.&lt;/li&gt;
&lt;li&gt;계속 틀렸다는 말이 나와서 2차원 배열 visited를 이용하여 문제를 변경해서 풀어봤는데, 이때는 시간 초과가 났습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 핵심 아이디어&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정한 지점 (x, y)에 도달하는 데 있어서 &lt;span style=&quot;color: #1b711d;&quot;&gt;벽을 1번 부수고 가는 경우&lt;/span&gt;와 &lt;span style=&quot;color: #f89009;&quot;&gt;벽을 아예 부수지 않고 가는 경우&lt;/span&gt; 중에서 &lt;span style=&quot;color: #1b711d;&quot;&gt;벽을 1번 부수고 가는 경우가 더 빠를 수 있습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;특정 지점 (x, y)까지의 결과는 위와 같지만, 최종 (N, M)까지 도달하는 데 있어서는 &lt;span style=&quot;color: #f89009;&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;542&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lQTJX/btsjaswUnvQ/Ks03lew5sfHpwBQGqocqLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lQTJX/btsjaswUnvQ/Ks03lew5sfHpwBQGqocqLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lQTJX/btsjaswUnvQ/Ks03lew5sfHpwBQGqocqLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlQTJX%2FbtsjaswUnvQ%2FKs03lew5sfHpwBQGqocqLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;516&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;546&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 그림 처럼 파란 지점까지 벽을 한 번 부수고 가면 3번이면 가는데, 벽을 부수지 않고 가면 13번이 걸립니다.&lt;/li&gt;
&lt;li&gt;하지만 벽을 한번 부수고 3번으로 지나가면 (N, M)에 도달하지 못하고 정답이 될 수 없습니다.&lt;/li&gt;
&lt;li&gt;파란 지점에 &lt;span style=&quot;color: #1a5490;&quot;&gt;벽을 한번 부순것이 먼저 도달해서 visited[1][3]을 true로 만들었고&lt;/span&gt;, 벽을 부수지 않고 지나가는 경우에 같은 vistied를 사용하게 된다면 해당 경우는 도달할 수 있는 경우가 -1이 됩니다.&lt;/li&gt;
&lt;li&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;벽을 부수고 지나가는 경우에 대한 visited와 벽을 부수지 않고 지나가는 경우에 대한 visited를 나눠서 처리&lt;/span&gt;해줘야 합니다.&lt;/li&gt;
&lt;li&gt;이 문제에서 2차원 배열을 2개 만드는 것 보다 &lt;span style=&quot;color: #1a5490;&quot;&gt;visited[][][]를 3차원 배열&lt;/span&gt;로 만들어서 visited[row][col][0]은 벽을 부수고 지나가는 경우에 대한 visited로 사용하고, visited[row][col][1]은 벽을 부수지 않고 지나가는 경우에 대한 visited로 사용하면 쉽게 처리할 수 있습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 소스코드&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1686200726708&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    static int N, M;
    static int[][] map;
    static boolean[][][] visited;
    static int[][] move = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        map = new int[N + 1][M + 1];
        visited = new boolean[N + 1][M + 1][2]; // (벽을 부수고 가는 경우, 벽을 부수지 않고 가는 경우)
        for (int row = 1; row &amp;lt;= N; row++) {
            char[] input = br.readLine().toCharArray();
            for (int col = 1; col &amp;lt;= M; col++) {
                map[row][col] = input[col - 1] - '0';
            }
        }
        int answer = bfs();
        System.out.println(answer);
    }

    public static int bfs() {
        Queue&amp;lt;Node&amp;gt; nodes = new LinkedList&amp;lt;&amp;gt;();
        nodes.add(new Node(1, 1, 1, false));
        visited[1][1][1] = true;

        while (!nodes.isEmpty()) {
            Node curr = nodes.poll();

            if (curr.row == N &amp;amp;&amp;amp; curr.col == M) {
                return curr.dist;
            }

            int nextDist = curr.dist + 1;
            for (int[] m : move) {
                int nextRow = curr.row + m[0];
                int nextCol = curr.col + m[1];
                if (!isValid(nextRow, nextCol)) {
                    continue;
                }

                if (map[nextRow][nextCol] == 1) { // 벽인 경우
                    if (!curr.useBreak &amp;amp;&amp;amp; !visited[nextRow][nextCol][0]) { // 아직 부순적이 없는 경우
                        nodes.add(new Node(nextRow, nextCol, nextDist, true));
                        visited[nextRow][nextCol][0] = true;
                    }
                } else if (map[nextRow][nextCol] == 0) { // 벽이 아닌 경우
                    if (curr.useBreak &amp;amp;&amp;amp; !visited[nextRow][nextCol][0]) { // 벽을 부수고 가는 경우
                        nodes.add(new Node(nextRow, nextCol, nextDist, true));
                        visited[nextRow][nextCol][0] = true;
                    } else if (!curr.useBreak &amp;amp;&amp;amp; !visited[nextRow][nextCol][1]) { // 벽을 부수지 않고 가는 경우
                        nodes.add(new Node(nextRow, nextCol, nextDist, false));
                        visited[nextRow][nextCol][1] = true;
                    }
                }
            }
        }
        return -1;
    }

    public static boolean isValid(int row, int col) {
        return row &amp;gt;= 1 &amp;amp;&amp;amp; col &amp;gt;= 1 &amp;amp;&amp;amp; row &amp;lt;= N &amp;amp;&amp;amp; col &amp;lt;= M;
    }

    static class Node {
        int row, col, dist;
        boolean useBreak;

        public Node (int row, int col, int dist, boolean useBreak) {
            this.row = row;
            this.col = col;
            this.dist = dist;
            this.useBreak = useBreak;
        }
    }
}&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 id=&quot;code_1686200786103&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (map[nextRow][nextCol] == 1) {
    if (!curr.useBreak &amp;amp;&amp;amp; !visited[nextRow][nextCol][0]) {
        nodes.add(new Node(nextRow, nextCol, nextDist, true));
        visited[nextRow][nextCol][0] = true;
    }
} else if (map[nextRow][nextCol] == 0) {
    if (curr.useBreak &amp;amp;&amp;amp; !visited[nextRow][nextCol][0]) {
        nodes.add(new Node(nextRow, nextCol, nextDist, true));
        visited[nextRow][nextCol][0] = true;
    } else if (!curr.useBreak &amp;amp;&amp;amp; !visited[nextRow][nextCol][1]) {
        nodes.add(new Node(nextRow, nextCol, nextDist, false));
        visited[nextRow][nextCol][1] = true;
    }
}&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;만약 다음에 내가 가야할 곳이 벽(1)이라면&lt;br /&gt;&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;내가 Break를 사용했는지 판단합니다. (curr.useBreak가 false인지 확인)&lt;/li&gt;
&lt;li&gt;벽을 부수고 지나가는 경우(visited[][][0])에 이미 방문된 칸인지 확인합니다.&lt;/li&gt;
&lt;li&gt;Break을 이미 사용한 경우에 벽(1)을 만나면 Queue에는 아무런 노드도 넣지 않습니다. (이미 끝)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;만약 다음에 내가 가야할 곳이 벽이 아니라면(0)&lt;br /&gt;&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;내가 Break를 사용했는지 판단합니다.&lt;/li&gt;
&lt;li&gt;만약 이미 Break를 사용했다면 visited[][][0]의 방문처리 상태를 확인합니다.&lt;/li&gt;
&lt;li&gt;아직 Break를 사용하지 않았다면 visited[][][1]의 방문처리 상태를 확인합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 문제에 접근해볼 수 있습니다. 사실 핵심 아이디어만 떠올렸다면 절대 어렵지 않은 문제였던 거 같습니다. 벽을 부술 수 있는 횟수가 정해져 있기 때문에 이런 식으로 접근해 볼 수 있을 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visited를 3차원 배열로...메..모...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;052&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/052.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/052.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;</description>
      <category> &amp;zwj;  Dev/알고리즘</category>
      <category>백준 2206 자바</category>
      <category>벽 부수고 이동하기 자바</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/73</guid>
      <comments>https://hoonsb.tistory.com/73#entry73comment</comments>
      <pubDate>Thu, 8 Jun 2023 14:13:34 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 1967번 트리의 지름 (Java)</title>
      <link>https://hoonsb.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1967&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1967번 트리의 지름&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;1152&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nSNoI/btsi0Z2tuBR/x9ze4IyctpDZDRebkBvndK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nSNoI/btsi0Z2tuBR/x9ze4IyctpDZDRebkBvndK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nSNoI/btsi0Z2tuBR/x9ze4IyctpDZDRebkBvndK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnSNoI%2Fbtsi0Z2tuBR%2Fx9ze4IyctpDZDRebkBvndK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1152&quot; height=&quot;549&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;549&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;1156&quot; data-origin-height=&quot;703&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNHaYS/btsiQjnwSwu/EEqYMU1j6BHaYrG24Hm0d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNHaYS/btsiQjnwSwu/EEqYMU1j6BHaYrG24Hm0d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNHaYS/btsiQjnwSwu/EEqYMU1j6BHaYrG24Hm0d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNHaYS%2FbtsiQjnwSwu%2FEEqYMU1j6BHaYrG24Hm0d1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1156&quot; height=&quot;703&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;703&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제 접근&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;1번 노드부터 탐색해서 가장 가중치가 큰 노드 하나를 선택합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;가장 가중치가 큰 노드는 반드시 트리의 지름의 한 점이 된다고 생각&lt;/span&gt;했습니다.&lt;/li&gt;
&lt;li&gt;가장 가중치가 큰 노드를 하나 선택했으면, 해당 노드부터 BFS로 다시 탐색을 시작해서 가장 길이가 긴 값(트리의 지름)을 반환합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 접근 과정에서 실수했던 부분&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제에서 &quot;간선에 대한 정보는 부모 노드의 번호가 작은 것이 먼저 입력되고, 부모 노드의 번호가 같으면 자식 노드의 번호가 작은 것이 먼저 입력된다.&quot;라는 부분이 있었습니다.&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;b&gt;(1) 입력을 받으면서 가장 가중치가 큰 노드를 선택&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1686109622044&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Node[] nodes = new Node[n + 1]; // 가장 가중치가 큰 노드를 찾기 위한 배열
nodes[0] = new Node(0, 0); // NullPointerException 방지를 위해
nodes[1] = new Node(1, 0); // 1번은 루트이기 때문에 가중치가 0

for (int i = 1; i &amp;lt; n; i++) {
    StringTokenizer st = new StringTokenizer(br.readLine());
    int first = Integer.parseInt(st.nextToken());
    int second = Integer.parseInt(st.nextToken());
    int weight = Integer.parseInt(st.nextToken());
    map[first].add(new Node(second, weight)); // 트리의 양방향 설정
    map[second].add(new Node(first, weight)); // 트리의 양방향 설정
    int beforeWeight = nodes[first] == null ? 0 : nodes[first].weight;
    nodes[second] = new Node(second, beforeWeight + weight);
}

Arrays.sort(nodes, (n1, n2) -&amp;gt; n2.weight - n1.weight);
// 도달 거리가 가장 큰 놈을 하나 선택
int start = nodes[0].end;&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;O(N)에 모든 것을 다 챙겨가려는 오만한 생각에 코드를 이렇게 짰습니다.&lt;/li&gt;
&lt;li&gt;이렇게 해서 문제 제출을 해보니 67%쯤에서 계속 틀렸다고 나왔습니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 도움이 됐던 반례&lt;/b&gt;&lt;/h4&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;pre id=&quot;code_1686109785157&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;12
1 3 3
1 12 2
2 5 1
2 11 7
3 2 50
4 8 15
4 9 4
6 7 6
6 10 10
12 4 11
12 6 9

정답 : 88&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;/li&gt;
&lt;li&gt;&quot;간선에 대한 정보는 부모 노드의 번호가 작은 것이 먼저 입력되고, 부모 노드의 번호가 같으면 자식 노드의 번호가 작은 것이 먼저 입력된다.&quot;라는 조건은 확실하게 만족하는 입력 값들이었습니다.&lt;/li&gt;
&lt;li&gt;제 알고리즘대로라면 {2 5 1}이 입력되었을 때, 5라는 노드의 가중치는 2라는 노드의 가중치에 1을 더한 값이 세팅되어야 합니다. 하지만 위 입력에서는 아직 노드 2에 대한 제대로 된 가중치가 저장되기 전이기 때문에 옳지 않은 정답을 출력한 것을 알 수 있습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 문제 해결&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;입력은 그대로 인접 리스트를 사용하여 양방향 정보를 저장하고, BFS 탐색을 2번 했습니다.&lt;/li&gt;
&lt;li&gt;첫 번째 탐색에서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;가중치의 최대값을 갱신하면서 그 최대 가중치를 갖는 노드(start)&lt;/span&gt;를 찾아냈습니다.&lt;/li&gt;
&lt;li&gt;두 번째 탐색에서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;최대 가중치를 갖는 노드(start)부터 다시 탐색을 시작해서 가장 가중치가 긴 값(트리의 지름)을 갱신&lt;/span&gt;해서 반환했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1686110146484&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {

    static int n, start;
    static List&amp;lt;Node&amp;gt;[] map;
    static boolean[] visited;

    public static void main(String[] args) throws IOException {
        System.setIn(new FileInputStream(&quot;src/main.txt&quot;));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        map = new ArrayList[n + 1];
        for (int i = 0; i &amp;lt;= n; i++) {
            map[i] = new ArrayList&amp;lt;&amp;gt;();
        }

        for (int i = 1; i &amp;lt; n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int first = Integer.parseInt(st.nextToken());
            int second = Integer.parseInt(st.nextToken());
            int weight = Integer.parseInt(st.nextToken());
            map[first].add(new Node(second, weight));
            map[second].add(new Node(first, weight));
        }
        // 가장 큰 노드를 탐색
        visited = new boolean[n + 1];
        bfs(1);

        // 해당 노드부터 탐색을 시작해서 최장 거리를 찾음
        visited = new boolean[n + 1];
        int result = bfs(start);
        System.out.println(result);
    }

    public static int bfs(int startNodeNumber) {
        int result = 0;
        Queue&amp;lt;Node&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();
        queue.add(new Node(startNodeNumber, 0));
        visited[startNodeNumber] = true;

        while (!queue.isEmpty()) {
            Node curr = queue.poll();
            if (result &amp;lt; curr.weight) {
                result = curr.weight;
                start = curr.end;
            }

            for (Node node : map[curr.end]) {
                if (visited[node.end]) continue;
                queue.add(new Node(node.end, curr.weight + node.weight));
                visited[node.end] = true;
            }
        }

        return result;
    }

    static class Node {
        int end;
        int weight;

        public Node(int end, int weight) {
            this.end = end;
            this.weight = weight;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category> &amp;zwj;  Dev/알고리즘</category>
      <category>트리의 지름</category>
      <category>트리의 지름 반례</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/72</guid>
      <comments>https://hoonsb.tistory.com/72#entry72comment</comments>
      <pubDate>Wed, 7 Jun 2023 12:58:26 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 1238번 파티 (Java)</title>
      <link>https://hoonsb.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1238&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1238번 파티 문제 풀러 가기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685932244637&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;1238번: 파티&quot; data-og-description=&quot;첫째 줄에 N(1 &amp;le;&amp;nbsp;N &amp;le; 1,000), M(1 &amp;le; M &amp;le; 10,000), X가 공백으로 구분되어 입력된다. 두 번째 줄부터 M+1번째 줄까지 i번째 도로의 시작점, 끝점, 그리고 이 도로를 지나는데 필요한 소요시간 Ti가 들어&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/1238&quot; data-og-url=&quot;https://www.acmicpc.net/problem/1238&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/js8qn/hySTXuq5IG/7qFGt4FU5izVxlPjibFC2k/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1238&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/1238&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/js8qn/hySTXuq5IG/7qFGt4FU5izVxlPjibFC2k/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&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;1238번: 파티&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 N(1 &amp;le;&amp;nbsp;N &amp;le; 1,000), M(1 &amp;le; M &amp;le; 10,000), X가 공백으로 구분되어 입력된다. 두 번째 줄부터 M+1번째 줄까지 i번째 도로의 시작점, 끝점, 그리고 이 도로를 지나는데 필요한 소요시간 Ti가 들어&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&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-origin-width=&quot;2330&quot; data-origin-height=&quot;1124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wcv7X/btsiJ8ZNGVb/mkr9wDBFHM4AZMlN3jNY20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wcv7X/btsiJ8ZNGVb/mkr9wDBFHM4AZMlN3jNY20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wcv7X/btsiJ8ZNGVb/mkr9wDBFHM4AZMlN3jNY20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwcv7X%2FbtsiJ8ZNGVb%2Fmkr9wDBFHM4AZMlN3jNY20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2330&quot; height=&quot;1124&quot; data-origin-width=&quot;2330&quot; data-origin-height=&quot;1124&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;b&gt;1. 문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;N명의 학생들은 X번 마을까지 갔다가 다시 돌아와야 한다.&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;&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;b&gt;2. 생각해 보기&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;플로이드 와샬 알고리즘은 O(N^3)의 시간 복잡도가 필요하다. 마을의 개수(N)가 최대 10^3이기 때문에 플로이드 와샬을 사용하면 최대 10^9의 시간이 걸릴 수 있어 시간 초과가 날 가능성이 있다.&lt;/li&gt;
&lt;li&gt;그럼 고려해 볼 수 있는 알고리즘은 &lt;span style=&quot;color: #1a5490;&quot;&gt;다익스트라 알고리즘&lt;/span&gt;이다.&lt;/li&gt;
&lt;li&gt;다음은 각 마을에서 X로 가는 최단 거리와, X에서 다시 각 마을로 돌아가는 최단 거리를 구해야 한다.&lt;/li&gt;
&lt;li&gt;그대로 구현한다면 1, 3, 4 마을에서 각각 2번 마을까지 갈 수 있는 거리를 구해야 한다. 이를 반대로 생각해 보면 다음과 같이 접근해 볼 수 있다.&lt;br /&gt;&amp;nbsp;
&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: #1a5490;&quot;&gt;map&lt;/span&gt;과 (끝 마을) (시작 마을) (거리)로 저장하는 경우&lt;span style=&quot;color: #1a5490;&quot;&gt;reverseMap&lt;/span&gt;을 모두 저장한다.&lt;/li&gt;
&lt;li&gt;X 마을에서 각 마을(1, 3, 4)로 돌아가는 경우를 구할 때는 map을 사용하면 X 마을을 시작점으로 각 마을로 돌아가는 거리를 구할 수 있다.&lt;/li&gt;
&lt;li&gt;각 마을(1, 3, 4)에서 X 마을로 가는 경우를 구할 때는 reverseMap을 사용하면 X 마을을 시작점으로 각 마을로 가는 거리를 구할 수 있다.&lt;/li&gt;
&lt;li&gt;이렇게 reverseMap을 사용하면 X 마을이 도착점이 아닌 시작 마을로 해서 간단하게 다익스트라 알고리즘을 2번 돌려주면 원하는 값을 얻을 수 있다.&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;&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;b&gt;3. 소스코드&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1685932993379&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    static int N, M, X;
    static List&amp;lt;Node&amp;gt;[] map, reverseMap; // 인접 리스트로 저장

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        X = Integer.parseInt(st.nextToken());
        map = new ArrayList[N + 1];
        reverseMap = new ArrayList[N + 1];

        for (int i = 1; i &amp;lt;= N; i++) {
            map[i] = new ArrayList&amp;lt;&amp;gt;();
            reverseMap[i] = new ArrayList&amp;lt;&amp;gt;();
        }

        for (int i = 0; i &amp;lt; M; i++) {
            st = new StringTokenizer(br.readLine());
            int start = Integer.parseInt(st.nextToken());
            int end = Integer.parseInt(st.nextToken());
            int dist = Integer.parseInt(st.nextToken());
            map[start].add(new Node(end, dist));
            reverseMap[end].add(new Node(start, dist));
        }
        int[] go = dijkstra(reverseMap); // 각 노드에서 X로 가는 최단 경로
        int[] back = dijkstra(map); // X에서 각 노드로 가는 최단 경로

        int answer = 0;
        for (int i = 1; i &amp;lt;= N; i++) {
            answer = Math.max(answer, go[i] + back[i]);
        }
        System.out.println(answer);
    }

    public static int[] dijkstra(List&amp;lt;Node&amp;gt;[] inputMap) {
        boolean[] visited = new boolean[N + 1];
        int[] result = new int[N + 1];
        Arrays.fill(result, Integer.MAX_VALUE);
        result[X] = 0; // X에서 X로 가는 경로는 0으로 초기화

        // 다익스트라 알고리즘 시작
        PriorityQueue&amp;lt;Node&amp;gt; queue = new PriorityQueue&amp;lt;&amp;gt;((n1, n2) -&amp;gt; n1.dist - n2.dist);
        queue.add(new Node(X, 0));

        while (!queue.isEmpty()) {
            Node curr = queue.poll();
            if (visited[curr.end]) {
                continue;
            }
            visited[curr.end] = true;
            for (Node node : inputMap[curr.end]) {
                if (curr.dist + node.dist &amp;lt; result[node.end]) {
                    result[node.end] = curr.dist + node.dist;
                }
                queue.add(new Node(node.end, result[node.end]));
            }
        }
        return result;
    }

    static class Node {
        int end, dist;

        public Node (int end, int dist) {
            this.end = end;
            this.dist = dist;
        }
    }
}&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;저는 다익스트라 알고리즘에 사용될 map과 reverseMap을 인접리스트 형식으로 저장했습니다.&lt;/li&gt;
&lt;li&gt;PriorityQueue를 사용해서 Node의 dist(거리)를 기준으로 오름차순 정렬되도록 선언했습니다.&lt;/li&gt;
&lt;li&gt;거리 기준 오름차순으로 저장하면 해당 노드로 도달할 수 있는 최단 거리를 얻을 수 있고, 같은 노드에 대한 정보가 나중에 들어오면 visited를 통해서 contitnue로 걸러줬습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 알게 된 점&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래프에서 어떤 특정 노드까지 이동을 하고, 다시 되돌아와야 하는 경우가 존재한다면, 해당 특정 노드를 시작점으로 해결해 볼 수 있다는 것을 알게 되었습니다. (입력을 거꾸로 저장, reverseMap)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category> &amp;zwj;  Dev/알고리즘</category>
      <category>다익스트라 알고리즘</category>
      <category>백준 1238 파티 자바</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/71</guid>
      <comments>https://hoonsb.tistory.com/71#entry71comment</comments>
      <pubDate>Mon, 5 Jun 2023 11:51:26 +0900</pubDate>
    </item>
    <item>
      <title>[네트워크] PDU (Protocol Data Unit)</title>
      <link>https://hoonsb.tistory.com/70</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. PDU (Protocol Data Unit)이란?&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크의 &lt;span style=&quot;color: #1a5490;&quot;&gt;계층과 계층 사이에 데이터가 전달될 때 한 덩어리의 단위&lt;/span&gt;를 PDU라고 합니다.&lt;/li&gt;
&lt;li&gt;PDU는 헤더와 페이로드로 구성되어 있고, 계층마다 부르는 명칭이 다릅니다.&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;607&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0QsGi/btsityZY50u/jMbYvRmO378ueUClKDVi1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0QsGi/btsityZY50u/jMbYvRmO378ueUClKDVi1K/img.png&quot; data-alt=&quot;계충 별로 불리는 PDU 명칭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0QsGi/btsityZY50u/jMbYvRmO378ueUClKDVi1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0QsGi%2FbtsityZY50u%2FjMbYvRmO378ueUClKDVi1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;607&quot; height=&quot;225&quot; data-origin-width=&quot;607&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;계충 별로 불리는 PDU 명칭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 헤더 (Header)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;각 계층마다 필요한 정보 및 기능&lt;/span&gt;이 담겨있습니다.&lt;/li&gt;
&lt;li&gt;제어 관련 정보들이 포함되어 있습니다.&lt;/li&gt;
&lt;li&gt;(송신) 상위 계층에서 전달된 PDU에 자신의 계층에서 만든 헤더를 추가해서 하위 계층으로 전달합니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 페이로드 (Payload)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;실제 데이터&lt;/span&gt;를 의미합니다.&lt;/li&gt;
&lt;li&gt;택배를 예로 들면 박스, 송장, 포장지 등은 페이로드에 속하지 않고, 받기로 한 택배물이 페이로드라고 생각하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Computer Science/네트워크</category>
      <category>header</category>
      <category>payload</category>
      <category>PDU</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/70</guid>
      <comments>https://hoonsb.tistory.com/70#entry70comment</comments>
      <pubDate>Sat, 3 Jun 2023 12:24:07 +0900</pubDate>
    </item>
    <item>
      <title>[네트워크] 네트워크 프로토콜 표준화</title>
      <link>https://hoonsb.tistory.com/69</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 네트워크 프로토콜이란?&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;color: #1a5490;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 프로토콜의 표준화가 필요한 이유?&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 송신자와 모든 수신자가 &lt;span style=&quot;color: #1a5490;&quot;&gt;표준화된 프로토콜을 지키기만 한다면 서로 통신이 가능&lt;/span&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;1018&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAX1eE/btsiuAJsCO2/mkSUvtIPXcegyeko0wc4t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAX1eE/btsiuAJsCO2/mkSUvtIPXcegyeko0wc4t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAX1eE/btsiuAJsCO2/mkSUvtIPXcegyeko0wc4t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAX1eE%2FbtsiuAJsCO2%2FmkSUvtIPXcegyeko0wc4t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1018&quot; height=&quot;433&quot; data-origin-width=&quot;1018&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;&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;b&gt;3. 프로토콜의 종류&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;많이 들어본 프로토콜 위주로 정리해보면 &lt;span style=&quot;color: #1a5490;&quot;&gt;TCP, IP, UDP, HTTP&lt;/span&gt; 등이 있습니다. 여기서 마지막에 붙은 'P'가 Protocol의 약자입니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) TCP (Transmission Control Protocol)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;OSI 참조 모델 &lt;span style=&quot;color: #1a5490;&quot;&gt;4계층의 전송 계층에서 사용되는 네트워크 프로토콜&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;TCP는 데이터의 손실이 없이 확실하게 상대방에게 전송하기 때문에 &lt;span style=&quot;color: #1a5490;&quot;&gt;신뢰성이 높은 프로토콜&lt;/span&gt;입니다. (수신 확인을 진행)&lt;/li&gt;
&lt;li&gt;현재는 TCP와 IP를 조합한 &lt;span style=&quot;color: #1a5490;&quot;&gt;TCP/IP&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) IP (Internet Protocol)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;OSI 참조 모델의 &lt;span style=&quot;color: #1a5490;&quot;&gt;3 계층인 네트워크 계층에서 사용되는 네트워크 프로토콜&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;각 장치(기기)에 주소를 할당&lt;/span&gt;하고 경로를 선택하여 데이터를 전송하기 위해 사용되는 프로토콜입니다.&lt;/li&gt;
&lt;li&gt;상위 계층에서 패킷을 보내주면, &lt;span style=&quot;color: #1a5490;&quot;&gt;패킷을 수신한 뒤에 IP 헤더를 추가&lt;/span&gt;하여 네트워크에 전송하게 됩니다.&lt;/li&gt;
&lt;li&gt;IP 헤더에는 보내는 사람(수신자)의 IP 주소, 받는 사람(송신자)의 IP 주소와 같은 정보들이 모여있고, 이 &lt;span style=&quot;color: #1a5490;&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;944&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0eSSS/btsivRDZU7o/j6w9jjFOc7ftKfZS52U5J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0eSSS/btsivRDZU7o/j6w9jjFOc7ftKfZS52U5J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0eSSS/btsivRDZU7o/j6w9jjFOc7ftKfZS52U5J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0eSSS%2FbtsivRDZU7o%2Fj6w9jjFOc7ftKfZS52U5J0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;944&quot; height=&quot;461&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;461&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;b&gt;(3) UDP (User Datagram Protocol)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;TCP와 마찬가지로 &lt;span style=&quot;color: #1a5490;&quot;&gt;OSI 참조 모델 4 계층의 전송 계층에서 사용되는 네트워크 프로토콜&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;TCP에서는 신뢰성을 확보하기 위해서 송신 측은 수신 측에 패킷이 정상적으로 도달했는지 확인을 진행합니다.&lt;/li&gt;
&lt;li&gt;UDP에서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;단순히 패킷을 보내기만 할 뿐 수신측에 패킷이 정상적으로 도달했는지 확인하지 않습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;도중에 패킷이 분신되어도 알 수 있는 방법이 없어 &lt;span style=&quot;color: #1a5490;&quot;&gt;신뢰성은 떨어지지만 처리가 가벼워서 속도가 빠르다는 장점&lt;/span&gt;이 있습니다.&lt;/li&gt;
&lt;li&gt;동영상과 같이 도중에 일부가 끊어지는 것보다는 &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(4) HTTP (Hyper Text Transfer Protocol)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;WWW&lt;/span&gt; 클라이언트(웹 브라우저)와 WWW 서버 사이의 통신에 사용되는 네트워크 프로토콜&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;웹 브라우저에서 요청이 들어오면 WWW 서버는 응답하는 방식으로 작동되는 간단한 프로토콜입니다.&lt;/li&gt;
&lt;li&gt;일반적인 통신 포트는 80번 포트를 사용합니다.&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;635&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k9Nhg/btsityZXOLQ/YJAJV0WIxjsDPnbXoISbpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k9Nhg/btsityZXOLQ/YJAJV0WIxjsDPnbXoISbpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k9Nhg/btsityZXOLQ/YJAJV0WIxjsDPnbXoISbpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk9Nhg%2FbtsityZXOLQ%2FYJAJV0WIxjsDPnbXoISbpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;393&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;393&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  면접에서 나올 수 있는 질문&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;네트워크 프로토콜이 무엇인가요?&lt;/li&gt;
&lt;li&gt;네트워크 프로토콜의 표준화가 필요한 이유에 대해서 설명해 보세요.&lt;/li&gt;
&lt;li&gt;TCP와 UDP을 비교하여 설명해 보세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Computer Science/네트워크</category>
      <category>HTTP</category>
      <category>ip</category>
      <category>TCP</category>
      <category>UDP</category>
      <category>네트워크 프로토콜</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/69</guid>
      <comments>https://hoonsb.tistory.com/69#entry69comment</comments>
      <pubDate>Sat, 3 Jun 2023 12:10:57 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스] 조인의 원리</title>
      <link>https://hoonsb.tistory.com/68</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 중첩 루프 조인 (NLJ, Nested Loop Join)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;중첩 for 문과 같은 원리&lt;/span&gt;로 조건에 맞는 조인을 하는 방법을 말합니다.&lt;/li&gt;
&lt;li&gt;Index에 의한 Random Access 비용이 많이 증가하기 때문에 &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;중첩 루프 조인의 작동&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1685434442810&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# CS 전공지식 노트 226 page

for each row in t1 matching reference key {
    for each row in t2 matching reference key {
        if row satisfies join conditions, send to client
    }
}&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;위에 있는 코드는 중첩 루프 조인을 사용하여 t1 테이블과 t2 테이블을 조인하는 의사 코드입니다.&lt;/li&gt;
&lt;li&gt;t1 테이블에서 행을 한 번에 하나씩 읽고, t2 테이블에서도 행을 하나씩 읽으며 조건에 맞는 데이터를 찾아서 반환하게 됩니다.&lt;/li&gt;
&lt;li&gt;여기서 t1을 &lt;span style=&quot;color: #1b711d;&quot;&gt;Driving Table&lt;/span&gt;이라고 하며, t2를 &lt;span style=&quot;color: #1b711d;&quot;&gt;Driven Table&lt;/span&gt;이라고 합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;중첩 루프 조인에서는 Driving Table을 적절하게 선택해야 합니다. 처음 &lt;span style=&quot;color: #1a5490;&quot;&gt;Driving Table에 해당되는 row가 너무 많다면, 그만큼의 반복&lt;/span&gt;을 해줘야하기 때문에 &lt;span style=&quot;color: #1a5490;&quot;&gt;Driving Table에서 Where절로 row 수를 최대한 줄일 수 있어야 합니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;Driving Table의 Join Column에 index가 존재하는지도 확인&lt;/span&gt;해봐야 합니다. 존재하지 않는다면 Random Access가 아닌 Full Table Scan을 통해서 모두 비교해야 하기 때문에 index를 생성하는 것이 좋습니다.&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;547&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R1NEG/btsh2RrmXHG/jxDigiAWy1k6XFuEvHQ3Bk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R1NEG/btsh2RrmXHG/jxDigiAWy1k6XFuEvHQ3Bk/img.jpg&quot; data-alt=&quot;[출처]&amp;amp;amp;nbsp;https://dataonair.or.kr/db-tech-reference/d-guide/sql/?mod=document&amp;amp;amp;uid=356&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R1NEG/btsh2RrmXHG/jxDigiAWy1k6XFuEvHQ3Bk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR1NEG%2Fbtsh2RrmXHG%2FjxDigiAWy1k6XFuEvHQ3Bk%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;473&quot; height=&quot;354&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[출처]&amp;amp;nbsp;https://dataonair.or.kr/db-tech-reference/d-guide/sql/?mod=document&amp;amp;uid=356&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;* &lt;b&gt;Driving Table&lt;/b&gt; : Join 할 때, 먼저 Access되어 Access Path를 주도하는 테이블&lt;br /&gt;* &lt;b&gt;Driven Table&lt;/b&gt; : Join 할 때, 나중에 Access되는 테이블&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 정렬 병합 조인 (Sort Merge Join)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각의 테이블을 &lt;span style=&quot;color: #1a5490;&quot;&gt;조인할 필드 기준으로 정렬한 후 조인 작업을 수행&lt;/span&gt;하는 조인 방법입니다.&lt;/li&gt;
&lt;li&gt;조인할 때 사용할 &lt;span style=&quot;color: #1a5490;&quot;&gt;적절한 인덱스가 존재하지 않고, 대용량 테이블을 조인하는 데&lt;/span&gt; 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;조인 조건으로 비교 연산자(&amp;gt;, &amp;lt;, &amp;gt;=, &amp;lt;=)&lt;/span&gt;가 있을 때도 사용됩니다.&lt;/li&gt;
&lt;li&gt;조회 범위가 좁을 때 유리했던 Nested Loop Join과는 다르게 조회 범위가 많을 때 주로 선택되어 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정렬 병합 조인의 작동&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;각 테이블에 대해서 &lt;span style=&quot;color: #1a5490;&quot;&gt;독립적으로 데이터를 읽어옵니다&lt;/span&gt;. 인덱스가 존재하는 컬럼의 테이블은 Random Access, 인덱스가 존재하지 않는 컬럼의 테이블은 Full Table Scan을 사용합니다.&lt;/li&gt;
&lt;li&gt;읽힌 각 테이블의 데이터를 조인을 위한 연결고리에 대하여 &lt;span style=&quot;color: #1a5490;&quot;&gt;정렬을 수행&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;정렬이 모두 끝난 뒤 정렬된 데이터를 차례로 Scan 하면서 &lt;span style=&quot;color: #1a5490;&quot;&gt;연결고리의 조건으로 Merge&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S0L7m/btsh32spozw/HKOKTmMctKsZAGYdFdVFCK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S0L7m/btsh32spozw/HKOKTmMctKsZAGYdFdVFCK/img.jpg&quot; data-alt=&quot;[출처]&amp;amp;amp;nbsp;https://dataonair.or.kr/db-tech-reference/d-guide/sql/?mod=document&amp;amp;amp;uid=356&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S0L7m/btsh32spozw/HKOKTmMctKsZAGYdFdVFCK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS0L7m%2Fbtsh32spozw%2FHKOKTmMctKsZAGYdFdVFCK%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;498&quot; height=&quot;259&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[출처]&amp;amp;nbsp;https://dataonair.or.kr/db-tech-reference/d-guide/sql/?mod=document&amp;amp;uid=356&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 해시 조인 (Hash Join)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조인할 &lt;span style=&quot;color: #1a5490;&quot;&gt;두 테이블 중 하나를 해시 테이블로 선정&lt;/span&gt;하여 조인에 사용되는 컬럼 값을 &lt;span style=&quot;color: #1a5490;&quot;&gt;해시 알고리즘으로 비교하여 조인하는 방법&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;동등(=) 조인에서만 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Sort Merge Join을 할 때, 테이블 크기가 너무 커서 Sort에 드는 부하가 너무 클 때 해시 조인가 사용됩니다.&lt;/li&gt;
&lt;li&gt;조인에 사용되는 컬럼에 적절한 index가 존재하지 않아서 Nested Loop Join을 사용하기에 효율적이지 못할 때 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해시 조인의 작동&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;A 테이블과 B 테이블을 조인할 때, 둘 중 바이트가 더 작은 테이블을 읽어서 Hash Area에 해시 테이블을 생성&lt;/span&gt;합니다. 이때 해시 테이블의 Key는 조인에 사용되는 필드가 됩니다.&lt;/li&gt;
&lt;li&gt;만약 A 테이블이 해시 테이블로 생성되었다면, A 테이블은 &lt;span style=&quot;color: #1a5490;&quot;&gt;Build Input&lt;/span&gt;이라고 하고, B 테이블은 &lt;span style=&quot;color: #1a5490;&quot;&gt;Probe Input&lt;/span&gt;이라고 합니다.&lt;/li&gt;
&lt;li&gt;B 테이블을 스캔하며 조인 컬럼을 해시 함수에 넣고, 반환되는 버킷 주소로 찾아가 해시 체인을 스캔하며 Join 할 데이터를 찾아서 반환합니다.&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;598&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KIaRN/btsh4niY2wj/iq2whioI0C1kTbxKEtZJNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KIaRN/btsh4niY2wj/iq2whioI0C1kTbxKEtZJNk/img.jpg&quot; data-alt=&quot;[출처]&amp;amp;amp;nbsp;https://dataonair.or.kr/db-tech-reference/d-guide/sql/?mod=document&amp;amp;amp;uid=356&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KIaRN/btsh4niY2wj/iq2whioI0C1kTbxKEtZJNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKIaRN%2Fbtsh4niY2wj%2Fiq2whioI0C1kTbxKEtZJNk%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;485&quot; height=&quot;253&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[출처]&amp;amp;nbsp;https://dataonair.or.kr/db-tech-reference/d-guide/sql/?mod=document&amp;amp;uid=356&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  나올 수 있는 질문&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt; 중첩 루프 조인(Nested Loop Join)에 대해서 설명해보세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Computer Science/데이터베이스</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/68</guid>
      <comments>https://hoonsb.tistory.com/68#entry68comment</comments>
      <pubDate>Tue, 30 May 2023 18:44:56 +0900</pubDate>
    </item>
    <item>
      <title>[IntelliJ] 지역변수 밑줄 제거</title>
      <link>https://hoonsb.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;백준 문제를 IDE로 풀고 있었는데, 사소한 오타로 계속 시간을 허비하게 되어서 인텔리제이 설정을 하면서 기록해 두려고 이 글을 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발생했던 휴먼에러는 i와 j를 구분하지 못했던 것. i와 j 밑에 밑줄이 그어지니깐 구분을 못하겠어서 불편해 죽겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 아마도 Material Dark 테마를 설정하고 있는 사람만 해당될 문제라고 생각된다.&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;426&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GXfQN/btsgL9TD0Fy/IumYl9tk9ffVKFmHSl7CK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GXfQN/btsgL9TD0Fy/IumYl9tk9ffVKFmHSl7CK0/img.png&quot; data-alt=&quot;뭐가 i이고, 뭐가 j인지 거북목 생길 거 같다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GXfQN/btsgL9TD0Fy/IumYl9tk9ffVKFmHSl7CK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGXfQN%2FbtsgL9TD0Fy%2FIumYl9tk9ffVKFmHSl7CK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;240&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;뭐가 i이고, 뭐가 j인지 거북목 생길 거 같다.&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;위 사진처럼 확대하면 구분할 수 있을거 같지만 작은 글씨로 보면 i와 j 밑에 밑줄이 있으면 구분을 못하겠다. j를 써야 할 자리에 자꾸 i를 쓴다던가 하는 오류를 줄이기 위해서 밑줄을 없애버리자.&lt;/p&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;b&gt;설정하기&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설정창을 열기 위해서 command + , 을 눌러 준다.&lt;/li&gt;
&lt;li&gt;검색창에 &quot;color scheme&quot; 검색&lt;/li&gt;
&lt;li&gt;Langauge Defaults -&amp;gt; Identifiers -&amp;gt; Reassigned local variable 선택&lt;/li&gt;
&lt;li&gt;Effects 체크 풀어주기&lt;/li&gt;
&lt;li&gt;Apply 후 저장&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;1942&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tuWq3/btsgGmFU967/StUGuBjY6tEK4ETUCdz0U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tuWq3/btsgGmFU967/StUGuBjY6tEK4ETUCdz0U1/img.png&quot; data-alt=&quot;한 눈에 보는 설정 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tuWq3/btsgGmFU967/StUGuBjY6tEK4ETUCdz0U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtuWq3%2FbtsgGmFU967%2FStUGuBjY6tEK4ETUCdz0U1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1942&quot; height=&quot;1054&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1054&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lErm7/btsgELlSbs1/nYB2OGxPF4dtgqeTYF9FLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lErm7/btsgELlSbs1/nYB2OGxPF4dtgqeTYF9FLk/img.png&quot; data-alt=&quot;밑줄이 제거된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lErm7/btsgELlSbs1/nYB2OGxPF4dtgqeTYF9FLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlErm7%2FbtsgELlSbs1%2FnYB2OGxPF4dtgqeTYF9FLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;284&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;354&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;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;020&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/020.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/020.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category> &amp;zwj;  Dev/여러가지 팁</category>
      <category>반복문 i와 j 밑줄 제거</category>
      <category>인텔리제이 지역변수 밑줄 제거</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/67</guid>
      <comments>https://hoonsb.tistory.com/67#entry67comment</comments>
      <pubDate>Sat, 20 May 2023 19:26:55 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스] 데이터베이스의 종류</title>
      <link>https://hoonsb.tistory.com/66</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 관계형 데이터베이스 (RDBMS)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;SQL이라는 언어를 써서 조작할 수 있습니다.&lt;/li&gt;
&lt;li&gt;관계형 데이터베이스의 종류에는 MySQL, Oracle, PostgreSQL, MSSQL 등이 있습니다.&lt;/li&gt;
&lt;li&gt;관계형 데이터베이스는 표준 SQL을 지키기는 하지만 각 제품에서 특화시킨 SQL을 사용합니다.&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) MySQL&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;오픈소스 관계형 데이터베이스&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;MySQL의 최고 장점은 오픈소스이기 때문에 무료라는 것입니다.&lt;/li&gt;
&lt;li&gt;문자열 비교에 있어서 &lt;span style=&quot;color: #1a5490;&quot;&gt;대소문자를 구분하지 않습니다&lt;/span&gt;. BINARY 설정 등을 이용하면 추가 설정이 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;nested loop join만을 제공&lt;/span&gt;한다는 단점이 있습니다. MySQL 8.0.18 릴리스 버전 이후로는 &lt;span style=&quot;color: #1b711d;&quot;&gt;hash join&lt;/span&gt;도 제공하게 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;nested loop join (중첩 루프 조인)&lt;br /&gt;&lt;/b&gt;: 바깥 테이블의 처리 범위를 하나씩 접근하며 추출된 값으로 안쪽 테이블을 조인하는 방법입니다.&lt;br /&gt;중첩 for문과 비슷한 동일한 원리라고 생각하면 됩니다. 비용이 많이 들어서 대용량 테이블에서는 사용하지 않습니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;hash Join (해시 조인)&lt;/b&gt;&lt;br /&gt;: 해시 테이블을 기반으로 조인하는 방법을 말합니다. 2개의 테이블을 조인할 때, 하나의 테이블을 해시 테이블로서 사용합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) Oracle DB&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Oracle 사에서 만든 관계형 데이터베이스입니다.&lt;/li&gt;
&lt;li&gt;MySQL, MSSQL 보다 &lt;span style=&quot;color: #1a5490;&quot;&gt;대용량 정보를 관리할 때 성능이 좋습니&lt;/span&gt;다.&lt;/li&gt;
&lt;li&gt;고성능 트랜잭션 처리를 제공하여 속도가 빠릅니다.&lt;/li&gt;
&lt;li&gt;SQL문을 실행하는 가장 효율적인 방법을 제공합니다. 쿼리비용 최소화를 위해서 &lt;span style=&quot;color: #1a5490;&quot;&gt;테이블 인덱싱 분석&lt;/span&gt;을 합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;과거 시점의 데이터 조회&lt;/span&gt;도 가능하고, &lt;span style=&quot;color: #1a5490;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) PostgreSQL&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;다양한 Join 방법을 제공&lt;/span&gt;합니다. (nested loop join, &lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;hash join,&lt;/span&gt; sort merge join&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;update 시에는 기존에 있던 행을 지우고, 변경된 데이터를 가지는 새로운 행을 추가하는 방식을 사용하여 &lt;span style=&quot;color: #1a5490;&quot;&gt;update 성능은 비교적 좋지 않습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;데이터베이스 클러스터 백업 기능&lt;/span&gt;을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;hot backup&lt;/span&gt;과 wal replay를 활용하여 &lt;span style=&quot;color: #1a5490;&quot;&gt;원하는 시점의 데이터 복구가 가능&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;SQL 뿐만 아니라 &lt;span style=&quot;color: #1a5490;&quot;&gt;JSON을 이용해서도 데이터에 접근&lt;/span&gt;할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;sort merge join (정렬 병합 조인)&lt;/b&gt;&lt;br /&gt;: 각각의 테이블에서 조인할 필드 기준으로 정렬한 후 조인 작업을 수행하는 조인방법입니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;hot backup&lt;/b&gt;&lt;br /&gt;: 데이터베이스가 실행 중인 상태에서 데이터를 백업받는 방식입니다.&amp;nbsp;&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;b&gt;(4) MSSQL&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;마이크로소프트 사에서 사이베이스(Sybase)를 기반으로 개발한 관계형 데이터베이스입니다.&lt;/li&gt;
&lt;li&gt;윈도우 개발환경에서 DB가 필요할 경우 MSSQL을 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;windows 기반 서버에서만 작동되도록 설계&lt;/span&gt;되어 있고, 라이센스 비용이 비싸다는 단점이 있습니다.&lt;/li&gt;
&lt;li&gt;하지만 &lt;span style=&quot;color: #1a5490;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. NoSQL 데이터베이스&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;대표적으로 알려진 NoSQL 데이터베이스는 MongoDB와 Redis가 존재합니다.&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) MongoDB&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;JSON을 통해서 데이터에 접근할 수 있습니다.&lt;/li&gt;
&lt;li&gt;데이터가 저장될 때는 Binary JSON 형태로 데이터가 저장되며 &lt;span style=&quot;color: #1a5490;&quot;&gt;도큐먼트 기반의 데이터베이스&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;확장성이 뛰어나고, 빅데이터를 저장&lt;/span&gt;할 때 성능이 좋습니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;스키마를 정해 놓지 않고 데이터를 삽입&lt;/span&gt;하기 때문에 다양한 도메인의 데이터베이스를 기반으로 분석할 수 있습니다.&lt;/li&gt;
&lt;li&gt;도큐먼트를 생성할 때마다 다른 컬렉션과 구별하기 위해서 유니크한 ObjectId가 생성됩니다.&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;780&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1AtAB/btsgELzly1s/dv3ITcDz1YR4v1Lfw8LM60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1AtAB/btsgELzly1s/dv3ITcDz1YR4v1Lfw8LM60/img.png&quot; data-alt=&quot;[출처] CS 전공지식 노트 p.216&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1AtAB/btsgELzly1s/dv3ITcDz1YR4v1Lfw8LM60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1AtAB%2FbtsgELzly1s%2Fdv3ITcDz1YR4v1Lfw8LM60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;433&quot; height=&quot;132&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[출처] CS 전공지식 노트 p.216&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;b&gt;(2) Redis&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;인메모리 데이터베이스&lt;/span&gt;이며, Key-Value 데이터 모델 기반의 데이터베이스입니다.&lt;/li&gt;
&lt;li&gt;다른 데이터베이스를 앞단에 두어 사용하는 &lt;span style=&quot;color: #1a5490;&quot;&gt;캐싱 계층으로 사용&lt;/span&gt;됩니다.&lt;/li&gt;
&lt;li&gt;기본적인 데이터 타입은 문자열이며 최대 512MB까지 저장할 수 있습니다. 이외에도 Set과 Hash 등을 지원합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  참고 자료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.siner.io/2021/10/11/rdbms-comparison/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;주요 RDBMS의 종류&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #ffffff; text-align: start;&quot; href=&quot;http://www.yes24.com/Product/Goods/108887922?pid=123487&amp;amp;cosemkid=go16505206189583564&amp;amp;gclid=Cj0KCQjw6cKiBhD5ARIsAKXUdyaYETrnD8WkQWtiuyHQIdRN2Aub6gXoiCQ6BBrkz4i2L2x64h3m-zQaAqc2EALw_wcB&quot;&gt;면접을 위한 CS 전공지식 노트&lt;/a&gt;&lt;/p&gt;</description>
      <category>  Computer Science/데이터베이스</category>
      <category>NoSQL 데이터베이스</category>
      <category>RDBMS</category>
      <category>데이터베이스 종류</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/66</guid>
      <comments>https://hoonsb.tistory.com/66#entry66comment</comments>
      <pubDate>Sat, 20 May 2023 16:33:23 +0900</pubDate>
    </item>
    <item>
      <title>[패스트캠퍼스] 패스트캠퍼스 백엔드 개발 9주차 회고</title>
      <link>https://hoonsb.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 3주 차 회고를 작성한 지 벌써 6주가 흘러 9주 차 회고를 쓰게 되었습니다. 준비할 건 많고, 하루는 부족하고... 하루하루가 정말 빠르게 지나가는 것 같습니다.&lt;/p&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;b&gt;  Java 실시간 강의 종료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9주 차에 회고를 쓰게 된 이유는 Java 실시간 강의가 끝났기 때문입니다. 사실 여러 부트캠프를 보면 Java를 엄청 빠르게 끝내는 부트캠프들이 많은데, 제가 듣고 있는 과정에서는 화요일, 목요일에&amp;nbsp;Java 실시간 강의를 진행했었습니다.&lt;/p&gt;
&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: #1a5490;&quot;&gt;Java의 상당히 깊은 부분까지 고려하면서 강의를 진행해 주셔서&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  백준 실버 달성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 골드, 플래티넘이신 분들에게는 가소로워 보일 수도 있지만 실버를 달성했습니다. 골드를 향해서 빠르게 달려가고 있습니다.  &lt;/p&gt;
&lt;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;562&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boS5bb/btsgklWpVvP/BzyrOmuY1EKBKBo9vvIt10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boS5bb/btsgklWpVvP/BzyrOmuY1EKBKBo9vvIt10/img.png&quot; data-alt=&quot;Solved.ac 티어&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boS5bb/btsgklWpVvP/BzyrOmuY1EKBKBo9vvIt10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboS5bb%2FbtsgklWpVvP%2FBzyrOmuY1EKBKBo9vvIt10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;233&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Solved.ac 티어&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;3주 차와 비교했을 때 자료구조, 알고리즘 제대로 학습을 한 뒤부터는 문제를 보는 눈이 조금은 달라졌다고 생각합니다. 문제를 읽었을 때, &lt;span style=&quot;color: #1a5490;&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;기존에는 문제를 보면 그냥 바로 코드부터 치면서 시작했었는데, 경험해 보신 분들은 아시겠지만 이렇게 작성하다가 중간에 막히면 정말 답도 없습니다. 코딩 테스트 부분에서는 패스트캠퍼스에서 지정된 멘토님의 영향을 조금 받았다고 생각합니다. 사실 매일 적어도 1문제씩은 풀어보기라는 과제를 내주신 분이기도 하고, 알고리즘 문제 접근법이나 좋은 습관을 형성하는 데 있어서 도움을 많이 받았습니다. 다음 회고 글에서는 골드가 되어있었으면 좋겠습니다.  &lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  그룹 CS 스터디&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 부트캠프 정규 커리큘럼에는 존재하지 않지만, 각자 4명에서 5명으로 지정된 피어 그룹이 있는데, 저희 그룹원들과 따로 진행하고 있는 스터디입니다. 도서는 &lt;a href=&quot;http://www.yes24.com/Product/Goods/108887922&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;면접을 위한 CS 전공지식 노트&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: #1a5490;&quot;&gt;책에 있는 내용 이외에도 추가적으로 학습해서 정리&lt;/span&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;스터디 규칙은 매주 맡은 부분에 대해서 발표를 하고, 자신이 맡은 부분이 아니어도 해당 내용을 모두 숙지하고 오자는 규칙을 정했습니다. 그리고 따로 Github Organization을 만들어서 레포지토리를 만들고, 각자 정리한 내용을 Pull Request 하는 방식으로 스터디를 진행하고 있습니다.&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;937&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qXNJe/btsgkXUZWNg/uakFo5KbbH4mg4oNWvC9r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qXNJe/btsgkXUZWNg/uakFo5KbbH4mg4oNWvC9r0/img.png&quot; data-alt=&quot;Github Repository Readme 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qXNJe/btsgkXUZWNg/uakFo5KbbH4mg4oNWvC9r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqXNJe%2FbtsgkXUZWNg%2FuakFo5KbbH4mg4oNWvC9r0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;937&quot; height=&quot;377&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Github Repository Readme 파일&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  타 그룹과의 교류&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부트캠프 후반부에 협업으로 진행하는 프로젝트가 존재합니다. 그래서 같은 기수에 교육을 듣고 계신 많은 분들과 소통해 볼 수 있는 자리가 있었으면 좋겠어서 저희 그룹 조장님을 앞세워 타 그룹과의 교류를 진행했었습니다.&lt;/p&gt;
&lt;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: #1a5490;&quot;&gt;다양한 분들을 만나볼 수 있었던 시간&lt;/span&gt;이 있었습니다. 교류를 하는데 이용했던 시간은 주로 1주일에 2번 존재하는 그룹 활동 시간을 이용했습니다.&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;그때의 연이 닿아서 지금은 다른 그룹원분들과 함께 진행하고 있는 발표 스터디도 진행 중에 있습니다. &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;span style=&quot;color: #1a5490;&quot;&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-origin-width=&quot;422&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o82Zu/btsglxuXBin/J7NxxVHNc0Zi8TzNzkxaG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o82Zu/btsglxuXBin/J7NxxVHNc0Zi8TzNzkxaG1/img.png&quot; data-alt=&quot;발표 스터디 진행 노션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o82Zu/btsglxuXBin/J7NxxVHNc0Zi8TzNzkxaG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo82Zu%2FbtsglxuXBin%2FJ7NxxVHNc0Zi8TzNzkxaG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;310&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;310&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;zwj;♂️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Activity/패스트캠퍼스</category>
      <category>백엔드</category>
      <category>패스트캠퍼스</category>
      <category>패스트캠퍼스 백엔드 개발 5기</category>
      <category>회고</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/65</guid>
      <comments>https://hoonsb.tistory.com/65#entry65comment</comments>
      <pubDate>Wed, 17 May 2023 21:43:40 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 정적 팩토리 메소드 (Static Factory Method)</title>
      <link>https://hoonsb.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Java 클래스에서 of(), from(), getInstance()와 같은 정적 팩토리 메소드를 많이 사용하는 것을 보고, 정적 팩토리 메소드를 사용하는 이유에 대해서 궁금하여 글을 정리하게 되었습니다.&lt;/p&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;b&gt;1. 정적 팩토리 메소드(static factory method)는 무엇인가?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정적 팩토리 메소드는 &lt;span style=&quot;color: #1a5490;&quot;&gt;객체 생성의 역할을 하는 클래스 메소드&lt;/span&gt;이다.&lt;/li&gt;
&lt;li&gt;자바 코드를 짜봤다면 정적 팩토리 메소드를 써본 적이 있을 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1684224412066&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Example {
    public static void main(String[] args) {
        // 직접 생성자를 통해서 객체를 생성
        List&amp;lt;String&amp;gt; list1 = new ArrayList&amp;lt;&amp;gt;();
        list1.add(&quot;김씨&quot;);
        list1.add(&quot;박씨&quot;);
        list1.add(&quot;정씨&quot;);
        list1.add(&quot;이씨&quot;);
        list1.add(&quot;윤씨&quot;);
        
        // 정적 팩토리 메소드를 통해서 객체를 생성
        List&amp;lt;String&amp;gt; list2 = List.of(&quot;김씨&quot;, &quot;박씨&quot;, &quot;정씨&quot;, &quot;이씨&quot;, &quot;윤씨&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 List.of()를 사용하여 직접 생성자를 사용하지 않고, 리스트를 만드는 방법에 대한 코드입니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 정적 팩토리 메소드는 왜 사용하는가?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실 저도 코드를 짜면서 이 부분이 가장 의문이었습니다. &lt;span style=&quot;color: #1a5490;&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 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;b&gt;3. 정적 팩토리 메소드와 생성자의 차이&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 이름을 가질 수 있습니다.&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;목적에 맞게 객체를 생성하려면 해당 클래스의 내부 구조를 잘 알고 있어야 합니다&lt;/span&gt;.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;정적 팩토리 메소드를 사용하면 &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 호출할 때마다 새로운 객체를 생성할 필요가 없습니다.&lt;/b&gt;&lt;/h4&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;span style=&quot;color: #1a5490;&quot;&gt;호출할 때마다 새로운 객체를 생성하지 않아도 됩니다. 정적 팩토리 메소드를 사용해서 객체의 생성을 제한하고 싶다면 객체의 생성자를 private로 설정&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 하위 자료형 객체를 반환할 수 있습니다.&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;반환값을 가지고 있기 때문에 하위 타입의 객체를 반환할 수 있습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;아래는 점수만 입력하면 해당 점수에 맞는 티어의 타입을 반환해 주는 분기분을 팩토리 메소드에서 구현할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1684230908783&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Level {
    public static Level of(int score) {
        if (score &amp;lt; 1000) {
            return new Bronze();
        } else if (score &amp;lt; 10000) {
            return new Silver();
        } else if (score &amp;lt; 100000) {
            return new Gold();
        } else {
            return new Platinum();
        }
    }
}

class Bronze extends Level {
    public Bronze() {
        System.out.println(&quot;브론즈 티어&quot;);
    }
}

class Silver extends Level {
    public Silver() {
        System.out.println(&quot;실버 티어&quot;);
    }
}

class Gold extends Level {
    public Gold() {
        System.out.println(&quot;골드 티어&quot;);
    }
}

class Platinum extends Level {
    public Platinum() {
        System.out.println(&quot;플래티넘 티어&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(4) 객체 생성을 캡슐화할 수 있습니다.&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;생성자에 private를 붙이고, 정적 팩토리 메소드 안에서 생성자를 호출함으로써 &lt;span style=&quot;color: #1a5490;&quot;&gt;내부 상태를 외부에 드러낼 필요 없이 객체 생성 인터페이스를 단순화&lt;/span&gt;할 수 있습니다.&lt;/li&gt;
&lt;li&gt;DTO에서 사용하는 from()을 한 가지 예시로 볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;StudentDto의 내부 구현을 모르고 있더라도 Student 객체를 정적 팩토리 메소드 from()의 파라미터로 넣어주면 해당 Student에 대한 Dto 객체를 얻을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1684230442071&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StudentDto {
    private String name;
    private Integer age;
    
    private StudentDto(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    public static StudentDto from(Student student) {
        return new StudentDto(student.getName(), student.getAge());
    }
    
    // Getter, Setter 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 사이의 형변환이 필요하거나, 객체 생성이 반복되는 경우에는 생성자보다는 정적 팩토리 메소드를 활용해 보는 것이 좋을 것으로 보입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  참고자료&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0b0c0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;정적 팩토리 메서드(Static Factory Method)는 왜 사용할까?&lt;/a&gt;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/Java</category>
      <category>static factory method</category>
      <category>정적 팩토리 메소드</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/64</guid>
      <comments>https://hoonsb.tistory.com/64#entry64comment</comments>
      <pubDate>Tue, 16 May 2023 19:00:18 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스] 관계와 키</title>
      <link>https://hoonsb.tistory.com/63</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 관계&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;/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;611&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FRrQ4/btsf2IJPLJC/nSwfjckIhY12MuWUmX9MY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FRrQ4/btsf2IJPLJC/nSwfjckIhY12MuWUmX9MY0/img.png&quot; data-alt=&quot;[그림 1] 관계화살표 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FRrQ4/btsf2IJPLJC/nSwfjckIhY12MuWUmX9MY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFRrQ4%2Fbtsf2IJPLJC%2FnSwfjckIhY12MuWUmX9MY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;370&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 가지 관계의 종류에 대해서 알아봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 1 : 1 관계&lt;/b&gt;&lt;/h4&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;span style=&quot;color: #1a5490;&quot;&gt;유저와 유저 이메일은 1 : 1 관계&lt;/span&gt;를 가지게 됩니다.&lt;/li&gt;
&lt;li&gt;1 : 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;570&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkArIz/btsfOI5nuwT/QqnddcPmvG8b1Vk8yQOV01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkArIz/btsfOI5nuwT/QqnddcPmvG8b1Vk8yQOV01/img.png&quot; data-alt=&quot;[그림 2] 1 : 1 관계의 관계 화살표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkArIz/btsfOI5nuwT/QqnddcPmvG8b1Vk8yQOV01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkArIz%2FbtsfOI5nuwT%2FQqnddcPmvG8b1Vk8yQOV01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;240&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 2] 1 : 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 1 : N 관계&lt;/b&gt;&lt;/h4&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;span style=&quot;color: #1a5490;&quot;&gt;유저와 상품이 1 : N 관계&lt;/span&gt;를 가지게 됩니다.&lt;/li&gt;
&lt;li&gt;여기서 주의해야 할 점은 유저가 장바구니에 어떠한 상품도 안 담을 수도 있기 때문에 0이라는 값도 생각해야 합니다.&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;569&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi73No/btsfWWbg2xQ/d0jJY7aahB4GTGJlgLKwf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi73No/btsfWWbg2xQ/d0jJY7aahB4GTGJlgLKwf0/img.png&quot; data-alt=&quot;[그림 3] 1 : N 관계의 관계 화살표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi73No/btsfWWbg2xQ/d0jJY7aahB4GTGJlgLKwf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi73No%2FbtsfWWbg2xQ%2Fd0jJY7aahB4GTGJlgLKwf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;270&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 3] 1 : N 관계의 관계 화살표&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) N : M 관계&lt;/b&gt;&lt;/h4&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;span style=&quot;color: #1a5490;&quot;&gt;학생과 강의가 N : M 관계&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;699&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7NGKF/btsfWW3rQvb/X89x0yonWdknAEOnZtuhak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7NGKF/btsfWW3rQvb/X89x0yonWdknAEOnZtuhak/img.png&quot; data-alt=&quot;[그림 4] N : M 관계의 관계 화살표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7NGKF/btsfWW3rQvb/X89x0yonWdknAEOnZtuhak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7NGKF%2FbtsfWW3rQvb%2FX89x0yonWdknAEOnZtuhak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;699&quot; height=&quot;322&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 4] N : M 관계의 관계 화살표&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 키&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 사이의 관계를 명확하게 구분할 수 있게 해 주고, 테이블 자체의 인덱스를 위해서 존재하는 개념이 &lt;span style=&quot;color: #1a5490;&quot;&gt;'키(Key)'&lt;/span&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;695&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p1plv/btsfXd41e3W/4LSaTGbwfJNKWaJKP5HOb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p1plv/btsfXd41e3W/4LSaTGbwfJNKWaJKP5HOb0/img.png&quot; data-alt=&quot;[그림 5] 슈퍼키, 후보키, 기본키, 대체키의 관계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p1plv/btsfXd41e3W/4LSaTGbwfJNKWaJKP5HOb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp1plv%2FbtsfXd41e3W%2F4LSaTGbwfJNKWaJKP5HOb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;365&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;365&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 유일성과 최소성&lt;/b&gt;&lt;/h4&gt;
&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;/li&gt;
&lt;li&gt;&lt;b&gt;최소성&lt;/b&gt;은 해당 키를 구성하는 모든 부분 집합들이 유일성을 가지지 않는 성질을 말합니다.&lt;/li&gt;
&lt;li&gt;다시 말해서 &lt;span style=&quot;color: #1a5490;&quot;&gt;유일성은 하나의 Key로 하나의 Tuple을 유일하게 구별할 수 있는 성질&lt;/span&gt;을 말하고, &lt;span style=&quot;color: #1a5490;&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;1132&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccdTFb/btsf3ftW6EJ/KkKkw86OPWc1VoJY1AYbuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccdTFb/btsf3ftW6EJ/KkKkw86OPWc1VoJY1AYbuK/img.png&quot; data-alt=&quot;[그림 6] 속성 A, B, C에 대한 표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccdTFb/btsf3ftW6EJ/KkKkw86OPWc1VoJY1AYbuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccdTFb%2Fbtsf3ftW6EJ%2FKkKkw86OPWc1VoJY1AYbuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;505&quot; height=&quot;172&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 6] 속성 A, B, C에 대한 표&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;[그림 6]을 예시로 들면서 유일성과 최소성을 이해해 봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xEjUl/btsf2WuxS9t/e4rGubk2OBWScf2C8YfGV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xEjUl/btsf2WuxS9t/e4rGubk2OBWScf2C8YfGV1/img.png&quot; data-alt=&quot;[그림 7] 그림 6을 분석해본 표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xEjUl/btsf2WuxS9t/e4rGubk2OBWScf2C8YfGV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxEjUl%2Fbtsf2WuxS9t%2Fe4rGubk2OBWScf2C8YfGV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1962&quot; height=&quot;688&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 7] 그림 6을 분석해본 표&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;[그림 7]은 키가 될 수 있는 요소의 부분 집합을 알아보고, 유일성과 최소성 그리고 키로 사용할 수 있는지의 여부를 분석해 본 표입니다.&lt;br /&gt;&lt;br /&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;color: #1a5490;&quot;&gt;(A), (B), (C)&lt;/span&gt;는 각각 겹치는 값들이 존재하기 때문에 유일성을 만족하지 않습니다. 유일성을 만족하지 않기 때문에 최소성은 볼 필요도 없이 존재하지 않고, &lt;span style=&quot;color: #1a5490;&quot;&gt;키로 사용할 수 없습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;(A, B), (B, C), (A, C)&lt;/span&gt;는 각각 겹치는 값들이 없기 때문에 유일성을 만족합니다. 최소성을 보기 위해서 각 요소의 부분 집합을 확인해 보면 각 부분 집합의 요소들이 모두 유일성을 가지고 있지 않기 때문에 &lt;span style=&quot;color: #1a5490;&quot;&gt;키로 사용할 수 있습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;(A, B, C)&lt;/span&gt;는 겹치는 값들이 없기 떄문에 유일성을 만족합니다. 하지만 부분 집합 중 {A, B}, {B, C}, {A, C}는 이미 유일성을 가지고 있기 때문에 &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 기본키 (Primary Key, PK)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;유일성과 최소성을 모두 만족&lt;/span&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;# 자연키&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 그대로 속성 중 자연스럽게 중복되지 않는 것들을 뽑다가 나오는 키를 말합니다. &quot;이름&quot;, &quot;성별&quot;, &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;# 인조키&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소마다 인위적으로 어떤 Id 값을 부여하는 것을 말합니다. 대표적으로 MySQL의 auto-increment가 인조키의 예시에 속합니다. 사용해 봤다면 알겠지만, MySQL에서 데이터를 추가하게 되면 순차적으로 Id 값이 증가하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 외래키 (Foreign Key, FK)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;개체와의 관계를 식별하는 데 사용&lt;/span&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;527&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sp1MO/btsf4enZb32/NQ2qHhcp5hg8RfgMQsGtOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sp1MO/btsf4enZb32/NQ2qHhcp5hg8RfgMQsGtOK/img.png&quot; data-alt=&quot;[그림 8] Product 테이블에 있는 외래키 user_id&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sp1MO/btsf4enZb32/NQ2qHhcp5hg8RfgMQsGtOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsp1MO%2Fbtsf4enZb32%2FNQ2qHhcp5hg8RfgMQsGtOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;515&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 8] Product 테이블에 있는 외래키 user_id&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;Product와 User는 N : 1 관계를 가지고 있기 때문에 Product에서 자신을 주문한 User에 대한 정보를 가지고 있어야 합니다. 이때 관계를 연결시켜 주기 위해서 Product 테이블에 자신을 주문한 User의 Id 값을 외래키로 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(4) 후보키 (Candidate Key)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;기본키(PK)가 될 수 있는 후보들의 집합&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(5) 대체키 (Alternate Key)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;기본키로 대체될 수 있는 키&lt;/span&gt;를 말합니다.&lt;/li&gt;
&lt;li&gt;후보키가 여러 개인 경우 1개를 기본키(PK)로 지정하고, 기본키가 되지 못한 나머지 후보키들을 모두 대체키라고 할 수 있습니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(6) 슈퍼키 (Super Key)&lt;/b&gt;&lt;/h4&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  면접에서 나올 수 있는 질문들&lt;/b&gt;&lt;/h2&gt;
&lt;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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Computer Science/데이터베이스</category>
      <category>1대1 관계</category>
      <category>1대N 관계</category>
      <category>N대M 관계</category>
      <category>기본키</category>
      <category>데이터베이스 관계</category>
      <category>데이터베이스 키</category>
      <category>슈퍼키</category>
      <category>외래키</category>
      <category>유일성과 최소성</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/63</guid>
      <comments>https://hoonsb.tistory.com/63#entry63comment</comments>
      <pubDate>Mon, 15 May 2023 17:40:23 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 1931번 회의실 배정 - Java</title>
      <link>https://hoonsb.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1931&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;백준 1931번 회의실 배정&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1684048916128&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;1931번: 회의실 배정&quot; data-og-description=&quot;(1,4), (5,7), (8,11), (12,14) 를 이용할 수 있다.&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/1931&quot; data-og-url=&quot;https://www.acmicpc.net/problem/1931&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/M7igB/hySCWWZnKr/c1Vmuu8Bz28loQKmrXa1Q1/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1931&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/1931&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/M7igB/hySCWWZnKr/c1Vmuu8Bz28loQKmrXa1Q1/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&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;1931번: 회의실 배정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;(1,4), (5,7), (8,11), (12,14) 를 이용할 수 있다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2778&quot; data-origin-height=&quot;1344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjBcMx/btsfpevYzOn/i1gPckXn3cYoidDHQ8K3QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjBcMx/btsfpevYzOn/i1gPckXn3cYoidDHQ8K3QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjBcMx/btsfpevYzOn/i1gPckXn3cYoidDHQ8K3QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjBcMx%2FbtsfpevYzOn%2Fi1gPckXn3cYoidDHQ8K3QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2778&quot; height=&quot;1344&quot; data-origin-width=&quot;2778&quot; data-origin-height=&quot;1344&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;&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;b&gt;  접근 방법&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회의 일정에는 시작 시간과 종료 시간이 존재합니다.&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;span style=&quot;color: #1a5490;&quot;&gt;종료 시간 기준으로 오름차순 정렬하여 종료 시간이 빠른 일정부터 해결해나가면 정답에 접근&lt;/span&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 회의 일정을 저장할 수 있는 클래스 선언 후 Comparable 구현하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 사용하는 풀이도 많았지만 가장 직관적이고 자바라는 언어 특성을 사용할 수 있도록 Meeting 클래스를 만들어 객체의 비교 기준을 정의할 수 있는 Comparable를 구현해 줬습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1684049256814&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Meeting implements Comparable&amp;lt;Meeting&amp;gt; {
    int start;
    int end;

    public Meeting(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public int compareTo(Meeting obj) {
        // 종료 시간이 동일하다면 시작 시점이 빠른 순으로 정렬
        if (this.end == obj.end) {
            return this.start - obj.start;
        }
        // 종료 시점이 빠른 순으로 회의 일정을 정렬
        return this.end - obj.end;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start와 end는 회의의 시작 시간과 종료 시간이고, Comparable을 구현하기 위한 compareTo() 메소드를 보면 정렬 조건을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서는 정렬 조건을 종료 시간만을 언급했지만, 문제에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;회의의 시작시간과 끝나는 시간이 같을 수도 있다.&quot;&lt;/span&gt;란 조건이 존재하기 때문에 회의 시작 시간도 신경 써줘야 합니다. 반례를 한번 생각해 볼까요&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[입력]&lt;/b&gt;&lt;br /&gt;4&lt;br /&gt;3 5&lt;br /&gt;1 3&lt;br /&gt;8 8&lt;br /&gt;5 8&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 만약 입력이 들어왔다고 했을 때, 정답이 몇일까요? 정답은 4입니다. 하지만 회의의 종료 시간만을 고려해서 정렬하게 되면 3이라는 결과를 얻을 수도 있습니다. 어떻게 3이 나올 수 있었을까요. 회의의 종료시간을 기준으로 정렬을 해보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Meeting A(1, 3) -&amp;gt; Meeting B(3, 5) -&amp;gt; Meeting C(8, 8) -&amp;gt; Meeting D(5, 8)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회의의 종료 시간을 오름차순으로 정렬했습니다. 이제 처음 데이터부터 확인하면서 회의 일정을 확인해 보면 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1시 - 3시 : Meeting A&lt;br /&gt;3시 - 5시 : Meeting B&lt;br /&gt;8시 - 8시 : Meeting C&lt;br /&gt;&lt;br /&gt;Meeting D의 시작 시간인 5시가 이전 미팅 Meeting C의 종료 시간 8시보다 작기 때문에 이어서 회의를 진행할 수 없습니다.&lt;/blockquote&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 입력받은 데이터로 Meeting 인스턴스 만들어서 PriorityQueue에 넣기&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1684050474220&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
PriorityQueue&amp;lt;Meeting&amp;gt; meetings = new PriorityQueue&amp;lt;&amp;gt;();
int n = Integer.parseInt(br.readLine());
for (int i = 0; i &amp;lt; n; i++) {
    st = new StringTokenizer(br.readLine());
    int start = Integer.parseInt(st.nextToken());
    int end = Integer.parseInt(st.nextToken());
    meetings.offer(new Meeting(start, end));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Meeting 인스턴스를 저장해서 바로 우리가 정의한 Comparable의 정렬 조건대로 정렬하기 위해서 &lt;span style=&quot;color: #1a5490;&quot;&gt;우선순위 큐(Priority Queue)를 사용&lt;/span&gt;했습니다. 물론 ArrayList를 사용해서 Collections.sort()를 사용해 줘도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Priority Queue의 Meeting 객체를 하나씩 꺼내오면서 이전 Meeting의 종료 시간과 비교하여 정답을 확인&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1684050836956&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Meeting prevMeeting = meetings.poll();
int answer = 1; // 가장 처음 회의 일정은 추가되기 때문에 1부터 시작
while (!meetings.isEmpty()) {
    Meeting meeting = meetings.poll();
    // 현재 회의 시작 시간이 이전 회의 일정의 종료 시간과 동일하거나 크면 정답에 추가
    if (prevMeeting.end &amp;lt;= meeting.start) {
        answer++;
        // 현재 회의를 이전 회의로 지정
        prevMeeting = meeting;
    }
}
System.out.println(answer);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prevMeeting(이전 회의)에 가장 우선순위가 높은 회의 일정을 하나 넣어줍니다. N은 1 이상이기 때문에 비어있는 검사를 하지 않고 바로 poll()을 사용해서 넣어줬습니다. 방금 꺼내준 회의 일정은 바로 추가되기 때문에 answer는 1로 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이제 Priority Queue가 비워질 때까지 반복문을 돌면서 회의 일정을 확인합니다. 만약 이전 회의의 종료 시간보다 현재 회의의 시작 시간이 크거나 같다면 정답 카운트를 증가시키고, prevMeeting를 다시 현재 회의 meeting으로 갱신합니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;zwj;  전체 코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1684051045526&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        PriorityQueue&amp;lt;Meeting&amp;gt; meetings = new PriorityQueue&amp;lt;&amp;gt;();
        int n = Integer.parseInt(br.readLine());
        for (int i = 0; i &amp;lt; n; i++) {
            st = new StringTokenizer(br.readLine());
            int start = Integer.parseInt(st.nextToken());
            int end = Integer.parseInt(st.nextToken());
            meetings.offer(new Meeting(start, end));
        }
        Meeting prevMeeting = meetings.poll();
        int answer = 1; // 가장 처음 회의 일정은 추가되기 때문에 1부터 시작
        while (!meetings.isEmpty()) {
            Meeting meeting = meetings.poll();
            // 현재 회의 시작 시간이 이전 회의 일정의 종료 시간과 동일하거나 크면 정답에 추가
            if (prevMeeting.end &amp;lt;= meeting.start) {
                answer++;
                // 현재 회의를 이전 회의로 지정
                prevMeeting = meeting;
            }
        }
        System.out.println(answer);
    }
}

class Meeting implements Comparable&amp;lt;Meeting&amp;gt; {
    int start;
    int end;

    public Meeting(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public int compareTo(Meeting obj) {
        // 종료 시간이 동일하다면 시작 시점이 빠른 순으로 정렬
        if (this.end == obj.end) {
            return this.start - obj.start;
        }
        // 종료 시점이 빠른 순으로 회의 일정을 정렬
        return this.end - obj.end;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category> &amp;zwj;  Dev/알고리즘</category>
      <category>그리디 알고리즘</category>
      <category>회의실 배정 자바</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/62</guid>
      <comments>https://hoonsb.tistory.com/62#entry62comment</comments>
      <pubDate>Sun, 14 May 2023 17:05:51 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] CPU 스케줄링 알고리즘</title>
      <link>https://hoonsb.tistory.com/61</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 개요&lt;/b&gt;&lt;/h2&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;프로세스가 여러 개라면 어떤 프로세스에게 먼저 CPU 소유권을 제공해야 할까요? 이런 고민 속에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;CPU의 이용률은 높이고, 프로세스의 대기 시간은 줄이면서, 응답 시간은 짧게 하기 위해&lt;/span&gt;서 나온 것이 CPU 스케줄링 알고리즘입니다.&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;1626&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdjhVA/btsex34mgmZ/SI7OC7Uwmpwpo8cHLsk8c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdjhVA/btsex34mgmZ/SI7OC7Uwmpwpo8cHLsk8c0/img.png&quot; data-alt=&quot;CPU 스케줄링 알고리즘의 7가지 종류 (이외에도 더 존재하지만 여기서는 이정도만)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdjhVA/btsex34mgmZ/SI7OC7Uwmpwpo8cHLsk8c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdjhVA%2Fbtsex34mgmZ%2FSI7OC7Uwmpwpo8cHLsk8c0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1626&quot; height=&quot;584&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CPU 스케줄링 알고리즘의 7가지 종류 (이외에도 더 존재하지만 여기서는 이정도만)&lt;/figcaption&gt;
&lt;/figure&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;CPU 스케줄링 알고리즘은 크게 비선점형과 선점형으로 나눌 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;비선점형 방식&lt;/span&gt;에는 FCFS(First Come, First Served), SJF(Shortest Job First), 우선순위 알고리즘이 존재합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;선점형 방식&lt;/span&gt;에는 Round Robin(RR), SRT(Shortest Remaining Time), 다단계 큐, 다단계 피드백 큐 방식이 존재합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 비선점형 방식 (non-preemptive)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;li&gt;다시 말해서 &lt;span style=&quot;color: #1a5490;&quot;&gt;CPU가 실행되는 도중 중간에 강제로 프로세스를 중지하지 않는 방식&lt;/span&gt;을 말합니다.&lt;/li&gt;
&lt;li&gt;Context Switching이 적게 일어나기 때문에 부하가 적다는 특징이 있습니다.&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) FCFS (First Come First Served)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이름 그대로 Ready Queue에 &lt;span style=&quot;color: #1a5490;&quot;&gt;먼저 들어온 순서대로 처리하는 알고리즘&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;먼저 요청한 프로세스부터 CPU를 할당하여 처리합니다.&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;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uDomW/btsex2LbYWe/Z9JImxiW4ZI3TpeWKZwtn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uDomW/btsex2LbYWe/Z9JImxiW4ZI3TpeWKZwtn0/img.png&quot; data-alt=&quot;Process 1이 처리되는 오랜 시간동안 기다리는 다른 프로세스들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uDomW/btsex2LbYWe/Z9JImxiW4ZI3TpeWKZwtn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuDomW%2Fbtsex2LbYWe%2FZ9JImxiW4ZI3TpeWKZwtn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;472&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Process 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) SJF (Shortest Job First)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;FCFS에서 발생하는 호위 효과를 방지할 수 있는 알고리즘입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;CPU를 점유하는 시간이 짧은 프로세스부터 실행&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;호위 효과는 피할 수 있지만, 긴 처리 시간을 요구하는 프로세스가 실행되지 않는 &lt;span style=&quot;color: #ee2323;&quot;&gt;기아현상(Starvation)&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;1486&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SPeaz/btseyMgoKL4/xcKVAPxQO0k2kPDaxaqGek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SPeaz/btseyMgoKL4/xcKVAPxQO0k2kPDaxaqGek/img.png&quot; data-alt=&quot;파란색 Process가 처리된 후 빨간색이 들어오고, 그 다음으로 노란색이 들어와서 처리되는 상황 (초록색이 실행되지 않는 기아현상)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SPeaz/btseyMgoKL4/xcKVAPxQO0k2kPDaxaqGek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSPeaz%2FbtseyMgoKL4%2FxcKVAPxQO0k2kPDaxaqGek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;707&quot; height=&quot;422&quot; data-origin-width=&quot;1486&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파란색 Process가 처리된 후 빨간색이 들어오고, 그 다음으로 노란색이 들어와서 처리되는 상황 (초록색이 실행되지 않는 기아현상)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 우선순위 알고리즘&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;우선순위를 부여하고, 그 우선순위대로 프로세스를 처리하는 알고리즘&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;만약 2개의 프로세스가 우선순위가 동일하다면 먼저 Ready Queue에 들어온 순서대로 처리합니다.&lt;/li&gt;
&lt;li&gt;위에서 설명한 FCFS, SJF 알고리즘도 우선순위 알고리즘에 포함시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;우선순위 알고리즘에서 발생할 수 있는 위험 요소는 위에서 말했던 &lt;span style=&quot;color: #1a5490;&quot;&gt;&quot;기아현상(Starvation)&quot;&lt;/span&gt;이 있습니다.&lt;/li&gt;
&lt;li&gt;기아현상을 해결하기 위해서 오랫동안 대기상태였던 프로세스의 우선순위를 점차 증가시키는 &lt;span style=&quot;color: #ee2323;&quot;&gt;Aging(에이징) 기법&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 선점형 방식&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선점형은 &lt;span style=&quot;color: #1a5490;&quot;&gt;현재 실행 중인 프로세스를 강제로 중단시켜 다른 프로세스에게 CPU 소유권을 할당&lt;/span&gt;하는 방식입니다.&lt;/li&gt;
&lt;li&gt;비선점형에 비해 Context Switching이 빈번하게 발생한다는 특징이 있습니다.&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) Round Robin(RR, 라운드 로빈)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;FCFS 알고리즘에 &lt;span style=&quot;color: #1b711d;&quot;&gt;Time Slice(타임 슬라이스)&lt;/span&gt; 개념을 추가한 알고리즘으로 &lt;span style=&quot;color: #1a5490;&quot;&gt;현대적인 CPU 스케줄링 방법&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;정해진 타임 슬라이스만큼 프로세스에게 CPU 소유권을 줘서 처리하는 방식&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;정해진 타임 슬라이스 만큼 CPU를 선점하여 프로세스를 처리하고, 만약 타임 슬라이스가 지났는데도 아직 처리되지 못했다면 Ready Queue의 가장 뒤쪽에 삽입됩니다.&lt;/li&gt;
&lt;li&gt;타임 슬라이스가 너무 크다면 FCFS와 다를 것이 없어 호위 효과가 발생할 수 있고, 타임 슬라이스가 너무 작다면 Context Switching이 너무 빈번하게 발생하여 오버헤드가 증가되는 현상이 발생할 수 있습니다.&lt;/li&gt;
&lt;li&gt;적당한 타임 슬라이스를 정하는 것이 중요한 알고리즘입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Time Slice&lt;/b&gt; : 각 프로세스가 CPU를 사용할 수 있는 정해진 시간을 말합니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) SRT (Shortest Remaining Time)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;Rround Robin 알고리즘에 SJF 알고리즘을 합친 알고리즘&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;SRT에서는 프로세스가 정해진 시간(타임 슬라이스)만큼 CPU의 소유권을 얻어 처리될 수 있습니다. (Round Robin)&lt;/li&gt;
&lt;li&gt;이때 CPU의 소유권을 얻는 우선순위는 남은 처리 시간이 적은 순으로 결정됩니다. (SJF)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 다단계 큐 스케줄링 (Multilevel Queue 스케줄링)&lt;/b&gt;&lt;/h4&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;color: #1a5490;&quot;&gt;우선순위별로 Ready Queue를 여러개 사용하는 방식&lt;/span&gt;의 스케줄링 알고리즘입니다.&lt;/li&gt;
&lt;li&gt;우선순위가 낮은 Queue에 있는 프로세스부터 모두 처리하고, 그다음 우선순위의 Queue에 있는 프로세스를 처리합니다.&lt;/li&gt;
&lt;li&gt;각 Ready Queue는 사용하는 알고리즘이 다를 수 있습니다.&lt;/li&gt;
&lt;li&gt;대표적인 특징으로는 &lt;span style=&quot;color: #1a5490;&quot;&gt;Queue에서 Queue 사이에 프로세스 이동은 불가능&lt;/span&gt;하기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;Starvation(기아현상)이 발생&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(4) 다단계 피드백 큐 스케줄링 (Multilevel Feedback Queue 스케줄링)&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&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;color: #000000;&quot;&gt;다단계 큐 스케줄링 방식에서 발전시킨 알고리즘입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;Queue와 Queue 사이에 프로세스 이동이 가능&lt;/span&gt;하고, &lt;span style=&quot;color: #1a5490;&quot;&gt;Aging 기법도 적용할 수 있는&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;1234&quot; data-origin-height=&quot;1118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eH3r3n/btseAcrS1Eb/xxzpPjOfAAyrpBCvSXaHx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eH3r3n/btseAcrS1Eb/xxzpPjOfAAyrpBCvSXaHx1/img.png&quot; data-alt=&quot;다단계 피드백 큐 스케줄링 방식의 프로세스 처리 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eH3r3n/btseAcrS1Eb/xxzpPjOfAAyrpBCvSXaHx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeH3r3n%2FbtseAcrS1Eb%2FxxzpPjOfAAyrpBCvSXaHx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1118&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;1118&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;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;위 과정에서 우선순위가 낮은 CPU 집중 프로세스들에서 Starvation이 발생할 수 있기 때문에 Aging 기법을 적용하여 이런 문제점을 보완합니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  면접에서 나올 수 있는 질문&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CPU 스케줄링 알고리즘의 종류와 특징을 설명하고, 장점과 단점에 대해서 설명해 보세요.&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;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;a style=&quot;color: #ffffff; text-align: start;&quot; href=&quot;http://www.yes24.com/Product/Goods/108887922?pid=123487&amp;amp;cosemkid=go16505206189583564&amp;amp;gclid=Cj0KCQjw6cKiBhD5ARIsAKXUdyaYETrnD8WkQWtiuyHQIdRN2Aub6gXoiCQ6BBrkz4i2L2x64h3m-zQaAqc2EALw_wcB&quot;&gt;면접을 위한 CS 전공지식 노트&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=isj4sZhoxjk&amp;amp;list=PLYH7OjNUOWLUz15j4Q9M6INxK5J3-59GC&amp;amp;index=2&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Computer Science/운영체제</category>
      <category>CPU 스케줄링 알고리즘</category>
      <category>비선점형 방식</category>
      <category>선점형 방식</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/61</guid>
      <comments>https://hoonsb.tistory.com/61#entry61comment</comments>
      <pubDate>Tue, 9 May 2023 21:06:47 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] 프로세스의 메모리 구조 (JVM 메모리 구조)</title>
      <link>https://hoonsb.tistory.com/60</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 프로세스의 메모리 구조&lt;/b&gt;&lt;/h2&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-origin-width=&quot;636&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pe0mV/btsdH2XNlBj/EzaJxMN7XkWgmKHAHCycK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pe0mV/btsdH2XNlBj/EzaJxMN7XkWgmKHAHCycK1/img.png&quot; data-alt=&quot;프로세스의 메모리 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pe0mV/btsdH2XNlBj/EzaJxMN7XkWgmKHAHCycK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPe0mV%2FbtsdH2XNlBj%2FEzaJxMN7XkWgmKHAHCycK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;403&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;412&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;color: #1a5490;&quot;&gt;스택 영역과 힙 영역은 크기가 동적으로 변하는(화살표가 존재) 구조&lt;/span&gt;를 가지고 있고, &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 스택 영역 (Stack Area)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스택 영역은 &lt;span style=&quot;color: #1a5490;&quot;&gt;함수(메서드)&lt;/span&gt;, 함수에 인자로 들어가는 &lt;span style=&quot;color: #1a5490;&quot;&gt;매개변수(Parameter)&lt;/span&gt;, 함수 안에 있는 &lt;span style=&quot;color: #1a5490;&quot;&gt;지역변수(Local Variable)&lt;/span&gt;가 저장되는 영역입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;컴파일 시에 크기가 결정&lt;/span&gt;되고, &lt;span style=&quot;color: #1a5490;&quot;&gt;동적인 구조&lt;/span&gt;를 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;위 그림에서 볼 수 있듯이 위쪽에 있는 주소부터 할당됩니다.&lt;/li&gt;
&lt;li&gt;스택 영역은 함수를 재귀적으로 호출할 때 크기가 동적으로 늘어날 수 있습니다.&lt;/li&gt;
&lt;li&gt;함수가 호출되면 &lt;span style=&quot;color: #1a5490;&quot;&gt;Stack Frame&lt;/span&gt;이 스택 영역에 생성되고, 해당 Stack Frame 내부에 함수의 매개변수, 지역변수들이 쌓이게 됩니다.&lt;/li&gt;
&lt;li&gt;Java 코드에서 무한적인 재귀 호출이 발생되도록 코드를 잘못 작성하면 볼 수 있는 예외인 StackOverFlow는 &lt;span style=&quot;color: #1a5490;&quot;&gt;제한되어 있는 스택 영역으로 인해 발생하는 예외&lt;/span&gt;입니다.&amp;nbsp;&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;(2) 힙 영역 (Heap Area)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;힙 영역은 &lt;span style=&quot;color: #1a5490;&quot;&gt;동적으로 할당되는 변수가 저장&lt;/span&gt;되는 영역입니다. Java에서는 객체가 Heap 영역에 저장됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;런타임 시에 크기가 결정&lt;/span&gt;되고, &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 데이터 영역&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 영역은 &lt;span style=&quot;color: #1a5490;&quot;&gt;전역변수, 정적변수가 저장&lt;/span&gt;되는 영역입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;정적인 구조&lt;/span&gt;를 가지고 있고, 프로그램이 종료되면 사라지는 변수들이 들어가 있는 영역입니다.&lt;/li&gt;
&lt;li&gt;데이터 영역은 &lt;span style=&quot;color: #1a5490;&quot;&gt;BSS(Block Started by Symbol) 영역&lt;/span&gt;과 &lt;span style=&quot;color: #1a5490;&quot;&gt;Data 영역&lt;/span&gt;으로 나뉘게 됩니다.&lt;/li&gt;
&lt;li&gt;BSS 영역에는 초기화 되지 않은 변수가 저장되는 영역입니다.&lt;/li&gt;
&lt;li&gt;Data 영역에는 다른 값으로 초기화된 변수가 저장되는 영역입니다.&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;(4) 코드 영역&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 영역은 &lt;span style=&quot;color: #1a5490;&quot;&gt;실제 소스 코드가 기계어의 형태로 저장되는 영역&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;정적인 구조&lt;/span&gt;를 가지고 있고, 수정이 불가능한 기계어로 저장되어 있기 때문에 Read Only의 특징을 가지고 있습니다. (수정 불가)&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. JVM(Java Virtual Machine)의 메모리 구조&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java를 학습하는 사람으로서 빼먹고 넘어갈 수 없는 내용이라고 생각되어 추가적으로 기록해보고자 합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;JVM은 운영체제(OS)로 부터 별도의 메모리 공간을 할당받아서 Java Application을 실행하는데 사용&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;이런 JVM의 메모리를 &lt;span style=&quot;color: #1a5490;&quot;&gt;Runtime Data Area&lt;/span&gt;라고 부릅니다.&lt;/li&gt;
&lt;li&gt;Runtime Data Area는 &lt;span style=&quot;color: #1a5490;&quot;&gt;Stack Area, Heap Area, Method Area, PC register, Native Method Stack&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;782&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnk5uN/btsdG9wfkmV/KyBJwZSKhvZBMNhZGZ8rak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnk5uN/btsdG9wfkmV/KyBJwZSKhvZBMNhZGZ8rak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnk5uN/btsdG9wfkmV/KyBJwZSKhvZBMNhZGZ8rak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnk5uN%2FbtsdG9wfkmV%2FKyBJwZSKhvZBMNhZGZ8rak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;782&quot; height=&quot;324&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) Method Area (Static Area)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; &lt;span style=&quot;color: #1a5490;&quot;&gt;클래스 정보&lt;/span&gt;와 &lt;span style=&quot;color: #1a5490;&quot;&gt;클래스 변수(static), 상수(final), 메서드 코드, 런타임 상수 풀&lt;/span&gt; 등의 데이터를 저장하는 영역입니다.&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;한번 로드된 이후 &lt;span style=&quot;color: #1a5490;&quot;&gt;메모리에 항상 상주하고 있는 영역&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) Stack Area&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수(메서드)에서 직접 사용할 수 있는 &lt;span style=&quot;color: #1a5490;&quot;&gt;지역변수(local variable), 매개변수(parameter), 객체의 경우에는 주소값&lt;/span&gt;을 저장합니다.&lt;/li&gt;
&lt;li&gt;함수가 호출되면 Stack Area에 &lt;span style=&quot;color: #1a5490;&quot;&gt;Stack Frame&lt;/span&gt;이 생성되고, 해당 Stack Frame 내부에 지역변수, 매개변수 등이 쌓이게 됩니다.&lt;/li&gt;
&lt;li&gt;함수의 호출이 종료되면 pop으로 해당 Stack Frame이 제거됩니다.&lt;/li&gt;
&lt;li&gt;JVM에서 Stack Area는 스레드마다 하나씩 가지고 있습니다.&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;(3) Heap Area&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;new를 통해서 생성된 객체, 배열&lt;/span&gt; 등이 저장되는 영역입니다.&lt;/li&gt;
&lt;li&gt;JVM의 &lt;span style=&quot;color: #1a5490;&quot;&gt;Statck Area에서 참조가 가능&lt;/span&gt;합니다. Heap Area에 존재하는데, 어떠한 곳에서도 참조를 하고 있지 않다면 메모리를 낭비하고 있기 때문에 GC(Garbage Collector)가 Heap Area에서 지워줍니다.&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;(4) PC(Program Counter) Register&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 생성될 때마다 생성되는 영역으로  &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(5) Native Method Stack&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;Java 코드 이외 다른 언어로 작성된 네이티브 코드를 실행하기 위한 영역&lt;/span&gt;입니다.&lt;/li&gt;
&lt;li&gt;성능 향상을 위해서 Java Application에서 다른 언어와 함께 사용되는 경우가 있습니다. 이때 &lt;span style=&quot;color: #1a5490;&quot;&gt;다른 언어로 작성되어 제공되는 Method의 정보가 저장&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 면접에서 나올 수 있는 질문들&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;JVM의 메모리 구조와 각 영역에 대해서 설명해보세요.&lt;/li&gt;
&lt;li&gt;Garbage Collector가 어떤 역할을 하는지 설명해보세요.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;zwj;♂️ 마무리 하며&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;JVM은 프로세스인가&lt;/span&gt;라는 궁금점이 있었습니다. 글을 정리하고 싶었지만 정확한 근거가 되는 글을 찾을 수 없어서 따로 기록해보지 못했습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;운영체제 위에서 작동되고, 운영체제에 의해서 별도의 메모리 공간을 할당 받기 때문에 프로세스라고 생각은 되는데, 일반적인 프로세스의 메모리 구조와는 다른 구조를 가지고 있고, JVM의 메모리 관리 방식도 일반적인 프로세스와는 다르다고 알고 있습니다.&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;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://honbabzone.com/java/java-jvm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://honbabzone.com/java/java-jvm/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/108887922?pid=123487&amp;amp;cosemkid=go16505206189583564&amp;amp;gclid=Cj0KCQjw6cKiBhD5ARIsAKXUdyaYETrnD8WkQWtiuyHQIdRN2Aub6gXoiCQ6BBrkz4i2L2x64h3m-zQaAqc2EALw_wcB&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;면접을 위한 CS 전공지식 노트&lt;/a&gt;&lt;/p&gt;</description>
      <category>  Computer Science/운영체제</category>
      <category>garbage collector</category>
      <category>JVM 메모리 구조</category>
      <category>메모리 구조</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/60</guid>
      <comments>https://hoonsb.tistory.com/60#entry60comment</comments>
      <pubDate>Tue, 2 May 2023 18:56:24 +0900</pubDate>
    </item>
    <item>
      <title>[백준] 1260번 DFS와 BFS 자바</title>
      <link>https://hoonsb.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DFS 문제 따로 BFS 문제 따로 풀어본 적은 있는데 &lt;span style=&quot;color: #1a5490;&quot;&gt;한 문제에서 동일한 그래프로 DFS와 BFS를 모두 사용해야 하는&lt;/span&gt; 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 링크&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1260&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/1260&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1682662430479&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;1260번: DFS와 BFS&quot; data-og-description=&quot;첫째 줄에 정점의 개수 N(1 &amp;le; N &amp;le; 1,000), 간선의 개수 M(1 &amp;le; M &amp;le; 10,000), 탐색을 시작할 정점의 번호 V가 주어진다. 다음 M개의 줄에는 간선이 연결하는 두 정점의 번호가 주어진다. 어떤 두 정점 사&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/1260&quot; data-og-url=&quot;https://www.acmicpc.net/problem/1260&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1260&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/1260&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;1260번: DFS와 BFS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 정점의 개수 N(1 &amp;le; N &amp;le; 1,000), 간선의 개수 M(1 &amp;le; M &amp;le; 10,000), 탐색을 시작할 정점의 번호 V가 주어진다. 다음 M개의 줄에는 간선이 연결하는 두 정점의 번호가 주어진다. 어떤 두 정점 사&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 째줄에 정점의 개수(N, 노드의 개수)와 간선의 개수(M) 그리고 탐색 시작 정점(V)이 공백으로 주어집니다.&lt;/li&gt;
&lt;li&gt;그다음 간선의 개수(M)만큼의 입력이 주어지고, 각 입력 줄에는 연결되어 있는 Node와 Node가 공백으로 주어집니다.&lt;/li&gt;
&lt;li&gt;정점의 번호는 1번부터 N번까지 이며, DFS 탐색 결과와 BFS 탐색 결과를 출력해 주면 되는 문제입니다.&lt;/li&gt;
&lt;li&gt;여기서 신경써줘야 할 점은 &lt;span style=&quot;color: #1a5490;&quot;&gt;연결된 Node가 여러 개라면 크기가 작은 것부터 탐색&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; ️ 문제 풀이 코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1682662757592&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    static int n;
    static StringBuilder builder;
    static boolean[] visited;

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        String[] inputData = reader.readLine().split(&quot; &quot;);
        n = Integer.parseInt(inputData[0]);
        int m = Integer.parseInt(inputData[1]);
        int v = Integer.parseInt(inputData[2]);

        // step 1. 그래프 만들기
        int[][] graph = new int[n + 1][n + 1];
        for (int i = 0; i &amp;lt; m; i++) {
            inputData = reader.readLine().split(&quot; &quot;);
            int startNum = Integer.parseInt(inputData[0]);
            int endNum = Integer.parseInt(inputData[1]);

            graph[startNum][endNum] = 1;
            graph[endNum][startNum] = 1;
        }
        builder = new StringBuilder();
        visited = new boolean[n + 1];
        dfs(v, graph);
        builder.append(&quot;\n&quot;);
        visited = new boolean[n + 1];
        bfs(v, graph);
        System.out.println(builder);
    }

    // step 2. BFS 탐색 (Queue)
    public static void bfs(int start, int[][] graph) {
        Queue&amp;lt;Integer&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();
        queue.offer(start);
        visited[start] = true;

        while (!queue.isEmpty()) {
            int vortex = queue.poll();
            builder.append(vortex).append(&quot; &quot;);
            int[] vortexWithNodes = graph[vortex];

            for (int i = 1; i &amp;lt;= n; i++) {
                if (vortexWithNodes[i] == 1 &amp;amp;&amp;amp; !visited[i]) {
                    queue.offer(i);
                    visited[i] = true;
                }
            }
        }
    }

    // step 3. DFS 탐색 (재귀)
    public static void dfs(int start, int[][] graph) {
        visited[start] = true;
        builder.append(start).append(&quot; &quot;);
        int[] startWithNodes = graph[start];

        for (int i = 1; i &amp;lt;= n; i++) {
            if (startWithNodes[i] == 1 &amp;amp;&amp;amp; !visited[i]) {
                dfs(i, graph);
            }
        }
    }
}&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;처음에 Graph를 어떤 방식으로 해놓을지 고민했었습니다. 첫 번째 방법은 위에 구현한 방법과 같은 &lt;span style=&quot;color: #1a5490;&quot;&gt;인접 행렬 방법&lt;/span&gt;을 사용하는 방법이 있고, 두 번째는 각 노드에 연결된 점들을 리스트에 모아두는 &lt;span style=&quot;color: #1a5490;&quot;&gt;인접 리스트 방법&lt;/span&gt;이 존재합니다.&lt;/li&gt;
&lt;li&gt;인접 행렬 방법을 선택한 이유는 간단합니다. &lt;span style=&quot;color: #1a5490;&quot;&gt;&quot;연결된 노드가 여러 개라면 크기가 작은 노드부터 방문합니다.&quot;라는 조건에서 연결된 Node들이 정렬되어 있어야 된다는&lt;/span&gt; 것 때문입니다.&lt;/li&gt;
&lt;li&gt;만약 인접 리스트 방법을 사용했다면, 연결된 노드들을 정렬해 주는 Collections.sort()를 사용해야 됐습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;zwj;  문제 풀이 전략&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. BFS는 Queue를 이용해서 구현했습니다.&lt;/h4&gt;
&lt;pre id=&quot;code_1682663286355&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void bfs(int start, int[][] graph) {
    Queue&amp;lt;Integer&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();
    queue.offer(start);
    visited[start] = true;

    while (!queue.isEmpty()) {
        int vortex = queue.poll();
        builder.append(vortex).append(&quot; &quot;);
        int[] vortexWithNodes = graph[vortex];

        for (int i = 1; i &amp;lt;= n; i++) {
            if (vortexWithNodes[i] == 1 &amp;amp;&amp;amp; !visited[i]) { // &amp;lt;-- 이미 탐색한 노드인지 확인
                queue.offer(i);
                visited[i] = true;
            }
        }
    }
}&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;큐(Queue)를 통해서 탐색 노드에 연결된 노드들을 모두 탐색하고, 탐색이 가능한 데이터(값이 1이고, 방문기록이 false인 노드)라면 Queue에 모두 넣습니다.&lt;/li&gt;
&lt;li&gt;이때 Queue에 넣으면서 해당 노드의 방문처리를 해줘야 합니다.&lt;/li&gt;
&lt;li&gt;Queue에 데이터가 없을 때까지 반복하여 BFS 탐색을 진행합니다.&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. DFS는 재귀를 이용해서 구현했습니다.&lt;/h4&gt;
&lt;pre id=&quot;code_1682663316385&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void dfs(int start, int[][] graph) {
    visited[start] = true;
    builder.append(start).append(&quot; &quot;);
    int[] startWithNodes = graph[start];

    for (int i = 1; i &amp;lt;= n; i++) {
        if (startWithNodes[i] == 1 &amp;amp;&amp;amp; !visited[i]) { // &amp;lt;-- 이미 탐색한 노드인지 확인
            dfs(i, graph);
        }
    }
}&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;/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;3. Graph와 Visited는 공유하는 데이터입니다.&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래프 탐색을 하는데 있어서 Graph의 내부 값을 변경시키지는 않습니다.&lt;/li&gt;
&lt;li&gt;하지만 Visited는 값을 false에서 true로 변경시키며 탐색하기 때문에 static으로 선언해서 &lt;span style=&quot;color: #1a5490;&quot;&gt;탐색 시작 전에 new boolean[]으로 초기화&lt;/span&gt;를 진행해줬습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1682663264021&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;visited = new boolean[n + 1]; // &amp;lt;-- 탐색 전 초기화
dfs(v, graph);

visited = new boolean[n + 1]; // &amp;lt;-- 탐색 전 초기화
bfs(v, graph);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/알고리즘</category>
      <category>BFS와 DFS java</category>
      <category>백준 1260번 자바</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/59</guid>
      <comments>https://hoonsb.tistory.com/59#entry59comment</comments>
      <pubDate>Fri, 28 Apr 2023 15:33:27 +0900</pubDate>
    </item>
    <item>
      <title>[오류해결] IntelliJ 커스텀 properties 자동완성 안되는 오류</title>
      <link>https://hoonsb.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;크리티컬 한 오류는 아니지만 IntelliJ에서 작동이 안 되길래 찾아본 오류입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제발생&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;@ConfigurationProperties(prefix = &quot;spring.thymeleaf&quot;)&lt;/span&gt;를 사용하여 사용자 정의 프로퍼티(spring.thymeleaf3)를 만드는 코드를 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 main 메서드가 있는 클래스에서 해당 프로퍼티를 스캔할 수 있도록 @ConfigurationPropertiesScan도 붙여줬습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1682587354920&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*  앞 코드 생략  */

@Getter
@RequiredArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = &quot;spring.thymeleaf3&quot;)
public static class Thymeleaf3Properties {
    /**
     * Thymeleaf 3 Decoupled Logic 기능의 활성화
     */
    private final boolean decoupledLogic;
}

/*  뒤 코드 생략  */&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;그리고 나서 사용자 정의 프로퍼티에 대한 지원을 해주는 의존성인 configuration-processor도 추가해 줬습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1682587605142&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle

dependencies {
    /* 생략 */
    
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}&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;위 의존성을 추가해 주고, gradle을 다시 build 했습니다. 그리고 나서 application.properties에 들어가서 사용자 정의한 &lt;span style=&quot;color: #1a5490;&quot;&gt;&quot;spring.thymeleaf3&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;&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;b&gt; &amp;zwj;♂️ 시도 &amp;amp; 해결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링에 들어갔습니다. 주요 검색 키워드는 configuration-processor is not working...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) Clean 하고 다시 Build를 해보자.&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b65JPy/btscZUfuPK2/K0pFhvcvZWskTjvKXM1j5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b65JPy/btscZUfuPK2/K0pFhvcvZWskTjvKXM1j5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b65JPy/btscZUfuPK2/K0pFhvcvZWskTjvKXM1j5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb65JPy%2FbtscZUfuPK2%2FK0pFhvcvZWskTjvKXM1j5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;332&quot; height=&quot;756&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;756&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;수도 없이 해봤지만 작동하지 않았습니다. IntelliJ 캐시 문제인가..?라는 생각이 들어서 캐시를 비우고, IDE를 재시작하고 여러 방법을 시도해 봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brbdv1/btscVlyFMuN/jLeURK0ntfT7DA13Icifxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brbdv1/btscVlyFMuN/jLeURK0ntfT7DA13Icifxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brbdv1/btscVlyFMuN/jLeURK0ntfT7DA13Icifxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrbdv1%2FbtscVlyFMuN%2FjLeURK0ntfT7DA13Icifxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;326&quot; height=&quot;413&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;1036&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 사용자 정의 프로퍼티를 만들면 spring-configuration-metadata.json 파일이 생성된다는 글을 발견&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디에 생성되는지 찾아보니 아래와 같은 경로에 생성되었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;build/classes/java/main/&lt;b&gt;META-INF/spring-configuration-metadata.json&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json 파일로 생성되고, 우리가 Java Doc으로 작성한 내용도 저장되며, 해당 프로퍼티에 대한 메타 데이터가 들어가 있는 파일이었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1682588033436&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;groups&quot;: [
    {
      &quot;name&quot;: &quot;spring.thymeleaf3&quot;,
      &quot;type&quot;: &quot;com.fastcampus.getinline.config.ThymeleafConfig$Thymeleaf3Properties&quot;,
      &quot;sourceType&quot;: &quot;com.fastcampus.getinline.config.ThymeleafConfig$Thymeleaf3Properties&quot;
    }
  ],
  &quot;properties&quot;: [
    {
      &quot;name&quot;: &quot;spring.thymeleaf3.decoupled-logic&quot;,
      &quot;type&quot;: &quot;java.lang.Boolean&quot;,
      &quot;description&quot;: &quot;Thymeleaf 3 Decoupled Logic 기능의 활성화&quot;,
      &quot;sourceType&quot;: &quot;com.fastcampus.getinline.config.ThymeleafConfig$Thymeleaf3Properties&quot;,
      &quot;defaultValue&quot;: false
    }
  ],
  &quot;hints&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;즉, Build는 정상적으로 되었고 메타 데이터도 정상적으로 들어왔다는 것이 확인되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 문제는 하나 IntelliJ에서 해당 메타 데이터를 인식하지 못한다는 점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) IntelliJ Project Setting 변경&lt;/b&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;&lt;a href=&quot;https://youtrack.jetbrains.com/issue/IDEA-191886&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://youtrack.jetbrains.com/issue/IDEA-191886&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1682588157535&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;spring-configuration-metadata.json ignored in Gradle &amp;quot;build&amp;quot; directory : IDEA-191886&quot; data-og-description=&quot;I am trying to get IDEA to recognize custom properties in my Spring Boot application. The required metadata file is generated by Gradle in &amp;quot;build/classes/java/main/META-INF/spring-configuration-metadata.json&amp;quot; but IDEA does not seem to recognise it. As soon&quot; data-og-host=&quot;youtrack.jetbrains.com&quot; data-og-source-url=&quot;https://youtrack.jetbrains.com/issue/IDEA-191886&quot; data-og-url=&quot;https://youtrack.jetbrains.com/issue/IDEA-191886&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://youtrack.jetbrains.com/issue/IDEA-191886&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://youtrack.jetbrains.com/issue/IDEA-191886&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;spring-configuration-metadata.json ignored in Gradle &quot;build&quot; directory : IDEA-191886&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I am trying to get IDEA to recognize custom properties in my Spring Boot application. The required metadata file is generated by Gradle in &quot;build/classes/java/main/META-INF/spring-configuration-metadata.json&quot; but IDEA does not seem to recognise it. As soon&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;youtrack.jetbrains.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제목부터가 &quot;spring-configuration-metadata.json ignored in Gradle &quot;build&quot; directory&quot;이어서 뭔가 내가 찾던 문제와 같은 문제를 겪고 있는 거 같았습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;The required metadata file is generated by Gradle in &quot;build/classes/java/main/META-INF/spring-configuration-metadata.json&quot; &lt;b&gt;but IDEA does not seem to recognise it&lt;/b&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;해당 글 중에서 제가 말했던 경로에 metadata.json이 만들어는 졌는데 IDEA가 그걸 인지하지 못한다는 질문이었습니다. 역시... 구글링은 영어로 하는 것이 정답인가... 영어를....&lt;/p&gt;
&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;IntelliJ에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;Project Structrue&lt;/span&gt;에 들어갑니다. 맥에서는 {Command + ;}를 누르면 들어가집니다. 위 Tool bar를 통해서도 들어갈 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6nuk5/btscZrxPSvN/mSSplmkogbMkmE1pBzWrS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6nuk5/btscZrxPSvN/mSSplmkogbMkmE1pBzWrS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6nuk5/btscZrxPSvN/mSSplmkogbMkmE1pBzWrS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6nuk5%2FbtscZrxPSvN%2FmSSplmkogbMkmE1pBzWrS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;436&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;436&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;Modules -&amp;gt; main -&amp;gt; Paths를 눌러보면 아래와 같은 화면이 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4BpSw/btscZtCunLW/CEqeO1T5NnfrgD88vIUPh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4BpSw/btscZtCunLW/CEqeO1T5NnfrgD88vIUPh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4BpSw/btscZtCunLW/CEqeO1T5NnfrgD88vIUPh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4BpSw%2FbtscZtCunLW%2FCEqeO1T5NnfrgD88vIUPh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;478&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;478&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;Output path를 보면 지금 out/production/classes로 되어 있는데, 이것을 Metadata.json이 있던 경로로 변경해 줬습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2sRRr/btscYMJeCsz/AzCKjUYAfikOpTBPTA5Pa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2sRRr/btscYMJeCsz/AzCKjUYAfikOpTBPTA5Pa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2sRRr/btscYMJeCsz/AzCKjUYAfikOpTBPTA5Pa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2sRRr%2FbtscYMJeCsz%2FAzCKjUYAfikOpTBPTA5Pa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;207&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;434&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;color: #ee2323;&quot;&gt;out/production/classes -&amp;gt; build/classes/java/main&lt;/span&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;그리고 나서 Gradle clean을 하고 다시 build를 해주고 나서 application.properties에서 사용자 정의한 &quot;spring.thymeleaf3&quot;이 자동완성이 되는지 해봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR9eBr/btscZShDddT/Tz0qmQvQYoy8AzIxIZagg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR9eBr/btscZShDddT/Tz0qmQvQYoy8AzIxIZagg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR9eBr/btscZShDddT/Tz0qmQvQYoy8AzIxIZagg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR9eBr%2FbtscZShDddT%2FTz0qmQvQYoy8AzIxIZagg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;659&quot; height=&quot;187&quot; data-origin-width=&quot;1288&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우와...됐다... 이전에는 자동완성이 나오지 않았었는데, spring.만 쳐도 thymeleaf3가 나오는 것을 볼 수 있습니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;zwj;♂️ 알게 된 점 &amp;amp; 느낀 점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구글링을 한글로 막 검색하다 보니 자료도 많이 안 나왔던 거 같습니다.&lt;/li&gt;
&lt;li&gt;해당 문제가 뭐 때문에 발생하는지 판단을 먼저 해야 정확한 구글링이 될 수 있다는 것을 느꼈습니다.&lt;/li&gt;
&lt;li&gt;저는 해당 문제가 Spring configuration processor의 문제라고 생각하고, 그쪽으로 검색을 엄청했었는데, IntelliJ의 문제일 수도 있겠다는 생각으로 검색을 하니 방법을 찾을 수 있었다고 생각합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  참고자료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation&quot;&gt;https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1682589181961&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;Configuration Metadata&quot; data-og-description=&quot;Configuration metadata files are located inside jars under META-INF/spring-configuration-metadata.json. They use a JSON format with items categorized under either &amp;ldquo;groups&amp;rdquo; or &amp;ldquo;properties&amp;rdquo; and additional values hints categorized under &amp;quot;hints&amp;quot;, as sh&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.automatic-metadata-generation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Configuration Metadata&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Configuration metadata files are located inside jars under META-INF/spring-configuration-metadata.json. They use a JSON format with items categorized under either &amp;ldquo;groups&amp;rdquo; or &amp;ldquo;properties&amp;rdquo; and additional values hints categorized under &quot;hints&quot;, as sh&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/오류해결</category>
      <category>@configurationProperties</category>
      <category>application.properties 자동완성</category>
      <category>configuration-processor</category>
      <category>IntelliJ annotation processor</category>
      <category>spring-configuration-metadata.json</category>
      <category>인텔리제이 프로퍼티 자동완성 안됨</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/58</guid>
      <comments>https://hoonsb.tistory.com/58#entry58comment</comments>
      <pubDate>Thu, 27 Apr 2023 18:51:02 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 불변 객체(Immutable Object)와 Record</title>
      <link>https://hoonsb.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;평소에 늘 JDK11 버전을 사용해서 모르고 있었는데, 최근 JDK16으로 버전을 바꿔보면서 새롭게 사용해 본 기능이 있어서 기록해 볼까 합니다. 추가로 평소에 궁금했었던 불변 객체에 대해서도 조금 찾아서 기록해보려고 합니다.&lt;/p&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;b&gt;  불변 객체(Immutable Object)란?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;생성 후 그 상태를 바꿀 수 없는 객체&lt;/span&gt;를 의미합니다.&lt;/li&gt;
&lt;li&gt;불변객체는 읽을 수만 있는 Read-Only 메서드만을 제공합니다.&lt;/li&gt;
&lt;li&gt;Java의 대표적인 불변객체로는 String이 있습니다.&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;# String Class Description&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;89&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HB53c/btscGyDZxyS/x1YdxdE33Rtowh2Noxkqb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HB53c/btscGyDZxyS/x1YdxdE33Rtowh2Noxkqb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HB53c/btscGyDZxyS/x1YdxdE33Rtowh2Noxkqb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHB53c%2FbtscGyDZxyS%2Fx1YdxdE33Rtowh2Noxkqb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;89&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;89&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;그럼 String은 생성된 후 값을 변경할 수 없다는건가? 그럼 아래 코드는 무엇인가? 값을 변경하는 것이 아닌가?&lt;/p&gt;
&lt;pre id=&quot;code_1682404757223&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {
    public static void main(String[] args) {
        String stringEx = &quot;Hello&quot;;
        System.out.println(stringEx); // Hello
        
        stringEx = &quot;World&quot;;
        System.out.println(stringEx); // World
    }
}&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;stringEx라는 String 객체의 값을 Hello에서 World로 바꾼 것이 아닌가?라는 생각이 들 수 있습니다.&lt;/li&gt;
&lt;li&gt;이는 값을 변경한 것이 아니고 &lt;span style=&quot;color: #1a5490;&quot;&gt;기존에는 String Constant Pool에 있는 &quot;Hello&quot;를 참조하고 있다가 &quot;World&quot;로 참조 방향이 변경&lt;/span&gt;된 것 입니다.&amp;nbsp;즉, stringEx의 값이 변경된 것이 아닙니다.&lt;/li&gt;
&lt;li&gt;String 포스팅이 아니기 때문에 &lt;a href=&quot;https://starkying.tistory.com/entry/what-is-java-string-pool&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;해당 링크&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  불변 객체를 만드는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. final 키워드를 이용하자&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;Java에서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;불변성을 확보하기 위해서 final&lt;/span&gt;이라는 키워드를 제공합니다.&lt;/li&gt;
&lt;li&gt;Java의 기본 필드는 가변적이지만 final 키워드를 앞에 붙여주면 &lt;span style=&quot;color: #1a5490;&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;# final 키워드를 이용한 필드 불변성 확보 시 문제점&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;하지만 final 키워드를 사용하더라도 해당 &lt;span style=&quot;color: #1a5490;&quot;&gt;객체 내부의 상태를 변경시키지 못하는 것은 아닙니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;아래의 예시는 List에 final 키워드를 붙여서 불변성을 확보했습니다. 그리고 List에 값을 추가해 보는 예시입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1682405919683&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Ex {
    public static void main(String[] args) {
        WorkSpace workSpace = new WorkSpace(&quot;Test WorkSpace&quot;);
        workSpace.getWorkSpace().add(new Worker(&quot;윤씨&quot;, 27)); // 불변 필드의 내부 상태를 변경
        workSpace.getWorkSpace().add(new Worker(&quot;정씨&quot;, 29)); // 불변 필드의 내부 상태를 변경
        
        workSpace.getWorkers().forEach(worker -&amp;gt; {
            System.out.println(worker);
        });
        // [출력]
        // Worker[김씨, 26]
        // Worker[박씨, 30]
        // Worker[윤씨, 27]
        // Worker[정씨, 29]
    }
}

class WorkSpace {
    // 불변성을 확보하기 위해서 필드를 final로 선언
    private final List&amp;lt;Worker&amp;gt; workers;
    private final String workSpaceName;

    public WorkSpace(String workSpaceName) {
        this.workers = new ArrayList&amp;lt;&amp;gt;(List.of(new Worker(&quot;김씨&quot;, 26), new Worker(&quot;박씨&quot;, 30)));
        this.workSpaceName = workSpaceName;
    }

    // final 필드를 읽기만 하는 Read-Only 메서드 선언
    public List&amp;lt;Worker&amp;gt; getWorkers() {
        return this.workers;
    }
    
    public String getWorkSpaceName() {
        return this.workSpaceName;
    }
}

class Worker {
    private String name;
    private Integer age;

    public Worker(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return &quot;Worker[&quot; + name + &quot;, &quot; + age + &quot;]&quot;;
    }
}&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;위 코드를 보면 알 수 있듯이 WorkSpace Class 내부의 workSpace 필드를 &lt;span style=&quot;color: #1a5490;&quot;&gt;불변성 확보를 위해서 final 키워드로 선언&lt;/span&gt;했습니다.&lt;/li&gt;
&lt;li&gt;그리고 해당 필드를 읽을 수 있는 &lt;span style=&quot;color: #1a5490;&quot;&gt;Getter 메서드를 선언&lt;/span&gt;해줬습니다.&lt;/li&gt;
&lt;li&gt;하지만 외부(Ex Class)에서 final로 불변성을 확보한 필드 내부에 새로운 객체가 계속 추가돼도 문제가 없는 상태가 되었습니다.&lt;/li&gt;
&lt;li&gt;Java에서는 위와 같이 참조에 의해서 값이 변경될 수 있는 점을 주의해야 합니다. 그래서 완벽한 불변 객체를 만들기 위해서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 불변 클래스로 만들자&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불변 클래스가 되기 위해서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;Class를 final 키워드를 사용해서 선언&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;Class의 &lt;span style=&quot;color: #1a5490;&quot;&gt;모든 필드를 private final로 선언&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;객체를 생성할 수 있는 &lt;span style=&quot;color: #1a5490;&quot;&gt;생성자&lt;/span&gt; 또는 &lt;span style=&quot;color: #1a5490;&quot;&gt;정적 팩토리 메서드(of)&lt;/span&gt;를 선언합니다.&lt;/li&gt;
&lt;li&gt;위에서 봤던 List와 같이 참조에 의해서 변경될 가능성이 있는 필드는 &lt;span style=&quot;color: #1a5490;&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;# WorkSpace를 완벽한 불변 클래스로 만드는 리펙토링&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1682407740494&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// final 키워드로 class를 선언
final class WorkSpace {
    // 불변성을 확보하기 위해서 필드를 private final로 선언
    private final List&amp;lt;Worker&amp;gt; workers;
    private final String workSpaceName;

    // 생성자를 내부적으로 호출할 수 있도록 private로 선언
    private WorkSpace(String workSpaceName) {
        this.workers = new ArrayList&amp;lt;&amp;gt;(List.of(new Worker(&quot;김씨&quot;, 26), new Worker(&quot;박씨&quot;, 30)));
        this.workSpaceName = workSpaceName;
    }

    // 정적 팩토리 메서드를 선언
    public static WorkSpace of(String workSpaceName) {
        return new WorkSpace(workSpaceName);
    }

    // 방어적 복사를 이용하여 workers를 제공
    public List&amp;lt;Worker&amp;gt; getWorkers() {
        return Collections.unmodifiableList(this.workers);
    }

    public String getWorkSpaceName() {
        return this.workSpaceName;
    }
}&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;color: #1a5490;&quot;&gt;final 키워드로 class를 선언&lt;/span&gt;하여 불변 클래스로 만들었습니다.&lt;/li&gt;
&lt;li&gt;두 번째로 &lt;span style=&quot;color: #1a5490;&quot;&gt;모든 필드는 private final&lt;/span&gt;로 선언해 줬습니다.&lt;/li&gt;
&lt;li&gt;세 번째로 내부 생성자를 private로 선언하여 &lt;span style=&quot;color: #1a5490;&quot;&gt;정적 팩토리 메서드를 통해서만 객체를 생성&lt;/span&gt;할 수 있도록 했습니다.&lt;br /&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 설정한 이유는 아무런 생성자 없이 정적 팩토리 메서드만 생성한다면 기본적으로 비어있는 내부 생성자를 컴파일러에서 자동으로 만들어줍니다. 그렇기 때문에 클래스 외부에서 어디서든 객체를 만들고 접근할 수 있기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마지막으로 workers List가 필요한 경우가 있을 수 있기 때문에 불변성을 확보하기 위해서 &lt;span style=&quot;color: #1a5490;&quot;&gt;방어적 복사를 사용&lt;/span&gt;하여 List를 제공했습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Java의 Record&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDK14에서 preview로 소개하고, JDK16 버전부터 정식으로 사용할 수 있게 되었습니다.&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;979&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/81M3N/btscxnw40Sr/sKzMPudt20s1sBH9fxAuDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/81M3N/btscxnw40Sr/sKzMPudt20s1sBH9fxAuDK/img.png&quot; data-alt=&quot;출처: https://www.baeldung.com/java-15-new&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/81M3N/btscxnw40Sr/sKzMPudt20s1sBH9fxAuDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F81M3N%2Fbtscxnw40Sr%2FsKzMPudt20s1sBH9fxAuDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;979&quot; height=&quot;195&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://www.baeldung.com/java-15-new&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Record의 특징&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;Record는 따로 적어주지 않아도 &lt;span style=&quot;color: #1a5490;&quot;&gt;암묵적으로 final 클래스이기 때문에 상속이 불가능&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;다른 클래스를 상속(extends) 받을 수는 없지만, 인터페이스를 구현(implements)은 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;필드에 따로 &lt;span style=&quot;color: #1a5490;&quot;&gt;private final 키워드를 붙여주지 않아&lt;/span&gt;도 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;모든 필드를 파라미터로 갖는 생성자를 기본적으로 제공&lt;/span&gt;합니다.&lt;/li&gt;
&lt;li&gt;객체 생성 시 재정의 해줘야 하는 &lt;span style=&quot;color: #1a5490;&quot;&gt;toString(), hashCode(), equals()를 기본적으로 제공&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Class&amp;nbsp; vs Record (비교)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교를 위해서 먼저 Record를 사용하지 않고, 불변 객체를 만드는 코드를 작성해 봅니다.&lt;/p&gt;
&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;# 기존의 Class를 이용하여 불변 객체 Student를 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1682411247759&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class Student {
    private final String studentId;
    private final String name;
    private final Integer age;
    
    public Student(String studentId, String name, Integer age) {
        this.studentId = studentId;
        this.name = name;
        this.age = age;
    }

    public String getStudentId() {
        return this.studentId;
    }

    public String getName() {
        return this.name;
    }

    public Integer getAge() {
        return this.age;
    }

    @Override
    public String toString() {
        return String.format(&quot;Student[studentId=%s, name=%s, age=%d]&quot;, studentId, name, age);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (!Objects.equals(studentId, student.studentId)) return false;
        if (!Objects.equals(name, student.name)) return false;
        return Objects.equals(age, student.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(studentId, name, age);
    }
}&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;# Record를 이용하여 불변객체 Student 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1682411318572&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record Student(String studentId, String name, Integer age) {
   // 끝
}&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;color: #1a5490;&quot;&gt;값을 가져올 수 있는 getter()도 모두 기본적으로 제공&lt;/span&gt;됩니다. 메서드의 이름은 만약 studentId를 가져오고 싶다면 그냥 studentId()라는 메서드로 사용하면 getter와 동일한 기능을 합니다.&lt;/li&gt;
&lt;li&gt;@Override 해서 재정의해줬던 &lt;span style=&quot;color: #1a5490;&quot;&gt;toString(), hashCode(), equals()도 모두 기본적으로 제공&lt;/span&gt;하기 때문에 따로 정의해주지 않아도 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Record의 추가적인 기능&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적인 기능을 기존의 class와 비교하면서 설명드리겠습니다.&lt;/p&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;(1) 생성자로 들어오는 파라미터 유효성 검사하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20살이 넘는 age 값이 들어왔을 때는 객체가 생성되지 않고, 예외를 던지도록 코드를 설정해 봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1682411834913&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Class 사용

public final class Student {
    private final String studentId;
    private final String name;
    private final Integer age;

    public Student(String studentId, String name, Integer age) {
        if (age &amp;gt;= 20) {
            throw new IllegalArgumentException(&quot;20살 이상은 학생이 아닙니다!&quot;);
        }
        this.studentId = studentId;
        this.name = name;
        this.age = age;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1682411955587&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Record 사용

public record Student(String studentId, String name, Integer age) {

    public Student {
        if (age &amp;gt;= 20) {
            throw new IllegalArgumentException(&quot;20살 이상은 학생이 아닙니다!&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법을 이용하면 Null에 대한 처리도 충분히 할 수 있을 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) Person interface 구현하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1682412021203&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Person Interface

public interface Person {
    void walk();
    void eat();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1682412061730&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Class 사용

public final class Student implements Person {
    /* 코드 생략 */
    
    @Override
    public void walk() {
        System.out.println(this.name + &quot;가 걷습니다.&quot;);
    }

    @Override
    public void eat() {
        System.out.println(this.name + &quot;가 밥을 먹습니다.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1682412094963&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Record 사용

public record Student(String studentId, String name, Integer age) implements Person {
    /* 코드 생략 */
    
    @Override
    public void walk() {
        System.out.println(this.name + &quot;가 걷습니다.&quot;);
    }

    @Override
    public void eat() {
        System.out.println(this.name + &quot;가 밥을 먹습니다.&quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Record는 Interface는 기존의 Class처럼 구현할 수 있습니다. 하지만 &lt;span style=&quot;color: #1a5490;&quot;&gt;Class를 상속(extends) 받는 것은 불가능&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blpCE2/btscyGDcCmc/nkCYpguxqkyp2Gj8sxX0v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blpCE2/btscyGDcCmc/nkCYpguxqkyp2Gj8sxX0v1/img.png&quot; data-alt=&quot;Record에서 extends를 하려고 할 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blpCE2/btscyGDcCmc/nkCYpguxqkyp2Gj8sxX0v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblpCE2%2FbtscyGDcCmc%2FnkCYpguxqkyp2Gj8sxX0v1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;205&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;205&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Record에서 extends를 하려고 할 때&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 Lombok 라이브러리나 IDE에서 toString(), hashCode(), equals()를 자동으로 Generate해주는 기능도 보면서 정말 편하게 사용하기 위해서 여러 가지 기능을 만들고 있구나라는 생각을 했는데, Record를 보고 나서 발전은 정말 끝이 없다는 것을 느꼈습니다. 나중에는 어떤 기능들이 점점 더 추가될지 기대되면서도 무섭다는 생각을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/Java</category>
      <category>Java Record</category>
      <category>record</category>
      <category>불변객체</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/57</guid>
      <comments>https://hoonsb.tistory.com/57#entry57comment</comments>
      <pubDate>Tue, 25 Apr 2023 17:47:34 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] 메모리 관리 (가상 메모리, 스레싱, 메모리 할당)</title>
      <link>https://hoonsb.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 &lt;span style=&quot;color: #1a5490;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  가상 메모리 (Virtual Memory)&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 가상 메모리란?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 관리 기법 중 하나로 &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 가상 메모리를 이용하면 좋은 점&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;물리 메모리(RAM)의 크기의 제약을 받지 않게 됩니다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(알고리즘에 집중할 수 있습니다)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;많은 프로그램을 동시에 실행할 수 있게 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;프로그램을 메모리에 올리고 Swap(스왑)을 하는데 필요한 입출력(IO) 횟수가 줄어들기 때문에 프로그램을 보다 더 빠르게 실행시킬 수 있습니다.&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;(3) 요구 페이징 (Demanding Paging)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스에서 &lt;span style=&quot;color: #1a5490;&quot;&gt;필요한 것들만 골라서 메모리에 적재할 수 있도록 해주는 전략&lt;/span&gt;을 요구 페이징이라고 합니다.&lt;/li&gt;
&lt;li&gt;초기에는 필요한 부분만 메모리에 적재하여 프로세스를 실행하고, 필요할 때마다 요청하여 메모리에 적재하는 방법을 사용합니다.&lt;/li&gt;
&lt;li&gt;한 번도 접근하지 않은 &lt;span style=&quot;color: #1b711d;&quot;&gt;*페이지&lt;/span&gt;는 물리 메모리에 적재되지 않습니다.&lt;/li&gt;
&lt;li&gt;해당 페이지가 물리 메모리에 존재하는지 안 하는지에 대한 여부는 &lt;span style=&quot;color: #1a5490;&quot;&gt;유-무효(valid-invalid) 비트&lt;/span&gt;를 사용하여 표시합니다.&lt;/li&gt;
&lt;li&gt;가상 메모리 공간에는 존재하지만 물리 메모리 공간에는 없는 데이터에 접근했을 때는 &lt;span style=&quot;color: #1a5490;&quot;&gt;페이지 폴트(Page Fault)&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;color: #1a5490;&quot;&gt;스와핑(Swaping)&lt;/span&gt;을 통해서 마치 페이지 폴트가 발생하지 않은 것처럼 프로그램을 작동시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;*&lt;b&gt;페이지(Page)&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;# 스와핑 (Swaping)&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;페이지 폴트를 방지하기 위해서 당장 사용하지 않는 영역을 하드디스크(보조기억장치, 스왑영역)로 옮기고, 필요할 때 다시 하드디스크에서 RAM으로 불러오는 작업을 스와핑이라고 합니다.&lt;/li&gt;
&lt;li&gt;하드디스크에서 RAM으로 불러오는 것을 &lt;span style=&quot;color: #1a5490;&quot;&gt;Swap-In&lt;/span&gt;이라고 하며, RAM에서 하드디스크로 내리는 작업을 &lt;span style=&quot;color: #1a5490;&quot;&gt;Swap-Out&lt;/span&gt;이라고 합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Swap을 하는 데는 큰 디스크 전송시간이 요구되기 때문에 메모리가 부족한 경우에만 Swapping을 진행합니다.&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;# 요구 페이징을 통해 페이지 폴트를 처리하는 과정&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;840&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGZUwY/btsb6ZiOYps/ABPlacGuDWC8tpSChK0RPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGZUwY/btsb6ZiOYps/ABPlacGuDWC8tpSChK0RPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGZUwY/btsb6ZiOYps/ABPlacGuDWC8tpSChK0RPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGZUwY%2Fbtsb6ZiOYps%2FABPlacGuDWC8tpSChK0RPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;816&quot; height=&quot;758&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;*&lt;b&gt;프레임(Frame)&lt;/b&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;&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;b&gt;  스레싱 (Thrashing)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;너무 &lt;span style=&quot;color: #1a5490;&quot;&gt;많은 프로세스가 동시에 올라오게 되면서 스와핑(Swaping)이 빈번하게 발생&lt;/span&gt;하여 생기는 문제가 스레싱입니다.&lt;/li&gt;
&lt;li&gt;즉, 페이지 폴드율이 높아짐에 따라서 발생하는 문제입니다. 스레싱은 컴퓨터의 심각한 성능 저하를 발생시킵니다.&lt;/li&gt;
&lt;li&gt;다시 정리해 보면 어떤 프로세스가 실행되는 시간보다 더 많은 시간이 페이징 하는 데 사용되고 있다면 스레싱이 발생했다고 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;OS가 평소에 CPU의 이용률을 감시합니다. &lt;span style=&quot;color: #1a5490;&quot;&gt;페이지 폴드율이 높아지면 그만큼 CPU의 대기 시간이 길어지기 때문에 CPU 이용률이 감소&lt;/span&gt;하게 됩니다. 그래서 OS는 CPU의 이용률이 낮기 때문에 여유롭다고 판단하고 가용성을 높이기 위해서 더 많은 프로세스를 메모리에 올리는 작업을 하게 됩니다.&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) 스레싱을 해결할 수 있는 방법&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;작업 세트, 작업 집합 (Working Set)&lt;/li&gt;
&lt;li&gt;페이지 폴트 빈도 (PFF, Page Fault Frequency)&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;# 작업 세트, 작업 집합 (Working Set)&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;프로세스가 실행이 될 때, 어떤 특정한 지역에서만 메모리를 참조하게 됩니다. 이것을 *&lt;span style=&quot;color: #1a5490;&quot;&gt;지역성(Locality)&lt;/span&gt;라고 합니다.&lt;/li&gt;
&lt;li&gt;지역성 특징을 기반으로 하여 어느 일정 시간 동안 활발하게 사용되었던 페이지들을 이용하여 작업 집합을 만들어 미리 메모리에 로드하는 방법을 말합니다.&lt;/li&gt;
&lt;li&gt;이렇게 미리 메모리에 로드하게 되면 탐색에 드는 비용을 줄일 수 있고, 스와핑 횟수도 줄일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;*&lt;b&gt;지역성&lt;/b&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;# 페이지 폴트 빈도(PFF, Page Fault Frequency)&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;페이지 폴트율의 상한과 하한을 지정하여 프로세스에 할당할 메모리의 크기를 동적으로 예측하고 조절하는 방법입니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  메모리 할당&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 연속 메모리 할당&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&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;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;운영체제가 메모리 내에서 빈 공간을 순서대로 탐색하다가 적재가 가능한 공간을 발견하면 그 공간에 프로세스를 바로 적재하는 방식을 말합니다.&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: 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;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;운영체제가 메모리의 비어있는 공간을 모두 검색한 뒤에 가장 큰 공간에 적재하는 방식을 말합니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 연속 메모리 할당의 문제점&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연속 메모리 할당 방법은 메모리를 효율적으로 사용하는 방법이 아닙니다. 잠재적으로 &lt;span style=&quot;color: #1a5490;&quot;&gt;외부 단편화&lt;/span&gt;가 발생할 수 있습니다.&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: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #1a5490;&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;836&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b37kyU/btscwoBD5u2/QtfqBcWXGH7s8IkKkCUB11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b37kyU/btscwoBD5u2/QtfqBcWXGH7s8IkKkCUB11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b37kyU/btscwoBD5u2/QtfqBcWXGH7s8IkKkCUB11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb37kyU%2FbtscwoBD5u2%2FQtfqBcWXGH7s8IkKkCUB11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;324&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;# 메모리 압축 (Compaction) - 외부 단편화 해결&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;비어있는 공간들을 하나로 모아서 하나의 큰 비어있는 공간으로 만드는 방법을 말합니다.&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: circle;&quot; data-ke-list-type=&quot;circle&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;558&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhclrk/btscwLpQTnM/0ucEoNoDd4RKcfmFgWqp00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhclrk/btscwLpQTnM/0ucEoNoDd4RKcfmFgWqp00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhclrk/btscwLpQTnM/0ucEoNoDd4RKcfmFgWqp00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhclrk%2FbtscwLpQTnM%2F0ucEoNoDd4RKcfmFgWqp00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;558&quot; height=&quot;316&quot; data-origin-width=&quot;558&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 불연속 메모리 할당&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불연속 메모리 할당 방법은 현대 운영체제가 쓰는 방법인 페이징 기법이 존재합니다.&lt;/li&gt;
&lt;li&gt;페이징 기법은 메모리를 동일한 크기의 페이지로 나누고 프로그램(프로세스)마다 &lt;span style=&quot;color: #1a5490;&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;# 페이징 기법 (Paging)&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;프로세스에게 연속적인 메모리를 할당하지 않고, 메모리를 페이지 단위로 나누어 서로 다른 위치에 프로세스를 할당합니다.&lt;/li&gt;
&lt;li&gt;논리적 메모리(가상 메모리)에서는 프로세스를 &lt;span style=&quot;color: #1a5490;&quot;&gt;페이지 단위&lt;/span&gt;로 나누어서 관리하고, 물리적 메모리(RAM)는 &lt;span style=&quot;color: #1a5490;&quot;&gt;프레임 단위&lt;/span&gt;로 나누어져 있습니다.&lt;/li&gt;
&lt;li&gt;하나의 프로세스가 사용하는 &lt;span style=&quot;color: #1a5490;&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;661&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BuUvn/btscv0AYgkc/BwxCdLbLcVOImSJoi6thd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BuUvn/btscv0AYgkc/BwxCdLbLcVOImSJoi6thd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BuUvn/btscv0AYgkc/BwxCdLbLcVOImSJoi6thd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBuUvn%2Fbtscv0AYgkc%2FBwxCdLbLcVOImSJoi6thd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;661&quot; height=&quot;381&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;381&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;# 세그멘테이션&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;정해진 사이즈의 블록 크기 페이지 단위가 아닌 서로 사이즈가 다른 논리적인 세그먼트(Segment)로 나누는 방법을 말합니다.&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;&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;b&gt;  페이지 교체 알고리즘&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리는 한정되어 있어서 스와핑이 빈번하게 일어납니다.&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;# 오프라인 알고리즘 (Offline Algorithm)&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;/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;# FIFO (First In First Out)&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;페이지 교체 시점이 오면 먼저 메모리에 할당되었던 순서대로 먼저 나가게 되는 간단한 알고리즘입니다.&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;# LRU (Least Recentle Used) : 기간&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;페이지 교체 시점이 오면 가장 오랫동안 사용되지 않는 페이지를 선택하여 교체합니다.&lt;/li&gt;
&lt;li&gt;대체적으로 FIFO 알고리즘보다는 우수한 성능을 갖습니다.&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;# LFU (Least Frequency Used) : 횟수&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;페이지 교체 시점이 오면 가장 참조 횟수가 적은 페이지를 선택하여 교체합니다.&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;# 최적 페이지 교체 (Optimal Page Replacement, OPR)&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;앞으로 오랫동안 사용되지 않을 페이지를 선택하여 교체합니다.&lt;/li&gt;
&lt;li&gt;프로세스가 앞으로 사용할 페이지를 미리 알아야 한다는 조건이 필요합니다.&lt;/li&gt;
&lt;li&gt;앞으로 사용할 페이지를 아는 것은 불가능하기 때문에 구현이 불가능한 알고리즘입니다.&lt;/li&gt;
&lt;li&gt;알고리즘 구현의 목적보다는 알고리즘 연구 비교의 목적으로 사용되는 알고리즘입니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Computer Science/운영체제</category>
      <category>가상 메모리</category>
      <category>메모리 관리</category>
      <category>메모리 할당</category>
      <category>운영체제</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/56</guid>
      <comments>https://hoonsb.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 24 Apr 2023 20:03:54 +0900</pubDate>
    </item>
    <item>
      <title>[IntelliJ] 인텔리제이 주석 밑줄 제거하기</title>
      <link>https://hoonsb.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ 사용 중 아래와 같이 계속 주석에 밑줄이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9RAtW/btsbXIsXeMA/edEdkJvLAsOcm4WcSXlezK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9RAtW/btsbXIsXeMA/edEdkJvLAsOcm4WcSXlezK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9RAtW/btsbXIsXeMA/edEdkJvLAsOcm4WcSXlezK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9RAtW%2FbtsbXIsXeMA%2FedEdkJvLAsOcm4WcSXlezK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;98&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;116&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Setting으로 들어가기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맥북은 &lt;b&gt;Command + ,&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/canHKs/btsbSLEkghe/JgKcycFQAVgUUpAdkHZVkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/canHKs/btsbSLEkghe/JgKcycFQAVgUUpAdkHZVkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/canHKs/btsbSLEkghe/JgKcycFQAVgUUpAdkHZVkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcanHKs%2FbtsbSLEkghe%2FJgKcycFQAVgUUpAdkHZVkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;396&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;396&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. &quot;Inspection&quot; 검색&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zJToe/btsbVba73d0/XubgdmzzKXPonGoWo0NxaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zJToe/btsbVba73d0/XubgdmzzKXPonGoWo0NxaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zJToe/btsbVba73d0/XubgdmzzKXPonGoWo0NxaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzJToe%2FbtsbVba73d0%2FXubgdmzzKXPonGoWo0NxaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;636&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;636&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. &quot;Proofreading&quot; 검색&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFUKbd/btsbTWyLAxr/g6CaGBkxKl4D3Nqo5TVWOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFUKbd/btsbTWyLAxr/g6CaGBkxKl4D3Nqo5TVWOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFUKbd/btsbTWyLAxr/g6CaGBkxKl4D3Nqo5TVWOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFUKbd%2FbtsbTWyLAxr%2Fg6CaGBkxKl4D3Nqo5TVWOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;972&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;972&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;이렇게 Proofreading을 검색하고 들어가서 &quot;Typo&quot;를 누르면 오른쪽에 체크박스가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에서 Process comments의 체크박스를 풀어주면 됩니다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 사라졌는지 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPi9Qz/btsbUpgndId/9uL8bqf9ym6BlTh1AzR6N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPi9Qz/btsbUpgndId/9uL8bqf9ym6BlTh1AzR6N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPi9Qz/btsbUpgndId/9uL8bqf9ym6BlTh1AzR6N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPi9Qz%2FbtsbUpgndId%2F9uL8bqf9ym6BlTh1AzR6N1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;515&quot; height=&quot;118&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;다른 프로젝트로 변경하여 설정에 들어가서 동일하게 Inspection -&amp;gt; Proofreading -&amp;gt; Typo를 들어가 보면 아래와 같이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bewfMU/btsbUptVEqV/vCfI6DXRHIf7WzFYpHkQN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bewfMU/btsbUptVEqV/vCfI6DXRHIf7WzFYpHkQN1/img.png&quot; data-alt=&quot;다른 프로젝트로 들어가서 다시 확인해봤을 때의 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bewfMU/btsbUptVEqV/vCfI6DXRHIf7WzFYpHkQN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbewfMU%2FbtsbUptVEqV%2FvCfI6DXRHIf7WzFYpHkQN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;427&quot; height=&quot;377&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;974&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;b&gt;결론&lt;/b&gt;은 번거로워서 자주 해주는 설정은 아니지만, 오랫동안 봐야 하는 프로젝트를 할 때마다 설정 방법을 까먹어서 이렇게 기록해 두고 쓰려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑줄 아무렇지 않아 보이지만 코드 작성할 때마다 계속 신경 쓰일 겁니다  &lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/여러가지 팁</category>
      <category>인텔리제이 밑줄 제거</category>
      <category>인텔리제이 설정</category>
      <category>인텔리제이 주석 밑줄 제거</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/55</guid>
      <comments>https://hoonsb.tistory.com/55#entry55comment</comments>
      <pubDate>Sat, 22 Apr 2023 02:05:09 +0900</pubDate>
    </item>
    <item>
      <title>[오류해결] No serializer found for class (InvalidDefinitionException)</title>
      <link>https://hoonsb.tistory.com/54</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 발생&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Request 요청을 위한 DTO로 사용하기 위한 클래스를 생성한 후 해당 객체를 ObjectMapper의 writeValueAsString() 메서드에 넣어 serialize 해서 json 데이터로 mvc에 content에 담아 POST 요청을 하는 테스트를 진행했습니다.&lt;/p&gt;
&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;# PlaceRequest.java 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1681921721994&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class PlaceRequest {
    private PlaceType placeType;
    private String placeName;
    private String address;
    private String phoneNumber;
    private Integer capacity;
    private String memo;

    public static PlaceRequest of(
            PlaceType placeType,
            String placeName,
            String address,
            String phoneNumber,
            Integer capacity,
            String memo
    ) {
        return new PlaceRequest(placeType, placeName, address, phoneNumber, capacity, memo);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 static 메서드인 of()로 만든 객체를 ObjectMapper.writeValueAsString()에 넣어 serialize를 진행했고, 아래와 같은 오류가 발생했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;com.fasterxml.jackson.databind.exc.&lt;b&gt;InvalidDefinitionException&lt;/b&gt;: No serializer found for class&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;zwj;♂️ 시도 / 해결&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. PlaceRequest 클래스에 빈 생성자가 존재하지 않으면 Serializer가 작동하지 않는다.&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1681921997495&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor
@AllArgsConstructor
public class PlaceRequest {
    /* 코드생략 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@NoArgsConstructor 애노테이션을 붙여 비어있는 생성자를 만들어줬습니다. 그리고 다시 실행했을 때의 결과는 동일한 예외가 발생되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JackSon에서 Deserialize(역직렬화, json -&amp;gt; Object)를 할 때는 기본 생성자로 객체를 생성한 후 필드 값을 찾아서 바인딩해준다고 합니다.&amp;nbsp;하지만 우리가 하려는 과정은 Deserialize가 아닌 Serialize였기 때문에 사실상 지금은 @NoArgsConstructor가 아무런 효과가 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. serialize를 하는 과정에서 PlaceRequest 클래스의 필드에 접근하게 되는데, 이때 public 접근제한자 또는 getter를 통해서 접근하게 된다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 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;b&gt;(1) 필드를 모두 public으로 바꿔보자&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1681922227203&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class PlaceRequest {
    public PlaceType placeType;
    public String placeName;
    public String address;
    public String phoneNumber;
    public Integer capacity;
    public String memo;
    
    /* 코드 생략 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZK95N/btsbmemXXVF/4uhU2LfX21RNlpr5YvHuu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZK95N/btsbmemXXVF/4uhU2LfX21RNlpr5YvHuu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZK95N/btsbmemXXVF/4uhU2LfX21RNlpr5YvHuu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZK95N%2FbtsbmemXXVF%2F4uhU2LfX21RNlpr5YvHuu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;329&quot; height=&quot;184&quot; data-origin-width=&quot;560&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;테스트를 돌려보니 통과하는 것을 볼 수 있습니다.&lt;/p&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;(2) 필드는 그대로 private로 두고, Getter를 만들어보자&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1681922384066&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@AllArgsConstructor
public class PlaceRequest {
    private PlaceType placeType;
    private String placeName;
    private String address;
    private String phoneNumber;
    private Integer capacity;
    private String memo;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F3Gz3/btsblqnUBRV/0VKpsst9q8uFxaM6HuPUk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F3Gz3/btsblqnUBRV/0VKpsst9q8uFxaM6HuPUk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F3Gz3/btsblqnUBRV/0VKpsst9q8uFxaM6HuPUk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF3Gz3%2FbtsblqnUBRV%2F0VKpsst9q8uFxaM6HuPUk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;344&quot; height=&quot;105&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법도 동일하게 테스트가 통과되는 것을 볼 수 있습니다. 저는 private 필드를 사용하기 위해서 두 번째 방법을 사용해서 코드를 작성했습니다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  알게된 점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JackSon을 사용해서 &lt;span style=&quot;color: #1a5490;&quot;&gt;Serialize(Object -&amp;gt; json)&lt;/span&gt;할 때는 &lt;span style=&quot;color: #ee2323;&quot;&gt;필드를 public으로 지정&lt;/span&gt;하거나 private으로 해두었다면 JackSon이 해당 필드에 접근할 수 있도록 &lt;span style=&quot;color: #ee2323;&quot;&gt;Getter를 만들어&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: #1a5490;&quot;&gt;Deserialize(json -&amp;gt; Object)&lt;/span&gt;를 할 때는 비어있는 기본 생성자(NoArgsConstructor)로 객체를 생성하기 때문에 &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;사실 해결 방법에는 @JsonProperty를 필드에 붙여주는 방법도 찾았던 거 같은데, 이 부분은 나중에 따로 시간을 내서 알아보고자 합니다.&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/오류해결</category>
      <category>deserialize</category>
      <category>InvalidDefinitionException</category>
      <category>Jackson</category>
      <category>Serialize</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/54</guid>
      <comments>https://hoonsb.tistory.com/54#entry54comment</comments>
      <pubDate>Thu, 20 Apr 2023 02:07:52 +0900</pubDate>
    </item>
    <item>
      <title>[IntelliJ] 여러줄 한번에 수정하기 (Mac)</title>
      <link>https://hoonsb.tistory.com/53</link>
      <description>&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oFeeY/btsbmTh9qL4/4A1FJtYWktHa4Ax9u30Dnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oFeeY/btsbmTh9qL4/4A1FJtYWktHa4Ax9u30Dnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oFeeY/btsbmTh9qL4/4A1FJtYWktHa4Ax9u30Dnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoFeeY%2FbtsbmTh9qL4%2F4A1FJtYWktHa4Ax9u30Dnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;417&quot; height=&quot;291&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;372&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;&quot;$.data~~&quot;에서 data뒤에 배열의 인덱스를 나타내는 [0]을 추가하고 싶습니다.&lt;/p&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;Shift + 아래키&quot;라고 쓰여있어서 했는데, 아래와 같이 모든 줄이 선택됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9ltuO/btsbmfsgJ68/G3VnxPOZ3PYk7VS48VqC91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9ltuO/btsbmfsgJ68/G3VnxPOZ3PYk7VS48VqC91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9ltuO/btsbmfsgJ68/G3VnxPOZ3PYk7VS48VqC91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9ltuO%2FbtsbmfsgJ68%2FG3VnxPOZ3PYk7VS48VqC91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;191&quot; data-origin-width=&quot;580&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &quot;Command + Shift + 8&quot;을 입력하면 인텔리제이 오른쪽 밑 부분에 Column이라는 글자가 나타나면서 모드가 변경됩니다.&lt;/p&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;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZK4Uv/btsbmP07UK5/yLuQ4Zm8IAEW649DfI8gFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZK4Uv/btsbmP07UK5/yLuQ4Zm8IAEW649DfI8gFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZK4Uv/btsbmP07UK5/yLuQ4Zm8IAEW649DfI8gFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZK4Uv%2FbtsbmP07UK5%2FyLuQ4Zm8IAEW649DfI8gFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;63&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;100&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;# 단축키를 누른 후&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwpgyj/btsbmT3vUQf/3Fk0CfUMRqoY9qOeFgHLSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwpgyj/btsbmT3vUQf/3Fk0CfUMRqoY9qOeFgHLSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwpgyj/btsbmT3vUQf/3Fk0CfUMRqoY9qOeFgHLSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcwpgyj%2FbtsbmT3vUQf%2F3Fk0CfUMRqoY9qOeFgHLSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;435&quot; height=&quot;54&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;82&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;Column이라는 글자가 생긴 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 다시 &quot;Shift + 아래키&quot;를 눌러 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JFH9B/btsbmYjsExh/Z4NEKIdbTk7AFEvYaRGIIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JFH9B/btsbmYjsExh/Z4NEKIdbTk7AFEvYaRGIIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JFH9B/btsbmYjsExh/Z4NEKIdbTk7AFEvYaRGIIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJFH9B%2FbtsbmYjsExh%2FZ4NEKIdbTk7AFEvYaRGIIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;221&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;272&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;오호 성공입니다. 만약 커서를 다시 원상태로 복구하고 싶다면 Esc를 눌러주시면 됩니다.&lt;/p&gt;
&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjaT07/btsbmfMzTVl/ZJkNjCdxf7hRYWKaVzcTdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjaT07/btsbmfMzTVl/ZJkNjCdxf7hRYWKaVzcTdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjaT07/btsbmfMzTVl/ZJkNjCdxf7hRYWKaVzcTdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjaT07%2FbtsbmfMzTVl%2FZJkNjCdxf7hRYWKaVzcTdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;208&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;258&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;모든 수정이 끝나면 다시 &quot;Command + Shift + 8&quot;을 눌러주면 기존 모드로 변경되어 &quot;Shift + 아래키&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;하나씩 변경해 줘도 상관없지만 한 번 수정에 1초면 5초를 Save 했습니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Dev/여러가지 팁</category>
      <category>인텔리제이 다중커서</category>
      <category>인텔리제이 단축키</category>
      <category>인텔리제이 여러줄 동시에 선택</category>
      <author>sope</author>
      <guid isPermaLink="true">https://hoonsb.tistory.com/53</guid>
      <comments>https://hoonsb.tistory.com/53#entry53comment</comments>
      <pubDate>Wed, 19 Apr 2023 19:00:38 +0900</pubDate>
    </item>
  </channel>
</rss>