<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sung_hyuki.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 29 Jan 2023 05:23:10 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sung_hyuki.log</title>
            <url>https://images.velog.io/images/sung_hyuki/profile/deb52692-5ef7-4680-a4c1-2645abb525fc/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sung_hyuki.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sung_hyuki" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[트랜잭션과 잠금]]></title>
            <link>https://velog.io/@sung_hyuki/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EA%B3%BC-%EC%9E%A0%EA%B8%88</link>
            <guid>https://velog.io/@sung_hyuki/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EA%B3%BC-%EC%9E%A0%EA%B8%88</guid>
            <pubDate>Sun, 29 Jan 2023 05:23:10 GMT</pubDate>
            <description><![CDATA[<aside>
💡 트랜잭션은 작업의 완전성을 보장해 주는 것입니다. 즉, 논리적인 작업 셋 자체가 100% 적용되거나(COMMIT을 실행했을 때) 아무것도 적용되지 않아야(ROLLBACK 또는 트랜잭션을 ROLLBACK시키는 오류가 발생했을 때) 함을 보장해 주는 것입니다.

</aside>
<br>

<aside>
💡 잠금은 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할을 합니다.

</aside>
<br>

<aside>
💡 격리 수준(Isolation Level)은 하나의 트랜잭션 내에서 또는 여러 트랜잭션 간의 작업 내용을 어떻게 공유하고 차단할 것인지를 결정하는 레벨입니다.

</aside>
<br>

<aside>
💡 잠금은 동시성을 제어하기 위한 기능이고 트랜잭션은 데이터의 정합성을 보장하기 위한 기능입니다.

</aside>
<br>

<p>트랜잭션을 지원하는 엔진 → InnoDB
<br></p>
<pre><code class="language-java">try {
    START TRANSACTION; 
    INSERT INTO tab a ...;  
    INSERT INTO tab b ...; 
    COMMIT ;
} catch(exception) { 
    ROLLBACK;
}</code></pre>
<br>

<p>트랜잭션을 지원하지 않는 엔진 → MyISAM</p>
<ul>
<li>부분 업데이트를 Partial Update라고 부릅니다.</li>
<li>부분 업데이트 현상은 테이블 데이터의 정합성을 맞추는데 상당히 어려운 문제를 만들어 냅니다.<br>

</li>
</ul>
<aside>
💡 일반적인 데이터베이스 커넥션은 개수가 제한적이어서 각 단위 프로그램이 커넥션을 소유하는 시간이 길어질수록 사용 가능한 여유 커넥션의 개수는 줄어들게 됩니다. 그리고 어느 순간에는 각 단위 프로그램에서 커넥션을 가져가기 위해 기다려야 하는 상황이 발생할 수도 있습니다. 그렇기 때문에 트랜잭션과 DBMS의 커넥션은 꼭 필요한 최소의 코드에만 적용하는 것이 좋습니다.

</aside>
<br>

<aside>
💡 네트워크 작업이 있는 경우 반드시 트랜잭션에서 배제해야 합니다. 프로그램을 실행하는 동안 통신 서버와의 통신을 할 수 없는 상황이라면 웹 서버뿐 아니라 DBMS 서버까지 위험해지는 상황이 발생하기 때문입니다.

</aside>
<br>

<h2 id="mysql-엔진의-잠금">MySQL 엔진의 잠금</h2>
<h3 id="글로벌-락global-lock">글로벌 락(Global Lock)</h3>
<ul>
<li><code>FLUSH TABLES WITH READ LOCK</code> 명령을 통해 획득할 수 있습니다.</li>
<li>MySQL에서 제공하는 잠금 가운데 가장 범위가 큰 잠금입니다.</li>
<li>한 세션에서 글로벌 락을 획득하면 다른 세션에서 SELECT를 제외한 대부분의 DDL 문장이나 DML 문장을 실행하는 경우 글로벌 락이 해제될 때까지 해당 문장이 대기 상태로 남습니다.</li>
<li>글로벌 락이 영향을 미치는 범위는 MySQL 서버 전체입니다.</li>
<li>여러 데이터베이스에 존재하는 MyISAM이나 MEMORY 테이블에 대해 mysqldump로 일괄된 백업을 받아야 할 때는 글로벌 락을 사용해야 합니다.</li>
<li>InnoDB 스토리지 엔진이 기본 스토리지 엔진으로 채택되면서 일관된 데이터 상태를 위해 모든 데이터 변경 작업을 멈출 필요는 없어졌습니다. 조금 더 가벼운 글로벌 락으로 Xtrabackup이나 Enterprise Backup이 도입됨<ul>
<li>백업 락으로 사용하는데 정상적으로 복제는 실행되지만 백업의 실패를 막기 위해 DDL 명령이 실행되면 복제를 일시 중지하는 역할을 합니다</li>
</ul>
</li>
</ul>
<pre><code class="language-java">mysql&gt; LOCK INSTANCE FOR BACKUP;
-- // 백업 실행
mysql&gt; UNLOCK INSTANCE;</code></pre>
<br>

<h3 id="테이블-락table-lock">테이블 락(Table Lock)</h3>
<ul>
<li>개별 테이블 단위로 설정되는 잠금입니다.</li>
<li>명시적 또는 묵시적으로 특정 테이블의 락을 획득할 수 있습니다.<ul>
<li><strong>명시적</strong>으로는 <code>LOCK TABLES table_name [ READ | WRITE ]</code> 명령을 통해 특정 테이블 락을 획득할 수 있습니다. 테이블 락은 MyISAM뿐 아니라 InnoDB 스토리지 엔진을 사용하는 테이블도 동일하게 설정할 수 있습니다. 명시적으로 획득한 잠금은 <code>UNLOCK TABLES</code> 명령을 통해 잠금을 반납할 수 있습니다.</li>
<li><strong>묵시적</strong>인 테이블락은 MyISAM이나 MEMORY 테이블에 데이터를 변경하는 쿼리를 실행하면 발생합니다. MySQL 서버가 데이터가 변경되는 테이블에 잠금을 설정하고 데이터를 변경한 후, 즉시 잠금을 해제하는 형태로 사용됩니다. 즉, <strong>묵시적인 테이블 락은 쿼리가 실행되는 동안 자동으로 획득됐다가 쿼리가 완료된 후 자동 해제됩니다.</strong> 하지만 InnoDB 테이블의 경우 스토리지 엔진 차원에서 레코드 기반의 잠금을 제공하기 때문에 단순 데이터 변경 쿼리로 인해 묵시적인 테이블 락이 설정되지는 않습니다. 더 정확히는 InnoDB 테이블에도 테이블 락이 설정되지만 대부분의 데이터 변경(DML) 쿼리에서는 무시되고 스키마를 변경하는 쿼리(DDL)의 경우에만 영향을 미칩니다.<br>

</li>
</ul>
</li>
</ul>
<h3 id="네임드-락named-lock">네임드 락(Named Lock)</h3>
<ul>
<li>GET_LOCK() 함수를 이용해 <strong>임의의 문자열</strong>에 대해 잠금을 설정할 수 있습니다.</li>
<li>여러 클라이언트가 상호 동기화를 처리해야 할 때 네임드 락을 이용하면 쉽게 해결 할 수 있습니다.</li>
</ul>
<pre><code class="language-sql">-- // &quot;mylock&#39;’01라는 문자열에 대해 잠금을 획득한다.
-- // 이미 잠금을 사용 중이면 2초 동안만 대기한다. (2초 이후 자동 잠금 해제됨) 
mysql&gt; SELECT GET_LOCK(&#39;mylock&#39;, 2);

-- // &quot;mylock&quot;OI라는 문자열에 대해 잠금이 설정돼 있는지 확인한다. 
mysql&gt; SELECT IS_FREE_LOCK(&#39;mylock&#39;);

-- // &quot;mylock&quot;이라는 문자열에 대해 획득했던 잠금을 반납(해제)한다. 
mysql&gt; SELECT RELEASE_LOCK(&#39;mylock&#39;);

-- // 3개 함수 모두 정상적으로 락을 획득하거나 해제한 경우에는 1을,
-- // 아니면 NULLOI나 0을 반환한다.</code></pre>
<ul>
<li>많은 레코드에 대해서 복잡한 요건으로 레코드를 변경하는 트랜잭션에 유용하게 사용할 수 있습니다.</li>
<li>MySQL 8.0 버전부터는 네임드 락을 중첩해서 사용 가능합니다. 획득한 락을 한 번에 해제하는 기능도 추가되었습니다.</li>
</ul>
<pre><code class="language-sql">mysql&gt; SELECT GET_LOCK(&#39;mylock_1&#39; , 10);
-- // mylock_1에 대한 작업 실행
mysql&gt; SELECT GET_LOCK(&#39;mylock_2&#39; , 10);
-- // mylock_1과 mylock_2에 대한 작업 실행

mysql&gt; SELECT RELEASE_LOCK(&#39;mylock_2&#39;);
mysql&gt; SELECT RELEASE_LOCK(&#39;mylock_1&#39;);

-- // mylock_1과 mylock_2를 동시에 모두 해제하고자 한다면 RELEASE_ALL_LOCKS() 함수 사용 
mysql&gt; SELECT RELEASE_ALL_LOCKS();</code></pre>
<br>

<h3 id="메타데이터-락metadata-lock">메타데이터 락(Metadata Lock)</h3>
<ul>
<li>데이터베이스 객체(대표적으로 테이블이나 뷰 등)의 이름이나 구조를 변경하는 경우에 획득하는 잠금입니다.</li>
<li><code>RENAME TABLE tab_a TO tab_b</code> 같이 테이블의 이름을 변경하는 경우 자동으로 획득하는 잠금입니다.</li>
</ul>
<pre><code class="language-sql">-- // 배치 프로그램에서 별도의 임시 테이블 (rank_new)에 서비스용 랭킹 데이터를 생성
-- // 랭킹 배치가 완료되면 현재 서비스용 랭킹 테이블 (rank)을 rank_backup으로 백업하고 
-- // 새로 만들어진 랭킹 테이블(rank_new)을 서비스용으로 대제하고자 하는 경우
mysql&gt; RENAME TABLE rank TO rank_backup , rank_new TO rank;</code></pre>
<p>위의 쿼리를 밑의 쿼리와 같이 변경할 경우 아주 짧은 시간이지만 rank 테이블이 존재하지 않는 순간이 생깁니다.</p>
<pre><code class="language-sql">mysql&gt; RENAME TABLE rank TO rank_backup; 
mysql&gt; RENAME TABLE rank_new TO rank;</code></pre>
<br>

<h2 id="innodb-스토리지-엔진-잠금">InnoDB 스토리지 엔진 잠금</h2>
<ul>
<li>MySQL에서 제공하는 잠금과는 별개로 스토리지 엔진 내부에서 레코드 기반의 잠금 방식을 탑재하고 있습니다.</li>
<li><strong>레코드 기반의 잠금 방식으로 인해 뛰어난 동시성 처리를 제공합니다.</strong></li>
<li>하지만, 이원화된 잠금 처리로 인한 불편함을 개선하기 위해 InnoDB의 트랜잭션과 잠금, 그리고 잠금 대기 중인 트랜잭션의 목록을 조회할 수 있는 방법이 도입됐습니다.<ul>
<li>MySQL 서버의 information_schema 데이터베이스에 존재하는 <code>INNODB_TRX</code>, <code>INNODB_LOCKS</code>, <code>INNODB_LOCK_WAITS</code> 라는 테이블을 조인해서 사용</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/5ab84a3e-ba3e-4851-94d2-9dc5183d0449/image.png" alt=""></p>
<h3 id="레코드-락record-lock-record-only-lock">레코드 락(Record lock, Record only lock)</h3>
<ul>
<li>레코드 자체만을 잠그는 것</li>
<li>InnoDB 스토리지 엔진은 레코드 자체가 아니라 인덱스의 레코드를 잠그는 것이 특징<br>

</li>
</ul>
<h3 id="갭-락gap-lock">갭 락(Gap lock)</h3>
<ul>
<li>레코드 자체가 아니라 레코드와 바로 인접한 레코드 사이의 간격만을 잠그는 것</li>
<li><strong>레코드와 레코드 사이의 간격에 새로운 레코드가 생성(INSERT)되는 것을 제어하는 것</strong> → 레코드와 레코드 사이의 간격이라는 말은 메모리 파편화로 인해 생긴 공간을 말하는 것인가(?)<br>

</li>
</ul>
<h3 id="넥스트-키-락next-key-lock">넥스트 키 락(Next key lock)</h3>
<ul>
<li>레코드 락과 갭 락을 합쳐 놓은 형태의 잠금</li>
<li>STATEMENT 포맷의 바이너리 로그를 사용하는 MySQL 서버에서는 REPEATABLE READ 격리 수준을 사용해야 합니다.</li>
<li>또한, innodb_locks_unsafe_for_binlog 시스템 변수가 비활성화되면 변경을 위해 검색하는 레코드에는 넥스트 키 락 방식으로 잠금이 걸립니다.</li>
<li>바이너리 로그에 기로되는 쿼리가 레플리카 서버에서 실행될 때 소스 서버에서 만들어 낸 결과와 동일한 결과를 만들어내도록 보장하는 것이 주목적입니다.</li>
<li>바이너리 로그 포맷 ROW 형태로 바꿔서 넥스트 키 락이나 갭 락을 줄이는 것이 좋습니다.<br>

</li>
</ul>
<h3 id="자동-증가-락auto-increment-lock">자동 증가 락(Auto increment lock)</h3>
<ul>
<li>AUTO_INCREMENT가 걸린 컬럼은 동시에 여러 레코드가 INSERT되는 상황에서도 저장된 순서대로 증가하는 일련번호를 가져야 합니다. 이를 위해 내부적으로 AUTO_INCREMENT 락이라고 하는 테이블 수준의 잠금을 사용합니다.</li>
<li>InnoDB의 다른 잠금(레코드 락이나 넥스트 커 락)괴는 달리 AUTO_INCREMENT 락은 트랜잭션과 관계없이 INSERT나 REPLACE 문장에서 AUTO_INCREMENT 값 을 가져오는 순간만 락이 걸렸다가 즉시 해제됩니다.</li>
</ul>
<table>
<thead>
<tr>
<th>innodb_autoinc_lock_mod=0</th>
<th>모든 INSERT 문장은 자동 증가 락을 사용한다.</th>
</tr>
</thead>
<tbody><tr>
<td>innodb_autoinc_lock_mod=1</td>
<td>INSERT되는 레코드의 건수를 정확히 예측할 수 있을 때는 자동 증가 락보다 가볍고 빠른 래치(뮤텍스)를 이용해 처리한다.</td>
</tr>
<tr>
<td>innodb_autoinc_lock_mod=2</td>
<td>경량화된 래치(뮤텍스)를 사용한다. 대량 INSERT 문장이 실행되는 중에도 다른 커넥션에서 INSERT를 수행할 수 있으므로 동시 처리 성능이 높아진다. 하지만 소스 서버와 레플리카 서버의 자동 증가 값이 달라질 수 있음.</td>
</tr>
</tbody></table>
<br>
INSERT 쿼리가 실패했더라도 한 번 증가된 AUTO_INCREMENT 값은 다시 줄어들지 않고 그대로 남는다 → 이는 트랜잭션 롤백 상황에서 이 롤백 수행 과정을 다른 스레드들에서는 기다려야 하기 때문에 문제가 발생하기 때문일까? 즉 성능을 빠르게 하기 위해서
<br>

<h3 id="인덱스와-잠금">인덱스와 잠금</h3>
<p>InnoDB의 잠금은 레코드를 잠그는 것이 아니라 인덱스를 잠그는 방식입니다.
<br></p>
<aside>
💡 테이블의 레코드 잠금 범위가 달라질 수 있기 때문에 인덱스를 어느 컬럼에 걸어야 할지는 정말 중요합니다.

</aside>
<br>

<aside>
💡 레코드가 오래 사용되지 않는다면 레코드 수준의 잠금은 테이블 수준의 잠금 방식보다 발견하기가 쉽지 않습니다. 하지만 MySQL 5.1부터는 레코드 잠금과 잠금 대기에 대한 조회가 가능합니다.

</aside>
<br>

<p>MySQL의 performance_schema와 information_schema 테이블을 이용해 잠금과 잠금 대기를 확인할 수 있습니다.
<br></p>
<h2 id="mysql의-격리-수준">MySQL의 격리 수준</h2>
<p>격리 수준(isolation level) : 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것
<br></p>
<p><strong>격리 수준의 종류</strong></p>
<ul>
<li>READ UNCOMMITED → “DIRTY READ” 문제 발생</li>
<li>READ COMMITED</li>
<li>REPEATABLE READ</li>
<li>SERIALIZABLE</li>
</ul>
<p>뒤로 갈수록 고립의 정도가 높아지며, 동시 처리 성능도 떨어지는 것이 일반적입닏.
<br></p>
<table>
<thead>
<tr>
<th></th>
<th>DIRTY READ</th>
<th>NON-REPEATABLE READ</th>
<th>PHANTOM READ</th>
</tr>
</thead>
<tbody><tr>
<td>READ UNCOMMITED</td>
<td>발생</td>
<td>발생</td>
<td>발생</td>
</tr>
<tr>
<td>READ COMMITED</td>
<td>없음</td>
<td>발생</td>
<td>발생</td>
</tr>
<tr>
<td>REPEATABLE READ</td>
<td>없음</td>
<td>없음</td>
<td>발생(InnoDB는 없음)</td>
</tr>
<tr>
<td>SERIALIZABLE</td>
<td>없음</td>
<td>없음</td>
<td>없음</td>
</tr>
<tr>
<td><br></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="read-uncommited">READ UNCOMMITED</h3>
<aside>
💡 각 트랜잭션에서의 변경 내용이 COMMIT이나 ROLLBACK 여부에 상관없이 다른 트랜잭션에서 보입니다.

</aside>
<br>

<p><strong>Dirty read</strong></p>
<p>: 어떤 트랜잭션에서 처리한 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있는 현상</p>
<p>→ 데이터가 나타났다가 사라졌다 하는 현상을 초래하므로 애플리케이션 개발자와 사용자를 혼란스럽게 만듭니다.
<br></p>
<h3 id="read-commited">READ COMMITED</h3>
<aside>
💡 어떤 트랜잭션에서 데이터를 변경했더라도 COMMIT이 완료된 데이터만 다른 트랜잭션에서 조회할 수 있습니다.

</aside>
<br>

<p><strong>NON-REPEATABLE READ</strong></p>
<p>: 반복 읽기를 했을 때 데이터 부정합의 문제가 발생합니다. (하나의 트랜잭션 내에서 반복 읽기를 수행했을 때 같은 결과값을 보장해야 합니다.)
<br></p>
<h3 id="repeatable-read">REPEATABLE READ</h3>
<aside>
💡 MySQL의 InnoDB 스토리지 엔진에서 기본으로 사용되는 격리 수준입니다. 트랜잭션이 ROLLBACK될 가능성에 대비해 변경되기 전 레코드를 언두(Undo) 공간에 백업해두고 실제 레코드 값을 변경합니다.

</aside>
<br>

<p>언두 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있게 보장합니다.</p>
<p>모든 InnoDB의 트랜잭션은 고유한 트랜잭션 번호를 가지며, 언두 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함돼 있습니다. 그리고 언두 영역의 백업된 데이터는 InnoDB 스토리지 엔진이 불필요하다고 판단하는 시점에 주기적으로 삭제합니다.</p>
<p>REPEATABLE READ 격리 수준에서는 MVCC를 보장하기 위해 실행 중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호보다 트랜잭션 번호가 앞선 언두 영역의 데이터는 삭제할 수가 없습니다. 
<br></p>
<p>다음 시나리오를 통해 이를 이해할 수 있스빈다. 사용자 A가 em_no가 500000인 사원의 이름을 변경하는 과정에서 사용자 B가 emp_no=500000인 사원을 SELECT할 때 어떤 과정을 거칠까요?</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/0402e6e1-42bb-460d-af61-e984680ddcfa/image.png" alt=""></p>
<p>사용자 B가 트랜잭션을 시작하고 emp_no이 500000인 사원을 조회하면 Lara가 나옵니다. 사용자 B가 트랜잭션을 종료하지 않은 상황에서 사용자 B가 emp_no가 500000인 Lara의 이름을 Toto로 변경시키고 트랜잭션을 커밋시킵니다. 하지만 사용자 B의 트랜잭션은 커밋되지 않았기 때문에 emp_no가 500000인 사원을 조회했을 때 <strong>반복 가능한 읽기 수준을 보장하며 Lara를 반환해야 합니다. 사용자 B의 10번 트래잭션 안에서 실행되는 모든 SELECT 쿼리는 트랜잭션 번호 10(자신의 트랜잭션 번호)보다 작은 트랜잭션 번호에서 변경한 것만 보이게 됩니다.</strong>
<br></p>
<p><strong>PHANTOM READ</strong></p>
<p>: 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다 안 보였다 하는 현상</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/74c584ef-606f-49a6-b602-dfc7168bf413/image.png" alt=""></p>
<p>SELECT …. FOR UPDATE 쿼리는 SELECT하는 레코드에 쓰기 잠금을 걸어야 하는데, 언두 레코드에는 잠금을 걸 수 없습니다. 그래서 SELECT … FOR UPDATE나 SELECT … LOCK IN SHARE MODE로 조회되는 레코드는 언두 영역의 변경 전 데이터를 가져오는 것이 아니라 현재 레코드의 값을 가져오게 되는 것입니다.
<br></p>
<h3 id="serializable">SERIALIZABLE</h3>
<aside>
💡 가장 엄격한 격리 수준으로, 한 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에서는 절대 접근할 수 없습니다.

</aside>
<br>

<p>InnoDB 스토리지 엔진에서는 갭 락과 넥스트 키 락 덕분에 REPEATABLE READ 격리 수준에서도 이미 “PHANTOM READ”가 발생하지 않기 때문에 굳이 SERIALIZABLE을 사용할 필요성은 없습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[서버 측의 LAN에는 무엇이 있는가?]]></title>
            <link>https://velog.io/@sung_hyuki/%EC%84%9C%EB%B2%84-%EC%B8%A1%EC%9D%98-LAN%EC%97%90%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EC%9E%88%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@sung_hyuki/%EC%84%9C%EB%B2%84-%EC%B8%A1%EC%9D%98-LAN%EC%97%90%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EC%9E%88%EB%8A%94%EA%B0%80</guid>
            <pubDate>Wed, 25 Jan 2023 05:58:40 GMT</pubDate>
            <description><![CDATA[<h3 id="방화벽의-원리와-동작">방화벽의 원리와 동작</h3>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/30670a67-90b4-4cd9-849b-09d40d1cd999/image.png" alt=""></p>
<p>(a)와 같이 환경을 구성하는 경우 IP 주소가 부족해지고 서버는 노출 상태로 방치되기 때문에 선호되지 않는 형태입니다.</p>
<p>(b)와 특정 서버에서 동작하는 애플리케이션에 액세스하는 패킷만 통과시키도록 방화벽을 두는 형태가 일반적입니다.
<br></p>
<aside>
💡 특정 서버와 해당 서버 안의 특정 애플리케이션에 액세스하는 패킷만 통과시키고, 그 외의 패킷은 차단합니다.

</aside>
<br>

<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/35f41495-71ff-499e-98cc-a065d93effaa/image.png" alt=""></p>
<ul>
<li>패킷을 받으면 정확하게 도착했는지를 송신측에 알리는 수신 확인 응답의 구조가 작용하므로 웹 서버에서 인터넷측으로 흐르는 패킷도 있습니다.</li>
</ul>
<ul>
<li><p>애플리케이션을 한정할 때 포트 번호를 사용합니다.</p>
</li>
<li><p>컨트롤 비트를 사용하여 패킷이 흐르는 방향이 아니라 액세스 방향을 판단하여 접속 방향을 판단해야 합니다.</p>
<br>

</li>
</ul>
<aside>
💡 수신처 IP 주소, 송신처 IP 주소, 수신처 포트 번호, 송신처 포트 번호, TCP 컨트롤 비트를 조건으로 하고, 통신의 시점과 종점, 애플리케이션의 종류, 액세스 방향을 판별하여 방화벽 설정을 해야 서버가 위험한 상태에 빠지는 것을 예방할 수 있습니다.

</aside>
<br>

<p>방화벽은 패킷의 시점과 종점만을 가지고 판단하므로 위험한 패킷이 서버에 도착하여 서버를 다운시키는 상황을 예방할 수는 없습니다. 이 문제의 원인은 웹 서버의 소프트웨어의 버거에 있으므로 버그를 고쳐서 다운되지 않도록 합니다. 또한, 패킷의 내용을 조사하여 위험한 데이터가 포함된 패킷을 차단하는 별도의 준비가 필요합니다.
<br></p>
<h3 id="서버의-부하-분산">서버의 부하 분산</h3>
<aside>
💡 복수의 서버를 사용하여 처리를 분담하는 방법

</aside>

<p>클라이언트에서는 부하 분산 장치에 액세스하고 부하 분산 장치가 복수의 웹 서버에 요청을 전달합니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/b7bd35b8-1f38-4f93-beb0-9093f4b4c235/image.png" alt=""></p>
<p><strong>부하 분산 장치를 이용해 복수의 웹 서버로 분할</strong></p>
<p>부하 분산 장치(로드 밸런서)를 사용하여 웹 서버와 정기적으로 정보를 교환하여 CPU나 메모리의 사용률 등을 수집하고, 이것을 바탕으로 어느 웹 서버의 부하가 낮은지 판단하거나, 시험 패킷을 웹 서버에 보내 응답 시간으로 부하를 판단하는 방법으로 부하를 판단합니다.
<br></p>
<h3 id="캐시-서버를-이용한-서버의-부하-분산">캐시 서버를 이용한 서버의 부하 분산</h3>
<p>여러 대의 서버를 활용한 부하 분산 뿐만 아니라 캐시 서버를 활용하여 부하 분산을 합니다.</p>
<ul>
<li><strong>프록시</strong> : 웹 서버와 클라이언트 사이에 들어가서 웹 서버에 대한 액세스 동작을 중개하는 역할을 하는데, 액세스 동작을 중개할 때 웹 서버에서 받은 데이터를 디스크에 저장해 두고 웹 서버를 대신하여 데이터를 클라이언트에 반송하는 기능을 가지고 있습니다. 이를 <strong>캐시</strong>라고 부릅니다.</li>
<li>하지만, 웹 서버측에서 데이터가 변경되었을 때, 해당 데이터와 캐시의 데이터 일관성을 유지하는 것이 중요합니다.<br>

</li>
</ul>
<p><strong>데이터가 캐시에 저장되어 있는 경우</strong></p>
<ol>
<li>캐시 서버에서 <strong>웹 서버측에 데이터가 변경되었는지 조사하기 위한 ‘If-Modified-Since’라는 헤더 필드</strong>를 추가하여 웹 서버에 전송합니다.</li>
<li>웹 서버는 If-Modified-Since 헤더 필드와 데이터의 최종 갱신 일시를 비교하여 변경이 없었을 경우, 웹 서버에서 캐시 서버에 304 Not Modified 메시지를 보냅니다.</li>
<li>캐시 서버는 캐시에 저장한 데이터를 추출하여 클라이언트에게 전송합니다.<br>

</li>
</ol>
<p><strong>데이터가 캐시에 저장되어 있지 않은 경우</strong></p>
<ol>
<li>캐시 서버는 리퀘스트 메시지에 <strong>캐시 서버를 경유한 것을 나타내는 ‘Via’ 헤더 필드</strong>를 추가하여 웹 서버에 리퀘스트를 전송합니다. </li>
<li>한 대의 캐시 서버로 여러 대의 서버의 데이터를 캐시에 저장한 경우 <strong>전송 대상의 웹 서버를 판단하기 위해 리퀘스트 메시지의 URI를 사용</strong>합니다. 그리고 웹 서버로 요청을 보냅니다.</li>
<li>캐시 서버가 웹 서버에게 응답을 받으면 응답 메시지에 Via 헤더 필드를 추가하여 프록시를 경유한 사실을 클라이언트에 알립니다. 그리고 응답 메시지를 캐시에 저장하고 저장 일시를 기록합니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/136fa93f-ce02-4a7b-a425-f3a6ecaaf3be/image.png" alt=""></p>
<p><strong>포워드 프록시</strong> : 클라이언트측에 캐시 서버를 두는 방법으로, 캐시 이용뿐만 아니라 방화벽의 역할을 수행합니다. 리퀘스트의 내용을 조사한 후 전송하므로 <strong>리퀘스트의 내용에 따라</strong> 액세스가 가능한지 판단할 수 있습니다. 보통 브라우저 설정 화면에 준비되어 있는 프록시 서버라는 항목에 포워드 프록시 IP 주소를 설정합니다.</p>
<ul>
<li>포워드 프록시를 설정하면 URL의 내용에 상관 없이 리퀘스트를 전부 포워드 프록시로 보내는 것이 특징이고, http://…… 라는 URL을 그대로 리퀘스트 URL에 기록합니다.</li>
</ul>
<p><strong>리버시 프록시</strong> : 포워드 프록시를 사용함으로써 발생하는 단점(브라우저에 별도의 설정이 필요하다는 점, 인터넷에 공개하는 웹 서버는 누가 액세스하는지 알 수 없는)을 극복하기 위해 리퀘스트 메시지의 URI에 쓰여있는 디렉토리명과 전송 대상의 웹 서버를 대응시켜서 전송할 수 있도록 하는 방법을 채택합니다. 서버측에 설치하는 캐시 서버</p>
<p><strong>트랜스페어런트 프록시</strong> : 캐시 서버에서 전송 대상을 판단하는 방법, 즉 리퀘스트 메시지의 패킷의 IP 헤더를 통해 액세스 대상 웹 서버를 알아내는 방법(전송 대상을 캐시 서버에 설정할 필요가 없습니다.)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[라우터의 패킷 중계]]></title>
            <link>https://velog.io/@sung_hyuki/%EB%9D%BC%EC%9A%B0%ED%84%B0%EC%9D%98-%ED%8C%A8%ED%82%B7-%EC%A4%91%EA%B3%84</link>
            <guid>https://velog.io/@sung_hyuki/%EB%9D%BC%EC%9A%B0%ED%84%B0%EC%9D%98-%ED%8C%A8%ED%82%B7-%EC%A4%91%EA%B3%84</guid>
            <pubDate>Wed, 25 Jan 2023 05:55:08 GMT</pubDate>
            <description><![CDATA[<p><strong>라우터</strong></p>
<ul>
<li>라우터는 <strong>중계 부분</strong>과 <strong>포트 부분</strong>으로 구성되는데 중계 부분은 패킷의 중계 대상을 판단하는 동작을 담당하고, 포트 부분이 패킷을 송/수신하는 동작을 담당합니다.</li>
<li>다른 허브들과는 달리 라우터의 포트는 수신/송신처가 될 수 있습니다.</li>
<li>라우터의 각 포트에는 MAC 주소와 IP 주소가 할당되어 있습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/112f367a-7b12-422b-9b67-9a930e7b3770/image.png" alt=""></p>
<p>각 포트들은 포트 부분의 통신 기술의 규칙에 따라 동작하고, 중계 부분에서는 수신처 IP 주소와 중계 대상을 등록한 표를 대조하여 중계 대상을 판단합니다.
<br></p>
<p><strong>라우터 경로 표를 확인하는 방법</strong></p>
<ul>
<li>라우터는 IP 주소로 중계 대상을 판단합니다.<ul>
<li>호스트 번호는 무시하고 네트워크 번호 부분만을 조사합니다.</li>
</ul>
</li>
<li>게이트웨이 or 인터페이스 항목은 패킷의 중계 대상을 나타냅니다.</li>
<li>메트릭은 수신처 IP 주소에 기록되어 있는 목적지와 가까운지, 먼지 여부를 나타냅니다.<ul>
<li>수가 작으면 가깝고, 수가 크면 먼 것</li>
</ul>
</li>
</ul>
<br>

<p><strong>라우터의 패킷 수신 동작</strong></p>
<ol>
<li>신호가 커넥터 부분에 도착하면 PHY 회로와 MAC 회로에서 신호를 디지털 데이터로 변환합니다.</li>
<li>패킷 끝 부분의 <strong>FCS를 대조하여 오류 유무를 검사</strong>합니다. 정상이면 수신하고 수신 버퍼 메모리에 패킷을 저장하고, 비정상일 경우 패킷을 폐기 → 어차피 TCP 프로토콜에서 패킷을 재전송시킵니다.</li>
<li>패킷의 수신 동작이 끝나면 패킷의 MAC 헤더를 패기합니다. <strong>(MAC 헤더의 역할은 라우터에 패킷을 건너주는 것)</strong></li>
<li>IP 헤더의 내용을 통해 패킷 중계 실시. <strong>넷마스크의 네트워크 부분만을 비교</strong>하여 복수의 후보 중 서브넷의 범위가 가장 작은 서브넷을 선택합니다. 네트워크 번호의 길이가 같을 경우 메트릭 값을 보고 중계 대상을 선택합니다. 해당하는 행이 하나도 발견되지 않을 경우 ICMP 메시지를 통해 송신처에 이 사실을 알립니다.</li>
<li>라우터는 패킷을 송신하기 전 <strong>TTL(Time To Live) 값</strong>을 확인하여 라우터를 경유할 때마다 이 값을 1씩 줄이다가 이 숫자가 0이 되면 패킷을 폐기합니다. → <strong>패킷이 같은 장소를 순환하는 것을 방지</strong></li>
<li>패킷의 크기가 출력측의 패킷 최대 길이를 초과한다면, <strong>조각 나누기(fragmentation)</strong>을 실행합니다. IP 헤더의 플래그 필드를 확인하여 조각 나누기가 가능할 경우 패킷을 분할하고, 가능하지 않을 경우 패킷을 폐기하고, ICMP 메시지를 실행.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/6c461b43-de0d-43ff-8186-e18e03fdb8f7/image.png" alt=""></p>
<p><strong>라우터 패킷의 송신 동작</strong></p>
<ol>
<li>출력측의 포트 규칙에 따라 패킷을 신호로 변환하여 송신합니다.</li>
<li>라우터 경로표를 통해 IP 주소를 얻고 IP 주소가 결정되면 ARP 방식으로 수신처 MAC 주소를 조사하여 MAC 주소를 설정합니다.</li>
<li>송신 패킷이 완성되면 전기 신호를 이를 변환하여 포트에서 송신합니다.<br>

</li>
</ol>
<aside>
💡 IP가 이더넷에 의로한다는 것은 최종 목적지까지 패킷을 운반하는 것이 아니라 다음 라우터에 패킷을 운반하는 것입니다. MAC 헤더를 만들 떄 IP 경로표에서 다음 라우터의 IP 주소를 조사하고, 여기에서 ARP로 조사한 MAC 주소를 수신처 MAC 주소에 기록합니다. 이것이 다음 라우터까지 패킷을 운반하도록 이더넷에 의뢰하는 것을 나타냅니다. 각 라우터를 거칠 떄마다 이 동작을 반복하여 패킷이 IP의 목적지까지 운반되는 것입니다. 통신 상대까지 패킷을 전달하는 전체의 동작은 IP(라우터)가 담당하고, 이 동작을 할 때 다음 라우터까지 패킷을 운반하는 부분은 이더넷(스위칭 허브)가 담당합니다.

</aside>
<br>

<h3 id="라우터의-부가-기능">라우터의 부가 기능</h3>
<p>주소 부족 사태에 대처하기 위해 사내용으로 프라이비트 주소(private address)를 사용하고, 회사에 할당된 고유한 주소를 글로벌 주소(global address)로 사용합니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/81c05b5b-0f46-45b3-b16a-53fd25afd861/image.png" alt=""></p>
<p>하지만 사내 네트워크는 완전히 독립되어 있는 것이 아니라 인터넷을 통해 많은 회사에 연결되므로 위와 같이 네트워크 환경을 구성합니다. 공개용 서버쪽에 글로벌 주소를 할당하고 공개용 서버를 통해 다른 사내 네트워크에 접속합니다.
<br></p>
<p><strong>주소 변환의 기본 동작</strong></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/50c35e1b-5a7c-420e-9a39-b72b6778a3e9/image.png" alt=""></p>
<p>주소 변환 장치는 주소의 대응표에서 글로벌 주소와 포트 번호를 찾아서 수신처를 대응하는 프라이빗 주소와 포트 번호로 바꿔쓰고, 사내 네트워크에 패킷을 보냅니다.</p>
<p>이외에도 패킷 필터링 기능이 존재합니다.
<br></p>
<p>..이하 액세스 회선을 통해 인터넷 내부를 탐험하는 부분은 생략하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로토콜 스택과 LAN 어댑터를 통해 디지털 데이터가 전기 신호로 만들어지기까지]]></title>
            <link>https://velog.io/@sung_hyuki/%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EC%8A%A4%ED%83%9D%EA%B3%BC-LAN-%EC%96%B4%EB%8C%91%ED%84%B0%EB%A5%BC-%ED%86%B5%ED%95%B4-%EB%94%94%EC%A7%80%ED%84%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%EC%A0%84%EA%B8%B0-%EC%8B%A0%ED%98%B8%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%A7%80%EA%B8%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@sung_hyuki/%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EC%8A%A4%ED%83%9D%EA%B3%BC-LAN-%EC%96%B4%EB%8C%91%ED%84%B0%EB%A5%BC-%ED%86%B5%ED%95%B4-%EB%94%94%EC%A7%80%ED%84%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%EC%A0%84%EA%B8%B0-%EC%8B%A0%ED%98%B8%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%A7%80%EA%B8%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Sat, 21 Jan 2023 08:28:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/88fac7cb-023c-4be9-bf3f-11d011d7bde2/image.png" alt=""></p>
<p><strong>ICMP</strong></p>
<aside>
💡 패킷을 운반할 때 발생하는 오류를 통지하거나 제어용 메시지를 통지합니다.

</aside>
<br>

<p><strong>ARP</strong></p>
<aside>
💡 IP 주소에 대응하는 이더넷의 MAC 주소를 조사할 때 사용합니다.

</aside>
<br>

<p><strong>LAN 드라이버</strong></p>
<aside>
💡 LAN 어댑터의 하드웨어를 제어하며, LAN 어댑터가 실제 송/수신 동작, 즉 케이블에 대해 신호를 송/수신하는 동작을 실행합니다.

</aside>
<br>

<p>소켓은 통신 동작을 제어하기 위한 여러 가지 제어 정보가 기록되어 있습니다. 그 예로는, 통신 상대의 IP 주소, 포트 번호, 통신 동작의 진행 상태, 데이터 송신 후 경과 시간 등
<br></p>
<p>MAC OS의 경우 터미널에 명령어 <code>lsof</code>를 통해 소켓의 내용을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/985d62f3-2999-45b5-ab47-77ba908a9636/image.png" alt=""></p>
<h3 id="접속-동작의-첫-번째-동작connect">접속 동작의 첫 번째 동작(Connect)</h3>
<p>: 통신 상대와의 사이에 제어 정보를 주고받아 소켓에 필요한 정보를 기록하고 데이터 송/수신이 가능한 상태로 만드는 것</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/9e5ca169-5604-4432-8ab9-e285722590b5/image.png" alt=""></p>
<p>패킷의 맨 앞 부분에 헤더, 즉 제어 정보를 추가합니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/26be4e57-18db-4b1f-aefe-02a01cb3a68f/image.png" alt=""></p>
<h3 id="3-way-handshake">3 Way HandShake</h3>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/6a7279ce-56e5-4905-8edc-008358dc5d49/image.png" alt=""></p>
<p>connect 동작을 3 way handshake로 설명할 수 있습니다.</p>
<p>프로토콜 스택이 받은 데이터는 바로 송신되는 것이 아닌 송신용 버퍼 메모리 영역에 우선 저장합니다. 프로토콜 스택에 건네주는 데이터의 길이는 애플리케이션의 종류나 만드는 방법에 따라 다르기 때문에 이를 우선 버퍼 메모리에 저장한 후 송신 동작을 수행합니다. (프로토콜 스택에 전해지는 데이터를 곧바로 송신한다면 네트워크 이용 효율이 떨어지기 때문에 그렇게 수행하지 않습니다.)</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/9862119f-bd19-485d-be7a-2829399ee022/image.png" alt=""></p>
<h3 id="송신-동작을-지정할-때의-판단-기준">송신 동작을 지정할 때의 판단 기준</h3>
<p><strong>MTU</strong></p>
<ul>
<li>한 패킷으로 운반할 수 있는 디지털 데이터의 최대 길이</li>
</ul>
<p><strong>MSS</strong></p>
<ul>
<li>헤더를 제외하고 한 개의 패킷으로 운반할 수 있는 TCP의 데이터의 최대 길이<br>

</li>
</ul>
<p>MSS의 길이를 가지고 데이터 전송 여부를 판단하기도 하지만 애플리케이션의 송신 속도가 느려지는 경우 MSS에 가깝게 데이터가 저장되면 송신하기도 합니다.
<br></p>
<p><strong>시퀀스 번호</strong></p>
<ul>
<li>데이터 조각을 송신할 때 조각이 통신 개시부터 따져서 몇 번째 바이트인지<br>

</li>
</ul>
<p>수신측에서는 시퀀스 번호를 통해 패킷의 누락 여부를 확인할 수 있습니다. 누락이 없다는 것을 확인했다면 수신측은 그 이전에 수신한 데이터와 합쳐서 데이터를 몇 번째 바이트까지 수신한 것인지 계산하고, 그 값을 TCP 헤더의 <strong>ACK 번호</strong>에 기록하여 송신측에 알려줍니다. (수신 확인 응답)
<br></p>
<aside>
💡 제어 비트로써의 ACK 비트와 SYN 비트의 역할은 각각 ACK 번호 필드가 유효하다, 시퀀스 번호의 초기값을 통지한다를 알리는 것입니다.

</aside>

<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/b8f636fc-95dd-4664-a8d5-0e183b18d0c8/image.png" alt=""></p>
<p>초기 3 단계는 <strong>3 way handshake 과정</strong>입니다. 클라이언트에서 <strong>시퀀스 번호의 초기값</strong>을 서버로 보내고, 서버는 초기값으로부터 <strong>ACK 번호</strong>를 산출하여 클라이언트에게 반송하는데 이때 클라이언트에 보내는 데이터에 관한 <strong>시퀀스 번호의 초기값을 함께 통지</strong>합니다. 그러면 클라이언트에서도 서버로부터 받은 시퀀스 번호로부터 <strong>ACK 번호</strong>를 산출하여 서버에 반송합니다.
<br></p>
<aside>
💡 ACK가 돌아오는 시간인 타임아웃 값을 통해 패킷의 재전송 여부를 결정합니다.

</aside>
<br>

<p><strong>윈도우 제어 방식</strong></p>
<aside>
💡 한 패킷을 보내고 ACK 번호를 기다리는 타임아웃 시간 동안의 시간 낭비를 막기 위해 ACK 번호를 기다리지 않고 연속해서 복수의 패킷을 보내는 방법

</aside>

<ul>
<li>수신 버퍼가 존재하여 애플리케이션에 건네주는 속도보다 빠른 속도로 패킷이 도착하여 패킷 오버플로우가 되지 않는 방식으로 수신 버퍼의 크기를 설정해야 합니다. → 수신 버퍼의 크기를 송신측에 통지하여 데이터 송신 속도를 조절</li>
<li>수신 가능한 데이터 양의 최대값을 <strong>윈도우 사이즈</strong>라고 부릅니다.</li>
<li>윈도우 통지 타이밍은 애플리케이션측에 데이터를 건네주고 수신 버퍼의 빈 영역이 늘어났을 때 이를 통지합니다.<br>

</li>
</ul>
<p><strong>연결 끊기 과정</strong></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/8659a8cd-ec16-4f22-8e1d-af9f41b09cf3/image.png" alt=""></p>
<br>

<h3 id="ip와-이더넷의-패킷-송수신-동작">IP와 이더넷의 패킷 송/수신 동작</h3>
<p><strong>허브</strong></p>
<ul>
<li><strong>이더넷</strong>의 규칙에 따라 패킷을 운반</li>
<li><strong>MAC 헤더</strong>를 수신처 판단 구조로 사용합니다.</li>
</ul>
<p><strong>라우터</strong></p>
<ul>
<li><strong>IP</strong>의 규칙에 따라 패킷을 운반</li>
<li><strong>IP 헤더</strong>를 통해<br>

</li>
</ul>
<aside>
💡 이더넷은 IP의 신뢰 받아 패킷을 운반할 수 있는 무선 LAN, ADSL, FTTH 등으로 대체될 수 있습니다.

</aside>
<br>

<p>이더넷과 같은 거대한 네트워크를 이동하기 위해서는 MAC 헤더를 통해 다음 수신지를 정하고 해당 수신지에 도착하면 새로운 MAC 헤더를 통해 다음 수신지를 정하는 것을 반복합니다.
<br></p>
<p><strong>IP 헤더</strong></p>
<ul>
<li>IP 프로토콜에 규정된 규칙에 따라 IP 주소로 표시된 목적지까지 패킷을 전달할 때 사용하는 제어 정보를 기록한 것</li>
</ul>
<p><strong>MAC 헤더</strong></p>
<ul>
<li>이더넷 등의 LAN을 사용하여 가장 가까운 라우터까지 패킷을 운반할 때 사용하는 제어 정보를 기록한 것<br>

</li>
</ul>
<p>LAN 어댑터에 패킷을 전해줄 때의 모습은 0과 1로 이루어진 이진 디지털 데이터이고, 이것이 LAN 어댑터에 의해 전기나 빛의 신호 상태로 바뀌어 케이블에 송출됩니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/a75f512e-e35d-4225-a4bb-0ac0b3980f36/image.png" alt=""></p>
<p>IP 헤더</p>
<p>라우팅 테이블을 확인하여 다음 라우터를 결정합니다.</p>
<p>MAC OS에서는 <code>netstat -nr</code> 명령을 통해 라우팅 테이블을 확인할 수 있습니다.</p>
<ul>
<li>Network Destination :  수신처의 IP 주소</li>
<li>Interface : LAN 어댑터 등의 네트워크 인터페이스 주소</li>
<li>Gateway : 다음 라우터의 IP 주소</li>
<li>넷마스크가 0.0.0.0인 경우 라우팅 테이블에 일치하는 주소가 없을 경우 해당 행이 해당하는 것으로 간주합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/80a14979-a2f2-4c51-9f4d-2573f9942a49/image.png" alt=""></p>
<p>수신처 라우터의 MAC 주소는 <strong>ARP</strong>를 통해 조사합니다. 이더넷에는 연결되어 있는 전원에게 패킷을 전달하는 브로드캐스트라는 구조가 있으며, <strong>브로드캐스트</strong>를 활용하여 수신처 라우터의 MAC 주소를 알아냅니다. ARP에도 캐시가 존재하여 한 번 조사한 값은 캐시를 통해 조회해서 사용합니다.
<br></p>
<p>IP 담당 부분에서 MAC 헤더 제조의 역할까지 수행한 후 LAN 어댑터로 데이터를 전송합니다. (이는 IP 이외의 다양한 패킷에도 LAN 어댑터가 대응할 수 있도록 하기 위함 입니다)
<br></p>
<h3 id="이더넷-패킷의-송수신-동작">이더넷 패킷의 송/수신 동작</h3>
<p>이더넷 패킷의 송/수신 동작은 LAN 어댑터에 의해 실행되는데, LAN 어댑터는 LAN 드라이버에 의해 제어됩니다.</p>
<p><strong>이더넷</strong></p>
<ul>
<li>다수의 컴퓨터가 여러 상대와 자유롭게 적은 비용으로 통신하기 위해 고안된 통신 기술</li>
</ul>
<p><strong>이더넷의 성질</strong></p>
<ul>
<li>MAC 헤더의 수신처 MAC 주소에 기억된 상대에게 패킷을 전달하고,</li>
<li>송신처 MAC 주소로 송신처를 나타낸 후</li>
<li>이더 타입으로 패킷의 내용물을 나타낸다<br>

</li>
</ul>
<p><strong>LAN 어댑터의 구조</strong></p>
<p>디지털 데이터를 전기나 빛 신호로 변환하여 네트워크의 케이블에 송출</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/2e18de0a-9d0d-4d90-aac2-459ef35cee26/image.png" alt=""></p>
<p>LAN 어댑터의 초기화 (MAC 회로에 MAC 주소를 설정이 가장 중요한 동작)</p>
<p><img src="blob:https://velog.io/1e1f609b-68eb-4d34-b93e-d2705cea0130" alt="업로드중.."></p>
<p><strong>프리앰블</strong></p>
<ul>
<li>송신하는 패킷을 읽을 때의 타이밍을 잡기 위한 것</li>
<li>수신측에서 신호 수신시 이 파형에서 타이밍을 판단</li>
</ul>
<p><strong>스타트 프레임 딜리미터</strong></p>
<ul>
<li>스타트 프레임 딜리미터는 끝이 11이라는 비트 패턴 → 갑자기 파형이 변하는 구간</li>
<li>이것을 패킷의 개시 위치로 간주함</li>
</ul>
<p><strong>FCS</strong></p>
<ul>
<li>패킷을 운반하는 도중에 잡음 등의 영향으로 파형이 흐트러져 데이터가 변한 경우 이것을 검출하기 위해 사용합니다. (<strong>오류 검출용 데이터</strong>)<br>

</li>
</ul>
<p>LAN 어댑터의 디지털 신호 → 전기 신호 변환 과정의 자세한 내용은 생략하도록 하겠습니다.</p>
<aside>
💡 LAN 어댑터의 MAC 회로가 공통 형식의 신호를 만들고 PHY(MAU) 회로가 케이블에 송출하는 형식으로 변환하여 케이블에 송신합니다.

</aside>
<br>

<p>이더넷 통신 방식은 송신 신호가 상대에게 완전히 도착했는지 확인하지 않습니다. <strong>오류가 발생한다면 이는 TCP가 검출합니다.</strong>
<br></p>
<p>LAN 어댑터의 패킷 수신 시 컴퓨터는 다른 할 일을 하고 있기 때문에 이를 알리기 위해 확장 버스 슬롯 부분에 있는 인터럽트용 신호선에 신호를 보냅니다.
<br></p>
<h3 id="서버에서-응답으로-오는-패킷">서버에서 응답으로 오는 패킷</h3>
<p>LAN 드라이버는 패킷의 프로토콜 타입을 검사하여 해당 프로토콜 스택에 패킷을 전달합니다. TCP/IP의 경우 0800</p>
<ul>
<li>수신처 IP 주소가 자신의 주소와 다르면 <strong>ICMP</strong>라는 메시지를 통해 통신 상대에게 오류를 통지합니다.</li>
<li>IP 프로토콜은 LAN 회선을 통해 운반된 패킷을<ul>
<li><strong>플래그</strong>를 통해 조각난 패킷인지를 판별하고</li>
<li>조각난 패킷이라면 메모리에 일시적으로 보관</li>
<li>IP 헤더에 있는 ID 정보를 통해 하나의 패킷인지를 검사하고 조각 모음</li>
<li><strong>프래그먼트 오프셋</strong>을 통해 패킷이 원래 패킷의 어느 위치에 있었는지를 판별</li>
<li>분할된 패킷을 원래의 패킷으로 되돌리는 <strong>리어셈블링</strong> 과정을 거칩니다</li>
</ul>
</li>
<li>TCP 헤더를 통해 해당하는 소켓을 찾습니다.<br>

</li>
</ul>
<h3 id="udp--수정-송신이-필요없는-데이터의-송신효율을-위해-설계된">UDP : 수정 송신이 필요없는 데이터의 송신(효율을 위해 설계된)</h3>
<ul>
<li>수신 확인이나 윈도우가 없어서 데이터 송/수신 전에 제어 정보를 주고 받을 필요가 없고, 접속이나 연결 끊기의 단계가 없습니다.</li>
<li>오류 발생이나 패킷 손실에 대해서 감지하지 않습니다.</li>
<li>음성이나 영상과 같이 신뢰성보다는 빠른 전송이 요구되는 애플리케이션에서 사용합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저에서 HTTP 메시지를 만들기까지]]></title>
            <link>https://velog.io/@sung_hyuki/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-HTTP-%EB%A9%94%EC%8B%9C%EC%A7%80%EB%A5%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@sung_hyuki/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-HTTP-%EB%A9%94%EC%8B%9C%EC%A7%80%EB%A5%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Thu, 19 Jan 2023 11:45:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/392c746c-04fb-4877-ac98-13fa567763c1/image.png" alt="">
웹 브라우저가 URL의 의미를 조사 후 그 의미에 따라 리퀘스트 메시지를 만듭니다. 그리고 이를 웹 서버로 전송합니다.
<br></p>
<p><strong>프로토콜 스택의 역할</strong></p>
<ul>
<li>메시지를 보내는 것 → 디지털 데이터를 운반하는 구조의 역할</li>
<li>메시지를 패킷에 저장하고, 수신처 주소 등의 제어 정보를 덧붙입니다.</li>
<li>통신 오류 발생 시 패킷을 고쳐서 보내거나 데이터 통신의 기본을 조절하는 등의 역할을 수행합니다.</li>
<li><strong>편지를 우체통에 넣고 오는 비서와 같은 것</strong><br>

</li>
</ul>
<p><strong>허브, 스위치, 라우터</strong></p>
<ul>
<li>LAN을 경유하여 인터넷에 접속합니다.</li>
<li><strong>우체통에 봉투를 넣으면 그 후에는 집배원이 상대에서 편지를 전달하는 것</strong><br>

</li>
</ul>
<p><strong>프로바이더(통신사</strong>)</p>
<ul>
<li>인터넷 접속용 라우터에는 <strong>액세스 회선</strong>이 연결되어 있고 이는 프로바이더까지 연결됩니다.</li>
<li>POP(Point Of Presence)라는 설비를 이용하여 패킷을 전국 또는 전 세계로 운반합니다.</li>
<li>POP는 <strong>가장 가까운 우체국의 역할로 우체통에서 회수한 편지는 우체국에서 분류되어 전국 또는 전 세계로 배송</strong><br>

</li>
</ul>
<p><strong>방화벽</strong></p>
<ul>
<li>웹 서버측의 LAN에 도착하면 방화벽에서 패킷을 검사<br>

</li>
</ul>
<p><strong>캐시 서버</strong></p>
<ul>
<li>방화벽을 통과한 데이터 중 다시 이용할 확률이 높은 데이터는 캐시 서버에 저장됩니다.</li>
<li>필요로 하는 데이터가 캐시 서버에 존재할 경우 웹 서버를 거치지 않고 응답을 할 수 있습니다.</li>
<li>부하 분산 장치로도 사용합니다.<br>

</li>
</ul>
<p><strong>웹 서버</strong></p>
<ul>
<li>패킷을 다시 리퀘스트 메시지로 복원하고 웹 서버 애플리케이션에 넘깁니다. (프로토콜 스택의 역할)</li>
<li>리퀘스트에 따른 응답 메시지를 넣어 다시 클라이언트에게 전송. 이는 지금까지의 작동과 반대의 과정을 거쳐 클라이언트에 전달<br>

</li>
</ul>
<aside>
💡 프로토콜 스택의 역할은 리퀘스트 메시지를 패킷으로 변환하고, 다시 패킷을 리퀘스트 메시지로 변환하는 것입니다.

</aside>
<br>

<h2 id="웹-브라우저">웹 브라우저</h2>
<aside>
💡 브라우저는 웹 서버에 액세스하는 클라이언트의 역할을 할 뿐만 아니라, 파일을 업로드/다운로드 하는 FTP, 메일 클라이언트 등의 역할을 하는 복합적인 클라이언트 소프트웨어입니다.

</aside>
<br>

<aside>
💡 브라우저는 URL을 해독하거나 HTTP 메시지를 만들지만, **메시지를 네트워크에 송출하는 기능은 없습니다.**

</aside>

<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/8f38d036-c408-4a14-acf3-8255d97227f1/image.png" alt=""></p>
<p><strong>Request</strong></p>
<ul>
<li>첫 번째 행은 <strong>리퀘스트 라인 :</strong> &lt;메소드&gt;&lt;공백&gt;<URI>&lt;공백&gt;&lt;HTTP 버전&gt;</li>
<li><strong>메시지 헤더 :</strong> 행 수는 상황에 따라 달라지며, 공백 행까지가 메시지 헤더에 포함됩니다.</li>
<li><strong>메시지 본문 :</strong> 데이터를 보낼 때 데이터를 넣는 필드</li>
</ul>
<p><strong>Response</strong></p>
<ul>
<li>&lt;HTTP 버전&gt;&lt;공백&gt;&lt;스테이터스 코드&gt;&lt;공백&gt;&lt;스테이터스 코드의 의미를 담은 짧은 설명문&gt;</li>
<li>메시지 헤더</li>
<li>메시지 본문<br>

</li>
</ul>
<aside>
💡 브라우저의 리퀘스트  메시지를 보내는 것은 URL 뿐만 아니라, 하이퍼링크, 버튼 등이 있습니다.

</aside>
<br>

<h3 id="ip-주소">IP 주소</h3>
<aside>
💡 IP 주소는 네트워크 번호와 호스트 번호의 두 주소를 합친 것인데, 책에서 설명하는 네트워크 번호의 의미는 하나의 서브넷 안에서 사용할 수 있는 호스트 번호들의 범위로 이해하면 될 것 같습니다. 호스트 번호는 해당 서브넷 안에서 각 기기에 부여할 수 있는 번호를 의미합니다.

</aside>
<br>

<p>그럼 여기서 IP 주소의 표기법에 대해서 알아봅시다.</p>
<ul>
<li><p>IP 주소 본체의 표기 방법</p>
<pre><code class="language-java">  10.11.12.13</code></pre>
</li>
<li><p>IP 주소 본체와 같은 방법으로 네트워크를 표기하는 방법</p>
<pre><code class="language-java">  10.11.12.13/255.255.255.0
   IP 주소 본체    넷마스크</code></pre>
</li>
<li><p>네트워크 번호의 비트수로 넷마스크를 표기하는 방법</p>
<pre><code class="language-java">  10.11.12.13/24</code></pre>
</li>
<li><p>서브넷을 나타내는 주소</p>
<pre><code class="language-java">  10.11.12.**0**/24</code></pre>
<p>  호스트 번호 부분의 비트가 모두 0인 것은 각 컴퓨터가 아니라 <strong>서브넷 자체</strong>를 나타냅니다.</p>
</li>
<li><p>서브넷의 브로드캐스트를 나타내는 주소</p>
<pre><code class="language-java">  10.11.12.**255**/24</code></pre>
<p>  호스트 번호 부분의 비트가 모두 1인 것은 서브넷 전체에 대한 브로드캐스트를 나타냅니다.</p>
</li>
</ul>
<br>

<h3 id="dns-서버를-통해-ip-주소를-찾는-원리">DNS 서버를 통해 IP 주소를 찾는 원리</h3>
<aside>
💡 브라우저는 어떻게 DNS 서버를 조회할 수 있을까요?

</aside>

<p>DNS 리졸버가 DNS 서버를 조회하는 기능을 가지고 있고 DNS 리졸버는 Socket 라이브러리에 있습니다. </p>
<p>리졸버를 호출하면 리졸버가 DNS 서버에 조회 메시지를 보내고, DNS 서버에서 응답 메시지가 돌아옵니다. 브라우저는 응답 메시지 속에 포함되어 있는 IP 주소를 추출하여 HTTP 리퀘스트 메시지와 함께 송신합니다.
<br></p>
<ul>
<li><p>DNS 서버는 클라이언트로부터 넘어오는 <strong>이름, 클래스, 타입 정보</strong>를 바탕으로 IP 주소를 찾아서 응답합니다.</p>
<ul>
<li>IP 주소 조회 : A 레코드</li>
<li>매일 배송 목적지 조회 : MX 레코드</li>
<li>etc…</li>
</ul>
<br>

</li>
</ul>
<aside>
💡 이름과 타입에 따라 조사하는 정보를 지정하고, 그것에 따라 해당하는 것을 찾아 클라이언트에 회답하는 것이 DNS 서버의 기본 동작입니다.

</aside>
<br>

<p>DNS 서버는 정보를 분산시켜서 다수의 DNS 서버에 등록하고, 다수의 DNS 서버가 연대하여 어디에 정보가 등록되어 있는지를 찾아내는 구조입니다. (도메인명을 통해)</p>
<br>
</br>

<p><strong>Recursive Query</strong> </p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/ae53aac2-411d-41ce-8aea-a1c4d43d84a1/image.png" alt=""></p>
<aside>
💡 DNS 서버는 캐시 기능으로 빠르게 회답할 수 있습니다.

</aside>

<p>프로토콜 스택 내부에서는 소켓 통신을 이용하여 데이터를 전달하기 때문에 데이터를 송/수신 하기 전 소켓 연결 과정이 필요합니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/1c399116-e9c1-4fee-bf13-04b65b3501e4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/f9a3b3cb-8e1e-40e1-86dc-c0150576687a/image.png" alt=""></p>
<aside>
💡 디스크립터를 통해 각 요청에 대해 생성된 소켓을 식별합니다. 이 디스크립터는 컴퓨터 한 대의 내부에서 소켓을 식별하기 위해서 사용하지만, 포트 번호는 접속 상대측에서 소켓을 식별하기 위해 사용합니다. 서버측의 디스크립터를 클라이언트에서 알 수 없으므로 클라이언트측에서 서버측의 디스크립터를 사용하여 소켓을 지정할 수 없습니다. 포트 번호를 통해 접속 측의 소켓을 식별합니다.

</aside>
<br>

<h3 id="정리">정리</h3>
<ul>
<li>애플리케이션이 소켓을 식별하는 것은 디스크립터입니다.</li>
<li>IP 주소와 포트 번호를 통해 클라이언트와 서버 간에 상대의 소켓을 식별합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Action]]></title>
            <link>https://velog.io/@sung_hyuki/Github-Action</link>
            <guid>https://velog.io/@sung_hyuki/Github-Action</guid>
            <pubDate>Tue, 10 Jan 2023 11:50:43 GMT</pubDate>
            <description><![CDATA[<h3 id="workflow">Workflow</h3>
<ul>
<li>자동화된 전체 프로세스. 하나 이상의 Job으로 구성되고, Event에 의해 예약되거나 트리거될 수 있는 자동화된 절차</li>
<li>Workflow 파일은 YAML으로 작성되고, Github Repository의 <code>.github/workflows</code> 폴더 아래에 저장. Github에게 YAML 파일로 정의한 자동화 동작을 전달하면, Github Actions는 해당 파일을 기반으로 그대로 실행시킨다.</li>
</ul>
<h3 id="event">Event</h3>
<ul>
<li>Workflow를 트리거(실행)하는 특정 활동이나 규칙. 예를 들어, 누군가가 커밋을 레포지토리에 푸시하거나 풀 요청이 생성될 때 Github에서 활동이 시작될 수 있다.</li>
</ul>
<h3 id="job">Job</h3>
<ul>
<li>Job은 여러 Step으로 구성되고, 단일 가상 환경에서 실행된다. 다른 Job에 의존 관계를 가질 수도 있고, 독립적으로 병렬로 실행될 수도 있다.</li>
</ul>
<h3 id="step">Step</h3>
<ul>
<li>Job 안에서 순차적으로 실행되는 프로세스 단위. step에서 명령을 내리거나, action을 실행할 수 있다.</li>
</ul>
<h3 id="action">Action</h3>
<ul>
<li>job을 구성하기 위한 step들의 조합으로 구성된 독립적인 명령</li>
<li>workflow의 가장 작은 빌드 단위</li>
<li>workflow에서 action을 사용하기 위해서는 action이 step을 포함해야 한다.</li>
<li>action을 구성하기 위해서 레포지토리와 상호작용하는 커스텀 코드를 만들 수도 있다.</li>
<li>사용자가 직접 커스터마이징하거나, 마켓플레이스에 있는 action을 가져다 사용할 수도 있다.</li>
</ul>
<h3 id="runner">Runner</h3>
<ul>
<li>Github Action Runner 애플리케이션이 설치된 머신으로, Workflow가 실행될 인스턴스<br>

</li>
</ul>
<h2 id="👨🏻💻-overview">👨🏻‍💻 Overview</h2>
<hr>
<p>Github Actions는 소프트웨어 개발 생명 주기 내에 자동화된 테스크를 수행할 수 있도록 도와줍니다. Github Action은 특정한 이벤트가 발생한 이후에 일련의 명령어들을 실행할 수 있는 Event-Driven 입니다. </p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/2ee41fe5-2dbb-4e3f-baea-4e4bb3bccf6f/image.png" alt=""></p>
<p>그림에서 보여지는 것과 같이, Event가 job을 포함하는 workflow를 자동으로 트리거합니다. 그런 다음 job은 step을 사용하여 action을 실행하는 순서를 제어합니다. </p>
<h2 id="👨🏻💻-github-actions의-컴포넌트">👨🏻‍💻 Github Actions의 컴포넌트</h2>
<hr>
<p>다음은 job을 실행하기 위해 함께 동작하는 여러 Github Actions 컴포넌트의 목록입니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/049cf894-3458-4904-aa7f-f447bc3fa842/image.png" alt=""></p>
<h3 id="workflows">Workflows</h3>
<ul>
<li>레포지토리에 추가하는 자동화된 절차</li>
<li>workflow는 하나 이상의 job으로 구성되며 이벤트에 의해 스케줄되거나 트리거될 수 있습니다.</li>
<li>workflow는 깃헙에서 프로젝트를 빌드, 테스트, 패키지, 릴리즈, 배포하는데 사용되어질 수 있습니다.</li>
</ul>
<h3 id="events">Events</h3>
<ul>
<li>workflow를 유발하는 특정한 활동</li>
</ul>
<h3 id="jobs">Jobs</h3>
<ul>
<li>동일한 runner를 실행하는 일련의 step</li>
<li>기본적으로, 여러 job이 있는 workflow는 해당 job들을 병렬적으로 실행합니다. 작업을 순차적으로 실행하도록 workflow를 구성할 수도 있습니다.</li>
</ul>
<h3 id="steps">Steps</h3>
<ul>
<li>job에서 명령을 실행할 수 있는 개별 작업</li>
<li>step은 action 또는 쉘 명령어일 수도 있습니다.</li>
<li>작업의 각 단계는 동일한 실행기에서 실행되므로 해당 job에서 action은 서로 데이터를 공유하는 것을 허용합니다.</li>
</ul>
<h3 id="actions">Actions</h3>
<ul>
<li>Action은 작업을 만들기 위해 step과 결합되는 standalone 명령어</li>
<li>Action은 워크플로우의 가장 작은 이식 가능한 구성 요소입니다.</li>
</ul>
<h3 id="runners">Runners</h3>
<ul>
<li>Github Action runner Application이 설치된 서버</li>
<li>깃헙에 의해 호스팅된 러너를 사용하거나 직접 호스팅할 수 있습니다.</li>
<li>러너는 사용 가능한 작업을 수신 대기하고 한 번에 하나의 작업을 실행하고 진행 상황, 로그 및 결과를 다시 Github에 보고합니다.</li>
<li>Github 호스팅 러너는 Ubuntu Linux, Microsoft Windows 및 macOS를 기반으로 하며 워크플로의 각 작업은 새로운 가상 환경에서 실행됩니다.</li>
</ul>
<p>Workflow 파일은 YAML으로 작성되고, Github Repository의 <code>.github/workflows</code> 폴더 아래에 저장. Github에게 YAML 파일로 정의한 자동화 동작을 전달하면, Github Actions는 해당 파일을 기반으로 그대로 실행시킨다.</p>
<p><strong>example</strong></p>
<p><code>name: learn-github-actions</code> : (선택) workflow의 이름으로 깃헙 레포지토리의 Action 탭에 나타남.</p>
<p><code>on: [push]</code> : workflow 파일을 자동으로 트리거하는 이벤트를 지정</p>
<p><code>jobs</code> :  &#39;learn-github-actions&#39; workflow 파일에서 동작하는 모든 작업을 함께 그룹화</p>
<p><code>check-bats-version</code> : 작업 섹션에 저장된 &#39;check-bats-version&#39; 작업을 정의</p>
<p><code>runs-on: ubuntu-latest</code> : Ubuntu Linux 실행기에서 실행되도록 작업을 구성합니다. 이는 작업이 GitHub에서 호스팅하는 새로운 가상 머신에서 실행됨을 의미합니다. 다른 러너를 사용하는 구문 예제는 &quot;<a href="https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-on">GitHub 작업에 대한 워크플로 구문</a>&quot;을 참조하십시오.</p>
<p><code>steps</code> : check-bats-version 작업에서 실행되는 모든 단계를 함께 그룹화. 이 섹션 아래에 중첩된 각 항목은 별도의 작업 또는 셸 명령입니다.</p>
<p><code>- uses: actions/checkout@v2</code> : uses 키워드는 actions/checkout@v2라는 커뮤니티 작업의 v2를 검색하도록 작업에 지시합니다. 이것은 리포지토리를 체크아웃하고 러너에 다운로드하여 코드에 대해 작업(예: 테스트 도구)을 실행할 수 있도록 하는 작업입니다.</p>
<pre><code>    - uses: actions/setup-node@v2
      with:
        node-version: &#39;14&#39;</code></pre><p>이 단계에서는 actions/setup-node@v2 작업을 사용하여 러너에 지정된 버전의 노드 소프트웨어 패키지를 설치합니다. 그러면 npm 명령에 액세스할 수 있습니다.</p>
<p><code>- run: npm install -g bats</code> : run 키워드는 러너에서 명령을 실행하도록 작업에 지시합니다. 이 경우 npm을 사용하여 bats 소프트웨어 테스트 패키지를 설치합니다.</p>
<p><code>run: bats -v</code> : 마지막으로 소프트웨어 버전을 출력하는 매개변수를 사용하여 bat 명령을 실행합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드가 푸시되면 자동으로 배포해 보자 - CI 배포 자동화]]></title>
            <link>https://velog.io/@sung_hyuki/%EC%BD%94%EB%93%9C%EA%B0%80-%ED%91%B8%EC%8B%9C%EB%90%98%EB%A9%B4-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%B4-%EB%B3%B4%EC%9E%90-CI-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94</link>
            <guid>https://velog.io/@sung_hyuki/%EC%BD%94%EB%93%9C%EA%B0%80-%ED%91%B8%EC%8B%9C%EB%90%98%EB%A9%B4-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%B4-%EB%B3%B4%EC%9E%90-CI-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94</guid>
            <pubDate>Tue, 10 Jan 2023 11:42:16 GMT</pubDate>
            <description><![CDATA[<h2 id="👨🏻💻-ci-cd란">👨🏻‍💻 CI, CD란?</h2>
<p>코드 버전 관리를 하는 VCS 시스템(Git, SVN 등)에 PUSH가 되면 자동으로 테스트와 빌드가 수행되어 안정적인 배포 파일을 만드는 과정을 CI(Continuous Integration - 지속적 통합)라고 하며, 이 빌드 결과를 자동으로 운영 서버에 무중단 배포까지 진행되는 과정을 CD(Continuous Deployment - 지속적인 배포)라고 한다.
<br></p>
<p><strong>CI에 대한 4가지 규칙</strong></p>
<ul>
<li>모든 소스 코드가 살아 있고(현재 실행되고) 누구든 현재의 소스에 접근할 수 있는 단일 지점을 유지할 것</li>
<li>빌드 프로세스를 자동화해서 누구든 소스로부터 시스템을 빌드하는 단일 명령어를 사용할 수 있게 할 것</li>
<li>테스팅을 자동화해서 단일 명령어로 언제든지 시스템에 대한 건전한 테스트 수트를 실행할 수 있게 할 것</li>
<li>누구나 현재 실행 파일을 얻으면 지금까지 가장 완전한 실행 파일을 얻었다는 확실하게 할 것<br>

</li>
</ul>
<h3 id="travis-ci">Travis CI</h3>
<ul>
<li>깃허브에서 제공하는 무료 CI 서비스</li>
<li>오픈소스 웹 서비스<br>

</li>
</ul>
<p>.travis.yml</p>
<pre><code>language: java
jdk:
  - openjdk8

branches:
  only:
    - master

# Travis CI 서버의 Home
cache:
  directories:
    - &#39;$HOME/.m2/repository&#39;
    - &#39;$HOME/.gradle&#39;

script: &quot;./gradlew clean build&quot;

# CI 실행 완료 시 메일로 알람
notifications:
  email:
    recipients:
      - sunghyuk1609@gmail.com</code></pre><ul>
<li><p>branches</p>
<ul>
<li>Travis CI를 어느 브랜치가 푸시될 때 수행할지 지정</li>
<li>현재 옵션은 오직 master 브랜치에 push될 때만 수행</li>
</ul>
</li>
<li><p>cache</p>
<ul>
<li>그레이들을 통해 의존성을 받게 되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포 때부터 다시 받지 않도록 설정</li>
</ul>
</li>
<li><p>script</p>
<ul>
<li>master 브랜치에 푸시되었을 때 수행하는 명령어</li>
<li>여기서는 프로젝트 내부에 둔 gradlew을 통해 clean &amp; build를 수행</li>
</ul>
</li>
<li><p>notifications</p>
<ul>
<li>Travis CI 실행 완료 시 자동으로 알람이 가도록 설정</li>
</ul>
</li>
</ul>
<br>

<blockquote>
<p>Travis CI 연동은 크레딧 부족으로 수행하지 못해서 Github Action을 사용하려고 한다.</p>
</blockquote>
<br>

<h3 id="전체적인-cicd-플로우">전체적인 CI/CD 플로우</h3>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/31e863f9-c3d7-49ed-8b0b-f7cc96f1035d/image.png" alt=""></p>
<h2 id="github-actions와-aws-s3-연동하기">Github Actions와 AWS S3 연동하기</h2>
<h3 id="👨🏻💻-s3란">👨🏻‍💻 S3란?</h3>
<ul>
<li>AWS에서 제공하는 일종의 파일 서버</li>
<li>이미지 파일을 비롯한 정적 파일들을 관리하거나 지금 진행하는 것처럼 배포 파일들을 관리하는 등의 기능을 지원</li>
<li>CodeDeploy는 저장 기능이 없습니다. 그래서 Github Action이 빌드한 결과물을 받아서 CodeDeploy가 가져갈 수 있도록 보관할 수 있는 공간이 필요합니다. 보통은 이럴 때 AWS S3를 사용</li>
<li>CodeDeploy가 빌드도 배포도 가능하지만 확장성이 떨어지기에 빌드와 배포를 분리하는 것을 추천합니다.<br>

</li>
</ul>
<pre><code># This is a basic workflow to help you get started with Actions

name: springboot2-webservice CI

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  push:
    branches: [ master ]

  # Allows you to run this workflow manually from the Actions tab
#  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called &quot;build&quot;
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up JDK 1.8
        uses: actions/setup-java@v1
        with:
          java-version: 1.8

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
        shell: bash

      - name: Build with Gradle
        run: ./gradlew build
        shell: bash</code></pre><ul>
<li>name<ul>
<li>workflow 의 이름을 지정합니다.</li>
</ul>
</li>
<li>on<ul>
<li>이 workflow 가 언제 실행될건지 트리거를 지정할 수 있습니다. 특정 브랜치가 push 되는 경우, Pull Request 가 생성될 경우, 또는 crontab 문법으로 스케줄링을 걸 수도 있습니다.</li>
<li><code>workflow_dispatch</code> 는 수동으로 해당 workflow 를 실행시키겠다는 의미입니다.</li>
<li>push 등의 이벤트에 의해 자동으로 배포가 되기 보다는 사람이 수동으로 빌드/배포를 실행하는 것이 안전하다고 생각합니다.</li>
</ul>
</li>
<li>job, steps<ul>
<li>workflow 는 하나 혹은 그 이상의 job 을 가질 수 있고 각 job 은 여러 step 에 따라 단계를 나눌 수 있습니다.</li>
</ul>
</li>
<li>runs-on<ul>
<li>해당 workflow 를 어떤 OS 환경에서 실행할 것인지 지정할 수 있습니다.</li>
</ul>
</li>
</ul>
<br>

<h3 id="iam-권한-사용자-생성하기">IAM 권한 사용자 생성하기</h3>
<h3 id="👨🏻💻-iam이란">👨🏻‍💻 IAM이란?</h3>
<ul>
<li>AWS에서 제공하는 서비스의 접근 방식과 권한을 관리</li>
<li>IAM을 통해 Github Actions가 AWS의 S3와 CodeDeploy에 접근할 수 있도록 함.</li>
<li><strong>사용자와 역할의 차이</strong><ul>
<li>역할<ul>
<li>AWS 서비스에만 할당할 수 있는 권한</li>
<li>EC2, CodeDeploy, SQS 등</li>
</ul>
</li>
<li>사용자<ul>
<li>AWS 서비스 외에 사용할 수 있는 권한</li>
<li>로컬 PC, IDC 서버 등</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/23e314fd-5806-4518-ad3e-9bdeb9a56c2c/image.png" alt=""></p>
<h3 id="👨🏻💻-codedeploy란">👨🏻‍💻 CodeDeploy란?</h3>
<ul>
<li>애플리케이션 배포를 자동화하는 AWS의 배포 서비스</li>
<li>EC2, AWS Lambda와 같은 서비스에 배포를 할 수 있고, 현재 위치 배포나 블루/그린 배포와 같은 무중단 배포를 지원</li>
<li>CodeDeploy Agent 는 EC2 인스턴스에 설치되어 CodeDeploy의 명령을 기다리고 있는 프로그램</li>
</ul>
<br>

<h2 id="github-actions와-codedeploy-nginx로-무중단-배포하기">Github Actions와 CodeDeploy, Nginx로 무중단 배포하기</h2>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/b370e85d-53dd-4e1f-af8f-c990d387e6ee/image.png" alt=""></p>
<h3 id="👨🏻💻-nginx란">👨🏻‍💻 Nginx란?</h3>
<ul>
<li>웹 서버, 리버스 프록시, 캐싱, 로드 밸런싱, 미디어 스트리밍 등을 위한 오픈소스 소프트웨어</li>
<li>리버스 프록시란 엔진엑스가 외부의 요청을 받아 백엔드 서버로 요청을 전달하는 행위. 리버스 프록시 서버(엔진엑스)는 요청을 전달하고, 실제 요청에 대한 처리는 뒷단의 웹 애플리케이션 서버들이 처리</li>
</ul>
<br>

<p><a href="https://wbluke.tistory.com/39">https://wbluke.tistory.com/39</a>
<a href="https://wbluke.tistory.com/40?category=418851">https://wbluke.tistory.com/40?category=418851</a>
<a href="https://wbluke.tistory.com/41?category=418851">https://wbluke.tistory.com/41?category=418851</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[훌륭한 자바 개발자가 되기 위한 소양 기르기 (3) - 1 멀티 스레드 프로그래밍을 이해하기 전 스레드에 관한 정리 ]]></title>
            <link>https://velog.io/@sung_hyuki/%ED%9B%8C%EB%A5%AD%ED%95%9C-%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%90%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EC%86%8C%EC%96%91-%EA%B8%B0%EB%A5%B4%EA%B8%B0-3-1-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%84-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%A0%84-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%97%90-%EA%B4%80%ED%95%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@sung_hyuki/%ED%9B%8C%EB%A5%AD%ED%95%9C-%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%90%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EC%86%8C%EC%96%91-%EA%B8%B0%EB%A5%B4%EA%B8%B0-3-1-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%84-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%A0%84-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%97%90-%EA%B4%80%ED%95%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 08 Oct 2022 16:03:01 GMT</pubDate>
            <description><![CDATA[<p>스프링에서 Servlet은 한 개의 요청에 한 스레드를 맵핑하여 요청을 처리합니다. 여러 개의 스레드들이 만들어져 병렬적으로 여러 요청을 처리할 수 있는데, 이러한 모델을 멀티 스레드 모델이라고 합니다. 멀티 스레드 모델은 여러 작업을 병렬적으로 멀티 프로세스 모델보다 가볍게 처리하기 위한 목적으로 나왔습니다.
<br></p>
<p>하지만 멀티 스레드 모델은 동일 메모리 영역을 공유하기 때문에 동일한 데이터에 대한 접근을 처리하여 동시성 문제를 해결해야 합니다.
<br></p>
<p>먼저 스레드의 라이프 사이클에 대해 알아보겠습니다.</p>
<h2 id="life-cycle-of-thread">Life Cycle of Thread</h2>
<p>스레드는 5가지 라이프 사이클을 가지고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/84e79ead-66da-4228-aada-a35b1e2ff52f/image.png" alt=""></p>
<p><strong>New</strong></p>
<p>스레드가 새롭게 생성되고, 아직 실행되지 않는 상태
<br></p>
<p><strong>RUNNABLE</strong></p>
<p>JVM에서 실행 가능한 상태. 실행 가능한 상태는 현재 실행 중이거나 OS로부터의 다른 자원을 기다리며 실행할 준비가 완료된 상태를 의미합니다.
<br></p>
<p><strong>BLOCKED</strong></p>
<p>Monitor lock을 획득하기를 기다리는 차단된 스레드의 스레드 상태입니다. 차단된 상태의 스레드는 동기화된 블록 메소드(임계영역을 의미)에 들어가기 위해  Monitor lock을 획득하기를 기다리거나 Object.wait를 호출한 후 동기화된 블록 메소드에 다시 들어가기를 기다리고 있습니다.</p>
<ul>
<li>모니터락을 얻어서 임계영역에 진입하고 모니터락을 풀어 임계영역에서 나간다.<br>

</li>
</ul>
<p><strong>WAITING</strong></p>
<p>다른 스레드가 작업을 수행할 때까지 대기 중인 스레드의 상태</p>
<p>다음 메서드에 의해서 실행될 수 있음.</p>
<ul>
<li>Object.wait with no timeout</li>
<li>Thread.join with no timeout</li>
<li>LockSupport.park<br>

</li>
</ul>
<p>예를 들어, 메인 스레드가 Thread.join() 을 (내부적으로 Object.wait() 를 사용함 → wait()는 락을 해체한 상태)) 호출하면 해당 스레드는 자식 스레드의 종료를 기다리고 있는 대기 상태가 됩니다. 그런 다음 자식 스레드의 작업이 끝났을 때 자식 스레드가 Object.notify() 또는 Object.notifyAll()(해당 작업을 통해 다시 락을 획득함)을 사용해 작업이 완료됨을 메인 스레드에게 알리면 메인 스레드는 WAITING 상태에서 다시 RUNNABLE 상태로 이동합니다.
<br> </p>
<pre><code class="language-java">public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis &lt; 0) {
            throw new IllegalArgumentException(&quot;timeout value is negative&quot;);
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay &lt;= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }</code></pre>
<br>

<p><strong>BLOCKED 상태와 WAITING 상태를 구분짓지 않는 경우도 있는데 엄밀히 따지자면 BLOCKED는 락을 획득하기 위해 차단된 상태이고 WAITING은 다른 스레드의 작업이 끝날 때까지 대기 중인 상태를 말합니다.</strong>
<br></p>
<p><strong>(Side) 락 획득과 방출에 대한 개념 코드</strong></p>
<pre><code class="language-java">class Customer {
    int amount = 10000;

    synchronized void withdraw(int amount) {
        System.out.println(&quot;going to withdraw...&quot;);

        if (this.amount &lt; amount) {
            System.out.println(&quot;Less balance; waiting for deposit...&quot;);
            try {
                wait();
            } catch (Exception e) {
            }
        }
        this.amount -= amount;
        System.out.println(&quot;withdraw completed...&quot;);
    }

    synchronized void deposit(int amount) {
        System.out.println(&quot;going to deposit...&quot;);
        this.amount += amount;
        System.out.println(&quot;deposit completed... &quot;);
        notify();
    }
}


class Test {
    public static void main(String args[]) {
        final Customer c = new Customer();
        new Thread() {
            public void run() {
                c.withdraw(15000);
            }
        }.start();
        new Thread() {
            public void run() {
                c.deposit(10000);
            }
        }.start();

    }
}

// 출력
going to withdraw...
Less balance; waiting for deposit...
going to deposit...
deposit completed... 
withdraw completed...</code></pre>
<br>

<p><strong>TIMED_WAITING</strong></p>
<p>시간 제한이 있는 대기 상태</p>
<p>다음 메서드에 의해서 실행될 수 있음.</p>
<ul>
<li>Thread.sleep</li>
<li>Object.wait with timeout</li>
<li>Thread.join with timeout</li>
<li>LockSupport.parkNanos</li>
<li>LockSupport.parkUntil<br>

</li>
</ul>
<p><strong>TERMINATED</strong></p>
<p>실행이 완료된 상태
<br></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/c375f304-18d6-4f2f-b049-15350a641e85/image.png" alt=""></p>
<br>

<p><strong>새 실행 스레드를 만드는 방법</strong></p>
<p>하나의 클래스를 Thread의 하위 클래스로 선언하는 방법과 Runnable 인터페이스를 구현하는 클래스를 만들어 스레드를 생성하여 인수로 해당 클래스를 전달하는 방법입니다. 두 가지 방법 모두 run() 메서드의 오버라이딩이 필요합니다.
<br></p>
<h3 id="thread-scheduler-in-java">Thread Scheduler in Java</h3>
<p>어떤 스레드를 실행할 것인지 그리고 어떤 스레드를 기다리게 할 것인지는 자바의 <strong>스레드 스케줄러</strong>가 해당 역할을 수행합니다.
<br></p>
<p>어떤 스레드를 먼저 수행할 것인지를 판단하는데 우선 순위와 도착 시간이라는 두 가지 기준이 존재합니다. 스레드의 우선 순위가 높은 스레드가 먼저 실행되고, 스레드들의 우선 순위가 같다면 실행 중인 스레드 큐에 먼저 도착한 스레드가 우선권을 가집니다.
<br></p>
<p>스케줄링을 할 때 중요하게 고려해야 할 점은 하나의 스레드가 CPU를 지속적으로 선점하여 다른 스레드들이 CPU라는 자원을 할당받지 못하는 상황을 만들지 않는 것 즉, 기아상태에 도달하지 않게 하는 것입니다.
<br></p>
<p><strong>용어 정리 |</strong> <strong>데몬 스레드란?</strong></p>
<p>user thread에게 서비스를 제공하는 서비스 제공자 스레드를 데몬 스레드라고 부릅니다. GC, Finalizer 등을 데몬 스레드라고 합니다. 데몬 스레드의 라이프 사이클은 사용자 스레드의 자비에 달려 있습니다. 즉, 모든 사용자 스레드가 죽으면 JVM은 이 스레드를 자동으로 종료합니다.
<br></p>
<h3 id="thread-pool">Thread Pool</h3>
<p>작업을 기다리고 여러 번 재사용되는 worker thread들이 모인 그룹을 스레드 풀이라고 합니다. 
<br></p>
<p><strong>My Opinion |</strong> 이렇게 커넥션 풀, 스레드 풀과 같은 풀을 만들어서 사용하는 이유는 새로운 무언가를 생성하는 시간이 절약되어 성능을 높이기 위함이라고 생각합니다. 다양한 애플리케이션의 특징에 따라 적절한 스레드 풀을 세팅하는 것도 성능을 높이기 위한 관건이라고 생각합니다.</p>
<p>스레드 누수가 발생하지 않는지 애플리케이션 코드를 검사해야 할 필요성이 있고 지속적으로 놀고 있는 스레드가 없는지 검사할 필요성 그리고 스레드들이 데드락과 같은 상황에 놓여 있지는 않은지 검사할 필요성이 있습니다.
<br></p>
<pre><code class="language-java">class Tasks implements Runnable {
    private String taskName;

    public Tasks(String str) {
        taskName = str;
    }

    public void run() {
        try {
            for (int j = 0; j &lt;= 5; j++) {
                if (j == 0) {
                    Date date = new Date();
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(&quot;hh : mm : ss&quot;);

                    System.out.println(&quot;Initialization time for the task name: &quot; + taskName + &quot; = &quot; + simpleDateFormat.format(date));

                } else {
                    Date date = new Date();
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(&quot;hh : mm : ss&quot;);

                    System.out.println(&quot;Time of execution for the task name: &quot; + taskName + &quot; = &quot; + simpleDateFormat.format(date));
                }

                Thread.sleep(1000);
            }

            System.out.println(taskName + &quot; is complete.&quot;);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadPoolExample {

    static final int MAX_TH = 3;

    public static void main(String argvs[]) {

        long startTime = System.currentTimeMillis();

        Runnable rb1 = new Tasks(&quot;task 1&quot;);
        Runnable rb2 = new Tasks(&quot;task 2&quot;);
        Runnable rb3 = new Tasks(&quot;task 3&quot;);
        Runnable rb4 = new Tasks(&quot;task 4&quot;);
        Runnable rb5 = new Tasks(&quot;task 5&quot;);

        ExecutorService executorService = Executors.newFixedThreadPool(MAX_TH);

        executorService.execute(rb1);
        executorService.execute(rb2);
        executorService.execute(rb3);
        executorService.execute(rb4);
        executorService.execute(rb5);

        executorService.shutdown();



        if(executorService.isTerminated()) {
            long stopTime = System.currentTimeMillis();
            System.out.println(stopTime - startTime);
        }
    }
}

// 출력

Initialization time for the task name: task 1 = 04 : 31 : 41
Initialization time for the task name: task 2 = 04 : 31 : 41
Initialization time for the task name: task 3 = 04 : 31 : 41
Time of execution for the task name: task 3 = 04 : 31 : 42
Time of execution for the task name: task 2 = 04 : 31 : 42
Time of execution for the task name: task 1 = 04 : 31 : 42
Time of execution for the task name: task 1 = 04 : 31 : 43
Time of execution for the task name: task 2 = 04 : 31 : 43
Time of execution for the task name: task 3 = 04 : 31 : 43
Time of execution for the task name: task 2 = 04 : 31 : 44
Time of execution for the task name: task 3 = 04 : 31 : 44
Time of execution for the task name: task 1 = 04 : 31 : 44
Time of execution for the task name: task 3 = 04 : 31 : 45
Time of execution for the task name: task 2 = 04 : 31 : 45
Time of execution for the task name: task 1 = 04 : 31 : 45
Time of execution for the task name: task 1 = 04 : 31 : 46
Time of execution for the task name: task 2 = 04 : 31 : 46
Time of execution for the task name: task 3 = 04 : 31 : 46
task 1 is complete.
task 3 is complete.
task 2 is complete.
Initialization time for the task name: task 4 = 04 : 31 : 47
Initialization time for the task name: task 5 = 04 : 31 : 47
Time of execution for the task name: task 5 = 04 : 31 : 48
Time of execution for the task name: task 4 = 04 : 31 : 48
Time of execution for the task name: task 4 = 04 : 31 : 49
Time of execution for the task name: task 5 = 04 : 31 : 49
Time of execution for the task name: task 5 = 04 : 31 : 50
Time of execution for the task name: task 4 = 04 : 31 : 50
Time of execution for the task name: task 5 = 04 : 31 : 51
Time of execution for the task name: task 4 = 04 : 31 : 51
Time of execution for the task name: task 4 = 04 : 31 : 52
Time of execution for the task name: task 5 = 04 : 31 : 52
task 4 is complete.
task 5 is complete.</code></pre>
<br>

<p>위의 작업을 보면 스레드 풀을 3으로 세팅했습니다. 유휴 스레드를 가질 경우 남은 태스크를 실행하는 것을 확인할 수 있습니다.
<br></p>
<p>자바에서 동시성을 해결하기 위한 다양한 방법이 제공됩니다.</p>
<ol>
<li>변수, 메서드, 블록 레벨에 사용할 수 있는 synchronized (상호 배제를 이용)</li>
<li>변수 레벨에 사용할 수 있는 volatile - 여러 스레드가 하나의 자원에 동시에 읽기/쓰기를 진행할 때 항상 메모리에 접근하지는 않는다. 캐시를 먼저 조회하는데 이 값은 메인 메모리의 값과 다를 수 있다. volatile을 이용해 실제 메인 메모리의 값을 볼 수 있다.(자원의 가시성)</li>
<li>concurrent 패키지</li>
<li>불변객체의 사용 - 내부적인 상태가 변하지 않으니 동시성 이슈가 발생하지 않는다.<br>

</li>
</ol>
<p>다음 시간에 자바의 동시성을 해결하는 방법 하나 하나를 자세히 다루도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[@Autowired의 원리와 스프링  팀에서 @Autowired의 사용을 지양하라고 하는 이유? (추가 수정 필요..)]]></title>
            <link>https://velog.io/@sung_hyuki/Autowired%EC%9D%98-%EC%9B%90%EB%A6%AC%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8C%80%EC%97%90%EC%84%9C-Autowired%EC%9D%98-%EC%82%AC%EC%9A%A9%EC%9D%84-%EC%A7%80%EC%96%91%ED%95%98%EB%9D%BC%EA%B3%A0-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EC%B6%94%EA%B0%80-%EC%88%98%EC%A0%95-%ED%95%84%EC%9A%94</link>
            <guid>https://velog.io/@sung_hyuki/Autowired%EC%9D%98-%EC%9B%90%EB%A6%AC%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8C%80%EC%97%90%EC%84%9C-Autowired%EC%9D%98-%EC%82%AC%EC%9A%A9%EC%9D%84-%EC%A7%80%EC%96%91%ED%95%98%EB%9D%BC%EA%B3%A0-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EC%B6%94%EA%B0%80-%EC%88%98%EC%A0%95-%ED%95%84%EC%9A%94</guid>
            <pubDate>Wed, 28 Sep 2022 08:37:41 GMT</pubDate>
            <description><![CDATA[<p>스프링의 철학은 POJO(Plain Old Java Object)로 스프링 프레임워크를 나중에 걷어내더라도 코드가 정상적으로 동작할 수 있도록 프렘웍에 의존적인 코드를 작성하지 않는 것인데 그런 의미에서 @Autowired 보다는 다른 방법을 이용하는 것이 좋다고 공부했었다. 그럼 왜 @Autowired 어노테이션을 이용한 DI를 지양해야 하는지 @Autowired의 원리에 대해서 알아보자.</p>
<ul>
<li><p>@Autowired 애노테이션을 이용하면 설정자 메서드를 이용하지 않고도 스프링 프레임워크가 설정 파일을 통해 설정자 메서드 대신 속성을 주입해 준다. (스프링 설정 파일을 보고 자동으로 속성의 설정자 메서드에 해당하는 역할을 해주겠다는 의미)</p>
</li>
<li><p>@Autowired는 type 기준으로 매칭을 하기 때문에 같은 타입을 구현한 클래스가 여러 개 있다면 그때 bean 태그의 id로 구분해서 매칭한다. (id 매칭 보다 type 매칭이 우선이다)</p>
<ul>
<li>@Resource의 경우 type과 id 가운데 매칭 우선순위는 id가 높다. id로 매칭할 빈을 찾지 못한 경우 type으로 매칭할 빈을 찾게 된다.<br>

</li>
</ul>
</li>
</ul>
<p><strong>자바 표준인 @Resource를 쓰는 것이 유리(내가 공부했던 책의 필자의 생각)</strong></p>
<p>@Autowired는 type을 기준으로 매칭하기 때문에 같은 타입을 구현한 클래스가 여러 개 있다면 단일 빈을 매칭하기에 부적절합니다.
<br></p>
<p><strong>How to solve such problem:</strong></p>
<ul>
<li>두 개의 구현 클래스가 존재하고, @Autowired 어노테이션을 사용하는 경우 userService를 UserService Impl01과 같이 특정한 구현 클래스의 이름으로 변경할 수 있습니다.</li>
<li>또한, @Qualifier(value = “userService Impl01”) 과 같이 추가하여 주입하려는 구현 클래스를 지정할 수 있습니다.</li>
<li>@Resource 어노테이션을 사용하여 구현 클래스의 이름을 수동으로 지정</li>
</ul>
<p>슈퍼 클래스가 같은 두 개 이상의 서브 클래스들을 구별하기 위해서 스프링 컨텍스트가 서브 클래스를 찾을 수 있는 특정한 이름을 지정해야 합니다.
<br></p>
<h2 id="언제-주입이-되는가-autowired-원리">언제 주입이 되는가? (@Autowired 원리)</h2>
<p>객체가 생성되면, 객체 변수에 할당될 때 주입됩니다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/406a335f-a080-4fc9-8aa4-359d42ea5e36/image.png" alt=""></p>
<ul>
<li>InstantiationAwareBeanPostProcessor<ul>
<li>인스턴스화 전후에 객체를 관리하는 기능</li>
</ul>
</li>
<li>BeanPostProcessor<ul>
<li>초기화 전후에 객체를 관리하는 기능</li>
</ul>
</li>
<li>BeanFactoryAware<ul>
<li>BeanFactory를 언제든지 가져오는 기능<br>

</li>
</ul>
</li>
</ul>
<p>컨테이너가 초기화되면, post processor의 초기화가 나머지 사용자 정의 빈(사용자 정의 서비스, 컨트롤러 등등..)의 초기화들보다 먼저 일어난다. 사용자 정의 빈 초기화는 <code>AbstractApplicationContext</code> 의 finishBeanFactoryInitialization 메서드에 의해 실행된다. </p>
<br>

<pre><code class="language-html">FinishBeanFactory Initialization (beanFactory)

– &gt; beanFactory. preInstantiate Singletons () 

– &gt; getBean (beanName) 

– &gt; doGetBean (beanName) &gt; to line 317 of AbstractBeanFactory (beanName, mbd, args) to create bean instances 

– &gt; to line 503 of AbstractBeanFactory CreateBean (beanTombuse, args) 

– &gt; and then to line 533 of AbstractBeanFactory AutowireableB In line 543 of eanFactory, instance Wrapper = createBeanInstance (beanName, mbd, args) has created the bean instance, except that instance Wrapper is a wrapped bean whose properties have not yet been assigned the actual value 

– &gt; and then comes to line 555, applyMergedBeanDefinitionPostProcessors (mbd, beanType, beanName).</code></pre>
<br>

<p>위의 흐름은 모든 뒤따라오는 빈들을 배치하는 것입니다. 프로세서는 그것을 꺼내 beanName 이라는 클래스의 변수를 InjectionMetadata의 주입된 Elements 세트에 캡슐화하고, 나중에 취득해, 인스턴스를 1개씩 작성하고, reflection을 통해 대응하는 클래스에 주입시킵니다.</p>
<p>남은 내용은 내가 생각했을 때 너무 DeepDive 하는 느낌이 들어 링크를 옮긴다. 나중에 궁금증이 더 생길 때 찾아보기로..</p>
<p><a href="https://developpaper.com/spring-source-analysis-autowire-annotation-principle-analysis/">Spring Source Analysis: @Autowire Annotation Principle Analysis - Develop Paper</a></p>
<blockquote>
<p>요약을 하자면 컨테이너가 시작되고 객체에 값을 할당할 때 @Autowired 어노테이션을 만나면 post-processor 매커니즘을 사용하여 애트리뷰트의 인스턴스를 생성한다. 그런다음 reflection 매커니즘을 사용하여 인스턴스화된 속성을 객체에 할당합니다.</p>
</blockquote>
<br>

<h2 id="의존성-주입의-유형">의존성 주입의 유형</h2>
<ul>
<li>Contructor-based<ul>
<li>생성자에 @Autowired 어노테이션을 붙인다. (스프링 4.3 부터는 생략 가능)</li>
<li>의존성 주입을 할 객체를 메서드 파라미터에 포함시킨다.</li>
<li><strong>의존성 주입을 시킬 빈이 IoC Container에 등록되었다는 것을 전제로 한다.</strong></li>
<li>Contructor-based injection의 가장 큰 이점은 주입될 필드를 final로 선언할 수 있다는 것입니다. 이것은 필수 의존성에 편리합니다.</li>
</ul>
</li>
<li>Setter-based<ul>
<li>세터 메서드에 @Autowired 어노테이션을 붙인다.</li>
<li>빈의 의존성 주입을 위해 인수가 없는 생성자를 사용하거나 인수가 없는 정적 팩토리 메서드를 통해 빈이 인스턴스화되면 스프링 컨테이너는 세터 메서드를 호출</li>
</ul>
</li>
<li>Field-based<ul>
<li>필드 / 프로퍼티에 @Autowired 어노테이션을 붙인다. 스프링 컨테이너는 해당 클래스가 인스턴스화되면 필드에 세팅한다.<br>

</li>
</ul>
</li>
</ul>
<p><strong>@Autowired를 통해 DI 코드가 깔끔해 보이는데 Field Injection을 지양하라고 하는 이유는 뭘까?</strong></p>
<ul>
<li>불변 필드 선언을 허락하지 않는다.<ul>
<li>final / immutable 으로 선언된 필드에서는 동작하지 않는다.</li>
<li>불변 의존성을 선언하는 유일한 방법은 Constructor-based</li>
</ul>
</li>
<li>단일 책임 원칙을 위반하기 쉽다.<ul>
<li>클래스에 @Autowired 필드 injection을 하는 것은 정말 쉬운 일이다. → 반면 생성자 기반 의존성 주입을 사용하면 의존성이 추가됨에 따라 코드가 냄새나 보이기 때문에 문제가 있다는 신호를 금방 알아챌 수 있다.</li>
</ul>
</li>
<li>의존성 주입 컨테이너와 긴밀하게 연결되었다.<ul>
<li>의존성 주입 설계 패턴은 클래스 의존성의 생성을 클래스 자체로부터 분리하고, 이 책임을 클래스 인젝터로 전송하고, 프로그램 설계를 느슨하게 결합하며, 단일 책임과 의존성 역전의 원칙(SOLID) 따를 수 있습니다</li>
<li>필드를 autowired 시킴으로써 클래스 인젝터가 결합되어 <strong>스프링 컨테이너 외부에서 클래스를 쓸 수 없게 만듭니다.</strong><ul>
<li>단위 테스트 시 애플리케이션 컨테이너 외부에서 클래스를 사용하려는 경우, 스프링 컨테이너를 사용하여 강제로(의도적으로) 클래스를 인스턴스화 해야합니다.</li>
</ul>
</li>
</ul>
</li>
<li>Hidden dependencies<ul>
<li>의존성 주입 패턴을 사용할 때 영향을 받는 클래스는 생성자에서 필수 의존성을 노출하거나 세터를 사용하여 선택적 의존성을 공개 인터페이스를 사용하여 <strong>의존성을 명확하게 노출해야</strong> 합니다.</li>
<li>의존성을 외부 세계에 노출시켜야 하는데 필드 기반 의존성 주입은 이러한 의존성을 외부 세계로부터 숨깁니다.</li>
</ul>
</li>
</ul>
<p>&lt;원문 참조&gt;</p>
<p><a href="https://developpaper.com/spring-source-analysis-autowire-annotation-principle-analysis/">Spring Source Analysis: @Autowire Annotation Principle Analysis - Develop Paper</a></p>
<p><a href="https://jjingho.tistory.com/6">[스프링 핵심기술] - @Autowired</a></p>
<p><a href="https://blog.marcnuri.com/field-injection-is-not-recommended">Field injection is not recommended - Spring IOC - Marc Nuri</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[훌륭한 자바 개발자가 되기 위한 소양 기르기 (2) Java Garbage Collection]]></title>
            <link>https://velog.io/@sung_hyuki/%ED%9B%8C%EB%A5%AD%ED%95%9C-%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%90%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EC%86%8C%EC%96%91-%EA%B8%B0%EB%A5%B4%EA%B8%B0-2-Java-Garbage-Collection</link>
            <guid>https://velog.io/@sung_hyuki/%ED%9B%8C%EB%A5%AD%ED%95%9C-%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%90%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EC%86%8C%EC%96%91-%EA%B8%B0%EB%A5%B4%EA%B8%B0-2-Java-Garbage-Collection</guid>
            <pubDate>Fri, 23 Sep 2022 12:27:33 GMT</pubDate>
            <description><![CDATA[<h3 id="stop-the-world란">Stop the world란?</h3>
<p>GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것</p>
<p>Stop the world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘고, GC 작업을 완료한 이후에 중단했던 작업을 재개한다. 그래서 대개의 경우 GC 튜닝의 관건은 Stop the world 시간을 줄이는 것이다.</p>
<h3 id="가비지-컬렉터의-역할">가비지 컬렉터의 역할</h3>
<p>더 이상 필요 없는 (쓰레기) 객체를 찾아 지우는 것</p>
<h3 id="jvm의-물리적-공간-구성---young-영역과-old-영역">JVM의 물리적 공간 구성 - Young 영역과 Old 영역</h3>
<p>JVM 메모리의 물리적 공간은 두 영역으로 나뉘는데 이를 Young 영역과 Old 영역으로 구분짓는다.</p>
<ul>
<li><strong>Young 영역(Young Generation 영역)</strong> : 새롭게 생성한 객체의 대부분이 여기에 위치한다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질때 Minor GC가 발생한다고 말한다.</li>
<li><strong>Old 영역(Old Generation 영역)</strong> : 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(혹은 Full GC)가 발생한다고 말한다.<br>

</li>
</ul>
<p><strong>GC 영역별 데이터 흐름</strong></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/af6f8fa0-c1e4-40ab-b1ce-e7e75b9101c5/image.png" alt=""></p>
<br>
위 그림의 Permanent Generation 영역(이하 Perm 영역)은 보통 Class의 Meta 정보나 Method의 Meta 정보, Static 변수와 상수 정보들이 저장되는 공간으로 흔히 메타데이터 저장 영역이라고도 한다. 이 영역은 Java 8 부터는 Native 영역으로 이동하여 Metaspace 영역으로 변경되었다. (다만, 기존 Perm 영역에 존재하던 Static Object는 Heap 영역으로 옮겨져서 GC의 대상이 최대한 될 수 있도록 하였다) Old 영역에서 살아남은 객체가 영원히 남아 있는 곳은 절대 아니다. 이 영역에서 GC가 발생할 수도 있는데, 여기서 GC가 발생해도 Major GC의 횟수에 포함된다. (변경된 이후에도 Major GC의 횟수에 포함되는가? 찾아보기)

<br>

<p><strong>(참고) JDK 8부터 Perm 영역은 삭제되고 Metaspace 영역으로 전환</strong></p>
<table>
<thead>
<tr>
<th></th>
<th>Java 7</th>
<th>Java 8</th>
</tr>
</thead>
<tbody><tr>
<td>Class 메타 데이터</td>
<td>저장</td>
<td>저장</td>
</tr>
<tr>
<td>Method 메타 데이터</td>
<td>저장</td>
<td>저장</td>
</tr>
<tr>
<td>Static Object 변수, 상수</td>
<td>저장</td>
<td>Heap 영역으로 이동</td>
</tr>
<tr>
<td>메모리 튜닝</td>
<td>Heap, Perm 영역 튜닝</td>
<td>Heap 튜닝, Native 영역은 OS가 동적 조정</td>
</tr>
<tr>
<td>메모리 옵션</td>
<td>-XX:PermSize-XX:MaxPermSize</td>
<td>-XX:MetaspaceSize-XX:MaxMetaspaceSize</td>
</tr>
<tr>
<td><br></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p><strong>Native 영역이란 무엇인가?</strong></p>
<p>Native 메모리는 OS 레벨에서 관리하는 영역이고, Heap 영역은 JVM에 의해 관리된 영역이다.
<br></p>
<p><strong>왜 Perm이 제거됐고 Metaspace 영역이 추가된 것인가?</strong></p>
<p>Metaspace가 Native 메모리를 이용함으로서 개발자는 영역 확보의 상한을 크게 의식할 필요가 없어지게 되었다.
<br></p>
<p><strong>그렇다면 &quot;Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우가 있을 때에는 어떻게 처리될까?&quot;</strong></p>
<p>이러한 경우를 처리하기 위해서 Old 영역에는 512바이트의 덩어리(chunk)로 되어 있는 카드 테이블(card table)이 존재한다.</p>
<p>카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시된다. Young 영역의 GC를 실행할 때에는 Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 GC 대상인지 식별한다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/9a1ecf78-ebe5-401b-9ba9-3951841f7862/image.png" alt=""></p>
<p>카드 테이블은 write barrier를 사용하여 관리한다. write barrier는 Minor GC를 빠르게 할 수 있도록 하는 장치이다. write barrirer때문에 약간의 오버헤드는 발생하지만 전반적인 GC 시간은 줄어들게 된다.
<br></p>
<p><strong>write barrier란 무엇인가?</strong></p>
<p>파일시스템의 메타데이터가 올바르게 기록되고 디스크에 제대로(심지어 디스크 전원이 나갈지라도) 반영되게 하기위한 커널 매커니즘
<br></p>
<h3 id="young-영역">Young 영역</h3>
<p>객체가 제일 먼저 생성되는 영역</p>
<p>Young 영역은 Eden 영역과 2개의 Survivor 영역으로 나뉜다.
<br></p>
<p><strong>각 영역의 처리 절차</strong></p>
<ul>
<li>새로 생성한 대부분의 객체는 Eden 영역에 위치한다.</li>
<li>Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다.</li>
<li>Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.</li>
<li>하나의 Survivor 영역이 가득차게 되면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득 찬 Survivor 영역은 아무 데이터도 없는 상태로 된다.</li>
<li>이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게 된다.</li>
<li>(Survivor 영역 중 하나는 반드시 비어있는 상태로 남아 있어야 한다.)<br>

</li>
</ul>
<p><strong>Minor GC 전과 후</strong></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/58302d54-fe6d-486d-88e7-1b671fb3329e/image.png" alt=""></p>
<br>

<h3 id="old-영역">Old 영역</h3>
<p>Young 영역에서 살아남은 객체는 Old 영역으로 이동한다. Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행</p>
<p>GC 방식은 JDK 버전의 업그레이드에 따라 다양한 알고리즘이 등장하였다.</p>
<br>

<p><strong>Serial GC (-XX:+UseSerialGC)</strong></p>
<p>운영 서버와 같은 멀티 쓰레드 환경에서는 절대 사용하면 안 되는 방식이며, 데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식. Young 영역의 GC는 앞에서 설명한 방식을 사용하고, Old 영역의 GC는 Mark-Sweep-Compact 알고리즘 방식을 사용한다. </p>
<ul>
<li><strong>Mark-Sweep-Compact 알고리즘이란?</strong>
 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업인 Mark 그리고 Mark 단계에서 사용되지 않는 메모리를 해제하는 작업인 Sweep 거기에 힙 영역에 유효한 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분 존재하지 않는 부분으로 나누는 작업인 Compact</li>
</ul>
<br>

<p><strong>Parallel GC (-XX:+UseParallelGC)</strong></p>
<p>Parallel GC는 Serial GC와 기본적인 알고리즘은 같지만 이를 Parallel하게 수행한다. 그렇기 때문에 빠르게 객체를 처리할 수 있다. 멀티 프로세서 또는 멀티 스레드 머신에서 중간 규모부터 대규모의 데이터를 처리하는 애플리케이션을 위해 고안되었으며, 옵션을 통해 애플리케이션의 최대 지연 시간 또는 GC를 수행할 스레드의 갯수 등을 설정해줄 수 있는 특징이 있다. 하지만 Parallel GC 역시도 가비지 수집을 하는 동안 애플리케이션이 멈추는 Stop The World를 피할 수는 없다는 단점이 존재한다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/43a84bd1-fe56-4e37-8700-1f15a5eb2fd5/image.png" alt=""></p>
<br>

<p><strong>Parallel Old GC (-XX:+UseParallelOldGC)</strong></p>
<p>앞서 설명한 Parallel GC와 비교하여 Old 영역의 GC 알고리즘만 다르다. 이 방식은 Mark-Summary-Compaction 단계를 거친다. Summary 단계는 앞서 GC를 수행한 영역에 대해서 별도로 살아 있는 객체를 식별한다는 점에서 Mark-Sweep-Compaction 알고리즘의 Sweep 단계와 다르며, 약간 더 복잡한 단계를 거친다.</p>
<br>

<p><strong>Concurrent Mark Sweep GC (-XX:+UseConcMarkSweepGC)</strong></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/dcbf6cfc-faed-4a48-a422-c3755889c130/image.png" alt=""></p>
<p>초기 Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝낸다. 따라서, 멈추는 시간은 매우 짧다. 그리고 Concurrent Mark 단계에서는 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다. 이 단계의 특징은 다른 스레드가 실행 중인 상태에서 동시에 진행된다는 것이다.</p>
<p>그 다음 Remark 단계에서는 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다. 마지막으로 Concurrent Sweep 단계에서는 쓰레기를 정리하는 작업을 실행한다. 이 작업도 다른 스레드가 실행되고 있는 상황에서 진행한다.</p>
<p>이러한 단계로 진행되는 GC 방식이기 때문에 <strong>stop-the-world 시간이 매우 짧다</strong>. 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 부른다.</p>
<p>반면, 다른 GC 방식보다 메모리와 CPU를 더 많이 사용하고 Compaction 단계가 기본적으로 제공되지 않는다. 조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 stop-the-world 시간보다 stop-the-world 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 한다.</p>
<p>JDK 9부터는 deprecated됨.</p>
<br>

<p><strong>G1 GC (XX:+UseG1GC)</strong></p>
<p>G1 GC는 지금까지 공부한 Young 영역과 Old 영역과 달리 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다. 그러다가, 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/e753b95c-b7dc-4d78-b7d6-b742e5389641/image.png" alt=""></p>
<p>G1 GC는 성능 효율성이 높기 때문에 CMS GC를 대체할 수 있다. 다른 GC와는 달리 G1 컬렉터는 힙을 동일한 크기의 힙 영역 세트로 분할하며, 각각의 힙 영역은 가상 메모리의 연속된 공간을 차지한다.
<br></p>
<p><strong>애플리케이션마다 성능 최적을 낼 수 있는 GC 알고리즘이 다르기 때문에 항상 하나의 알고리즘이 최적의 효과를 낼거라고는 생각하지 말자. 지속적인 튜닝과 모니터링을 통해서 해당 서비스에 가장 적합한 값을 찾아야 한다.</strong></p>
<br>


<p><a href="https://d2.naver.com/helloworld/1329">NAVER D2</a></p>
<p><a href="https://johngrib.github.io/wiki/java8-why-permgen-removed/">JDK 8에서 Perm 영역은 왜 삭제됐을까</a></p>
<p><a href="https://rhlinux.tistory.com/38">Write Barrier란 무엇인가?</a></p>
<p><a href="https://www.baeldung.com/jvm-garbage-collectors">JVM Garbage Collectors</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[훌륭한 자바 개발자가 되기 위한 소양 기르기 (1) JVM]]></title>
            <link>https://velog.io/@sung_hyuki/%ED%9B%8C%EB%A5%AD%ED%95%9C-%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%90%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EC%86%8C%EC%96%91-%EA%B8%B0%EB%A5%B4%EA%B8%B0-1-JVM</link>
            <guid>https://velog.io/@sung_hyuki/%ED%9B%8C%EB%A5%AD%ED%95%9C-%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%90%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EC%86%8C%EC%96%91-%EA%B8%B0%EB%A5%B4%EA%B8%B0-1-JVM</guid>
            <pubDate>Thu, 22 Sep 2022 08:48:39 GMT</pubDate>
            <description><![CDATA[<p>자바 바이트 코드를 실행하는 모든 하드웨어는 JVM을 통해 모든 하드웨어에서 자바 실행 코드를 변경하지 않고 실행할 수 있도록 합니다. 이는 플랫폼 의존적이지 않음을 의미합니다.
<br></p>
<p><strong>JVM의 특징</strong></p>
<ul>
<li>스택 기반의 동작 : JVM은 피연산자를 저장하고 가져올 때 스택을 활용한다. 이는 레지스터 기반의 동작보다 하드웨어에 덜 의존적이며(레지스터를 직접 다루지 않고 스택을 통한 연산), 명령어의 길이가 짧아진다. (다음 피연산자가 스택의 TOP에 존재하므로 피연산자의 메모리 주소를 사용할 필요가 없어진다.) 하지만 이는 장점만 있는 것은 아니다. 명령어의 길이가 짧아지는 대신 명령어의 수가 많아지며 스택을 사용하는 오버헤드가 존재한다.</li>
<li>심볼릭 레퍼런스 : 참조하는 클래스의 특정 메모리 주소를 참조 관계로 구성한 것이 아닌, 참조하는 대상의 이름을 통해 연결. Class 파일이 JVM에 올라가게 되면 심볼릭 레퍼런스를 통해 그 이름에 맞는 객체의 주소를 찾아서 연결하는 작업을 수행한다. 그러므로 실제 메모리 주소가 아닌 대상의 이름을 가진다.</li>
<li>가비지 컬렉션</li>
<li>기본 자료형을 명확하게 정의하여 호환성을 유지하고 플랫폼 독립성 보장</li>
<li>네트워크 바이트 오더(빅 엔디안)<br>

</li>
</ul>
<p>컴파일을 통해 만들어진 클래스 파일 자체는 바이너리 파일이므로 사람이 이해하기 쉽지 않습니다. 이에 JVM 벤더들은 역어셈블리를 제공하고 이를 통해 나온 Opcode와 Operand를 해석합니다.
<br></p>
<p><strong>자바 클래스 파일 포맷의 여러 제한으로 인해 자바 메서드는 65535 바이트를 넘을 수 없다.</strong>
<br></p>
<h3 id="jvm-구조-그리기">JVM 구조 그리기</h3>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/70080d47-29b7-4bc5-8139-fdca6c0fc214/image.png" alt=""></p>
<ul>
<li>클래스 로더가 컴파일된 자바 바이트 코드를 런타임 데이터 영역에 로드</li>
<li>실행 엔진이 자바 바이트 코드를 실행<br>

</li>
</ul>
<h3 id="class-loader">Class Loader</h3>
<ul>
<li>자바의 가장 큰 특징 : 런타임에 클래스를 처음 참조할 때 해당 클래스를 로드<ul>
<li>클래스를 처음 참조할 때 해당 클래스를 로드하고, 클래스 로더 캐시도 존재</li>
</ul>
</li>
<li>클래스를 로드할 때 먼저 상위 클래스 로더를 확인하여 상위 클래스 로더에 있다면 해당 클래스를 사용하고, 없다면 로드를 요청받은 클래스 로더가 클래스를 로드 (위임, 계층 구조)</li>
<li>하위 클래스 로더는 상위 클래스 로더의 클래스를 찾을 수 있지만, 상위 클래스 로더는 하위 클래스 로더의 클래스를 찾을 수 없다.</li>
<li>클래스 로더는 클래스 언로드 불가. 즉 언로드 대신 현재 클래스 로더를 삭제하고 아예 새로운 클래스 로더를 생성하는 방법을 사용</li>
<li>각 클래스 로더는 로드된 클래스들을 보관하는 네임스페이스(namespace)를 갖는다. 클래스를 로드할 때 이미 로드된 클래스인지 확인하기 위해서 네임스페이스에 보관된 FQCN(Fully Qualified Class Name)을 기준으로 클래스를 찾는다. 비록 FQCN이 같더라도 네임스페이스가 다르면, 즉 다른 클래스 로더가 로드한 클래스이면 다른 클래스로 간주된다.<br>

</li>
</ul>
<p><strong>클래스 로더 위임 모델</strong></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/d282a454-0888-49bb-b413-1ca208e374a7/image.png" alt=""></p>
<p>이전에 로드된 클래스인지 클래스 로더 캐시를 확인하고, 없으면 상위 클래스 로더를 거슬러 올라가며 확인한다. 부트스트랩 클래스 로더까지 확인해도 없으면 요청받은 클래스 로더가 파일 시스템에서 해당 클래스를 찾는다.
<br></p>
<ul>
<li>부트스트랩 클래스 로더: JVM을 기동할 때 생성되며, Object 클래스들을 비롯하여 자바 API들을 로드한다. 다른 클래스 로더와 달리 자바가 아니라 네이티브 코드로 구현되어 있다.</li>
<li>익스텐션 클래스 로더(Extension Class Loader): 기본 자바 API를 제외한 확장 클래스들을 로드한다. 다양한 보안 확장 기능 등을 여기에서 로드하게 된다.</li>
<li>시스템 클래스 로더(System Class Loader): 사용자가 지정한 클래스 패스의 클래스들을 로드</li>
<li>사용자 정의 클래스 로더(User-Defined Class Loader): 애플리케이션 사용자가 직접 코드 상에서 생성해서 사용하는 클래스 로더<br>

</li>
</ul>
<p><strong>클래스 로드 단계</strong></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/8df9b1fc-cade-4318-ad8e-27ce54a8bd9d/image.png" alt=""></p>
<br>

<h3 id="runtime-data-area">Runtime Data Area</h3>
<ul>
<li>JVM이라는 프로그램 운영체제 위에서 실행되면서 할당받는 메모리 영역</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/0b233333-8767-4d33-b58c-b9badebfe3bb/image.png" alt="">
<br></p>
<p><strong>런타임 데이터 영역</strong> <strong>구성</strong></p>
<ul>
<li>PC Register : 현재 실행 중인 JVM 명령의 주소 저장</li>
<li>JVM Stack : 지역 변수 배열, 피연산자 스택, 현재 실행 중인 메서드가 속한 클래스의 런타임 상수 풀에 대한 레퍼런스 저장<ul>
<li>저장되는 것들이 컴파일 타임에 결정되기 때문에 스택 프레임의 크기도 메서드에 따라 크기가 고정</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/173416b4-bc3f-4210-a4e0-eb426422f268/image.png" alt=""></p>
<ul>
<li>지역 변수 배열 : 0부터 시작하는 인덱스를 가진 배열. 0은 메서드가 속한 클래스 인스턴스의 this 레퍼런스이고, 1부터는 메서드에 전달된 파라미터들이 저장되며, 메서드 파라미터 이후에는 메서드의 지역 변수들이 저장</li>
<li>피연산자 스택 : 메서드의 실제 작업 공간</li>
<li>네이티브 메서드 스택 : 자바 외의 언어로 작성된 네이티브 코드를 위한 스택</li>
<li>Method Area : JVM이 읽어들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, static 변수, 메서드의 바이트코드 등을 보관</li>
<li>Runtime Constant Pool : 각 클래스와 인터페이스의 상수 뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블. 런타임 상수 풀을 통해 메서드나 필드의 실제 메모리상 주소를 찾음.<br>

</li>
</ul>
<h3 id="실행-엔진">실행 엔진</h3>
<ul>
<li>클래스 로더를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드는 실행 엔진에 의해 실행</li>
<li>명령어 단위로 읽어서 실행</li>
<li>자바 바이트 코드는 비교적 인간이 보기 편한 형태로 기술되었기 때문에 JVM 내부에서 기계가 실행할 수 있는 형태로 변경<br>

</li>
</ul>
<p>방식 1. 인터프리터</p>
<p>바이트코드 명령어 하나씩 해석하기 때문에 빠른 해석 but 인터프리팅 결과의 실행은 느리다
<br></p>
<p>방식 2. JIT(Just-In-Time) 컴파일러</p>
<p>인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행</p>
<ul>
<li>네이티브 코드로 직접 실행 → 하나씩 인터프리팅하는 것보다 빠르고, 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행</li>
<li>단, 한 번만 실행되는 코드는 인터프리팅하는 것이 유리 → JIT 컴파일러의 컴파일 과정은 오래 걸림.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/0d936e49-2d3a-445b-9029-e632214cd812/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/3cc5c32f-1e86-4d90-aaf9-aad2423df4cd/image.png" alt=""></p>
<p><a href="https://d2.naver.com/helloworld/1230">NAVER D2</a></p>
<p><a href="https://www.korecmblog.com/jvm-stack-and-register/">스택 기반 VM과 레지스터 기반 VM</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[필터와 인터셉터]]></title>
            <link>https://velog.io/@sung_hyuki/%ED%95%84%ED%84%B0%EC%99%80-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0</link>
            <guid>https://velog.io/@sung_hyuki/%ED%95%84%ED%84%B0%EC%99%80-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0</guid>
            <pubDate>Sun, 18 Sep 2022 08:44:38 GMT</pubDate>
            <description><![CDATA[<p>공통 관심사를 해결할 수 있는 방법에는 <strong>서블릿 필터</strong> 또는 <strong>스프링 인터셉터</strong> 그리고 <strong>스프링 AOP</strong>가 존재한다. </p>
<h2 id="필터">필터</h2>
<ul>
<li>Servlet이 제공하는 기능</li>
<li>리소스에 대한 요청(서블릿 또는 정적 콘텐츠)이나 리소스의 응답 혹은 둘 다에 대해 필터링 작업을 수행하는 객체<br>

</li>
</ul>
<p><strong>필터 사용의 예</strong></p>
<p>1) Authentication Filters </p>
<p>2) Logging and Auditing Filters </p>
<p>3) Image conversion Filters </p>
<p>4) Data compression Filters </p>
<p>5) Encryption Filters </p>
<p>6) Tokenizing Filters </p>
<p>7) Filters that trigger resource access events </p>
<p>8) XSL/T filters </p>
<p>9) Mime-type chain Filter
<br></p>
<p><strong>필터 흐름</strong></p>
<p>HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러
<br></p>
<p><strong>필터 체인</strong></p>
<p>HTTP 요청 → WAS → 필터1 → 필터2 → 필터3 → 서블릿 → 컨트롤러
<br></p>
<pre><code class="language-java">public interface Filter {

    public default void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    public default void destroy() {}
}</code></pre>
<br>

<p>필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리</p>
<ul>
<li><code>init()</code> : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.</li>
<li><code>doFilter()</code> : 고객의 요청이 올 때마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.</li>
<li><code>destroy()</code> : 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.<br>

</li>
</ul>
<p><code>chain.doFilter(request, response)</code> : 이 부분이 가장 중요하다. 다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출한다. 만약 이 로직을 호출하지 않으면 다음 단계로 진행되지 않는다.
<br></p>
<p><strong>필터 등록 방법</strong></p>
<ul>
<li>@Component (모든 URL에 적용)</li>
<li>@WebFilter + @ServletComponentScan (특정 URL 패턴에만 적용 가능)<ul>
<li>스프링 컨테이너가 아닌 서블릿 컨테이너에 등록됨.</li>
</ul>
</li>
<li>FilterRegistrationBean을 이용한 등록(순서와 URL 지정 등 옵션이 많은 것이 특징)</li>
</ul>
<br>

<blockquote>
<p>필터에는 다음에 설명할 스프링 인터셉터는 제공하지 않는, 아주 강력한 기능이 있는데
chain.doFilter(request, response); 를 호출해서 다음 필터 또는 서블릿을 호출할 때 request , response 를 다른 객체로 바꿀 수 있다. ServletRequest , ServletResponse 를 구현한 다른 객체를 만들어서 넘기면 해당 객체가 다음 필터 또는 서블릿에서 사용된다. 잘 사용하는 기능은 아니니 참고만 해두자.</p>
</blockquote>
<br>

<h2 id="인터셉터">인터셉터</h2>
<ul>
<li>스프링이 제공하는 기능<br>

</li>
</ul>
<p><strong>스프링 인터셉터 흐름</strong></p>
<p>HTTP 요청 → WAS → 필터 → 서블릿 → 스프링 인터셉터 → 컨트롤러
<br></p>
<p><strong>스프링 인터셉터 체인</strong></p>
<p>HTTP 요청 → WAS → 필터 → 서블릿 → 인터셉터1 → 인터셉터2 → 컨트롤러
<br></p>
<pre><code class="language-java">public interface HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
    }

}</code></pre>
<br>

<p>인터셉터는 컨트롤러 호출 전(preHandle), 호출 후(postHandle), 요청 완료 이후(afterCompletion)와 같이 단계적으로 세분화 되어 있다.</p>
<p>인터셉터는 서블릿 다음에 호출되기 때문에 어떤 컨트롤러가 호출되는지 호출 정보도 받을 수 있다. 그리고 어떤 modelAndView가 반환되는지 응답 정보도 받을 수 있다.
<br></p>
<p><strong>정상 흐름</strong></p>
<ul>
<li><code>preHandle</code> : 컨트롤러 호출 전에 호출된다. (더 정확히는 핸들러 어댑터 호출 전에 호출된다.) preHandle 의 응답값이 true 이면 다음으로 진행하고, false 이면 더는 진행하지 않 는다. false인 경우 나머지 인터셉터는 물론이고, 핸들러 어댑터도 호출되지 않는다. 그림에서 1번에서 끝이  나버린다.</li>
<li><code>postHandle</code> : 컨트롤러 호출 후에 호출된다. (더 정확히는 핸들러 어댑터 호출 후에 호출된다.)</li>
<li><code>afterCompletion</code> : 뷰가 렌더링 된 이후에 호출된다.<br>

</li>
</ul>
<p><strong>예외가 발생시</strong></p>
<ul>
<li><code>preHandle</code> : 컨트롤러 호출 전에 호출된다.</li>
<li><code>postHandle</code> : 컨트롤러에서 <strong>예외가 발생하면 postHandle 은 호출되지 않는다.</strong></li>
<li><code>afterCompletion</code> : <strong>afterCompletion 은 항상 호출된다.</strong> 이 경우 예외( ex )를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력할 수 있다.<br>

</li>
</ul>
<p><strong>인터셉터 등록 방법</strong></p>
<ul>
<li>WebConfig 클래스의 addInterceptors 메서드 오버라이드를 통한 등록<ul>
<li>인터셉터를 적용하거나 하지 않을 부분은 addPathPatterns 와 excludePathPatterns 에 작성하면 된다.<br>

</li>
</ul>
</li>
</ul>
<p>인터셉터는 스프링 MVC 구조에 특화된 필터 기능을 제공한다고 이해하면 된다. 스프링 MVC를 사용하고, 특별히 필터를 꼭 사용해야 하는 상황이 아니라면 인터셉터를 사용하는 것이 더 편리하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 예외 회고]]></title>
            <link>https://velog.io/@sung_hyuki/%EC%9E%90%EB%B0%94-%EC%98%88%EC%99%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sung_hyuki/%EC%9E%90%EB%B0%94-%EC%98%88%EC%99%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 15 Jun 2022 10:35:42 GMT</pubDate>
            <description><![CDATA[<p>네이버 웹툰 프리인터뷰 면접을 통해</p>
<ul>
<li>자바에서 Error와 Exception이 있는데 이 둘의 차이는 무엇일까요?</li>
<li>NullPointException은 checkedException일까요? uncheckedException일까요?<ul>
<li>checkedException, uncheckedException, RuntimeException의 차이점은 무엇일까요?</li>
</ul>
</li>
</ul>
<p>와 같은 질문을 받은 적이 있다. 기본적인 자바 개념들을 고민하지 않고 주입식으로 학습하였고, 언제 어떠한 상황에서 사용해야 되는지에 대한 고민을 해보지 못했다. 그렇기에 두루뭉실한 답변을 뱉을 수밖에 없었다.</p>
<p>이러한 나의 과거 상황을 반성하며 자바 예외에 대한 기본기를 탄탄하게 쌓고 스프링에서 예외를 처리하는 여러가지 매커니즘에 대해 확실히 이해하고자 한다.
<br></p>
<p><strong>오류와 예외의 차이는 무엇인가?</strong></p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/a0d5d9d8-209a-4372-9c19-c6b7e9de55e2/image.png" alt=""></p>
<p>자바의 모든 예외는 기본적으로 Throwable에서 시작하고, 예외 역시도 자바 객체이기 때문에 Throwable의 최상위 부모도 Object다. Throwable을 상속받는 클래스는 Exception과 Error가 있다.</p>
<p>Error는 메모리 부족이나 심각한 시스템 오류와 같이 시스템 레벨에서 발생하기 때문에 애플리케이션 수준에서 예외를 잡으려고 해서는 안된다.</p>
<p>Exception은 애플리케이션 수준에서 사용할 수 있는 실질적인 최상위 예외이다. 예외는 발생할 상황을 미리 예측할 수 있다. 즉, 개발자가 처리할 수 있기 때문에 예외를 구분하고 그에 따른 처리 방법을 명확히 알고 적용하는 것이 중요하다.
<br></p>
<p><strong>checkedException과 uncheckedException</strong></p>
<p>Exception의 하위 클래스 중 RuntimeException을 제외한 모든 Exception은 컴파일러가 체크하는 checkedException이며, RuntimeException과 그 하위 클래스들은 컴파일러가 체크하지 않는 uncheckedException이라고 부른다.</p>
<p>체크 예외는 컴파일 타임에 체크가 되기 때문에 예외를 잡아서 처리하거나 throws를 지정해서 예외를 밖으로 던진다는 선언을 필수로 해야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
<br></p>
<p><strong>체크 예외는 어떠한 장/단점을 가질까?</strong></p>
<p>우선 장점은 컴파일 타임에 문제가 발견되기 때문에 개발자가 실수로 예외를 누락하지 않도록 만들 수 있다. 처리할 수 있는 예외라면 서비스나 컨트롤러에서 처리할 수 있겠지만 데이터베이스나 네트워크와 같은 시스템 레벨에서 올라온 예외들은 대부분 복구가 불가능하다. 이런 아래에서 올라온 복구 불가능한 예외를 서비스, 컨트롤러 같은 각각의 클래스가 모두 알고 있어야 한다. 그래서 불필요한 의존관계 문제가 발생하게 된다.</p>
<p>SQLException 을 예로 들면 데이터베이스에 무언가 문제가 있어서 발생하는 예외이다. SQL 문법에 문제가 있을 수도 있고, 데이터베이스 자체에 뭔가 문제가 발생했을 수도 있다. 데이터베이스 서버가 중간에 다운 되었을 수도 있다. 이런 문제들은 대부분 복구가 불가능하다. 특히나 대부분의 서비스나 컨트롤러는 이런 문제를 해결할 수는 없다. 따라서 <strong>이런 문제들은 일관성 있게 공통으로 처리해야 한다.</strong> 오류 로그를 남기고 개발자가 해당 오류를 빠르게 인지하는 것이 필요하다. 서블릿 필터, 스프링 인터셉터, 스프링의 ControllerAdvice 를 사용하면 이런 부분을 깔끔하게 공통으로 해결할 수 있다.</p>
<p>또한 서비스 계층 그리고 컨트롤러 계층까지 SQLException과의 의존관계가 생기게 된다. 향후 데이터베이스 접근 기술을 JDBC 기술이 아닌 다른 기술로 변경한다면 JDBC 기술에 종속된 SQLException이 아닌 다른 예외가 발생하게 되고 의존 관계에 있는 코드들을 찾아서 모두 수정해야만 한다. 이는 결과적으로 OCP, DI를 통해 클라이언트 코드의 변경 없이 대상 구현체를 변경할 수 있다는 장점이 체크 예외 때문에 발목을 잡게 된다. </p>
<p>언체크 예외는 런타임에 발견되기 때문에 명시적으로 처리를 강제하지 않는다.
<br></p>
<p><strong>언체크 예외는 어떠한 장/단점을 가질까?</strong></p>
<p>throws를 생략할 수 있다. → 신경쓰고 싶지 않은 예외의 의존관계를 참조하지 않아도 되는 장점이 존재</p>
<p>반면 개발자가 명시적으로 코드를 짜지 않는 이상 실수로 예외를 누락시킬 수 있는 가능성이 존재한다.</p>
<p>체크 예외와 언체크 예외의 차이는 예외를 처리할 수 없을 때 예외를 밖으로 던지는 부분에 있다. 이 부분을 필수로 선언해야 하는가 혹은 생략할 수 있는가의 차이다.</p>
<p>위의 체크 예외를 사용함으로써 발생하는 문제를 언체크 예외를 사용함으로써 해결할 수 있다.</p>
<p>시스템에서 발생한 예외는 대부분 복구 불가능한 예외이기 때문에 이를 uncheckedException으로 추상화하면 서비스나 컨트롤러에서 이런 복구 불가능한 예외를 신경쓰지 않아도 된다. 하지만 이렇게 복구 불가능한 예외는 위에서 말한 것과 같이 일관성 있게 공통으로 처리해야 한다. 이는 의존 대상이 변경할 경우 공통 처리하는 곳에서만 변경하면 되기 때문에 변경의 영향 범위도 최소화 된다. 또한 체크 예외처럼 예외를 강제로 의존하지 않아도 된다.
<br></p>
<p><strong>두 예외를 어떠한 상황에 사용해야 하는가에 대한 기준을 확립해야 한다</strong></p>
<ul>
<li>기본적으로 언체크(런타임) 예외를 사용한다.</li>
<li>체크 예외는 비즈니스 로직 상 의도적으로 던지는 예외에만 사용한다.<ul>
<li>예. 계좌 이체 실패 예외, 결제 시 포인트 부족 예외, 로그인 ID, PW 불일치 예외 등</li>
<li>위의 예와 같은 예외들은 개발자가 실수로 놓칠 경우 애플리케이션 운영 시에 심각한 문제 상황을 발생시키기 때문에 컴파일 타임에 체크할 수 있도록 체크 예외를 사용하는 것이 좋다. 하지만 무조건 체크 예외로 만들어야 하는 것은 아니다. 상황을 깊이 들여다보고 판단하자.</li>
</ul>
</li>
</ul>
<p><strong>범위 밖의 내용이지만 예외를 전환할 경우 기존 예외를 포함해야한다. 스택 트레이스 확인을 통해 어떤 예외가 발생하는지 확인하는 것이 중요하기 때문</strong>
<br></p>
<h3 id="오개념-지우기">오개념 지우기</h3>
<p>checkedException은 예외 발생 시 트랜잭션 롤백을 하지 않음 vs uncheckedException은 예외 발생 시 트랜잭션 롤백을 함으로 정리된 표가 많은 블로그에서 참조되고 있다. 하지만 이는 특정 부분에 국한되어 이야기되는 것으로 트랜잭션은 이런 예외일 때 이렇게 하고, 저런 예외일 때는 저렇게 한다가 사실 정해져 있는 것이 아니다. 트랜잭션의 종류도 다양한데다가(DB 트랜잭션, 메세징 큐 트랜잭션.. 등) </p>
<p>모든 트랜잭션을 저 개념에 국한시켜 말하는 것은 아니라고 한다. checkedException이냐 uncheckedException 이냐에 따라 롤백을 할 것인가는 우리가 정하는 것이다.</p>
<p>스프링 트랜잭션의 예외 발생 시 롤백 여부는 트랜잭션의 옵션을 통해 설정이 가능하다. 자세한 사항은 스프링 도큐먼트 rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName 옵션을 확인하기 바란다.</p>
<h3 id="rollbackfor">rollbackFor</h3>
<p><strong>트랜잭션 작업 중 런타임 예외가 발생하면 롤백한다. 반면에 예외가 발생하지 않거나 체크 예외가 발생하면 커밋한다.</strong></p>
<p>체크 예외를 커밋 대상으로 삼는 이유는 체크 예외가 예외적인 상황에서 사용되기 보다는 리턴 값을 대신해서 비즈니스 적인 의미를 담은 결과로 돌려주는 용도로 사용되기 때문이다.</p>
<p>스프링에서는 데이터 엑세스 기술의 예외를 런타임 예외로 전환해서 던지므로 런타임 예외만 롤백대상으로 삼는다.</p>
<p><strong>하지만 원한다면 체크예외지만 롤백 대상으로 삼을 수 있다. rollbackFor또는 rollbackForClassName 속성을 이용해서 예외를 지정한다.</strong></p>
<p><a href="https://www.nextree.co.kr/p3239/">Java 예외(Exception) 처리에 대한 작은 생각</a></p>
<p><a href="https://www.youtube.com/watch?v=_WkMhytqoCc">자바 공부를 어떻게 하길래, &quot;언체크드 예외 발생시 트랜잭션 롤백?&quot;</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트랜잭션]]></title>
            <link>https://velog.io/@sung_hyuki/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</link>
            <guid>https://velog.io/@sung_hyuki/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</guid>
            <pubDate>Thu, 02 Jun 2022 06:32:48 GMT</pubDate>
            <description><![CDATA[<h2 id="트랜잭션이란">트랜잭션이란?</h2>
<p>데이터베이스의 상태를 변화시키는 작업의 단위를 트랜잭션이라고 한다.
<br></p>
<h2 id="트랜잭션의-성질acid">트랜잭션의 성질(ACID)</h2>
<br>

<p>원자성(Atomicity)</p>
<ul>
<li>한 트랜잭션 내에서 실행되는 작업들은 하나의 단위로 처리된다.</li>
<li>한 트랜잭션 내에서 실행한 작업들은 모두 성공하거나, 반대로 전부 실패되는 성질<br>

</li>
</ul>
<p>일관성(Consistency)</p>
<ul>
<li>데이터베이스의 상태가 일관되어야 한다.</li>
<li>다시 말해, 트랜잭션이 일어난 이후의 데이터베이스는 데이터베이스의 제약이나 규칙을 만족해야 한다는 뜻<br>

</li>
</ul>
<p><strong>고립성(Isolation) →</strong> Isolation level의 default 값을 알고 계시나요?</p>
<ul>
<li>모든 트랜잭션은 다른 트랜잭션으로부터 독립되어야 한다.</li>
<li>트랜잭션이 수행되고 있을 때, 다른 트랜잭션의 연산작업이 중간에 끼어들어 기존 작업에 영향을 주지 못하도록 하는 것</li>
<li>동시성과 관련된 성능 이슈로 인해 트랜잭션 격리 수준을 선택<br>

</li>
</ul>
<p>지속성(Durability)</p>
<ul>
<li>트랜잭션 성공 후 데이터베이스에 반영된 것은 영구히 반영되어있어야 한다는 것을 의미한다.</li>
<li>시스템에 문제가 발생하거나 종료되더라도 데이터베이스에 반영된 값은 그대로 유지되어야 한다.<br>

</li>
</ul>
<h2 id="선언적-트랜잭션">선언적 트랜잭션</h2>
<p>스프링에서 어노테이션 방식(<code>@Transactional</code> )으로 선언적 트랜잭션 처리를 지원한다.</p>
<p>트랜잭션 적용 범위에서 트랜잭션 기능이 포함된 프록시 객체가 생성되어 자동으로 commit, rollback을 해준다.
<br></p>
<h2 id="transactional-옵션">@Transactional 옵션</h2>
<ul>
<li>isolation<ul>
<li>동시에 여러 트랜잭션이 처리될 때, 트랜잭션끼리 얼마나 고립되어 있는지를 나타내는 것</li>
</ul>
</li>
<li>propagation<ul>
<li>트랜잭션 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 지정하는 옵션이다.</li>
</ul>
</li>
<li>noRollbackFor<ul>
<li>특정 예외 발생 시 rollback하지 않는다.</li>
</ul>
</li>
<li>rollbackFor<ul>
<li>특정 예외 발생 시 rollback한다.</li>
</ul>
</li>
<li>timeout<ul>
<li>지정한 시간 내에 메서드 수행이 완료되지 않으면 rollback한다. (-1일 경우 timeout을 사용하지 않는다)</li>
</ul>
</li>
<li>readOnly<ul>
<li>트랜잭션을 읽기 전용으로 설정한다.<br>

</li>
</ul>
</li>
</ul>
<h2 id="propagation"><strong>propagation</strong></h2>
<br>

<table>
<thead>
<tr>
<th>REQUIRED(DEFAULT)</th>
<th>이미 진행중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 진행중이 아니라면 새로운 트랜잭션을 생성한다.</th>
</tr>
</thead>
<tbody><tr>
<td>REQUIRED_NEW</td>
<td>항상 새로운 트랜잭션을 생성한다. 이미 진행중인 트랜잭션이 있다면 잠깐 보류하고 해당 트랜잭션 작업을 먼저 진행한다.</td>
</tr>
<tr>
<td>SUPPORT</td>
<td>이미 진행중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 없다면 트랜잭션을 설정하지 않는다.</td>
</tr>
<tr>
<td>NOT_SUPPORT</td>
<td>이미 진행중인 트랜잭션이 있다면 보류하고, 트랜잭션 없이 작업을 수행한다.</td>
</tr>
<tr>
<td>MANDATORY</td>
<td>이미 진행중인 트랜잭션이 있어야만, 작업을 수행한다. 없다면 Exception을 발생시킨다.</td>
</tr>
<tr>
<td>NEVER</td>
<td>트랜잭션이 진행중이지 않을 때 작업을 수행한다. 트랜잭션이 있다면 Exception을 발생시킨다.</td>
</tr>
<tr>
<td>NESTED</td>
<td>진행중인 트랜잭션이 있다면 중첩된 트랜잭션이 실행되며, 존재하지 않으면 REQUIRED와 동일하게 실행된다.</td>
</tr>
<tr>
<td><br></td>
<td></td>
</tr>
</tbody></table>
<h2 id="isolation"><strong>isolation</strong></h2>
<ul>
<li><p><strong>Default(기본 격리 수준)</strong></p>
<p>  DB의 isolation level을 따른다. 대부분의 RDB에서 기본적으로 사용되고 있는 격리 수준은 READ_COMMITED</p>
</li>
</ul>
<br>

<ul>
<li><p><strong>READ_COMMITED (level 0) - 커밋되기 전 데이터에 대한 읽기 허용</strong></p>
<p>  가장 낮은 격리 수준으로, 각 트랜잭션에서의 변경 내용이 커밋이나 롤백 여부에 상관없이 다른 트랜잭션에서 값을 읽을 수 있다.</p>
<ul>
<li><p><strong>어떤 문제가 있을까?</strong></p>
<p>  Dirty READ (트랜잭션 작업이 완료되지 않았지만 다른 트랜잭션이 값을 읽을 수 있는 현상)</p>
<p>  트랜잭션 1의 작업이 완료되지 않았는데 트랜잭션 2에서 읽을 수 있다. 만약 트랜잭션 1의 작업이 롤백되면, 트랜잭션 2가 읽었던 데이터는 잘못된 데이터가 되는 것. 데이터의 정합성이 깨짐.</p>
<ol>
<li><p>A 트랜잭션에서 10번 사원의 나이를 27살에서 28살로 바꿈</p>
</li>
<li><p>아직 커밋하지 않음</p>
</li>
<li><p>B 트랜잭션에서 10번 사원의 나이를 조회함</p>
</li>
<li><p>28살이 조회됨</p>
<blockquote>
<p>이를 <strong>더티 리드(Dirty Read)</strong>라고 한다</p>
</blockquote>
</li>
<li><p>A 트랜잭션에서 문제가 발생해 ROLLBACK</p>
</li>
<li><p>B 트랜잭션은 10번 사원이 여전히 28살이라고 생각하고 로직을 수행함</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><p><strong>READ_COMMITED (level 1) - 커밋된 데이터에 대한 읽기 허용</strong></p>
<p>  어떤 트랜잭션의 변경 내용이 커밋 되어야만 다른 트랜잭션에서 읽을 수 있도록 허용한다. Dirty READ가 발생하지 않도록 보장</p>
<ol>
<li><p>B 트랜잭션에서 10번 사원의 나이를 조회</p>
</li>
<li><p>27살이 조회됨(Undo 영역에 백업된 데이터를 조회)</p>
</li>
<li><p>A 트랜잭션에서 10번 사원의 나이를 27살에서 28살로 바꾸고 <code>커밋</code></p>
</li>
<li><p>B 트랜잭션에서 10번 사원의 나이를 다시 조회(변경되지 않은 이름이 조회됨)</p>
</li>
<li><p>28살이 조회됨</p>
<p>정합성 문제가 해결된 것처럼 보이지만, 하나의 트랜잭션내에서 똑같은 SELECT를 수행했을 경우 항상 같은 결과를 반환해야 한다는 <strong>REPEATABLE READ</strong> 정합성에 어긋나는 것이다.</p>
</li>
</ol>
</li>
</ul>
<br>

<ul>
<li><p><strong>REPEATABLE_READ (level 2)</strong></p>
<p>  트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 조회할 수 있는 격리수준</p>
<p>  MYSQL에서 기본으로 사용하고 있고, 해당 격리수준에서는 NON-REPEATABLE READ 부정합이 발생하지 않는다. </p>
<p> <img src="https://velog.velcdn.com/images/sung_hyuki/post/151a3ebc-bf17-4de0-aeb9-b0a909b2c161/image.png" alt=""></p>
</li>
</ul>
<pre><code>1. 10번 트랜잭션이 500000번 사원의 이름을 조회
2. 12번 트랜잭션이 500000번 사원의 이름 변경하고 커밋
3. 10번 트랜잭션이 50000번 사원을 다시 조회
4. Undo 영역에 백업된 데이터 반환</code></pre><br>

<p>MySQL에서는 트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽게 된다. Undo 영역에 백업된 모든 레코드는 변경을 발생시킨 트랜잭션의 포함되어 있다.
<br></p>
<h3 id="repeatable-read에서-발생할-수-있는-데이터-부정합">REPEATABLE READ에서 발생할 수 있는 데이터 부정합</h3>
<ol>
<li>UPDATE 부정합</li>
</ol>
<pre><code>```sql
START TRANSACTION; -- transaction id : 1
SELECT * FROM Member WHERE name=&#39;junyoung&#39;;

    START TRANSACTION; -- transaction id : 2
    SELECT * FROM Member WHERE name = &#39;junyoung&#39;;
    UPDATE Member SET name = &#39;joont&#39; WHERE name = &#39;junyoung&#39;;
    COMMIT;

UPDATE Member SET name = &#39;zion.t&#39; WHERE name = &#39;junyoung&#39;; -- 0 row(s) affected
COMMIT;
```</code></pre><p>이 상황에서 최종 결과는 <code>name = joont</code> 가 된다.</p>
<p>REPETABLE READ이기 때문에,</p>
<p>2번 트랜잭션에서 <code>name = joont</code>로 변경하고 COMMIT을 하면 <code>name = junyoung</code>의 내용을 Undo 영역에 남겨놔야 한다.</p>
<p>그래야 1번 트랜잭션에서 일관되게 데이터를 보는 것을 보장해줄 수 있기 때문이다.</p>
<p>이 상황에서 아래 구문에서 UPDATE 문을 실행하게 되는데, <strong>UPDATE의 경우 변경을 수행할 로우에 대해 잠금이 필요하다</strong></p>
<p>하지만 현재 1번 트랜잭션이 바라보고 있는 <code>name = junyoung</code>의 경우 레코드 데이터가 아닌 Undo 영역의 데이터이고, Undo 영역에 있는 데이터에 대해서는 쓰기 잠금을 걸 수가 없다.</p>
<p>그러므로 위의 UPDATE 구문은 레코드에 대해 쓰기 잠금을 시도하려고 하지만 <code>name = junyoung</code>인 레코드는 존재하지 않으므로, <code>0 row(s) affected</code>가 출력되고, 아무 변경도 일어나지 않게 된다.</p>
<p>그러므로 최종적으로 결과는 <code>name = joont</code>가 된다. 자이언티가 되지 못해 아쉽다.</p>
<ol>
<li><p>Phantom READ</p>
<p> 한 트랜잭션 내에서 같은 쿼리를 두 번 실행했는데, 첫 번째 쿼리에서 없던 유령(Phantom) 레코드가 두 번째 쿼리에서 나타나는 현상을 말한다.</p>
<p> REPETABLE READ 이하에서만 발생하고(SERIALIZABLE은 발생하지 않음), INSERT에 대해서만 발생한다.</p>
</li>
</ol>
<br>

<ul>
<li><strong>SERIALIZABLE (level 3)</strong><ul>
<li>가장 단순한 격리 수준이지만 가장 엄격한 격리 수준</li>
<li>InnoDB에서 기본적으로 순수한 SELECT 작업은 아무런 잠금을 걸지않고 동작하는데, 격리수준이 SERIALIZABLE일 경우 읽기 작업에도 공유 잠금을 설정하게 되고, 이러면 동시에 다른 트랜잭션에서 이 레코드를 변경하지 못하게 된다.</li>
<li>동시처리 능력이 다른 격리수준보다 떨어지고, 성능저하가 발생<br>

</li>
</ul>
</li>
</ul>
<h2 id="norollbackfor">noRollbackFor</h2>
<p>런타임 예외가 발생해도 지정한 런타임 예외면 커밋을 진행한다.
<br></p>
<h2 id="rollbackfor">rollbackFor</h2>
<p><strong>트랜잭션 작업 중 런타임 예외가 발생하면 롤백한다. 반면에 예외가 발생하지 않거나 체크 예외가 발생하면 커밋한다.</strong></p>
<p>체크 예외를 커밋 대상으로 삼는 이유는 체크 예외가 예외적인 상황에서 사용되기 보다는 리턴 값을 대신해서 비즈니스 적인 의미를 담은 결과로 돌려주는 용도로 사용되기 때문이다.</p>
<p>스프링에서는 데이터 엑세스 기술의 예외를 런타임 예외로 전환해서 던지므로 런타임 예외만 롤백대상으로 삼는다.</p>
<p><strong>하지만 원한다면 체크예외지만 롤백 대상으로 삼을 수 있다. rollbackFor또는 rollbackForClassName 속성을 이용해서 예외를 지정한다.</strong>
<br></p>
<h2 id="timeout">timeout</h2>
<p>지정한 시간 내에 메서드 수행이 완료되지 않으면 rollback한다. (-1일 경우 timeout을 사용하지 않는다)
<br></p>
<h2 id="readonly">readOnly</h2>
<p>트랜잭션을 읽기 전용으로 설정한다. 특정 트랜잭션 안에서 쓰기 작업이 일어나는 것을 의도적으로 방지하기 위해 사용하거나 조회 기능만 감겨두어 조회 속도가 개선됨. insert, update, delete 작업이 진행되면 예외가 발생한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테스트를 학습하며]]></title>
            <link>https://velog.io/@sung_hyuki/%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%ED%95%99%EC%8A%B5%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@sung_hyuki/%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%ED%95%99%EC%8A%B5%ED%95%98%EB%A9%B0</guid>
            <pubDate>Thu, 26 May 2022 04:33:58 GMT</pubDate>
            <description><![CDATA[<p><strong>무엇을 테스트할 것인가?가 제일 중요한 포인트</strong>
<br></p>
<p>스프링 부트에서는 애플리케이션 테스트를 도와주는 다양한 유틸리티와 어노테이션을 제공한다.</p>
<p>테스트는 두 가지 모듈에 의해서 제공된다.</p>
<ul>
<li><code>spring-boot-test</code> : 핵심 기능을 포함</li>
<li><code>spring-boot-test-autoconfigure</code> : 테스트를 위한 자동 설정을 제공<br>

</li>
</ul>
<p>개발자들의 편의를 위해 <code>spring-boot-starter-test</code> 라는 스타터를 제공
<br></p>
<p><strong><code>spring-boot-starter-test</code> 에서 제공되는 의존성</strong></p>
<ul>
<li>JUnit 5 : The de-facto standard for unit testing Java applications</li>
<li>Spring Test &amp; Spring Boot Tets : Utilities and integration test support for Spring Boot applications</li>
<li>AssertJ : A fluent assetion library</li>
<li>Hamcrest : A library of matcher objects (also known as constraints or predicates)</li>
<li>Mokito : A Java mocking framework</li>
<li>JSONassert : An assertion library for JSON</li>
<li>JsonPath : XPath for JSON<br>

</li>
</ul>
<h2 id="스프링-부트의-통합-테스트"><strong>스프링 부트의 통합 테스트</strong></h2>
<ul>
<li><code>@SpringBootTest</code><ul>
<li>스프링 부트에서는 Spring Boot 기능이 필요할 때 스프링 테스트 표준 @ContextConfiguration 어노테이션의 대안으로 사용할 수 있는 <code>@SpringBootTest</code> 어노테이션을 제공한다.<ul>
<li>@ContextConfiguration : 통합 테스트를 위한 애플리케이션 컨텍스트를 어떻게 로드하고 설정하는지를 결정하는데 사용되는 클래스 레벨의 메타 데이터들을 정의</li>
<li>통합 테스트에 필요한 빈들을 <strong>하나의 문맥 단위로 관리</strong></li>
<li>해당 어노테이션 선언 시 스프링이 실행되고 ApplicationContext를 생성하여 작동한다. 클래스를 따로 지정하지 않는다면 모든 빈을 올려서 테스트한다는 것을 의미.</li>
</ul>
</li>
<li>JUnit 4을 사용한다면 @RunWith(SpringRunner.class)와, JUnit 5를 사용한다면 @ExtendWith(SpringExtension.class)와 함께 사용해야 한다.<ul>
<li>@RunWith, @ExtendWith 모두 JUnit의 테스트 러너(Runner)를 확장하는 방법을 제공 : JUnit 은 JUnit에 내장된 러너 대신 스프링 구현체로 기능 확장 가능</li>
<li>(참고) JUnit5를 사용할 때는 @SpringBootTest 어노테이션 속에@ExtendWith(SpringExtension.class)가 이미 메타 애노테이션으로 사용됐기 때문에 @SpringBootTest를 사용하는 코드에 다시 선언하지 않아도 된다.</li>
</ul>
</li>
<li>@SpringBootTest 어노테이션의 사용은 기본적으로 서버를 시작시키지 않는다. 이제껏 스프링을 실행시킨다는 의미에 서버를 시작시킨다는 의미가 내포된 것인 줄 알았다. Intellij의 Run을 이용해 애플리케이션을 실행시키면 서버가 실행되었으니까! 스프링을 이용해 물론 서버를 실행시키도록 서버 환경을 구축할 수는 있지만 스프링은 IoC 컨테이너에 등록된 빈들을 관리해주는 역할을 수행하고 객체들 간의 의존 관계를 만들어준다. @SpringBootTest의 <code>webEnvironment 속성</code>을 사용하여 테스트 실행 방법을 세분화할 수 잇다.<ul>
<li>webEnvironment 속성에는 어떤 것들이 있을까?<ul>
<li>MOCK(Default) : web ApplicationContext를 로드하고 mock web environment(원래의 서블릿 컨테이너와는 다른 의미)를 제공한다. <strong>내장 서버는 해당 어노테이션을 사용할 때 시작되지 않는다.</strong> 클래스 경로에서 웹 환경을 이용할 수 없는 경우, 웹이 아닌 일반 ApplicationContext를 만드는 것으로 투명하게 대체된다. 웹 애플리케이션 mock 기반 테스트를 위해 @AutoConfigureMockMvc 또는 @AutoConfigureWebTestClient와 함께 사용할 수 있다.</li>
<li>RANDOM_PORT : WebServerApplicationContext를 로드하고 실제 웹 환경을 제공한다. 내장 서버가 시작되고 랜덤 포트에서 수신한다.</li>
<li>DEFINED_PORT : WebServerApplicationContext를 로드하고 실제 웹 환경을 제공한다. 내장 서버가 시작되고 정의된 포트 또는 기본 포트인 8080에서 수신한다.</li>
<li>NONE : 스프링 애플리케이션을 사용함으로써 ApplicaitonContext를 로드하지만 어떠한 웹 환경도 제공하지 않는다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<p><strong>그러면 여기서 통합 테스트란 무엇인가?</strong></p>
<ul>
<li>단위 테스트보다 더 큰 동작을 달성하기 위해 여러 모듈들을 모아 이들이 의도대로 협력하는지 확인하는 테스트<br>

</li>
</ul>
<blockquote>
<p>테스트에 @Transactional 어노테이션이 있는 경우, 기본적으로 각 테스트 메서드의 끝에서 트랜잭션을 롤백시킨다. 그러나  RANDOM_PORT 또는 DEFINED_PORT와 함께 이 배치를 사용하면 실제 서블릿 환경을 제공하므로 HTTP 클라이언트와 서버가 별도의 스레드에서 실행되고 별도의 트랜잭션이 실행된다. 이 경우에는 서버에서 시작된 어떤 트랜잭션도 롤백되지 않는다.</p>
</blockquote>
<br>

<h2 id="mock-환경에서의-테스트"><strong>Mock 환경에서의 테스트</strong></h2>
<p>@SpringBootTest는 내장 서버를 실행시키지 않는 대신 web endpoint를 테스트하기 위해 mock 환경을 설정한다. 스프링 MVC를 사용하면, <code>MockMvc</code> 또는 <code>WebTestClient</code>를 사용하여 web endpoint를 테스트할 수 있다.
<br></p>
<ul>
<li>ApplicationContext를 띄우지 않고 web layer에서 동작하는 테스트에만 집중하고 싶다면 @WebMvcTest 어노테이션을 사용<br>

</li>
</ul>
<p><strong>mock이란 무엇인가?</strong></p>
<ul>
<li>테스트를 수행할 때 테스트를 수행할 모듈과 연결되는 외부 서비스나 모듈을 실제 사용하는 모듈을 사용하지 않고 실제의 모듈을 흉내내는 가짜 모듈을 작성하여 테스트의 효율성을 높이는데 사용하는 객체이다.</li>
<li>실제 객체를 만들기에는 비용과 시간이 많이 들거나 의존성이 길게 걸쳐져 있어 제대로 구현하기 어려울 경우, 가짜 객체를 만들어 사용한다.<br>

</li>
</ul>
<p><strong>mock 객체는 언제 필요한가?</strong></p>
<ul>
<li>테스트 작성을 위한 환경 구축이 어려운 경우</li>
<li>테스트가 특정 경우나 순간에 의존적인 경우</li>
<li>테스트 시간이 오래 걸리는 경우</li>
<li>개인 PC의 성능이나 서버의 성능 문제로 오래 걸릴 수 있는 경우 시간을 단축하기 위해서도 사용한다.<br>

</li>
</ul>
<p><strong>mock에 대한 기본적인 분류 개념</strong></p>
<ul>
<li>테스트 더블(Test Double)<ul>
<li>테스트하려는 객체와 연관된 객체를 사용하기가 어렵고 모호할 때 연관된 객체를 대신해 줄 수 있는 객체</li>
<li>테스트 더블을 사용하면서 얻는 이점은?<ul>
<li>테스트 대상 코드 격리</li>
<li>테스트 속도 개선</li>
<li>예측 불가능한 실행 요소 제거</li>
<li>특수한 상황 테스트 가능</li>
<li>감춰진 정보를 확인 가능</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/acd5afb2-ccc4-4741-a7b4-4784f612b0a2/image.jpeg" alt=""></p>
<ul>
<li><p>더미 객체(Dummy Object)</p>
<ul>
<li>인스턴스화 된 객체가 필요하지만 <strong>기능까지는 필요하지 않은 경우</strong>에 사용한다.</li>
<li>Dummy Obejct의 메서드가 호출되었을 때 정상 동작은 보장하지 않는다.<br>
</li>
</ul>
</li>
<li><p>테스트 스텁(Test Stub)</p>
<ul>
<li>더미 객체가 <strong>실제 동작하는 것처럼 보이게 만들어 놓은 객체</strong>이다.</li>
<li>특정 상황을 가정해서 만들어 특정 값을 리턴해 주거나 특정 메시지를 출력해 주는 작업을 한다. (상태 검증을 위해)<ul>
<li>특정 상태를 가정해서 하드코딩된 형태이기 때문에 로직에 따른 값의 변경은 테스트 할 수 없다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><p>테스트 스파이(Test Spy)</p>
<ul>
<li>Stub의 역할을 가지면서 호출된 내용에 대해 약간의 정보를 기록할 때 사용한다.</li>
<li>테스트 더블로 구현된 객체에 자기 자신이 호출 되었을 때 확인이 필요한 부분을 기록하도록 구현한다.</li>
<li>실제 객체처럼 동작시킬 수도 있고, 필요한 부분에 대해서는 Stub로 만들어서 동작을 지정할 수도 있다.<br>
</li>
</ul>
</li>
<li><p>Mock 객체(Mock Obejct)</p>
<ul>
<li><strong>행위를 검증하기 위해</strong> 사용되는 객체를 지칭하며 수동으로 만들 수도 있고 프레임워크를 통해 만들 수도 있다.</li>
<li>호출에 대한 기대를 명세하고 내용에 따라 동작하도록 만들어진 객체</li>
<li>Mock 객체는 테스트 더블 하위객체로 써의 좁은 의미와 테스트 더블을 포함한 넓은 의미 2가지로 사용 될 수 있다.<br>
</li>
</ul>
</li>
<li><p>페이크 객체(Fake Object)</p>
<ul>
<li>여러 상태를 대표할 수 있도록 구현된 객체로 실제 로직이 구현된 것처럼 보이게 한다.</li>
<li>실제로 DB에 접속하지는 않지만 DB에 접속해서 비교할 때와 비슷한 시나리오로 동작하도록 객체 내부에 구현 할 수 있다.<ul>
<li>테스트 케이스 작성을 위해서 다른 객체들과의 의존성을 제거하기 위해 사용</li>
<li>페이크 객체를 만들 때 복잡도로 인해서 노력이 많이 들어갈 경우 적절한 수준에서 구현하거나, Mock 프레임워크를 사용</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>
이러한 테스트 더블들을 사용하기 편리하게 구현해놓은 Mocking Framework들이 존재한다.

<br>

<p><strong>왜 mock 환경에서 테스트를 수행하는가?</strong></p>
<ul>
<li>일반적으로 서블릿 컨테이너를 실행하는 테스트 환경보다 빠르다</li>
<li>그러나 mocking이 Spring MVC layer에서 발생하기 때문에 더 낮은 수준의 서블릿 컨테이너 동작에 의존하는 코드는 MockMvc를 사용하여 테스트할 수 없다. 그럼 이 문제를 어떻게 해결하지?<ul>
<li>실제 웹 환경을 제공하는 webEnvironment 속성을 사용하여 해결할 수 있다.</li>
<li>webTestClient는 live server와 mock environment 두 곳 모두 사용 가능하다.</li>
</ul>
</li>
</ul>
<br>

<h2 id="mocking과-spying-beans"><strong>Mocking과 Spying Beans</strong></h2>
<p>테스트를 진행할 때, 때때로 애플리케이션 컨텍스트 내에 있는 특정 컴포넌트에 목을 쳐서 사용할 필요성이 있고, 스프링 부트에는 ApplicationContext 안에 있는 빈을 목을 쳐서 사용할 수 있게 도와주는 Mockito Mock에서 제공하는 @MockBean 어노테이션을 제공한다.
<br></p>
<ul>
<li><p><code>@MockBean</code></p>
<ul>
<li>기존에 사용되던 스프링 Bean이 아닌 가짜 객체 Mock Bean을 주입한다.</li>
<li>Bean의 이름을 지정해야 함. Bean의 이름을 지정하지 않으면, 스프링에서 어떤 빈을 가져와야하는지 알 수 없어 오류가 발생</li>
<li>해당 어노테이션은 테스트 클래스 내의 필드 또는 @Configuration 클래스 및 필드에 직접 사용할 수 있다. @MockBean 어노테이션은 애플리케이션 컨텍스트가 새로 고침되는 동안 실행되는 빈의 행위를 목치는데 사용되어서는 안된다. 이 상황에서는 @Bean 어노테이션을 사용한다.</li>
<li>MockBean을 통해 주입된 가짜 객체의 행동을 선언해줘야 함<br>
</li>
</ul>
</li>
<li><p><code>@SpyBean</code></p>
<ul>
<li>이미 존재하는 Bean을 SpyBean으로 Wrapping한 형태라고 생각하면 된다.</li>
<li>선언한 코드 외에는 전부 실제 객체의 것을 사용한다. 선언한 코드 외의 부분은 실제 코드를 통해 전개되고 선언한 코드 부분은 특정 동작으로 목을 치고 싶을 경우 사용</li>
<li><strong>테스트의 범위가 너무 커지는 것은 아닌지 고려하고 사용할 필요성이 있다.</strong></li>
<li>@MockBean은 given에서 선언한 코드 외에는 전부 사용할 수 없지만, @SpyBean은 given에서 선언한 코드 외에는 전부 실제 객체의 것을 사용한다.<br>

</li>
</ul>
</li>
</ul>
<p><strong>mock을 치는 방법</strong></p>
<ul>
<li>목을 칠 대상 객체를 선정하고 해당 객체의 행위에 기반한 테스트 예상 결과를 설정한 후, 테스트 대상 메서드를 실행시킨다.<br>

</li>
</ul>
<h2 id="mockito--a-java-mocking-framework"><strong>Mockito : A Java mocking framework</strong></h2>
<p>단위 테스트를 도와주고 Mock이 필요한 테스트에 직관적으로 사용할 수 있도록 만들어졌다.</p>
<ul>
<li>Mocking</li>
<li>메서드의 행위 검증</li>
<li>테스트 스텁으로 사용<br>

</li>
</ul>
<p><strong>Mock과 MockBean의 차이점</strong></p>
<p>Mcok은 Mock 객체를 테스트를 실행하는 동안 사용할 수 있게 하기 위해 @RunWith(MockitoJUnitRunner.class)와 함게 사용해야 한다.</p>
<p>@MockBean의 경우 ApplicationContext에 존재하는 빈을 MockBean으로 교체해준다. 즉 ApplicationContext를 띄워서 테스트를 진행할 경우 MockBean을 사용
<br></p>
<h2 id="슬라이스-테스트">슬라이스 테스트</h2>
<p>스프링 부트의 auto-configuration 시스템은 테스트를 위해서는 너무 과할 수 있기 때문에 슬라이스 테이스를 위해 필요한 부분의 설정만을 로드할 필요가 있다. 스프링 부트의 <code>spring-boot-test-autoconfigure</code> 모듈은 슬라이스 테스트를 할 수 있도록 다양한 테스트 어노테이션을 제공한다.
<br></p>
<p><strong>슬라이스 테스트란?</strong></p>
<ul>
<li>각 계층을 독립적으로 테스트하기 위해 사용하는 테스트</li>
<li>각 계층을 하나의 단위로 보고 하는 단위 테스트<br>

</li>
</ul>
<p><strong>슬라이스 테스트를 하는 이유?</strong></p>
<ul>
<li>스프링 부트의 auto-configuration 시스템은 테스트를 위해서는 너무 과할 수 있다.<ul>
<li>@SpringBootTest 어노테이션을 사용해서 통합 테스트를 진행한다면 모든 빈을 로드하는데 드는 시간과 비용이 높다.</li>
</ul>
</li>
<li>통합 테스트의 테스트 단위가 너무 커서 각 레이어 단위 별 테스트가 필요하다.<br>

</li>
</ul>
<p><strong>슬라이스 테스트를 지원하는 어노테이션들</strong></p>
<ul>
<li><code>@JsonTest</code> : JSON 직렬화와 역직렬화가 예상대로 동작하고 있는지를 테스트하기 위해 사용되는 어노테이션</li>
<li><code>@WebMvcTest</code> : 스프링 MVC 컨트롤러가 예상대로 동작하고 있는지를 테스트하기 위해 사용되는 어노테이션</li>
<li><code>@DataJpaTest</code> : JPA 애플리케이션을 테스트하기 위해 사용되는 어노테이션</li>
</ul>
<p>이외에도 다양한 어노테이션들이 존재한다.
<br></p>
<p>현재는 컨트롤러에 대한 슬라이스 테스트를 진행할 것이기 때문에 @WebMvcTest 에 대해서 더 알아보고자 한다.
<br></p>
<h3 id="webmvctest">@WebMvcTest</h3>
<p>테스트를 위한 인프라 구축을 위해 <code>@Controller</code>, <code>@ControllerAdvice</code>, <code>@JsonComponent</code>, <code>Converter</code>, <code>GenericConverter</code>, <code>Filter</code>, <code>HandlerInterceptor</code>, <code>WebMvcConfigurer</code>, <code>WebMvcRegistrations</code>, <code>HandlerMethodArgumentResolver</code> 등을 스캔하고 이 밖에 테스트를 하는 데 필요하지 않은 컴포넌트들은 빈으로 등록하지 않는다.
<br></p>
<ul>
<li>대게 @MockBean과 MockMvc를 사용해서 테스트를 시작한다. MockMvc는 전체 HTTP 서버를 시작할 필요 없이 빠르게 테스트할 수 있는 강력한 방법을 제공한다.<br>

</li>
</ul>
<p><strong>MockMvc</strong></p>
<ul>
<li>자바독에 의하면 MockMvc는 서버 측 Spring MVC 테스트를 지원하는 주요 진입점이라고 설명되어있다.</li>
</ul>
<pre><code>```java
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

// ...

WebApplicationContext wac = ...;

MockMvc mockMvc = webAppContextSetup(wac).build();

mockMvc.perform(get(&quot;/form&quot;))
     .andExpect(status().isOk())
     .andExpect(content().mimeType(&quot;text/html&quot;))
     .andExpect(forwardedUrl(&quot;/WEB-INF/layouts/main.jsp&quot;));
```</code></pre><ul>
<li>서블릿 컨테이너의 구동 없이, 시뮬레이션된 MVC 환경에 모의 HTTP 서블릿 요청을 전송하는 기능을 제공하는 유틸리티</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[미션 중 발견한 커스텀 Exception 클래스에 존재하는 serialVersionUID는 무엇일까?]]></title>
            <link>https://velog.io/@sung_hyuki/%EB%AF%B8%EC%85%98-%EC%A4%91-%EB%B0%9C%EA%B2%AC%ED%95%9C-%EC%BB%A4%EC%8A%A4%ED%85%80-Exception-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%97%90-%EC%A1%B4%EC%9E%AC%ED%95%98%EB%8A%94-serialVersionUID%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@sung_hyuki/%EB%AF%B8%EC%85%98-%EC%A4%91-%EB%B0%9C%EA%B2%AC%ED%95%9C-%EC%BB%A4%EC%8A%A4%ED%85%80-Exception-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%97%90-%EC%A1%B4%EC%9E%AC%ED%95%98%EB%8A%94-serialVersionUID%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Mon, 16 May 2022 10:50:57 GMT</pubDate>
            <description><![CDATA[<p>ATDD 미션을 수행하는 중 커스텀 Exception을 만들 일이 필요했고, 기존에 구현된 UnAuthenticationException 그리고 UnAuthorizedException과 같은 커스텀 Exception에 <code>serialVersionUID</code>가 있는 것을 발견했다. 그것도 같은 long 타입의 값을 가지고 있었다.
<br></p>
<pre><code class="language-java">private static final long serialVersionUID = 1L;</code></pre>
<br>

<p>그래서 내가 구현하고자 하는 커스텀 Exception 역시 serialVersionUID를 적어줘야하나 그리고 Serializable을 구현하는 다른 클래스의 serialVersionUID 값이 서로 같아도 되는가에 대한 궁금증이 생겨 serialVersionUID가 무엇을 의미하는지 알아보고자 한다.
<br></p>
<p>serialVersionUID에 대해 이해하기 전 직렬화와 역직렬화에 대한 개념을 우선 이해하고 넘어가자.
<br></p>
<p><strong>직렬화란?</strong></p>
<p>데이터 객체를 쉽게 전송할 수 있는 형식인 일련의 바이트로 변환하는 프로세스를 말한다. 이렇게 직렬화된 데이터는 다른 데이터 저장소나 응용 프로그램 또는 다른 대상으로 전달될 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/1cb9924d-fd4c-4788-bd17-1620c4f16931/image.png" alt=""></p>
<p>자바 시스템 내부에서 사용되는 Java Obejct 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터를 변환하는 기술을 자바 직렬화라고 한다. 시스템적으로 이야기하자면 JVM의 메모리에 상주되어 있는 Java Object를 바이트 형태로 변환하는 기술
<br></p>
<p><strong>역직렬화란?</strong></p>
<p>직렬화된 일련의 바이트 형식의 데이터를 데이터 객체로 변환하는 프로세스를 말한다. 직렬화의 역 과정</p>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/2177bc84-4e28-4b6c-b7c8-86ed950904e6/image.png" alt=""></p>
<p>자바 시스템적으로 이야기하면 직렬화된 바이트 형태의 데이터를 Java Object로 변환해서 JVM에 상주시키는 형태를 이야기한다.
<br></p>
<p><strong>자바 기본 타입과 java.io.Serializable 인터페이스를 상속하도록 만든 객체를 직렬화 할 수 있는 기본 조건으로 가진다.</strong>
<br></p>
<h3 id="serialversionuid란-무엇인가">SerialVersionUID란 무엇인가?</h3>
<p>SerialVersionUID는 역직렬화 시 해당하는 클래스의 버전이 맞는지를 확인하는 중요한 장치이다.
<br></p>
<p>예시를 하나 들어보자.</p>
<p>다음과 같은 Serializable 인터페이스를 구현하는 Memeber 클래스가 존재한다.
<br></p>
<pre><code class="language-java">public class Memeber implements Serializable {
    private String name;
    private int age;
    ...
}</code></pre>
<br>

<p>Member 클래스를 인스턴스화하여 해당 객체를 직렬화 시키고, Member 클래스의 구조를 다음과 같이 변경한다.</p>
<pre><code class="language-java">public class Member implements Serializable {
    private String name;
    private int age;
    private int height;
    ...
}
</code></pre>
<br>

<p>이전에 직렬화된 데이터를 역직렬화 시킨다면 java.io.InvalidClassException이 발생한다. 이유가 무엇일까?
<br></p>
<p>직렬화 시키기 전 Member 클래스의 serialVersionUID와 Member 클래스의 구조를 변경시킨 후 serialVersionUID가 달라졌기 때문이다.
<br></p>
<p>serialVersionUID는 직접 기술하지 않아도 내부적으로 serialVersionUID 정보가 추가된다. 구조가 달라졌으니 serialVersionUID가 달라진 것이다. <strong>직접 serialVersionUID 값을 관리해주어야 클래스 변경 시 혼란을 줄일 수 있다.</strong>
<br></p>
<p>Serializable을 구현하는 클래스들의 serialVersionUID 값이 서로 같아도 되냐에 대한 답은 같아도 된다. serialVersionUID 사용의 이유는 위에서 찾을 수 있다.
<br></p>
<p>하지만 이외에도 serialVersionUID 값을 신경써야 할 부분이 있다.
<br></p>
<p>serialVersionUID 값이 동일할 때에도 문제가 발생하는 경우를 알아보자.</p>
<ol>
<li><p>멤버 변수명은 같은데 멤버 변수 타입이 변경 되었을 때 (자바 직렬화는 타입에 상당히 엄격하다.)</p>
<p> 에러 발생</p>
</li>
<li><p>직렬화 자바 데이터에 존재하는 멤버 변수가 삭제 되었을 때</p>
<p> 에러가 발생하지는 않지만 값 자체가 사라진다.</p>
</li>
<li><p>멤버 변수가 추가 되었을 때</p>
<p> 에러를 발생하지 않고, 추가된 데이터는 기본값으로 초기화된다.</p>
</li>
<li><p>멤버 변수의 이름이 바뀔 때</p>
<p> 오류는 발생하지 않지만 값이 할당되지 않는다.</p>
</li>
<li><p>접근 지정자의 변경</p>
<p> 접근 지정자의 변경은 직렬화에 영향을 주지 않는다.</p>
</li>
<li><p>static과 transient</p>
<p> static 멤버를 직렬화 후 non-static 멤버로 변경하게 되는 경우 직렬화된 값은 무시된다.</p>
<p> transient 키워드는 직렬화 대상에서 제외하는 선언이므로 역직렬화 시에 transient 선언을 제외하더라도 값은 채워지지 않는다.</p>
</li>
</ol>
<br>

<p><strong>사실 이러한 관점에서 직렬화를 사용할 때는 자주 변경될 소지가 있는 클래스의 객체에는 사용하지 않는 것이 좋다. 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체도 버전업을 통해 <code>SerialVersionUID</code>
가 변경될 경우가 있으므로 예상하지 못한 오류가 발생할 수 있다.</strong>
<br></p>
<p>&lt;원문 참조&gt;</p>
<p><a href="https://hazelcast.com/glossary/serialization/">https://hazelcast.com/glossary/serialization/</a></p>
<p><a href="https://hazelcast.com/glossary/deserialization/">https://hazelcast.com/glossary/deserialization/</a></p>
<p><a href="https://techblog.woowahan.com/2550/">https://techblog.woowahan.com/2550/</a></p>
<p><a href="https://techblog.woowahan.com/2551/">https://techblog.woowahan.com/2551/</a></p>
<p><a href="https://docs.oracle.com/javase/10/docs/specs/serialization/class.html">https://docs.oracle.com/javase/10/docs/specs/serialization/class.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 자바 인 액션] - 병렬 데이터 처리와 성능]]></title>
            <link>https://velog.io/@sung_hyuki/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-%EB%B3%91%EB%A0%AC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%EC%99%80-%EC%84%B1%EB%8A%A5-80fz9pnk</link>
            <guid>https://velog.io/@sung_hyuki/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-%EB%B3%91%EB%A0%AC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%EC%99%80-%EC%84%B1%EB%8A%A5-80fz9pnk</guid>
            <pubDate>Mon, 09 May 2022 08:45:37 GMT</pubDate>
            <description><![CDATA[<p>자바 7이 등장하기 전에는 데이터 컬렉션을 병렬로 처리하기가 어려웠다.</p>
<p>데이터를 서브파트로 분할하고, 분할된 서브파트를 각각의 스레드로 할당하고, 레이스 컨디션이 발생하지 않도록 동기화 문제에도 신경써야 했고, 마지막으로 부분 결과를 합치는 일련의 과정들을 거쳐야 했다.</p>
<p>자바 7은 <strong>더 쉽게 병렬화를 수행하면서 에러를 최소화할 수 있도록 포크/조인 프레임워크 기능을 제공</strong></p>
<aside>
📝 스트림으로 데이터 컬렉션 관련 동작을 얼마나 쉽게 병렬로 실행할 수 있는지

<p>포크/조인 프레임워크와 내부적인 병렬 스트림 처리는 어떤 관계가 있는지</p>
<p>병렬 스트림이 요소를 여러 청크로 분할하는 방법                                             </p>
</aside>

<br>

<h2 id="👨🏻💻-병렬-스트림">👨🏻‍💻 병렬 스트림</h2>
<p>각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림
<br></p>
<h3 id="숫자-n을-인수로-받아서-1부터-n까지의-모든-숫자의-합계를-반환">숫자 n을 인수로 받아서 1부터 n까지의 모든 숫자의 합계를 반환</h3>
<pre><code class="language-java">// 반복형
public static long iterativeSum(long n) {
    long result = 0;
    for(long i = 1L; i &lt;= n; i++) {
        result += i;
    }
    return result;
}

// 순차 스트림
public static long sequentialSum(long n) {
    return Stream.iterate(1L, i -&gt; i + 1) // 무한 자연수 스트림 생성
               .limit(n) // n 개 이하로 제한
               .reduce(0L, Long::sum); // 모든 숫자를 더하는 스트림 리듀싱 연산
}

// 병렬 스트림
public static long parallelSum(long n) {
    return Stream.iterate(1L, i -&gt; i + 1)
               .limit(n)
               .parallel()
               .reduce(0L, Long::sum);
}</code></pre>
<p>리듀싱 연산을 여러 청크에 병렬로 수행</p>
<p>리듀싱 연산으로 생성된 부분 결과를 다시 리듀싱 연산으로 합쳐서 전체 스트림의 리듀싱 결과를 도출</p>
<ul>
<li>내부적으로 parallel을 호출하면 연산이 병렬로 수행해야 함을 의미하는 불리언 플래그가 설정</li>
<li>parallel, sequential 이 두 메서드를 이용해 어떤 연산을 병렬로 실행하고 어떤 연산을 순차로 실행할지 제어 가능</li>
<li>두 메서드 중 최종적으로 호출된 메서드가 전체 파이프라인에 영향을 미친다.<br>

</li>
</ul>
<p><strong>병렬 스트림에서 사용하는 스레드 풀 설정</strong></p>
<ul>
<li>병렬 스트림은 내부적으로 ForkJoinPool을 사용<ul>
<li>기본적으로 ForkJoinPool은 프로세서 수, 즉 Runtime.getRunTime().availableProcessors()가 반환하는 값에 상응하는 스레드를 갖는다.<br>

</li>
</ul>
</li>
</ul>
<h3 id="스트림-성능-측정">스트림 성능 측정</h3>
<ul>
<li>자바 마이크로벤치마크 하니스(JMH)</li>
</ul>
<pre><code class="language-java">@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime) // 벤치마크 대상 메서드를 실행하는 데 걸린 평균 시간 측정
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 벤치마크 결과를 밀리초 단위로 출력
@Fork(value = 2, jvmArgs = {&quot;-Xms4G&quot;, &quot;-Xmx4G&quot;})
public class Sample {
    private static final long N = 10_000_000L;

    @Benchmark
    public long iterativeSum() {
        long result = 0;
        for (long i = 1L; i &lt;= N; i++) {
            result += i;
        }
        return result;
    }

    @Benchmark
    public long sequentialSum() {
        return Stream.iterate(1L, i -&gt; i + 1) // 무한 자연수 스트림 생성
                     .limit(N) // n 개 이하로 제한
                     .reduce(0L, Long::sum); // 모든 숫자를 더하는 스트림 리듀싱 연산
    }

    @Benchmark
    public long parallelSum() {
        return Stream.iterate(1L, i -&gt; i + 1)
                     .limit(N)
                     .parallel()
                     .reduce(0L, Long::sum);
    }

    @TearDown(Level.Invocation) // 매 번 벤치마크를 실행한 다음에도 가비지 컬렉터 동작 시도
    public void tearDown() {
        System.gc();
    }
}</code></pre>
<pre><code class="language-java">Benchmark             Mode  Cnt    Score   Error  Units
Sample.iterativeSum   avgt         3.182          ms/op
Sample.parallelSum    avgt       101.162          ms/op
Sample.sequentialSum  avgt        74.647          ms/op</code></pre>
<ul>
<li>iterate 연산은 이전 연산의 결과에 따라 다음 함수의 입력이 달라지기 때문에 청크로 분할하기가 어렵다. (순차적)<ul>
<li>더 본질적으로 리듀싱 과정을 시작하는 시점에 전체 숫자 리스트가 준비되지 않았기 때문에</li>
<li>병렬을 처리하도록 지시했지만 결국 순차처리 방식과 크게 다른 점이 없으므로 스레드를 할당하는 오버헤드만 증가<br>

</li>
</ul>
</li>
</ul>
<h3 id="더-특화된-메서드-사용">더 특화된 메서드 사용</h3>
<ul>
<li>LongStream.rangeClose<ul>
<li>기본형 long 사용 → 박싱과 언박싱 오버헤드 제거</li>
<li>숫자 범위를 생산</li>
</ul>
</li>
</ul>
<pre><code class="language-java">    @Benchmark
    public long rangedSum() {
        return LongStream.rangeClosed(1, N)
                         .reduce(0L, Long::sum);
    }

    @Benchmark
    public long parallelRangedSum() {
        return LongStream.rangeClosed(1, N)
                         .parallel()
                         .reduce(0L, Long::sum);
    }</code></pre>
<pre><code class="language-java">Benchmark                 Mode  Cnt    Score   Error  Units
Sample.iterativeSum       avgt         3.578          ms/op
Sample.parallelRangedSum  avgt         0.474          ms/op
Sample.parallelSum        avgt       110.169          ms/op
Sample.rangedSum          avgt         3.972          ms/op
Sample.sequentialSum      avgt        87.143          ms/op</code></pre>
<p>올바른 자료구조를 선택해야 최적의 성능을 발휘할 수 있다. 그리고 멀티코어 간의 데이터 이동의 비용은 비싸기 때문에 코어 간에 데이터 전송 시간보다 훨씬 오래 걸리는 작업만 병렬로 다른 코어에서 수행하는 것이 바람직
<br></p>
<h3 id="병렬-스트림의-올바른-사용법">병렬 스트림의 올바른 사용법</h3>
<ul>
<li>병렬 스트림의 올바른 동작을 위해서는 공유된 가변 상태를 피해야 한다.<br>

</li>
</ul>
<h3 id="병렬-스트림-효과적으로-사용하기">병렬 스트림 효과적으로 사용하기</h3>
<ul>
<li>적절한 벤치마크로 직접 성능을 측정하라</li>
<li>자동 박싱과 언박싱은 성능을 크게 저하시키는 요소이기 때문에 박싱을 주의하라</li>
<li>순차 스트림보다 병렬 스트림에서 성능이 떨어지는 연산이 존재<ul>
<li>요소에 순서에 의존하는 findFirst 보다는 요소의 순서와 상관없이 연산하는 findAny를</li>
<li>스트림 N개 요소가 있을 때 요소의 순서가 상관없다면 비정렬된 스트림에 limit를 호출하는 것이 더 효율적</li>
</ul>
</li>
<li>스트림에서 수행하는  전체 파이프라인 연산 비용을 고려하라. 하나의 요소를 처리하는데 드는 비용이 높아진다는 것은 병렬 스트림으로 성능을 개선할 수 있는 가능성이 있음</li>
<li>소량의 데이터에서는 병렬 스트림이 도움 되지 않는다.</li>
<li>스트림을 구성하는 자료구조가 적절한지를 확인하라</li>
<li>스트림의 특성과 파이프라인의 중간 연산이 스트림의 특성을 어떻게 바꾸는지에 따라 분해 과정의 성능이 달라질 수 있다.</li>
<li>최종 연산의 병합 과정 비용을 살펴봐라</li>
<li>마지막으로 병렬 스트림이 수행되는 내부 인프라구조도 확인하라.<br>

</li>
</ul>
<h2 id="👨🏻💻-포크조인-프레임워크">👨🏻‍💻 포크/조인 프레임워크</h2>
<p><strong>포크/조인 프레임워크란?</strong></p>
<ul>
<li>병렬화할 수 있는 작업을 재귀적으로 작은 작업으로 분할한 다음에 서브태스크 각각의 결과를 합쳐서 전체 결과를 만듬</li>
<li>서브태스크를 스레드 풀(ForkJoinPool)의 작업자 스레드에 분산 할당하는 ExecutorService 인터페이스를 구현<br>

</li>
</ul>
<h3 id="recursivetask-활용">RecursiveTask 활용</h3>
<ul>
<li><p>스레드 풀을 이용하려면 RecursiveTask<R>의 서브클래스를 만들어야 한다. R은 병렬화된 태스크가 생성하는 결과 형식을 의미하며, 결과가 없는 Void 형태일 경우 RecursiveAction을 이용</p>
</li>
<li><p>RecurisiveTask를 정의하려면 추상 메서드 compute를 구현해야 함</p>
<pre><code class="language-java">  // 주요 산술 연산이 해당 태스크에 의해 수행됨.
  protected abstract void compute();</code></pre>
<ul>
<li><p>compute 메서드는 태스크를 서브태스크로 분할하는 로직과 더 이상 분할할 수 없을 때 개별 서브태스크의 결과를 생성할 알고리즘을 정의</p>
<pre><code class="language-java">  if (태스크가 충분히 작거나 더 이상 분할할 수 없으면) {
      순차적으로 태스크 계산
  } else {
      태스크를 두 서브태스크로 분할
      태스크가 다시 서브태스크로 분할되도록 이 메서드를 재귀적으로 호출함
      모든 서브태스크의 연산이 완료될 때까지 기다림
      각 서브태스크의 결과를 합침
  }</code></pre>
</li>
</ul>
</li>
<li><p>포크 조인 과정</p>
<ul>
<li>각각의 서브태스크의 크기가 충분히 작아질 때까지 태스크를 재귀적으로 포크함</li>
<li>모든 서브태스크를 병렬로 수행</li>
<li>부분 결과를 조합</li>
</ul>
</li>
<li><p>범위의 숫자를 더하는 문제</p>
<pre><code class="language-java">  public class ForkJoinSumCalculator extends RecursiveTask&lt;Long&gt; {
      private final long[] numbers;
      private final int start;
      private final int end;
      private static final long THRESHOLD = 10_000; 

      public ForkJoinSumCalculator(long[] numbers) {
          this(numbers, 0, numbers.length);
      }

      private ForkJoinSumCalculator(long[] numbers, int start, int end) {
          this.numbers = numbers;
          this.start = start;
          this.end = end;
      }

      @Override
      protected Long compute() {
          int length = end - start;
          if(length &lt;= THRESHOLD) {
              return computeSequentially();
          }
          ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length/2);
          leftTask.fork();
          ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length/2, end);
          Long rightResult = rightTask.compute();
          Long leftResult = leftTask.join();
          return leftResult + rightResult;
      }

      private long computeSequentially() {
          long sum = 0;
          for(int i = start; i &lt; end; i++) {
              sum += numbers[i];
          }
          return sum;
      }
  }</code></pre>
<pre><code class="language-java">  public static long forkJoinSum(long n) {
      long[] numbers = LongStream.rangeClosed(1, n).toArray();
      ForkJoinTask&lt;Long&gt; task = new ForkJoinSumCalculator(numbers);
      return new ForkJoinPool().invoke(task);
  }</code></pre>
</li>
</ul>
<br>

<h3 id="forkjoinsumcalculator-실행">ForkJoinSumCalculator 실행</h3>
<ul>
<li>ForkJoinSumCalculator를 ForkJoinPool로 전달하면 풀의 스레드가 ForkJoinSumCalculator의 compute 메서드를 실행하면서 작업을 수행<br>

</li>
</ul>
<h3 id="포크조인-프레임워크를-제대로-사용하는-방법">포크/조인 프레임워크를 제대로 사용하는 방법</h3>
<ul>
<li>join 메서드를 태스크에 호출하면 태스크가 생산하는 결과가 준비될 때까지 호출자를 블록시킨다. 따라서 두 서브태스크가 모두 시작된 다음에 join을 호출해야 한다.</li>
<li>RecursiveTask 내에서는 ForkJoinPool의 invoke 메서드를 사용하지 말아야 한다. 대신 compute나 fork 메서드를 직접 호출할 수 있다. 순차 코드에서 병렬 계산을 시작할 때만 invoke를 사용</li>
<li>서브태스크에 fork 메서드를 호출해서 ForkJoinPool의 일정을 조절할 수 있다. 왼쪽 작업과 오른쪽 작업 모두에 fork 메서드를 호출하는 것이 자연스러울 것 같지만 한쪽 작업에는 fork를 호출하는 것보다 compute를 호출하는 것이 효율적이다. 그러면 두 서브태스크의 한 태스크에는 같은 스레드를 재사용할 수 있으므로 풀에서 불필요한 태스크를 할당하는 오버헤드를 피할 수 있다.</li>
<li>포크/조인 프레임워크를 이용하는 병렬 계산은 디버깅하기 어렵다. 보통 IDE로 디버깅할 때 스택 트레이스로 문제가 일어난 과정을 쉽게 확인할 수 있는데, 포크/조인 프레임워크에서는 fork라 불리는 다른 스레드에서 compute를 호출하므로 스택 트레이스가 도움이 되지 않는다.</li>
<li>병렬 스트림에서 살펴본 것처럼 멀티코어에 포크/조인 프레임워크를 사용하는 것이 순차 처리보다 무조건 빠를 거라는 생각은 버려야 한다. 병렬 처리로 성능을 개선하려면 테스크를 여러 독립적인 서브태스크로 분할할 수 있어야 한다. 각 서브태스크의 실행시간은 새로운 태스크를 포킹하는 데 드는 시간보다 길어야 한다. 예를 들어 I/O를 한 서비태스크에 할당하고 다른 서브태스크에서는 계산을 실행, 즉 I/O와 계산을 병렬로 실행할 수 있다. 또한 순차 버전과 병렬 버전의 성능을 비교할 때는 다른 요소도 고려해야 한다. 다른 자바 코드와 마찬가지로 JIT 컴파일러에 의해 최적화되려면 몇 차례의 ‘준비 과정&#39; 또는 실행 과정을 거쳐야 한다. 따라서 성능을 측정할 때는 지금까지 살펴본 하니스에서 그랬던 것처럼 여러 번 프로그램을 실행한 결과를 측정해야 한다. 또한 컴파일러 최적화는 병렬 버전보다는 순차 버전에 집중될 수 있다는 사실도 기억하자<br>

</li>
</ul>
<h3 id="작업-훔치기작업을-효율적으로-분할-하는-방법">작업 훔치기(작업을 효율적으로 분할 하는 방법)</h3>
<ul>
<li>할일이 없어진 스레드는 유휴 상태로 바뀌는 것이 아니라 다른 스레드 큐의 꼬리에서 작업을 훔쳐온다. 모든 태스크가 작업을 끝낼 때까지, 즉 모든 큐가 빌 때까지 이 과정을 반복<br>

</li>
</ul>
<h2 id="👨🏻💻-spliterator-인터페이스-분할할-수-있는-반복자">👨🏻‍💻 Spliterator 인터페이스 (분할할 수 있는 반복자)</h2>
<ul>
<li>자동으로 스트림을 분할하는 기법</li>
<li>자바 8은 컬렉션 프레임워크에 포함된 모든 자료구조에 사용할 수 있는 디폴트 Spliterator 구현을 제공(spliterator() 메서드)</li>
</ul>
<pre><code class="language-java">public interface Spliterator&lt;T&gt; {
    boolean tryAdvance(Consumer&lt;? super T&gt; action);
    Spliterator&lt;T&gt; trySplit();
    long estimateSize();
    int characteristics();</code></pre>
<ul>
<li>T는 Spliterator에서 탐색하는 요소의 형식</li>
<li>tryAdvance : Spliterator의 요소를 하나씩 순차적으로 소비하면서 탐색해야 할 요소가 남아있으면 참을 반환</li>
<li>trySplit : Spliterator의 일부 요소(자신이 반환한 요소)를 분할해서 두 번째 Spliterator를 생성하는 메서드</li>
<li>estimateSize : 탐색해야 할 요소 수 정보를 제공</li>
<li>characteristics : Spliterator와 그 요소의 특성을 반환<br>

</li>
</ul>
<h3 id="분할-과정">분할 과정</h3>
<ul>
<li>trySplit을 호출하면 두 번째 Spliterator 생성</li>
<li>두 개의 Spliterator에 trySplit을 호출하면 네 개의 Spliterator 생성</li>
<li>trySplit의 결과가 null이 될 때까지 이 과정을 반복(더 이상 자료구조를 분할할 수 없음을 의미)<br>

</li>
</ul>
<p><strong>Spliterator의 특성</strong></p>
<table>
<thead>
<tr>
<th>ORDERED</th>
<th>리스트처럼 요소에 정해진 순서가 있으므로 Spliterator는 요소를 탐색하고 분할할 때 이 순서에 유의해야 한다.</th>
</tr>
</thead>
<tbody><tr>
<td>DISTINCT</td>
<td>x, y 두 요소를 방문했을 때 x.equals(y)는 항상 false를 반환</td>
</tr>
<tr>
<td>SORTED</td>
<td>탐색된 요소는 미리 정의된 정렬 순서를 따른다.</td>
</tr>
<tr>
<td>SIZED</td>
<td>크기가 알려진 소스(예를 들면 Set)로 Spliterator를 생성했으므로 estimatedSize()는 정확한 값을 반환</td>
</tr>
<tr>
<td>NON-NULL</td>
<td>탐색하는 모든 요소는 null이 아니다</td>
</tr>
<tr>
<td>IMMUTABLE</td>
<td>이 Spliterator의 소스는 불변이다. 즉, 요소를 탐색하는 동안 요소를 추가하거나, 삭제하거나, 고칠 수 없다.</td>
</tr>
<tr>
<td>CONCURRENT</td>
<td>동기화 없이 Spliterator의 소스를 여러 스레드에서 동시에 고칠 수 있다.</td>
</tr>
<tr>
<td>SUBSIZED</td>
<td>이 Spliterator 그리고 분할되는 모든 Spliterator는 SIZED 특성을 갖는다.</td>
</tr>
<tr>
<td><br></td>
<td></td>
</tr>
</tbody></table>
<h3 id="커스텀-spliterator-구현하기">커스텀 Spliterator 구현하기</h3>
<p>문자열의 단어 수를 계산하는 단순한 메서드 구현</p>
<ul>
<li><p>반복형으로 단어 수를 세는 메서드</p>
<pre><code class="language-java">  public int countWordsIteratively(String s) {
      int counter = 0;
      boolean lastSpace = true;
      for (char c : s.toCharArray()) {
          if (Character.isWhitespace(c)) {
              lastSpace = true;
          } else {
              if (lastSpace) counter++;
              lastSpace = false;
          }
      }
      return counter;
  }</code></pre>
</li>
<li><p>함수형으로 직접 스레드를 동기화하지 않고 병렬 스트림으로 작업을 병렬화</p>
<ul>
<li><p>스트림에 리듀싱 연산을 통해 단어의 수를 계산할건데, 지금까지 발견한 단어의 수를 계산한느 int 변수와 마지막 문자가 공백이었는지 여부를 기억하는 Boolean 변수가 필요</p>
<pre><code class="language-java">public class WordCounter {
  private final int counter;
  private final boolean lastSpace;

  public WordCounter(int counter, boolean lastSpace) {
      this.counter = counter;
      this.lastSpace = lastSpace;
  }

  public WordCounter accumulate(Character c) {
      if (Character.isWhitespace(c)) {
          return lastSpace ? this : new WordCounter(counter, true);
      } else {
          return lastSpace ? new WordCounter(counter + 1, false) : this;
      }
  }

  public WordCounter combine(WordCounter wordCounter) {
      return new WordCounter(counter + wordCounter.counter, wordCounter.lastSpace));
  }

  public int getCounter() {
      return counter;
  }
}</code></pre>
</li>
<li><p>accumulate : Word의 상태를 어떻게 바꿀 것인지를 정의, 스트림을 탐색하면서 새로운 문자를 찾을 때마다 호출</p>
</li>
<li><p>새로운 비공백 문자를 탐색한 다음에 마지막 문자가 공백이면 counter 증가</p>
</li>
<li><p>combine : 문자열 서브 스트림을 처리한 WordCounter의 결과를 합친다.</p>
<pre><code class="language-java">private int countWords(Stream&lt;Character&gt; stream) {
  WordCounter wordCounter = stream.reduce(new WordCounter(0, true),
                                                                                          WordCounter::accumulate,
                                                                                          WordCounter::combine);
  return wordCounter.getCounter();
}</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>WordCounter 병렬로 수행하기(Spliterator 이용)</p>
<pre><code class="language-java">  public class WordCounterSpliterator implements Spliterator&lt;Character&gt; {
      private final String string;
      private int currentChar = 0;

      public WordCounterSpliterator(String string) {
          this.string = string;
      }

      @Override
      public boolean tryAdvance(Consumer&lt;? super Character&gt; action) {
          action.accept(string.charAt(currentChar++));
          return currentChar &lt; string.length();
      }

      @Override
      public Spliterator&lt;Character&gt; trySplit() {
          int currentSize = string.length() - currentChar;
          if(currentSize &lt; 10) {
              return null;
          }
          for(int splitPos = currentSize / 2 + currentChar; splitPos &lt; string.length(); splitPos++) {
              if(Character.isWhitespace(string.charAt(splitPos))) {
                  Spliterator&lt;Character&gt; spliterator = new WordCounterSpliterator(string.substring(currentChar, splitPos));
                  currentChar = splitPos;
                  return spliterator;
              }
          }
          return null;
      }

      @Override
      public long estimateSize() {
          return string.length() - currentChar;
      }

      @Override
      public int characteristics() {
          return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;
      }
  }</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[웹 개발자를 위한 웹을 지탱하는 기술] - HTML]]></title>
            <link>https://velog.io/@sung_hyuki/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%B9%EC%9D%84-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-HTML</link>
            <guid>https://velog.io/@sung_hyuki/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%B9%EC%9D%84-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-HTML</guid>
            <pubDate>Tue, 03 May 2022 01:56:24 GMT</pubDate>
            <description><![CDATA[<h2 id="👨🏻💻-htmlhypertext-markup-language이란-무엇인가">👨🏻‍💻 HTML(Hypertext Markup Language)이란 무엇인가</h2>
<ul>
<li>마크업 언어란 태그로 문서의 구조를 표현하는 컴퓨터 언어<ul>
<li>마크업 언어를 이용한 마크업 구조를 가진 문서를 ‘구조화 문서(Structured Document)’</li>
</ul>
</li>
<li>HTML은 4.01 버전은 SGML 기반으로 개발되었지만 SGML 문법이 복잡하여 처리 프로그램을 생성하기 힘들었기 때문에 스펙을 심플하게 만든 XML이 개발되었다.<ul>
<li>HTML 4.01을 XML 베이스로 바꾼 스펙이 XHTML 1.0</li>
<li>XHTML 1.0을 모듈화하고 확장 가능하게 한 스펙이 XHTML 1.1<br>

</li>
</ul>
</li>
</ul>
<h2 id="👨🏻💻-미디어-타입">👨🏻‍💻 미디어 타입</h2>
<ul>
<li><code>text/html</code>과 <code>application/xhtml+xml</code>이 존재<ul>
<li><code>text/html</code> : SGML 베이스의 HTML</li>
<li><code>application/xhtml+xml</code> : XML 베이스의 XHTML</li>
</ul>
</li>
<li>charset 파라미터를 통해 문자 인코딩 지정<br>

</li>
</ul>
<h2 id="👨🏻💻-확장자">👨🏻‍💻 확장자</h2>
<ul>
<li><code>.html</code> 또는 <code>.htm</code>  확장자를 이용<ul>
<li><code>.htm</code> 은 OS의 제약에 의한 것</li>
<li>명시적으로 HTML 표현을 가지게 하고 싶은 경우 URI에 <code>.html</code> 을 붙이도록 리소스를 설계<br>

</li>
</ul>
</li>
</ul>
<h2 id="👨🏻💻-xml의-기초지식">👨🏻‍💻 XML의 기초지식</h2>
<pre><code class="language-html">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
    &lt;head&gt;&lt;title&gt;첫 번째 HTML&lt;/title&gt;&lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;첫 번째 HTML&lt;/h1&gt;
        &lt;p&gt;HTML의 사양서는&lt;a href=&quot;http://www.w3.org&quot;&gt;W3C&lt;/a&gt;에 있습니다.&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><strong>XML의 트리구조</strong></p>
<ul>
<li>요소를 중첩하여 표현</li>
<li>부모 요소, 자식 요소 등</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/3331f8b0-8a96-4a8b-8d52-16e3048b31bb/image.png" alt=""></p>
<p><strong>요소</strong></p>
<ul>
<li><p>HTML은 요소로 문서의 구조를 나타냄</p>
</li>
<li><p>요소는 시작 태그, 내용, 종료 태그로 구성</p>
</li>
<li><p>빈 요소 : 내용을 가지지 않는 것</p>
<ul>
<li><p>빈 요소는 종료 태그를 생략할 수 있는데 다음과 같은 서식으로 나타냄</p>
<pre><code class="language-html">  &lt;br/&gt;

  // 2000년에 측정된 XHTML 1.0 스펙에서는 브라우저와의 호환성을 위해 &#39;/&gt;&#39; 앞에 공백을 넣는 기법을 권장
  &lt;br /&gt;</code></pre>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/6fedcb33-ea03-4d24-9d78-fa935e2b5ef8/image.png" alt=""></p>
<p><strong>속성</strong></p>
<ul>
<li>속성은 속성명과 속성 값의 쌍으로 이루어짐</li>
<li>시작 태그 안에 속성명=”속성 값&quot;의 형식으로 기술</li>
<li>속성명, 속성 값은 문자열</li>
<li>시작 태그는 속성을 여러 개 가질 수 있지만, 같은 이름의 속성은 하나만 기술</li>
<li>속성 중첩 불가</li>
<li>속성 순서는 의미가 없음<br>

</li>
</ul>
<p><strong>실제 참조와 문자 참조</strong></p>
<ul>
<li>다음의 다섯 문자는 실체 참조(Entity Reference)라는 기구를 이용해 표현</li>
</ul>
<pre><code>| 문자 | 실체 참조 |
| --- | --- |
| &lt; | &amp;It; |
| &gt; | &amp;gt; |
| “ | &amp;quote; |
| ‘ | &amp;apos; |
| &amp; | &amp;amp; |</code></pre><ul>
<li><p>문자 참조(Character Reference)</p>
<ul>
<li><p>문자를 에스케이프하여 표현하는 방법</p>
</li>
<li><p>Unicode 번호로 문자를 지정</p>
</li>
<li><p>카피라이트 기호는 십진수로 169이므로, 다음과 같이 표현</p>
<pre><code class="language-html">  &amp;#169;</code></pre>
</li>
<li><p>‘x’를 붙여서 16진수로도 기술 가능</p>
</li>
<li><p>카피라이트 기호는 16진수로 A9이므로, 다음과 같이 표현</p>
<pre><code class="language-html">  &amp;#xA9;</code></pre>
</li>
</ul>
</li>
</ul>
<p><strong>코멘트</strong></p>
<ul>
<li>코멘트 서식의 시작은 ‘&lt;!—’로, 끝은 ‘—&gt;’로</li>
<li>코멘트 가운데 ‘—&gt;’라는 문자열은 삽입할 수 없다.<br>

</li>
</ul>
<p><strong>XML 선언</strong></p>
<ul>
<li><p>XML 문서의 첫 머리에는 XML 선언(XML Declaration)을 기술</p>
<pre><code class="language-html">  &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
  &lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
  ...
  &lt;/html&gt;</code></pre>
<ul>
<li>XML의 버전과 문자 인코딩 방식을 지정<br>

</li>
</ul>
</li>
</ul>
<p><strong>이름공간</strong></p>
<ul>
<li><p>복수의 XML 포맷을 조합할 때, 이름의 충돌을 방지할 목적으로 사용하는 것</p>
</li>
<li><p>요소의 이름공간</p>
<pre><code class="language-html">  &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
  &lt;html xmlns=&quot;http://www.w3.org/1999/Atom&quot;
              xmlns:atom=&quot;http://www.w3.org/2005/Atom&quot;&gt;
      &lt;head&gt;
          &lt;link rel=&quot;stylesheet&quot; href=&quot;base.css&quot;/&gt;
          &lt;atom:link rel=&quot;enclosure&quot; href=&quot;attachment.mp3&quot;/&gt;
      &lt;/head&gt;
  ...
  &lt;/html&gt;</code></pre>
<ul>
<li><html> 요소에 xmlns로 시작되는 이름 공간을 선언</li>
<li>이름 공간의 선언은 xmlns:접두어=”이름공간명&quot;이라는 서식으로 기술</li>
<li>접두어에는 임의의 문자열이 들어감, ‘:접두어&#39;를 생략한 경우, 접두어가 없는 디폴트 이름공간을 의미</li>
<li>이름공간명에는 URI가 들어감</li>
<li><strong>이름공간 선언은 이름공간명과 접두어를 연결하는 역할을 하며 접두어를 사용해 이름의 충돌을 방지</strong></li>
</ul>
</li>
<li><p>속성의 이름공간</p>
<ul>
<li><p>속성의 이름공간 선언도 요소의 이름공간 선언과 동일</p>
<pre><code class="language-html">&lt;entry xmlns=&quot;http:/www.w3.org/2005/Atom&quot;
           xmlns:thr=&quot;http://purl.org/sydication/thread/1.0&quot;&gt;
  &lt;link href=&quot;blog.example.com/entries/1/commentsfeed&quot;
            thr:count=&quot;10&quot;&gt;
&lt;/entry&gt;</code></pre>
<ul>
<li>접두어가 붙지 않는 속성을 ‘로컬 속성’, 접두어가 붙은 속성을 가리켜 ‘글로벌 속성’</li>
<li>글로벌 속성은 속성을 확장하고자 할 때 이용<br>

</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="👨🏻💻-html의-구성요소">👨🏻‍💻 HTML의 구성요소</h2>
<pre><code class="language-html">&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
    &lt;head&gt;
        &lt;title&gt;첫 번째 HTML&lt;/title&gt;
        &lt;link rel=&quot;stylesheet&quot; href=&quot;http://example.com/main.css&quot;/&gt;
        &lt;script type=&quot;text/javascript&quot;
                    src=&quot;http://example.com/sample.js&quot;&gt;&lt;/script&gt;
        &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;application/xhtml+xml;charset=utf-8&quot;/&gt;
    &lt;/head&gt;
    &lt;body&gt; ... &lt;/body&gt;
&lt;/html&gt;</code></pre>
<br>

<h3 id="헤더">헤더</h3>
<table>
<thead>
<tr>
<th>요소</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>title</td>
<td>문서의 타이틀</td>
</tr>
<tr>
<td>link</td>
<td>다른 리소스로의 링크</td>
</tr>
<tr>
<td>script</td>
<td>JavaScript 등의 클라이언트 사이드 프로그램</td>
</tr>
<tr>
<td>meta</td>
<td>그 밖의 파라미터</td>
</tr>
<tr>
<td><br></td>
<td></td>
</tr>
</tbody></table>
<h3 id="바디">바디</h3>
<ul>
<li>바디에 들어갈 요소는 크게 <strong>블록 레벨 요소</strong>와 <strong>인라인 요소</strong>로 나눔<br>

</li>
</ul>
<h3 id="주요-블록-레벨-요소">주요 블록 레벨 요소</h3>
<ul>
<li>블록 레벨 요소는 문서의 단락과 표제 등 어느 정도 큰 덩어리를 표현</li>
</ul>
<table>
<thead>
<tr>
<th>요소</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>h1, h2, h3, h4, h5, h6</td>
<td>표제</td>
</tr>
<tr>
<td>dl, ul, ol</td>
<td>리스트</td>
</tr>
<tr>
<td>div</td>
<td>블록 레벨 요소의 그룹화</td>
</tr>
<tr>
<td>p</td>
<td>단락</td>
</tr>
<tr>
<td>address</td>
<td>어드레스 정보</td>
</tr>
<tr>
<td>pre</td>
<td>미리 포맷이 정해진 텍스트</td>
</tr>
<tr>
<td>table</td>
<td>표</td>
</tr>
<tr>
<td>form</td>
<td>폼</td>
</tr>
<tr>
<td>blockquote</td>
<td>인용</td>
</tr>
</tbody></table>
<pre><code class="language-html">&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
    &lt;head&gt;&lt;title&gt;블록 레벨 요소&lt;/title&gt;&lt;/head&gt;
    &lt;body&gt;
        &lt;div&gt;
            &lt;h1&gt;블록 레벨 요소&lt;/h1&gt;
            &lt;p&gt;HTML이란...&lt;/p&gt;
            &lt;ol&gt;
                &lt;li&gt;순서 있는 리스트1&lt;/li&gt;
                &lt;li&gt;순서 있는 리스트2&lt;/li&gt;
            &lt;/ol&gt;
            &lt;ul&gt;
                &lt;li&gt;순서 없는 리스트1&lt;/li&gt;
                &lt;li&gt;순서 없는 리스트2&lt;/li&gt;
            &lt;/ul&gt;
            &lt;dl&gt;
                &lt;dt&gt;정의어1&lt;/dt&gt;&lt;dd&gt;정의어1의 설명&lt;/dd&gt;
                &lt;dt&gt;정의어2&lt;/dt&gt;&lt;dd&gt;정의어2의 설명&lt;/dd&gt;
            &lt;/dl&gt;
            &lt;pre&gt;
function foo() {
    return true;
}
            &lt;/pre&gt;
            &lt;form action=&quot;http://example.com/search&quot;&gt;
                &lt;input type=&quot;text&quot; id=&quot;q&quot;/&gt;
                &lt;input tpye=&quot;submit&quot; value=&quot;검색&quot;/&gt;
            &lt;/form&gt;
            &lt;blockquote&gt;&lt;p&gt;인용문&lt;/p&gt;&lt;/blockquote&gt;
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/43277b11-fedc-4492-8078-2567b55ef038/image.png" alt=""></p>
<h3 id="인라인-요소">인라인 요소</h3>
<ul>
<li>인라인 요소는 블록 레벨 요소 안에 들어가는 요소로 강조나 줄 바꿈, 이미지 삽입 등을 표현</li>
</ul>
<table>
<thead>
<tr>
<th>요소</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>em</td>
<td>강조</td>
</tr>
<tr>
<td>strong</td>
<td>강한 강조</td>
</tr>
<tr>
<td>dfn</td>
<td>정의어</td>
</tr>
<tr>
<td>code</td>
<td>소스 코드</td>
</tr>
<tr>
<td>samp</td>
<td>예</td>
</tr>
<tr>
<td>kbd</td>
<td>키보드 입력문자</td>
</tr>
<tr>
<td>var</td>
<td>변수</td>
</tr>
<tr>
<td>cite</td>
<td>인용 또는 다른 리소스의 참조</td>
</tr>
<tr>
<td>abbr</td>
<td>WWW, HTTP 같은 약자, 생략형</td>
</tr>
<tr>
<td>a</td>
<td>앵커</td>
</tr>
<tr>
<td>q</td>
<td>인라인의 인용</td>
</tr>
<tr>
<td>sub</td>
<td>아래첨자</td>
</tr>
<tr>
<td>sup</td>
<td>위첨자</td>
</tr>
<tr>
<td>br</td>
<td>줄 바꿈</td>
</tr>
<tr>
<td>ins</td>
<td>삽입한 문자열</td>
</tr>
<tr>
<td>del</td>
<td>삭제한 문자열</td>
</tr>
<tr>
<td>img</td>
<td>이미지</td>
</tr>
<tr>
<td>object</td>
<td>오브젝트</td>
</tr>
</tbody></table>
<pre><code class="language-html">&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
    &lt;head&gt;
        &lt;title&gt;인라인 요소&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;인라인 요소&lt;/h1&gt;
        &lt;p&gt;&lt;**abbr**&gt;**HTML**&lt;/**abbr**&gt;에는 여러 가지 요소가 있습니다.&lt;/p&gt;
        &lt;p&gt;&lt;**em**&gt;**강조**&lt;/**em**&gt;,&lt;**strong**&gt;**강한 강조**&lt;/**strong**&gt;&lt;/p&gt;
        &lt;p&gt;&lt;**dfn**&gt;**스테이트리스성**&lt;/**dfn**&gt;이란...&lt;/p&gt;
        &lt;p&gt;&lt;**code**&gt;**p**&quot;**a**&quot; + &quot;**b**&quot;&lt;/**code**&gt;의 출력은&lt;**samp**&gt;&quot;**ab**&quot;&lt;/**samp**&gt;입니다.&lt;/p&gt;
        &lt;p&gt;자세한 사항은&lt;**cite**&gt;**웹을 지탱하는 기술**&lt;/**cite**&gt;을 참조해 주십시오.&lt;/p&gt;
        &lt;p&gt;공자 왈,&lt;**br**/&gt;&lt;**q**&gt;**배우고 때로 이를 익히면**...&lt;/**q**&gt;&lt;/p&gt;
        &lt;p&gt;H&lt;**sub**&gt;**2**&lt;/**sub**&gt;&lt;/p&gt;
        &lt;p&gt;E=mc&lt;**sup**&gt;**2**&lt;/**sup**&gt;&lt;/p&gt;
        &lt;p&gt;간식은&lt;**del**&gt;**300**&lt;/**del**&gt;&lt;**ins**&gt;**500**&lt;/**ins**&gt;원 이내&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/sung_hyuki/post/ed12d6be-d591-415f-92fb-e1b893a3441e/image.png" alt=""></p>
<h3 id="공통-속성">공통 속성</h3>
<ul>
<li>HTML의 모든 요소는 id 속성과 class 속성을 가질 수 있습니다.</li>
</ul>
<p><strong>id 속성</strong></p>
<ul>
<li><p>문서 내에서 유일한 ID</p>
</li>
<li><p>문서 내의 특정 부분을 URI로 나타낼 때 URI 프래그먼트(#다음에 지정하는 부분)로 이용하거나 CSS로 스타일을 지정하거나 할 때 이용</p>
<pre><code class="language-css">  http://example.com/test.html#title</code></pre>
</li>
</ul>
<p><strong>class 속성</strong></p>
<ul>
<li>요소가 속할 클래스</li>
<li>해당 요소가 어떠한 의미를 가질 것인지 지정하는 메타 데이터로서의 역할</li>
<li>CSS에서의 스타일 지정과 microformats 등에서 메타 데이터를 표현할 때 이용</li>
</ul>
<pre><code class="language-html">&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
    &lt;head&gt;&lt;title&gt;id속성과 class속성&lt;/title&gt;&lt;/head&gt;
    &lt;body&gt;
        &lt;h1 **id**=&quot;**title**&quot;&gt;첫 번째 HTML&lt;/h1&gt;
        &lt;p&gt;저자: &lt;span **class**=&quot;**author**&quot;&gt;야마다 타로&lt;/span&gt;&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<pre><code class="language-css">h1#title {
    font-size: 120%
}
span.author {
    color: red
}</code></pre>
<br>

<h2 id="👨🏻💻-링크">👨🏻‍💻 링크</h2>
<p><strong><code>&lt;a&gt;요소</code> - 앵커</strong></p>
<ul>
<li>HTML에서는 다른 웹 페이지에 링크하기 위해 앵커 태그 <code>&lt;a&gt;</code> 요소를 사용</li>
<li><code>&lt;a&gt;</code> 요소의 내용을 앵커 텍스트라고 함<br>

</li>
</ul>
<p><strong><code>&lt;link&gt;</code>요소</strong></p>
<ul>
<li>HTML의 헤더에서 웹 페이지 사이의 관계를 지정하기 위해 사용<ul>
<li>rel 속성은 링크의 의미를 나타냄<br>

</li>
</ul>
</li>
</ul>
<p><strong>오브젝트의 삽입</strong></p>
<ul>
<li><p>시대적인 경위에 의해 일반적으로는 이미지를 삽입하는 데는 <code>&lt;img&gt;</code>요소를, 그 밖의 오브젝트의 삽입에는 <code>&lt;object&gt;</code>요소를 이용</p>
<pre><code class="language-html">  &lt;!--&lt;img&gt;요소의 예--&gt;
  &lt;img src=&quot;http://example.com/children.png&quot; alt=&quot;아이들 사진&quot;/&gt;

  &lt;!--&lt;object&gt;요소의 예--&gt;
  &lt;object data=&quot;http://example.com/children.mpeg&quot;&gt;아이들의 동영상&lt;/object&gt;</code></pre>
</li>
</ul>
<br>

<p><strong>폼</strong></p>
<ul>
<li>HTML의 폼에서는 링크하는 곳의 URI에 대해 GET과 POST를 발생할 수 있습니다.<ul>
<li>폼에 의한 GET - 키워드 검색 등 사용자의 입력에 따라 URI를 생성할 때 이용</li>
<li>폼에 의한 POST - 리소스의 작성 등 사용자의 입력을 타킷 URI로 전송할 때 이용</li>
</ul>
</li>
<li>폼 컨트롤 요소에는 텍스트 입력과 라디오 버튼, 셀렉트 버튼 등이 존재</li>
<li>폼은 타깃이 되는 URI를 action 속성에 저장</li>
<li>이용할 메서드는 method 속성으로 지정<br>

</li>
</ul>
<h2 id="👨🏻💻-링크-관련---링크의-의미를-지정한다">👨🏻‍💻 링크 관련 - 링크의 의미를 지정한다</h2>
<ul>
<li>웹 API와 같이 프로그램이 클라이언트인 경우 각각의 링크가 어떠한 의미인지를 해석하고, 어느 링크를 따라가야 하는지를 기계적으로 판단하는 구조가 필요</li>
</ul>
<p><strong>rel 속성</strong></p>
<ul>
<li>링크하는 쪽과 링크되는 쪽의 리소스가 어떤 관계에 있는지를 기술</li>
<li>rel 속성의 값을 ‘링크 관계&#39;라고 부름<ul>
<li>stylesheet : 원 HTML 리소스를 CSS 리소스에 링크할 때 사용</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>alternate</th>
<th>번역 등의 대체 문서로 링크</th>
</tr>
</thead>
<tbody><tr>
<td>stylesheet</td>
<td>외부 스타일시트로 링크</td>
</tr>
<tr>
<td>start</td>
<td>문서군의 최초의 문서로 링크</td>
</tr>
<tr>
<td>next</td>
<td>문서군의 다음 문서로 링크</td>
</tr>
<tr>
<td>pre</td>
<td>문서군의 이전 문서로 링크</td>
</tr>
<tr>
<td>contents</td>
<td>목차로 링크</td>
</tr>
<tr>
<td>index</td>
<td>색인으로 링크</td>
</tr>
<tr>
<td>glossary</td>
<td>용어집으로 링크</td>
</tr>
<tr>
<td>copyright</td>
<td>저작권표시로 링크</td>
</tr>
<tr>
<td>chapter</td>
<td>챕터로 링크</td>
</tr>
<tr>
<td>section</td>
<td>섹션으로 링크</td>
</tr>
<tr>
<td>subsection</td>
<td>서브섹션으로 링크</td>
</tr>
<tr>
<td>appendix</td>
<td>부속서로 링크</td>
</tr>
<tr>
<td>help</td>
<td>헬프로 링크</td>
</tr>
<tr>
<td>bookmark</td>
<td>문서중의 북마크로 링크</td>
</tr>
</tbody></table>
<p><strong>microformats</strong></p>
<ul>
<li>현재의 웹에서는 다양한 리소스를 HTML로 표현하고 이런 요구사항을 대응하기 위해 HTML 링크 관계의 확장이 microformats 등에서 이루어지고 있습니다.<br>

</li>
</ul>
<h2 id="하이퍼미디어-포맷으로서의-html">하이퍼미디어 포맷으로서의 HTML</h2>
<ul>
<li>링크를 따라가는 것으로 애플리케이션의 상태가 변화한다</li>
<li>리소스끼리 바르게 접속하여 애플리케이션 상태를 표현할 수 있는지 여부는 HTML로 리소스를 표현할 때의 중요한 설계방침</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[웹 개발자를 위한 웹을 지탱하는 기술] - HTTP 헤더]]></title>
            <link>https://velog.io/@sung_hyuki/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%B9%EC%9D%84-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-HTTP-%ED%97%A4%EB%8D%94</link>
            <guid>https://velog.io/@sung_hyuki/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%B9%EC%9D%84-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-HTTP-%ED%97%A4%EB%8D%94</guid>
            <pubDate>Mon, 25 Apr 2022 15:47:40 GMT</pubDate>
            <description><![CDATA[<h2 id="👨🏻💻-http의-중요성">👨🏻‍💻 HTTP의 중요성</h2>
<p>헤더는 메시지의 바디에 대한 부가적인 정보, 즉 메타 데이터를 표현</p>
<h2 id="👨🏻💻-http-헤더의-태생">👨🏻‍💻 HTTP 헤더의 태생</h2>
<p>HTTP의 최초 버전 0.9에는 헤더가 없었지만 전자메일의 스펙의 헤더 형식을 빌려오는 식으로 추가되었다.</p>
<ul>
<li>HTTP 헤더에도 역시 문자 인코딩 제한이 있어, 라틴 알파벳을 위한 문자 인코딩인 ISO 8859-1 이외의 문자가 들어갈 수 없다.<br>

</li>
</ul>
<h2 id="👨🏻💻-날짜와-시간">👨🏻‍💻 날짜와 시간</h2>
<p>날짜와 시간을 가지는 헤더</p>
<table>
<thead>
<tr>
<th>이용하는 메시지</th>
<th>헤더</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>요청과 응답</td>
<td>Date</td>
<td>메시지를 생성한 일시</td>
</tr>
<tr>
<td>요청</td>
<td>If-Modified-Since</td>
<td>조건부 GET으로 리소스의 갱신일시를 지정할 때 이용</td>
</tr>
<tr>
<td>요청</td>
<td>If-Unmodified-Since</td>
<td>조건부 PUT으로, 조건부 DELETE로 리소스의 갱신ㅇ리시를 지정할 때 이용한다.</td>
</tr>
<tr>
<td>응답</td>
<td>Expires</td>
<td>응답을 캐시 할 수 있는 기한</td>
</tr>
<tr>
<td>응답</td>
<td>Last-Modified</td>
<td>리소스를 마지막으로 갱신한 일시</td>
</tr>
<tr>
<td>응답</td>
<td>Retry-After</td>
<td>다시 요청을 전송할 수 있는 일시의 기준</td>
</tr>
</tbody></table>
<ul>
<li>HTTP에 일시는 모두 GMT를 기술하도록 함으로써 섬머타임 등의 복잡한 문제를 회피할 수 있게 되었다.<br>

</li>
</ul>
<h2 id="👨🏻💻-mime-미디어-타입">👨🏻‍💻 MIME 미디어 타입</h2>
<p>메시지로 주고받는 리소스 표현의 종류를 지정하는 것</p>
<h3 id="content-type---미디어-타입을-지정">Content-Type - 미디어 타입을 지정</h3>
<ul>
<li>메시지의 바디 내용이 어떠한 종류인가를 미디어 타입으로 나타냄</li>
<li>‘/’를 기준으로 왼쪽은 타입, 오른쪽은 서브타입이라 부름<ul>
<li>타입의 종류는 임의로 늘릴 수 없음</li>
<li>서브타입은 비교적 자유롭게 늘릴 수 있음</li>
</ul>
</li>
<li>등록된 타입과 서브타입의 목록은 IANA가 관리</li>
</ul>
<p>타입</p>
<table>
<thead>
<tr>
<th>타입</th>
<th>의미</th>
<th>예</th>
</tr>
</thead>
<tbody><tr>
<td>text</td>
<td>사람이 읽고 직접 이해할 수 있는 텍스트</td>
<td>text/plain</td>
</tr>
<tr>
<td>image</td>
<td>그림 데이터</td>
<td>image/jpeg</td>
</tr>
<tr>
<td>audio</td>
<td>음성 데이터</td>
<td>audio/mpeg</td>
</tr>
<tr>
<td>video</td>
<td>동영상 데이터</td>
<td>video/mp4</td>
</tr>
<tr>
<td>application</td>
<td>그 밖의 데이터</td>
<td>application/pdf</td>
</tr>
<tr>
<td>multipart</td>
<td>복수의 데이터로 이루어진 복합 데이터</td>
<td>multipart/related</td>
</tr>
<tr>
<td>message</td>
<td>전자메일 메시지</td>
<td>message/rfc822</td>
</tr>
<tr>
<td>model</td>
<td>복수 차원으로 구성하는 모델 데이터</td>
<td>model/vrml</td>
</tr>
<tr>
<td>example</td>
<td>예시용</td>
<td>example/foo-bar</td>
</tr>
<tr>
<td><br></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="charset-파라미터---문자-인코딩을-지정">charset 파라미터 - 문자 인코딩을 지정</h3>
<ul>
<li>charset 파라미터는 생략 가능하지만 타입이 text인 경우 주의가 필요<ul>
<li>HTTP에서는 text 타입의 디폴트 문자 인코딩은 ISO 8859-1 이라고 정의하기 때문에 한글 텍스트가 들어가 있음에도 불구하고, 문자가 깨질 가능성이 있다.</li>
<li>XML처럼 문서 자체에서 문자 인코딩 방식을 선언 할 수 있는 경우라도, text 타입의 경우는 Content-Type 헤더의 charset 파라미터를 우선함</li>
<li>XML 문서의 경우 text/html을 사용하지 않고, application/xml이나 application/xhtml + xml과 같은 파라미터를 이용하고, 반드시 charset 파라미터를 붙이는 것이 현시점에서는 가장 바람직한 운용방법<br>

</li>
</ul>
</li>
</ul>
<h2 id="👨🏻💻-언어-태그">👨🏻‍💻 언어 태그</h2>
<p>Content-Language : 리소스 표현의 자연언어를 지정하는 헤더</p>
<ul>
<li>‘-’ 왼쪽에는 ISO 639가 정의하는 언어코드가, 오른편에는 ISO 3166이 정의하는 지역코드가 들어감<br>
## 👨🏻‍💻 콘텐트 네고시에이션

</li>
</ul>
<p>클라이언트와 교섭(네고시에이션)해서 미디어 타입과 문자 인코딩, 언어 태그를 정하는 방법</p>
<h3 id="accept---처리할-수-있는-미디어-타입을-전달">Accept - 처리할 수 있는 미디어 타입을 전달</h3>
<ul>
<li><p>클라이언트가 자신이 처리할 수 있는 미디어 타입을 서버에게 전달</p>
</li>
<li><p>q=이라는 파라미터의 값을 qvalue라고 합니다.</p>
<ul>
<li><p>미디어 타입의 우선 순위를 나타냄</p>
</li>
<li><p>qvalue는 소수점 이하 세 자리 이내의 0 ~ 1 까지의 수치</p>
<pre><code class="language-html">Accept: text/html, application/xhtml+xml, application/xml;q=0.9,*/*;q=0.8</code></pre>
</li>
<li><p>text/html, application/xhtml+xml은 디폴트인 1
application/xml은 0.9
그 밖의 모든 미디어 타입(<em>/</em>)는 0.8</p>
</li>
</ul>
</li>
<li><p>클라이언트가 Accept 헤더에 지정한 미디어 타입에 서버가 대응하고 있지 않다면 406 Not Acceptable이 반환</p>
<br>
### Accept-Charset - 처리할 수 있는 문자 인코딩 전달
</li>
<li><p>클라이언트가 자신이 처리할 수 있는 문자 인코딩을 서버에게 전달</p>
<pre><code class="language-html">  Accept-Charset: EUC-KR;utf-8;q=0.7,*/*;q=0.7</code></pre>
<ul>
<li>EUC-KR와 Accept-Charset 헤더의 기본 문자 인코딩 ISO 8859-1이 qvalue의 기본값인 우선도 1이 되지만, 좀 더 구체적인 EUC-KR를 우선함. 여기서 이어지는 UTF-8 및 그 밖의 모든 인코딩 방식이 0.7인 우선도가 됩니다.</li>
</ul>
</li>
</ul>
<br>

<h3 id="accept-language---처리할-수-있는-언어를-전달">Accept-Language - 처리할 수 있는 언어를 전달</h3>
<ul>
<li><p>클라이언트가 자신이 처리할 수 있는 언어 태그를 서버에게 전달</p>
<pre><code class="language-html">  Accept-Language: ko, en-us;q=0.7,en;q=0.3</code></pre>
<ul>
<li>ko가 기본값 1, en-us가 0.7, 지역을 특정하지 않는 en가 0.3 우선도를 가집니다.</li>
</ul>
</li>
</ul>
<br>

<h2 id="👨🏻💻-content-length와-청크chunk-전송">👨🏻‍💻 Content-Length와 청크(chunk) 전송</h2>
<p>메시지의 바디의 사이즈를 10진수의 바이트로 나타냄</p>
<ul>
<li>사이즈를 알고 있는 리소스인 정적인 파일 등을 전송할 때는 10진수의 바이트로 사이즈를 나타냄</li>
</ul>
<h3 id="청크-전송---바디를-분할하여-전송한다">청크 전송 - 바디를 분할하여 전송한다</h3>
<ul>
<li>동적으로 이미지를 생성하는 웹 서비스의 경우, 파일 사이즈가 정해질 때까지 응답할 수 없기 때문에 응답성능이 저하됨</li>
<li>→ Transfer-Encoding 헤더 사용</li>
<li>Transfer-Encoding 헤더에 Chunked를 지정하면, 최종적으로는 사이즈를 모르는 바디를 조금씩 전송할 수 있음<ul>
<li>각 청크의 시작에는 청크 사이즈가 16진수로 들어감</li>
<li>청크의 구분을 위해 빈 줄을 사용</li>
<li>마지막에는 반드시 길이가 0인 청크와 빈줄을 붙이도록 스펙에서 규정하고 있음</li>
</ul>
</li>
</ul>
<br>

<h2 id="👨🏻💻-인증">👨🏻‍💻 인증</h2>
<ul>
<li>현재 주류인 HTTP 인증 방식에는 HTTP 1.1이 규정하고 있는 Basic 인증과 Digest 인증이 존재</li>
<li>또한, 웹 API에서는 WSSE라는 HTTP 인증의 확장 스펙을 이용하기도 함</li>
<li>어떤 리소스에 액세스 제어가 걸려 있는 경우, 스테이터스 코드 401 Unauthorized(이 리소스에 접근하려면 적절한 인증이 필요)와 WWW-Authenticate 헤더를 이용해, 클라이언트에 리소스 접근에 필요한 인증정보를 통지<br>

</li>
</ul>
<p><strong>URI 공간이란?</strong></p>
<ul>
<li>URI에서 패스 이하를 가리키는 것</li>
<li>WWW-Authenticate 헤더의 realm의 값은 이 URI 공간의 이름<br>

</li>
</ul>
<h3 id="basic-인증">Basic 인증</h3>
<ul>
<li>유저 이름과 패스워드에 기반한 인증 방식</li>
<li>유저 이름과 패스워드는 Authorization 헤더에 넣어 요청마다 전송<ul>
<li>유저 이름과 패스워드를 ‘:’으로 연결하고 Base64 인코딩을 거쳐 문자열로 변환하여 전송</li>
<li>Base64는 간단히 디코딩이 가능하다는 점 주의</li>
</ul>
</li>
<li>Basic 인증을 사용할 때는 그것이 허용될 정도의 보안 강도가 좋은지, SSL와 TLS를 사용해 HTTPS 통신을 하고 통신선로 상에서 암호화할 것인지 검토해야만 함<br>

</li>
</ul>
<h3 id="digest-인증">Digest 인증</h3>
<ul>
<li>Basic 인증보다 보안이 강화된 인증 방식</li>
<li>Digest란 어떤 메시지에 대해 해시함수를 적용한 해시값<br>

</li>
</ul>
<h3 id="digest-인증-과정">Digest 인증 과정</h3>
<p>Digest 인증에서도 클라이언트는 우선 인증 정보 없이 요청을 전송. 그 결과로서 인증이 실패하고 401 unauthorized 반환</p>
<pre><code class="language-html">DELETE /test HTTP/1.1
Host: example.com</code></pre>
<pre><code class="language-html">HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest realm=&quot;example.com&quot;, nonce=&quot;1ac421d9e0a4k7q982z966p903372922&quot;, qop=&quot;auth&quot;, opaque=&quot;92eb5ffee6ae2fec3ad71c777531758f&quot;</code></pre>
<p>WWW-Authenticate 헤더의 값을 <strong>챌린지</strong> 라고 부릅니다. 클라이언트는 챌린지를 사용하여 다음 요청을 조립
<br>
<strong>nonce(number used once)</strong></p>
<ul>
<li>모든 요청에 대해 변화하는 문자열</li>
<li>서버 구현에 의존하는데, 기본적으로는 타임스탬프와 서버만 알 수 있는 패스워드를 이용해 생성<ul>
<li>타임스탬프가 포함되어 있는 이유는 nonce를 사용한 요청의 유효기간을 좁히기 위함</li>
</ul>
</li>
<li>생성할 해시 값의 보안을 좀 더 강화할 목적으로 이용<br>

</li>
</ul>
<p><strong>qop(quality of protection)</strong></p>
<ul>
<li>auth 나 auth-init를 지정<ul>
<li>auth<ul>
<li>메서드와 URI로부터 다이제스트를 작성</li>
</ul>
</li>
<li>auth-init<ul>
<li>메서드와 URI, 메시지 바디로부터 다이제스트를 작성</li>
<li>POST와 PUT으로 바디를 송신할 때 메시지 전체가 변경되지 않음을 보증</li>
</ul>
</li>
</ul>
</li>
<li>클라이언트가 송신할 다이제스트의 작성방법에 영향을 미침<br>

</li>
</ul>
<p><strong>opaque</strong></p>
<ul>
<li>클라이언트에는 불투명한 문자열</li>
<li>동일 URI 공간에 대한 요청에는 공통되게 클라이언트에서 서버로 보냅니다</li>
</ul>
<p>서버로부터 필요한 정보를 얻은 클라이언트는 자신의 유저 이름과 패스워드를 사용해 다이제스트를 생성
<br></p>
<p><strong>Digest 생성 알고리즘</strong></p>
<ol>
<li>유저 이름, realm, 패스워드는 ‘:’로 연결하고, MD5 해시 값을 구함</li>
<li>메서드와 URI의 패스를 ‘;’로 연결하고, MD5 해시 값을 구함</li>
<li>1 의 값, 서버로부터 얻은 nonce, 클라이언트가 nonce를 보낸 횟수, 클라이언트가 생성한 nonce, qop 값, 2의 값을 ‘:’로 연결하고, MD5 해시 값을 구함</li>
</ol>
<p>클라이언트는 생성한 다이제스트 값을 response라는 필드에 넣고 전송하고 인증을 통과하고 성공하면 200 OK가 반환
<br></p>
<p><strong>Digest 인증</strong></p>
<p>Pros</p>
<ul>
<li>패스워드를 도둑맞을 위험성은 없다</li>
<li>서버에 패스워드의 해시 값만 보관해 두면 되므로, 패스워드 자체를 서버에 맡겨두지 않아도 된다</li>
</ul>
<p>Cons</p>
<ul>
<li>패스워드만 암호화할 뿐 메시지 자체는 평문으로 네트워크를 흘러감<ul>
<li>메시지를 암호화하고 싶은 경우 Basic 인증의 경우와 마찬가지로 HTTPS를 이용</li>
</ul>
</li>
<li>Basic 인증은 같은 URI 공간의 리소스라면 클라이언트는 한번 인증되면 2번째부터는 자동저그올 유저 이름과 패스워드를 전송할 수 있지만 Digest 인증의 경우 서버로부터의 nonce가 없다면 클라이언트 쪽에서 다이제스트를 계산할 수 없기 때문에, 요청할 때마다 한번은 401 Unauthorized 응답을 얻어야만 함<ul>
<li>클라이언트 입장에서 조작이 번잡하기 때문에 그다지 보급되지 못한 원인</li>
</ul>
</li>
<li>Apache 등의 웹 서버에서는 Digest 인증이 옵션으로 되어 있어 호스팅 서비스에서는 지원하지 않을 가능성도 있음<ul>
<li>직접 인증 프로그램을 만들면 되지만 CGI처럼 별도의 프로세스로 동작하는 프로그램에는 보안 문제로 인해 Apache가 인증관련 헤더를 건네주지 않으므로 역시 Digest 인증 이용 불가<br>

</li>
</ul>
</li>
</ul>
<h3 id="wsse-인증">WSSE 인증</h3>
<ul>
<li>HTTP 1.1의 표준 외의 인증 방식</li>
<li>SSL과 TLS의 이용이 불가능해 Basic 인증을 사용하지 못하고, 호스팅 서비스 상의 CGI 스크립트 등으로 Digest 인증도 사용할 수 없는 경우에 어떻게든 패스워드를 그냥 네트워크로 흘려보내지 않고 인증하는 기구로서 민간에 의해 책정됨<br>

</li>
</ul>
<h3 id="wsse-인증-과정">WSSE 인증 과정</h3>
<p>클라이언트는 우선 인증 정보 등으로 요청을 보내고, 서버로부터 401 Unauthorized 응답을 받음</p>
<pre><code class="language-html">DELETE /test HTTP/1.1
Host: example.com</code></pre>
<pre><code class="language-html">HTTP/1.1 401 Unauthorized
WWW-Authenticate: **WSSE realm=&quot;example.com&quot;, profile=&quot;UsernameToken&quot;**</code></pre>
<p>클라이언트는 패스워드와 자신이 준비한 nonce와 일시를 연결한 문자열에 대해서 SHA-1 해시 값을 구해, 결과를 Base64 인코딩 → <strong>패스워드 다이제스트</strong></p>
<p>클라이언트는 Authorization 헤더에 ‘WSSE’와 ‘profile=”UsernameToken”’을 지정하고 X-WSSE 확장 헤더에 패스워드 다이제스트와 nonce, 일시정보를 넣어 요청을 보낸다.</p>
<pre><code class="language-html">DELETE /test HTTP/1.1
**Authorization : WSSE profile=&quot;UsernameToken&quot;
X-WSSE: UsernameToken Username=&quot;test&quot;, PasswordDigest=&quot;pkkkpksmpkikqqSrpK2krw==&quot;, Nonce=&quot;88akf2947cd33aa&quot;, Created=&quot;2010-05-10T09:45:22Z&quot;**</code></pre>
<p>서버 측에서는 데이터베이스 등에 보관하고 있는 사용자의 패스워드를 사용해 패스워드 다이제스트를 다시 계산하고, 그 값과 클라이언트가 신고한 값이 같게 되면 인증 통과</p>
<ul>
<li>WSSE 인증은 패스워드 자체를 네트워크상으로 흘려보내지 않아도 되는데다 Digest 인증만큼 복잡하지 않은 반면, 서버 측에서 패스워드를 그냥 보존해 둘 필요가 있는 등 Basic 인증과 Digest 인증의 중간에 위치하는 인증 방식<br>

</li>
</ul>
<h2 id="👨🏻💻-캐시">👨🏻‍💻 캐시</h2>
<ul>
<li>서버로부터 가져온 리소스를 로컬 스토리지(하드디스크 등)에 저장하여 재사용하는 방법</li>
<li>로컬 스토리지에 캐싱한 데이터 자체를 “캐시&quot;라고 부르기도 함</li>
<li>캐싱된 데이터는 유효 기간 내에서 재사용 가능<br>

</li>
</ul>
<h3 id="캐시용-헤더">캐시용 헤더</h3>
<ul>
<li>클라이언트는 서버에서 가져온 리소스의 캐시 가능 여부를 조사하고 가능한 경우는 로컬 스토리지에 저장합니다.</li>
<li>어떤 리소스가 캐시 가능한지는 그 리소스를 취득했을 때의 헤더로 판단<br>

</li>
</ul>
<p><strong>Pragma - 캐시를 억제한다</strong></p>
<ul>
<li>리소스를 캐시하지 말라</li>
</ul>
<pre><code class="language-html">HTTP/1.1 200 OK
Content-Type: application/xhtml+xml, charset=utf-8
**Pragma: no-cache**
...</code></pre>
<br>

<p><strong>Expires - 캐시의 유효기한을 나타낸다</strong></p>
<ul>
<li>캐시의 유효기간을 나타내는 헤더</li>
</ul>
<pre><code class="language-html">HTTP/1.1 200 OK
Content-Type: application/xhtml+xml, charset=utf-8
**Expires: Thu, 11 May 2010 16:00:00 GMT

캐시 가능한 데이터**</code></pre>
<ul>
<li>리소스를 변경할 가능성이 없는 경우, 캐시의 유효기간을 무한으로 설정하고 싶겠지만 그런 경우라도 Expires 헤더에는 최장 약 1년 이내로 일시를 넣을 것을 스펙에서 권장<br>

</li>
</ul>
<p><strong>Cache-Control - 상세한 캐시 방법을 지정한다</strong></p>
<ul>
<li>Pragma 헤더와 Expires 헤더의 기능은 Cache-Control 헤더로 완전히 대용 가능</li>
</ul>
<pre><code class="language-html">Pragme: no-cache</code></pre>
<p>→</p>
<pre><code class="language-html">Cache-Control: no-cache</code></pre>
<ul>
<li><p>Expires에서는 절대시간으로 유효기간을 표시하는 반면, Cache-Control에서는 현재로부터의 상대시간으로 유효기간을 설정 가능</p>
<pre><code class="language-html">  Cache-Control: max-age:86400</code></pre>
</li>
</ul>
<ul>
<li>더욱 섬세하게 캐시를 제어 가능<br>

</li>
</ul>
<p><strong>캐시용 헤더의 사용 구분</strong></p>
<ul>
<li>캐시를 시키지 않을 경우는 Pragma와 Cache-Control의 no-cache를 동시에 지정</li>
<li>캐시의 유효기간이 명확하게 정해져 있는 경우는 Expires를 지정</li>
<li>캐시의 유효기간을 상대적으로 지정하고자 하는 경우는 Cache-Control의 max-age로 상대시간을 지정한다.<br>

</li>
</ul>
<h3 id="조건부-get">조건부 GET</h3>
<ul>
<li>조건부 GET은 서버 측에 있는 리소스가 클라이언트 로컬의 캐시로부터 변경되어 있는지 여부를 조사하는 조건을 요청 헤더에 포함시킴으로써, 캐시를 그대로 사용할 수 있는지 검토하는 구조</li>
<li>리소스가 Last-Modified 헤더 또는 ETag 헤더를 가지고 있을 때 이용 가능<br>

</li>
</ul>
<p><strong>If-Modified-Since - 리소스의 갱신일시를 조건으로 한다</strong></p>
<ul>
<li>서버의 리소스가 변경되지 않았다면 304 Not Modified를 통해 조건부 GET에 대한 응답이 가능</li>
<li>리소스의 갱신일시는 Last-Modified 헤더로 확인<br>

</li>
</ul>
<p><strong>If-None-Match - 리소스의 ETag를 조건으로 한다</strong></p>
<ul>
<li>밀리 초 단위로 변경될 가능성이 있는 리소스에 사용하는 헤더</li>
<li>지정한 값과 매치하지 않으면 이라는 조건, If-None-Match 헤더에 지정하는 값은 캐시하고 있는 리소스의 ETag 헤더의 값</li>
<li>조건부 GET의 결과, 서버상의 리소스가 변경되어 있지 않으면 ETag 헤더의 값을 반환<ul>
<li>리소스 갱신의 경우 다른 값이 되는 것이면 어떤 문자라도 상관없이 반환</li>
</ul>
</li>
</ul>
<br>

<p><strong>If-Modified-Since와 If-None-Match의 사용 구분</strong></p>
<ul>
<li>클라이언트 입장에서는 서버가 ETag 헤더를 보내고 있다면, If-None-Match 헤더를 이용하는 편이 좋음(Last-Modified 헤더보다도 정확한 갱신의 유무를 확인할 수 있기 때문)</li>
<li>서버를 구현 중이라면, 캐시 가능한 리소스에는 가능한 한 ETag 헤더를 이용, ETag가 없고 Last-Modified 헤더밖에 모를 경우 If-Modified-Since 헤더를 사용<br>

</li>
</ul>
<h2 id="👨🏻💻-지속적-접속">👨🏻‍💻 지속적 접속</h2>
<p>지속적 접속에서는 클라이언트가 응답을 기다리지 않고 같은 서버에 요청을 송신할 수 있습니다. 이것을 ‘파이프라인화(Pipelining)&#39;라고 부릅니다.</p>
<ul>
<li>커넥션을 끊고 싶을 때는 요청의 Connection 헤더에 close라는 값을 지정<br>

</li>
</ul>
<h2 id="👨🏻💻-그-밖의-http-헤더">👨🏻‍💻 그 밖의 HTTP 헤더</h2>
<h3 id="content-disposition---파일명을-지정한다">Content-Disposition - 파일명을 지정한다</h3>
<ul>
<li>서버가 클라이언트에 대해 그 리소스의 파일명을 제공하기 위해 이용하는 응답 헤더<br>

</li>
</ul>
<h3 id="slug---파일명과-힌트를-지정한다">Slug - 파일명과 힌트를 지정한다</h3>
<ul>
<li>클라이언트가 Atom의 엔트리를 POST할 때 새로 생성할 리소스의 URI의 힌트가 되는 문자열을 서버에게 제시할 수 있습니다.<br>

</li>
</ul>
<h2 id="👨🏻💻-http-헤더를-활용하기-위해서">👨🏻‍💻 HTTP 헤더를 활용하기 위해서</h2>
<p>HTTP 헤더를 제대로 사용하기 위해서는 이들의 역사와 실제 서버와 브라우저의 구현 방식에 대해 조사할 수 있는 능력이 필요</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[웹 개발자를 위한 웹을 지탱하는 기술] - 스테이터스 코드
]]></title>
            <link>https://velog.io/@sung_hyuki/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%B9%EC%9D%84-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%85%8C%EC%9D%B4%ED%84%B0%EC%8A%A4-%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@sung_hyuki/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%B9%EC%9D%84-%EC%A7%80%ED%83%B1%ED%95%98%EB%8A%94-%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%85%8C%EC%9D%B4%ED%84%B0%EC%8A%A4-%EC%BD%94%EB%93%9C</guid>
            <pubDate>Mon, 25 Apr 2022 15:43:25 GMT</pubDate>
            <description><![CDATA[<h2 id="👨🏻💻-스테이터스-코드의-중요성">👨🏻‍💻 스테이터스 코드의 중요성</h2>
<ul>
<li>스테이터스 코드는 클라이언트의 움직임을 좌우하는 중요한 역할을 담당<ul>
<li>응답에 어떤 스테이터스 코드를 포함하느냐는 중요하다</li>
</ul>
</li>
</ul>
<br>

<h2 id="👨🏻💻-스테이터스-라인의-복습">👨🏻‍💻 스테이터스 라인의 복습</h2>
<ul>
<li>스테이터스 라인<ul>
<li>프로토콜 버전</li>
<li>스테이터스 코드</li>
<li>텍스트 프레이즈<ul>
<li>스테이터스 코드에 대응하는 설명구</li>
<li>스펙에 예시된 이외의 문구도 포함 가능, 단 사람이 읽을 수 있도록</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h2 id="👨🏻💻-스테이터스-코드의-분류와-의미">👨🏻‍💻 스테이터스 코드의 분류와 의미</h2>
<p>스테이터스 코드는 3자리의 숫자이며, 첫째 자리의 숫자에 따라 5가지로 분류함</p>
<ul>
<li>1XX : 처리중<ul>
<li>처리가 계속되고 있음을 나타낸다. 클라이언트는 그대로 요청을 계속하던지 서버의 지시에 따라 프로토콜을 업데이트 하여 재전송</li>
</ul>
</li>
<li>2XX : 성공<ul>
<li>요청이 성공했음</li>
</ul>
</li>
<li>3XX : 리다이렉트<ul>
<li>다른 리소스로의 리다이렉트를 나타낸다. 클라이언트는 이 스테이터스 코드를 받았을 때 응답 메시지의 Location 헤더를 보고 새로운 리소스로 접속한다.</li>
</ul>
</li>
<li>4XX : 클라이언트 에러<ul>
<li>클라이언트 에러를 나타낸다. 원인은 클라이언트의 요청에 있다. 에러를 해결하지 않는 한, 정상적인 결과를 얻을 수 없기 때문에 같은 요청을 그대로 재전송할 수는 없다.</li>
</ul>
</li>
<li>5XX : 서버 에러<ul>
<li>서버 에러를 나타낸다. 원인은 서버에 있다. 서버 측의 원인이 해결되면, 동일한 요청을 재전송해서 정상적인 결과를 얻을 가능성이 있다.</li>
</ul>
</li>
</ul>
<p>첫 번째 숫자를 이용한 스테이터스 코드의 분류 방식은 클라이언트와 서버 간의 약속을 최소한으로 억제하고 클라이언트와 서버의 결합을 가능한 한 완화하기 위해, 즉 <strong>소결합을 위해 고안된 것</strong>입니다.</p>
<ul>
<li>시스템이 소결합 되었을 때의 이점<ul>
<li>컴포넌트 간의 독립성이 높아짐</li>
<li>컴포넌트의 치환이나 확장이 용이해짐<br>

</li>
</ul>
</li>
</ul>
<h2 id="👨🏻💻-자주-사용되는-스테이터스-코드">👨🏻‍💻 자주 사용되는 스테이터스 코드</h2>
<h3 id="200-ok---요청-성공">200 OK - 요청 성공</h3>
<ul>
<li><p>GET의 경우 바디에 리소스의 표현이 들어갑니다.</p>
</li>
<li><p>PUT과 POST의 경우 바디에 처리결과가 들어갑니다.</p>
<br>
### 201 Created - 리소스 작성 성공
</li>
<li><p>응답 바디에 관습적으로 새로 작성한 리소스의 표현을 넣는 일이 많은데, 특별히 아무것도 넣지 않아도 상관없습니다.</p>
</li>
<li><p>POST의 경우, 새로 작성한 리소스의 URI는 응답 메시지의 Location 헤더에 절대 URI로 들어갑니다.</p>
</li>
<li><p>PUT의 경우, 클라이언트가 새로운 리소스의 URI를 이미 알고 있으므로 Location 헤더는 들어가지 않습니다.</p>
<br>
### 301 Moved Permanently - 리소스의 항구적인 이동
</li>
<li><p>요청에서 지정한 리소스를 새로운 URI로 이동했다는 것을 나타냅니다.</p>
<ul>
<li>예전 URI를 계속 유지하면서 새로운 URI로 이동할 때 이 스테이터스 코드를 이용</li>
<li>새로운 URI는 응답의 Location 헤더에 절대 URI로 들어감 <br>

</li>
</ul>
</li>
</ul>
<h3 id="303-see-other---다른-uri의-참조">303 See Other - 다른 URI의 참조</h3>
<ul>
<li><p>리다이렉트에 대한 처리 결과를 다른 URI로 취득할 수 있음</p>
</li>
<li><p>일반적으로 POST로 리소스를 조작한 결과를 GET으로 가져올 때 사용</p>
<br>
### 400 Bad Request - 요청 오류
</li>
<li><p>요청 구문이나 파라미터가 잘못됨</p>
</li>
<li><p>또한 적절한 클라이언트 에러를 나타내는 스테이터스 코드가 없는 경우에 사용</p>
</li>
<li><p>또한 클라이언트가 모르는 4XX 계열 스테이터스 코드가 반환된 경우, 400 Bad Request와 같은 처리를 하도록 스펙으로 정해짐</p>
<br>
### 401 Unauthorized - 접근 권한 없음, 인증 실패
</li>
<li><p>적절한 인증정보를 부여하지 않은 채 리다이렉트를 수행했다는 것을 나타냄</p>
</li>
<li><p>응답의 WWW-Authenticate 헤더에서 클라이언트에 대해 인증방식을 전달</p>
<br>
### 404 Not Found - 리소스 없음
</li>
<li><p>응답 바디에는 그 이유가 들어갑니다.</p>
<br>
### 500 Internal Server Error - 서버 내부 에러
</li>
<li><p>응답 바디에는 이상의 이유가 들어갑니다.</p>
</li>
<li><p>다른 적절한 서버 에러를 나타내는 스테이터스 코드가 없는 경우에도 사용</p>
</li>
<li><p>또한, 클라이언트가 알지 못하는 5XX 계열이 스테이터스 코드가 반환된 경우, 500 Internal Server Error와 같은 동작을 처리하도록 스펙으로 정해짐.</p>
<br>
### 503 Service Unavailable - 서비스 정지
</li>
<li><p>서버가 점검 등의 이유로 일시적으로 액세스 할 수 없다는 것을 알림</p>
</li>
<li><p>응답 바디에 그 이유가 들어감</p>
</li>
<li><p>응답의 Retry-After 헤더로 서비스 재개 시기가 대략 몇 십 초 후인지 통지할 수 있습니다.</p>
<br>
## 👨🏻‍💻 스테이터스 코드와 에러처리

</li>
</ul>
<p>에러 상황 시 바디에 어떤 에러 메시지가 들어가는지는 규정되어 있지 않기 때문에 보통 웹 서비스에서 404 Not Found이면, ‘지정한 페이지를 찾을 수 없다&#39;는 메시지가 들어간 HTML을 바디에 추가하는 것이 일반적.</p>
<p>하지만 프로그램용 웹 API의 경우 클라이언트가 반드시 HTML을 해석할 수 있다고는 단정할 수 없기 때문에 에러 메시지를 반환해 주는 것이 친절하다.</p>
<ul>
<li>프로토콜에 따른 포맷으로 에러를 반환</li>
<li>Accept 헤더에 따른 포맷으로 에러를 반환<ul>
<li>클라이언트가 Accept 헤더를 전송하고 있는 경우는, 그것을 이용해 에러 정보의 표현을 동적으로 변경 가능<br>

</li>
</ul>
</li>
</ul>
<h2 id="👨🏻💻-스테이터스-코드의-오용">👨🏻‍💻 스테이터스 코드의 오용</h2>
<p>스테이터스 코드는 바르게 목적에 맞게 사용해야 합니다.
<br></p>
<h2 id="👨🏻💻-스테이터스-코드를-의식해서-설계한다">👨🏻‍💻 스테이터스 코드를 의식해서 설계한다</h2>
<p>개발 중인 웹 서비스나 웹 API에서 에러가 발생했을 때, 어떤 스테이터스 코드를 반환할지 결정하는 것은 아주 중요한 설계 검토사항입니다.</p>
]]></description>
        </item>
    </channel>
</rss>