<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발자 누누님의 블로그</title>
    <link>https://icecupregular.tistory.com/</link>
    <description>인생의 스노우볼을 굴리는 개발자 누누(이재혁)의 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 5 Apr 2026 01:30:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>dev_noonoo</managingEditor>
    <image>
      <title>개발자 누누님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/7807051/attach/7ac348c603dd48a49219b18a1c134913</url>
      <link>https://icecupregular.tistory.com</link>
    </image>
    <item>
      <title>[MySQL 쿼리 최적화] 실행 계획을 분석해보자</title>
      <link>https://icecupregular.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트에 남은 인덱스는 두 개이지만, DB 테이블에 남아있는 인덱스는 실험하면서 걸어둔 인덱스가 모두 남아있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 쿼리는 현재 댓글 작성이 열려 있어서 댓글 작성이 진행되고 있는 게시글의 최신순 10건 조회 쿼리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 지금 인덱스를 잘 타고 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qq3WP/dJMcacWTI7y/dgboKgDZza5qe221BdVRe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qq3WP/dJMcacWTI7y/dgboKgDZza5qe221BdVRe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qq3WP/dJMcacWTI7y/dgboKgDZza5qe221BdVRe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqq3WP%2FdJMcacWTI7y%2FdgboKgDZza5qe221BdVRe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1296&quot; height=&quot;270&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TDG7S/dJMcabRd4EQ/EAkG6a90ekjWurKspj6iq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TDG7S/dJMcabRd4EQ/EAkG6a90ekjWurKspj6iq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TDG7S/dJMcabRd4EQ/EAkG6a90ekjWurKspj6iq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTDG7S%2FdJMcabRd4EQ%2FEAkG6a90ekjWurKspj6iq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1188&quot; height=&quot;482&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;항상 주요 지표인 select_type, type, Extra를 잘 살펴보자. select_type은 SIMPLE type은 ref Extra에 Using where&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 쿼리의 실행계획은 MySQL 서버에서 체크 조건(Using where)을 확인해주고 있긴 하지만 아주 효율적인 것을 알 수 있다. 어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어? 현재 인덱스 추가를 해 두지 않은 인덱스를 선택하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 걸어놓은 Posts 테이블의 인덱스는 idx_is_completed_created_at과 idx_is_completed_id_created_at 두 개이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 예전에 인덱스 설계를 하면서 작성하고 코드에서만 삭제 했던 인덱스가 DB에서는 남아있을 수 있다고 생각해서 Posts의 인덱스 목록을 살펴보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 입력하면 해당 테이블에 존재하는 모든 인덱스를 조회할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yx8q5/dJMcaaSlTzX/MTWXb0KQvAqxxkPvRIOQDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yx8q5/dJMcaaSlTzX/MTWXb0KQvAqxxkPvRIOQDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yx8q5/dJMcaaSlTzX/MTWXb0KQvAqxxkPvRIOQDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYx8q5%2FdJMcaaSlTzX%2FMTWXb0KQvAqxxkPvRIOQDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1392&quot; height=&quot;270&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하지 않는 인덱스는 모두 삭제해주자. 실무적으로는 invisible 설정을 하면 옵티마이저가 해당 인덱스를 사용하지 않는다. 사용하지 않는 것을 확인하면 삭제하도록 하자.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;DROP INDEX 인덱스명 ON 테이블명;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQbWga/dJMcagSyzMK/kzuBeby0jgG2In80wNik91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQbWga/dJMcagSyzMK/kzuBeby0jgG2In80wNik91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQbWga/dJMcagSyzMK/kzuBeby0jgG2In80wNik91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQbWga%2FdJMcagSyzMK%2FkzuBeby0jgG2In80wNik91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1286&quot; height=&quot;201&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCbzAF/dJMcacCCVrP/xbZOE8qubttKxTIJKF1BhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCbzAF/dJMcacCCVrP/xbZOE8qubttKxTIJKF1BhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCbzAF/dJMcacCCVrP/xbZOE8qubttKxTIJKF1BhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCbzAF%2FdJMcacCCVrP%2FxbZOE8qubttKxTIJKF1BhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1317&quot; height=&quot;223&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 겸 인덱스가 아닌 체크 조건으로 걸려있는 is_deleted를 빼고 테스트를 해봤는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 서버가 따로 체크조건을 필터링하지 않으니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서 읽은 것 처럼 Extra에 Using where이 바로 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 곳에서 책 읽은 보람을 찾는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UoKgM/dJMcahcQkmU/64INHvoPQnCB060ZIqouEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UoKgM/dJMcahcQkmU/64INHvoPQnCB060ZIqouEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UoKgM/dJMcahcQkmU/64INHvoPQnCB060ZIqouEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUoKgM%2FdJMcahcQkmU%2F64INHvoPQnCB060ZIqouEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;916&quot; height=&quot;496&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type 컬럼의 ref 접근 방법은 무려 4등이다. 이미 충분히 빠르다고 볼 수 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 ref 타입으로 접근하고 있는지 알아보고 혹여 인덱스가 잘못 걸려 있다면 개선해 나가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;lt;참고&amp;gt; 인덱스의 종류와 관계없이 동등(equal) 조건으로 검색할 때는 ref 접근 방법이 사용된다. ref 타입은 반환되는 레코드가 반드시 1건이라는 보장이 없으므로 const나 eq_ref보다는 빠르지 않다. 하지만 동등한 조건으로만 비교되므로 매우 빠른 레코드 조회 방법의 하나다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref 접근 방법의 설명을 읽어보고 쿼리문을 다시 읽어보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;explain select * from posts p where p.is_completed = false and p.is_deleted = false order by p.created_at desc limit 10;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 쿼리도 충분히 좋은 상태라는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 인덱스를 걸 때, 카디널리티가 높은 컬럼에 인덱스를 걸어야 효율이 좋은데 이 경우 뽑고 싶은 데이터는 &amp;lsquo;현재 완료 상태가 아닌데 소프트 딜리트 상태가 아니면서 생성일자가 최신인 데이터&amp;rsquo;이므로 소프트 딜리트 여부도 복합인덱스 컬럼으로 추가해주면 더 적합한 인덱스 설계라고 생각된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 고려할 점은 인덱스 키 길이가 그만큼 늘어난다는 점인데, 삭제 여부는 boolean 타입이므로 충분히 감안할 수 있다고 생각한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 가장 많이 필터링 할 수 있는 것을 앞에서부터 (is_completed, is_deleted, created_at) 순서로 인덱스를 설계했다. 이제 성능을 비교해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 걸기 전은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1365&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XKurr/dJMcabcBOuh/UFyy9Xeys2CE4XY1eLeAl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XKurr/dJMcabcBOuh/UFyy9Xeys2CE4XY1eLeAl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XKurr/dJMcabcBOuh/UFyy9Xeys2CE4XY1eLeAl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXKurr%2FdJMcabcBOuh%2FUFyy9Xeys2CE4XY1eLeAl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1365&quot; height=&quot;194&quot; data-origin-width=&quot;1365&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 건 후&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 인덱스 중 새로 만든 인덱스를 옵티마이저는 더 낫다고 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1347&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnkUQ3/dJMcagSyzNP/fdsKRTwUFjs26kOyMYK1I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnkUQ3/dJMcagSyzNP/fdsKRTwUFjs26kOyMYK1I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnkUQ3/dJMcagSyzNP/fdsKRTwUFjs26kOyMYK1I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnkUQ3%2FdJMcagSyzNP%2FfdsKRTwUFjs26kOyMYK1I1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1347&quot; height=&quot;162&quot; data-origin-width=&quot;1347&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 성능은..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1387&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqBAcB/dJMcaaLzmkn/m3TcjkN1TlanArP4el1adk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqBAcB/dJMcaaLzmkn/m3TcjkN1TlanArP4el1adk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqBAcB/dJMcaaLzmkn/m3TcjkN1TlanArP4el1adk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqBAcB%2FdJMcaaLzmkn%2Fm3TcjkN1TlanArP4el1adk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1387&quot; height=&quot;137&quot; data-origin-width=&quot;1387&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ujhDE/dJMcadOZHlN/i32EtRFa1f82gc0xD6IYD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ujhDE/dJMcadOZHlN/i32EtRFa1f82gc0xD6IYD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ujhDE/dJMcadOZHlN/i32EtRFa1f82gc0xD6IYD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FujhDE%2FdJMcadOZHlN%2Fi32EtRFa1f82gc0xD6IYD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1380&quot; height=&quot;127&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실, 성능 자체는 비슷하다. 이럴 경우 index의 key_length 를 is_deleted를 선택하지 않으면 더 줄일 수 있으니 둘 다 괜찮은 방법이라고 생각한다.&lt;/p&gt;</description>
      <category>DB</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/36</guid>
      <comments>https://icecupregular.tistory.com/36#entry36comment</comments>
      <pubDate>Thu, 2 Apr 2026 21:46:12 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] 버퍼풀과 innodb 버퍼풀 인스턴스 설정</title>
      <link>https://icecupregular.tistory.com/35</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;376&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6h8OP/dJMcabb2O4Y/S38knOMb7xKUT3IeK0KTVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6h8OP/dJMcabb2O4Y/S38knOMb7xKUT3IeK0KTVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6h8OP/dJMcabb2O4Y/S38knOMb7xKUT3IeK0KTVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6h8OP%2FdJMcabb2O4Y%2FS38knOMb7xKUT3IeK0KTVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;376&quot; height=&quot;603&quot; data-origin-width=&quot;376&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;1023&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PfDxK/dJMb99L3ywY/kKCYWheTzYOjkjgcCYr2hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PfDxK/dJMb99L3ywY/kKCYWheTzYOjkjgcCYr2hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PfDxK/dJMb99L3ywY/kKCYWheTzYOjkjgcCYr2hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPfDxK%2FdJMb99L3ywY%2FkKCYWheTzYOjkjgcCYr2hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;470&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;1023&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;버퍼 풀(Buffer Pool)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀은 주 메모리(RAM) 안에 있는 영역으로, InnoDB가 접근되는 테이블 데이터와 인덱스 데이터를 캐시(저장)하는 곳이다. 버퍼 풀 덕분에 자주 쓰는 데이터는 메모리에서 바로 접근할 수 있어 처리 속도가 빨라진다. 전용 서버에서는 보통 물리 메모리의 최대 80%까지 버퍼 풀에 할당하는 경우도 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대량 읽기(read)를 효율적으로 처리하기 위해, 버퍼 풀은 페이지(page) 단위로 나뉘며, 한 페이지에는 여러 행(row)이 들어갈 수 있다. 캐시 관리를 효율적으로 하기 위해 버퍼 풀은 페이지들의 연결 리스트(linked list)로 구현되어 있고, 거의 사용되지 않는 데이터는 LRU(Least Recently Used) 알고리즘의 변형으로 캐시에서 밀려나게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀을 잘 활용해 자주 접근하는 데이터를 메모리에 유지하는 방법을 아는 것은 MySQL 튜닝에서 중요한 요소다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;버퍼 풀 LRU 알고리즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀은 LRU 알고리즘의 변형을 사용한 리스트로 관리된다. 새 페이지를 넣기 위해 공간이 필요하면 가장 오랫동안 사용되지 않은 페이지가 제거되고(evict), 새 페이지는 리스트의 중간(midpoint)에 추가된다. 이 &amp;ldquo;중간 삽입&amp;rdquo;전략은 리스트를 두 개의 서브리스트로 나눠서 취급한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;머리(head): 최근 접근된 새로운(young) 페이지들의 서브리스트&lt;/li&gt;
&lt;li&gt;꼬리(tail): 덜 최근에 접근된 오래된(old) 페이지들의 서브리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 알고리즘은 자주 쓰는 페이지를 new(young) 서브리스트에 유지한다. old 서브리스트는 덜 쓰는 페이지들이며, 이 페이지들이 제거 후보가 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버퍼 풀의 3/8이 old 서브리스트에 할당된다.&lt;/li&gt;
&lt;li&gt;리스트의 중간지점(midpoint) 이 경계로서, new 서브리스트의 꼬리와 old 서브리스트의 머리가 만나는 곳이다.&lt;/li&gt;
&lt;li&gt;InnoDB가 페이지를 버퍼 풀로 읽어올 때, 처음에는 midpoint에 삽입한다.&lt;/li&gt;
&lt;li&gt;old 서브페이지에 있는 페이지를 접근하면 그 페이지는 young 이 되어 new 서브페이지 머리로 이동한다.&lt;/li&gt;
&lt;li&gt;결국 계속 안 쓰인 페이지는 old 서브리스트의 꼬리에 도달해 제거된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;버퍼 풀 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀의 여러 요소를 설정해서 성능을 개선할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이상적으로는, 다른 프로세스가 동작할 메모리를 남겨두되 가능한 한 크게 설정한다. 버퍼 풀이 클수록 InnoDB는 &amp;ldquo;디스크에서 한 번 읽고 이후에는 메모리에서 반복 접근&amp;rdquo; 하는 형태가 되어 메모리 DB처럼 동작한다.&lt;/li&gt;
&lt;li&gt;갑작스런 대량 스캔 때문에 자주 쓰는 데이터들이 밀려나버리는 것을 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;메모리가 충분하면 버퍼 풀을 여러 인스턴스로 나눠 동시 처리 시 경합을 줄일 수도 있다. &amp;rarr; 버퍼 풀 인스턴스 여러 개 설정.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 풀은 기본 설정에서는 보통 1개(하나의 큰 버퍼 풀)처럼 보이지만, 성능/동시성 때문에 여러 인스턴스로 나눠서 여러 개처럼 운영될 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼풀을 여러 인스턴스로 나누는 경우는 꽤 흔하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건이 맞으면 기본 튜닝 항목 중 하나다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나누는 편이 많은 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 RAM이 큰 편(8~16GB) 이고&lt;/li&gt;
&lt;li&gt;동시 접속/쿼리 수가 많아 버푸풀 내부 락/경합이 생길 수 있는 환경&lt;/li&gt;
&lt;li&gt;특히 OLTP(짧은 트랜잭션이 많이 도는 서비스)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이런 환경에서는 innodb_buffer_pool_instances를 여러 개로 두는게 흔하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버퍼 풀 내부에서 동시에 많은 스레드가 부딪히는 경합을 줄여서, 처리량과 지연시간을 개선한다.&lt;/li&gt;
&lt;li&gt;동시 쿼리가 많으면 여러 스레드가 여기 접근하면서 락을 잡고 기다리는 시간이 커진다. 버퍼 풀을 인스턴스로 쪼개면 이런 구조들이 분리되서 대기 시간이 감소되고 처리량이 증가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 도로의 차선을 늘려서 정체를 줄이는 것에 비유할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은, 버퍼 풀에는 메모리 덩어리만 있는 것이 아니라 LRU 관리, free list, flush list 등과 같은 &amp;ldquo;캐시 관리 작업&amp;rdquo;이 계속 일어나고, 백그라운드 스레드가 같은 지점에 몰리면서 락 대기 오버헤드가 커진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 버퍼 풀을 여러 인스턴스로 쪼개면 각 인스턴스가 자기만의 락을 따로 갖게 되어 경합이 분산되고, 처리량이 올라가면서 지연시간은 줄어드는 효과가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면, 캐시 관리의 병목을 줄이려고 쪼개는 튜닝&lt;/p&gt;</description>
      <category>DB</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/35</guid>
      <comments>https://icecupregular.tistory.com/35#entry35comment</comments>
      <pubDate>Thu, 12 Feb 2026 18:09:30 +0900</pubDate>
    </item>
    <item>
      <title>JSCODE 네트워크 스터디를 진행하며 느낀점들(feat. 나의 BDD스터디)</title>
      <link>https://icecupregular.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;평소에 저는 BDD라는 개발 서적 읽기 스터디를 꾸준히 운영해 오고 있습니다. 벌써 거의 반년이 넘어가도록 함께 하고 있는 멤버들도 있네요. 혼자서 개발 서적 읽기란 막막할 수 있지만 모두가 스터디를 통해 필수적으로 읽어야 하는 도서에 대해 한권씩 읽고 나면 책 읽는 습관이 생겨 관성적으로 양질의 지식을 학습하고 체화해볼 수 있다는 점에서 장기적인 관점으로 BDD(Book-Driven-Developers) 스터디를 운영하고 있습니다 ㅎㅎ.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSCODE의 스터디는 어떻게 좋은 스터디를 운영할 수 있을지 노하우를 엿볼 수 있는 기회였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유명 기업의 현직자 멘토님이 제시해주는 방향을 따라 매주 학습을 진행하고 어떤 식으로 면접 스터디를 운영하는지 확인하면서 앞으로의 BDD 스터디 운영에 대한 확신을 얻게된 것 같습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스터디를 어떻게 운영해야 할까에 대한 답을 얻다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스터디를 어떻게 운영하는지 관찰하며 처음 본 것은 의지할 수 있는 가이드라인이 있는 것이었습니다. 매주 멘토님이 내주시는 질문에 대해 스스로 답을 달아보고 주어진 문제들 보다 조금 더 어려운 질문을 늘 포함해서 해당 내용을 채워가면서 성장한다는 느낌을 받을 수 있었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스터디 모임 시간에는 15분 이라는 적정한 시간을 피드백을 주는 사람과 질문을 주는 사람을 따로 두어 면접자가 몰입할 수 있는 환경과 날카로운 피드백을 받을 수 있어서 더 잘 대답하기 위해 노력하는 과정에서 학습 효과를 더해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도 멘토님의 현업에서의 경험을 나눠주시고 의문이 드는 점을 답변해주시는 것들이 모두 스터디를 더 의미있게 만들었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트에 어떻게 적용해볼 수 있을까에 대한 아쉬움&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 아쉬운 점은 분명 있었습니다. 면접 스터디이기때문에 다소 이론 암기에만 그친 부분입니다. 이론으로 배운 내용이 내것이 되려면 실제로 코드로 작성해보거나 직접 눈으로 확인하는 과정이 꼭 필요하다고 생각했습니다. 스스로 해 본 경험은 그 지식을 더 잘 기억하게 해주고 내것으로 만들 수 있는 아주 좋은 방법이라고 생각하기 때문입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제로 구현해 보는 것이 좋겠다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 제가 운영하는 스터디에는 그 부분을 보완하여 실제로 운영체제 스터디를 진행하는 와중에 직접 CPU 스케줄링 알고리즘을 코드로 구현하는 과제를 제출했고 스터디원들의 만족도도 꽤 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경험을 통해 앞으로 어떻게 스터디를 꾸려가야 할지에 대한 물음표들에 많은 부분 결론지을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Book-Driven-Developers/25-OperatingSystem&quot;&gt;https://github.com/Book-Driven-Developers/25-OperatingSystem&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766330163757&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Book-Driven-Developers/25-OperatingSystem: 25년 4분기 혼자 공부하는 컴퓨터네트워크와 운영체제 북 &quot; data-og-description=&quot;25년 4분기 혼자 공부하는 컴퓨터네트워크와 운영체제 북 스터디. Contribute to Book-Driven-Developers/25-OperatingSystem development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Book-Driven-Developers/25-OperatingSystem&quot; data-og-url=&quot;https://github.com/Book-Driven-Developers/25-OperatingSystem&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dsZJ1N/hyZP2XrmUn/Yi1NkCK19iFgk07aWAhDLk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bxoqso/hyZP3B1FCe/zZ7DF7lT6miaqLx4L9uMQK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Book-Driven-Developers/25-OperatingSystem&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Book-Driven-Developers/25-OperatingSystem&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dsZJ1N/hyZP2XrmUn/Yi1NkCK19iFgk07aWAhDLk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bxoqso/hyZP3B1FCe/zZ7DF7lT6miaqLx4L9uMQK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Book-Driven-Developers/25-OperatingSystem: 25년 4분기 혼자 공부하는 컴퓨터네트워크와 운영체제 북&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;25년 4분기 혼자 공부하는 컴퓨터네트워크와 운영체제 북 스터디. Contribute to Book-Driven-Developers/25-OperatingSystem development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로도 꾸준히 스터디를 진행하며 팀원들에게 좋은 동료가 되는 방법, 꾸준한 학습 루틴을 갖는 법, 지식을 더 잘 체화하는 방법에 대한 방법론을 많이 고민하고 적용해볼 예정입니다. 감사합니다~&lt;/p&gt;</description>
      <category>회고</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/34</guid>
      <comments>https://icecupregular.tistory.com/34#entry34comment</comments>
      <pubDate>Mon, 22 Dec 2025 00:17:04 +0900</pubDate>
    </item>
    <item>
      <title>[컴퓨터 네트워크] 네트워크 레이어</title>
      <link>https://icecupregular.tistory.com/33</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;IP 주소란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 주소는 네트워크에서 호스트와 라우터의 각 인터페이스마다 할당되어 있는 식별 번호이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(인터페이스: 호스트와 물리 링크 사이의 경계)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ip주소는 네트워크 주소와 호스트 주소로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ip에는 두 가지 버전이 존재한다.(IPv4, IPv6)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;IPv4와 IPv6는 어떤 차이점이 있을까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IPv4
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;32비트 이진수로 구성된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;11000001 00100000 11011000 00001001&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일반적으로 다음과 같이 8비트 단위로 끊어서 4부분으로 10진수와 .으로 표기한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;193.32.216.9&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;IPv6
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;128비트 이진수로 구성된다.&lt;/li&gt;
&lt;li&gt;다음과 같이 16비트 단위로 끊어서 8부분으로 16진수 와 :으로 표기한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2004:2ba8:13aa:0011:0000:0000:0000:abaa&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서브넷과 서브넷 마스크의 차이&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drqK5n/dJMcadAvKws/G9KRQrVMdZSQxkqqUGVdn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drqK5n/dJMcadAvKws/G9KRQrVMdZSQxkqqUGVdn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drqK5n/dJMcadAvKws/G9KRQrVMdZSQxkqqUGVdn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrqK5n%2FdJMcadAvKws%2FG9KRQrVMdZSQxkqqUGVdn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;411&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브넷은 네트워크의 부분망이다. 위 그림에서 파란색으로 표시된 부분망 3개를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서브넷의 각 IP주소의 왼쪽 24비트(8bit.8bit.8bit.8bit)는 동일함을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 서브넷 마스크는 24이다. 이는 32비트 주소의 왼쪽 24비트가 서브넷 주소라는 것을 가리킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 왼쪽 24비트는 네트워크 주소 나머지 8비트는 호스트 주소이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서브넷에서는 2^8 -2 개의 호스트 IP가 존재할 수 있다. 왜 2^8 에서 -2를 ? &amp;rarr; 바로 브로드 캐스트 주소와 네트워크 주소로 사용되기 때문이다. 호스트 주소 11111111는 브로드 캐스트 주소로 사용된다.(브로드 캐스트 주소는 255.255.255.255 이다. 같은 서브넷에 존재하는 모든 호스트들에게 패킷이 전송된다.) 호스트 주소 00000000 는 네트워크 주소(서브넷 자체를 식별하기 위한 이름표)로 사용된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라우팅이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅은 어떤 네트워크 안에서 통신 데이터를 보낼 때 최적의 경로를 선택하는 과정이다. 최적의 경로는 주어진 데이터를 가장 짧은 거리로 또는 가장 적은 시간 안에 전송할 수 있는 경로다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무엇을 기준으로?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적지 IP가 어느 네트워크 대역(CIDR 프리픽스)에 속하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어디서 결정하나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우터(또는 L3 스위치)가 가진 라우팅 테이블&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 테이블은 보통 두 방식으로 채워진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정적 라우팅: 사람이 직접 경로를 넣음&lt;/li&gt;
&lt;li&gt;동적 라우팅: OSFP/RIP/BGP 같은 라우팅 프로토콜이 자동으로 경로를 학습/갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CIDR(Classless Inter-Domain Routing)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CIDR는 클래스 주소보다 더 유연하게 IP 주소를 나눠주는 라우팅 기법이다.&lt;/li&gt;
&lt;li&gt;IPv4 주소를 보다 효율적으로 사용하게 해준다.&lt;/li&gt;
&lt;li&gt;32비트 IP 주소를 아래와 같이 두 부분으로 나눈다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;a.b.c.d / x&lt;/li&gt;
&lt;li&gt;x는 처음 나오는 주소 부분의 비트 수를 의미한다. 이를 해당 주소의 prefix라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예: 10.0.0.0/16&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/16 &amp;rarr; 앞 16비트가 네트워크&lt;/li&gt;
&lt;li&gt;남은 16비트가 호스트 &amp;rarr; 호스트 경우의 수가 훨씬 많아짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범위:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;10.0.0.0 ~ 10.0.255.255&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CIDR(사이더)가 라우팅 테이블을 줄이는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 회사 내부망이 192.168.1.0/24라면 호스트는 200대 있을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우터가 호스트를 다 저장한다면:&lt;/li&gt;
&lt;li&gt;192.168.1.10/32, 192.168.1.11/32, &amp;hellip; 200줄 필요&lt;/li&gt;
&lt;li&gt;실제로는 보통 이렇게 저장함:&lt;/li&gt;
&lt;li&gt;192.168.1.0/24 -&amp;gt; (이쪽으로 보내면 됨) &lt;b&gt;딱 1줄&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 &amp;ldquo;저 네트워크로 가는 길&amp;rdquo;만 알면 되고, &lt;b&gt;그 네트워크 안에서 어떤 호스트인지(마지막 배송)&lt;/b&gt; 는 그 네트워크 내부에서 해결돼(스위치/ARP 같은 것).&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;라우터의 일:&lt;/b&gt; &amp;ldquo;어느 네트워크로 보낼지&amp;rdquo; 결정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마지막 홉(로컬망)의 일:&lt;/b&gt; &amp;ldquo;그 네트워크 안에서 정확히 어떤 기기인지&amp;rdquo; 찾아서 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Public IP와 Privte IP&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공인 IP(Pulbic IP)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷 사용자의 로컬 네트워크를 식별하기 위해 ISP(인터넷 서비스 제공자)가 제공하는 IP주소입니다. 외부로 공개되어 있는 IP주소입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공인 IP는 전세계에서 유일한 IP 주소를 갖습니다.&lt;/li&gt;
&lt;li&gt;공인 IP 주소가 외부에 공개되어 있기에 인터넷에 다른 PC로부터의 접근이 가능합니다.&lt;/li&gt;
&lt;li&gt;IPv4 주소의 부족으로 인해 IPv6 주소 체계가 도입되어 점차 확산되고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사설 IP(Private IP)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공인 IP주소가 공개형이라면 사설 IP 주소는 폐쇄형이다.&lt;/li&gt;
&lt;li&gt;즉, 사설 IP주소는 외부에 외부에 공개되지 않아 외부에서 검색, 접근이 불가능하다.&lt;/li&gt;
&lt;li&gt;사설 네트워크에 속한 여러 개의 호스트가 하나의 공인 IP 주소를 사용해서 인터넷에 접속하기 위해 사용한다.&lt;/li&gt;
&lt;li&gt;사설 IP는 사설망 안에서만 의미 있는 주소이다.&lt;/li&gt;
&lt;li&gt;사설 IP 주소는 주소 대역이 3개로 고정되어 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://192.168.xxx.xxx&quot;&gt;192.168.xxx.xxx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://172.10.xxx.xxx&quot;&gt;172.10.xxx.xxx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://10.xxx.xxx.xxx&quot;&gt;10.xxx.xxx.xxx&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예를 들어 인터넷 공유기 IP 주소가 100.100.100.100 이고, 공유기에 연결된 컴퓨터에는 192.168.0.10 등과 같은 사설 IP 주소가 할당된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라우팅 프로토콜이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라우팅 프로토콜(routing protocol)은 라우터들이 서로 정보를 주고받아서 &amp;ldquo;어떤 목적지 네트워크로 가려면 어느 이웃(next hop)으로 보내야 하는지&amp;rdquo;를 자동으로 계산/갱신하게 해주는 알고리즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 프로토콜 vs 라우팅(포워딩)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우팅 프로토콜: 길을 학습/계산해서 라우팅 테이블을 만든다&lt;/li&gt;
&lt;li&gt;포워딩: 실제 패킷이 들어오면 테이블 보고 다음 홉으로 전달한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;IP는 어떻게 할당될까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 직접 기기에 설정 &amp;rarr; 정적 IP(Static)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DHCP을 사용하여 동적으로 할당 &amp;rarr; DHCP(일반적으로 사용)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DHCP(동적 할당) - Dynamic Host Configuration Protocol&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL8iKF/dJMcaiokoGd/DrSmhMInp6JYi3x7eePGZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL8iKF/dJMcaiokoGd/DrSmhMInp6JYi3x7eePGZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL8iKF/dJMcaiokoGd/DrSmhMInp6JYi3x7eePGZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL8iKF%2FdJMcaiokoGd%2FDrSmhMInp6JYi3x7eePGZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;435&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DHCP는 새로운 호스트가 도착하면 IP를 할당해준다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동작과정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DHCP 서버 발견&lt;/li&gt;
&lt;li&gt;DHCP 서버 제공&lt;/li&gt;
&lt;li&gt;DHCP 요청&lt;/li&gt;
&lt;li&gt;DHCP ACK&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NAT(Network Address Translation)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVx67p/dJMcai2TkLx/FyM4FF86j8JaQ0SVktFKc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVx67p/dJMcai2TkLx/FyM4FF86j8JaQ0SVktFKc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVx67p/dJMcai2TkLx/FyM4FF86j8JaQ0SVktFKc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVx67p%2FdJMcai2TkLx%2FFyM4FF86j8JaQ0SVktFKc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;596&quot; height=&quot;397&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NAT는 공인 IP와 사설 IP를 변환해주는 기술이다.&lt;/li&gt;
&lt;li&gt;위 그림을 보자 IP주소 138.76.29.7을 갖는 라우터(또는 공유기)에 3대의 컴퓨터가 속해있다.&lt;/li&gt;
&lt;li&gt;10.0.0.4 라는 사설 IP 주소를 갖는 서브넷에 10.0.0.1, 10.0.0.2, 10.0.0.3 3개의 사설 IP주소를 갖고 있는 컴퓨터가 속해있다.&lt;/li&gt;
&lt;li&gt;사설 IP 10.0.0.1를 갖는 호스트가 IP 128.119.40.186인 웹 서버(포트80)에게 웹 페이지를 요청한다고 가정하자.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트는 자신의 IP와 출발지 포트번호 3345와 함께 패킷을 전송한다.&lt;/li&gt;
&lt;li&gt;NAT 라우터는 패킷을 받아서 패킷에 대한 출발지 포트번호 5001를 생성한다. 인터넷으로 연결되는 라우터 인터페이스 공인 IP 138.76.29.7 으로 출발지 IP 주소를 변경하고 새롭게 생성한 출발지 포트번호 5001로 포트번호를 바꾼다.&lt;/li&gt;
&lt;li&gt;웹 서버는 NAT 라우터 IP 138.76.29.7와 5001 포트번호에 대해 응답한다.&lt;/li&gt;
&lt;li&gt;패킷이 NAT 라우터에 도착했을 때 라우터는 사설망 IP 주소 10.0.0.1 포트번호 3345로 응답 패킷을 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ICMP(Internet Control Message Protocol)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 네트워크에서 &amp;ldquo;오류/상태를 알리는 메시지&amp;rdquo;를 주고받는 제어용 프로토콜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 옮기기 보단 &amp;ldquo;지금 전달이 안돼&amp;rdquo;, &amp;ldquo;시간 초과&amp;rdquo;, &amp;ldquo;경로를 알려줄게&amp;rdquo; 같은 피드백을 전달한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어디 계층?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 네트워크 계층쪽으로 분류하고 ICMP 메시지는 IP 패킷 안에 실려서 이동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;왜 필요할까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IP 자체는 실패/문제를 자세히 말해주지 않는다.&lt;/li&gt;
&lt;li&gt;ICMP가 그 빈자리를 채워서 진단/에러 리포트를 해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대표적인 ICMP 메시지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Echo Request / Echo Reply: ping이 쓰는 것(살아 있나 확인)&lt;/li&gt;
&lt;li&gt;Destination Unreachable: 목적지에 못 감&lt;/li&gt;
&lt;li&gt;Time Exceeded: TTL이 0이 돼서 폐기됨&lt;/li&gt;
&lt;li&gt;Redirect: 그 목적지는 나 말고 다른 게이트로 가는 것이 더 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TCP/UDP랑 관계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP/UDP는 데이터 전송용 vs ICMP는 네트워크 상태 알림용&lt;/li&gt;
&lt;li&gt;UDP로 보냈는데 상대 포트가 닫혀 있으면, 종종 ICMP Port Unreachable이 돌아와서 포트 닫힘 상태를 알려줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브라우저에 www.google.com을 입력하면 어떤 일이 일어날까&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주소창에 URL을 입력하고 ENTER입력&lt;/li&gt;
&lt;li&gt;웹 브라우저가 URL을 해석&lt;/li&gt;
&lt;li&gt;URL이 문법에 맞으면 URL에 인코딩을 적용&lt;/li&gt;
&lt;li&gt;HSTS(HTTP Strict Transport Security) 목록을 로드한다.&lt;/li&gt;
&lt;li&gt;DNS를 조회하여 IP주소를 얻어낸다.&lt;/li&gt;
&lt;li&gt;ARP(Address Resolution Protocol)로 IP주소에 대한 MAC 주소를 얻어낸다.&lt;/li&gt;
&lt;li&gt;TCP 통신을 시작한다.&lt;/li&gt;
&lt;li&gt;HTTPS인 경우 SSL(TLS) handshake 과정이 추가된다&lt;/li&gt;
&lt;li&gt;HTTP 프로토콜로 요청한다.&lt;/li&gt;
&lt;li&gt;HTTP 서버가 응답한다.&lt;/li&gt;
&lt;li&gt;웹 브라우저가 렌더링한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브라우저 주소창에 www.google.com을 입력했을 때 웹 브라우저 요청 흐름을 패킷 관점에서 설명해보자 (TCP/IP 통신의 흐름)&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;다음 4가지를 먼저 확정한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;목적지 IP(DNS 결과)&lt;/li&gt;
&lt;li&gt;나의 IP/ 서브넷 / 기본 게이트웨이&lt;/li&gt;
&lt;li&gt;기본 게이트웨이의 MAC&lt;/li&gt;
&lt;li&gt;사용할 전송계층 프로토콜/포트&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;PC&amp;rarr;DNS로 질의결과로 IP 값 획득&lt;/li&gt;
&lt;li&gt;ARP
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;구글 서버는 내 LAN 밖에 있으니까, 내 PC는 패킷을 기본 게이트웨이(공유기)로 보내야 한다. 근데 이더넷 프레임에는 목적지 MAC이 필요하니, 게이트웨이의 MAC을 모르면 먼저 ARP를 한다.(이미 ARP 캐시에 있으면 스킵 가능)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;TCP 3-way handshake
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;목적지 IP(구글)로 TCP연결을 건다.&lt;/li&gt;
&lt;li&gt;집 공유기 환경이면 NAT 때문에 공유기가 내 IP를 공인IP로 바꿔서 인터넷에 보낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;TLS handshake
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TCP 연결 위에서 TLS를 올린다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;HTTP 요청/응답&lt;/li&gt;
&lt;li&gt;연결 유지/종료
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;keep-alive로 연결을 유지할 수도 있고 필요 없어지면, FIN&amp;rarr;ACK&amp;rarr;FIN&amp;rarr;ACK(4-way close)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/3(QUIC): TCP가 아니라 UDP 443로 시작할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>네트워크</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/33</guid>
      <comments>https://icecupregular.tistory.com/33#entry33comment</comments>
      <pubDate>Mon, 15 Dec 2025 16:52:11 +0900</pubDate>
    </item>
    <item>
      <title>[컴퓨터 네트워크] TCP &amp;amp; UDP</title>
      <link>https://icecupregular.tistory.com/32</link>
      <description>&lt;h1&gt;UDP&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UDP란&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxbsHn/dJMcafd2YCN/7aDPjYDxAf3Dcfmc1GTw80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxbsHn/dJMcafd2YCN/7aDPjYDxAf3Dcfmc1GTw80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxbsHn/dJMcafd2YCN/7aDPjYDxAf3Dcfmc1GTw80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxbsHn%2FdJMcafd2YCN%2F7aDPjYDxAf3Dcfmc1GTw80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;148&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User Data Protocol의 약자로 인터넷에서 데이터를 보낼 때 쓰는 전송 계층 프로토콜 중 하나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;빨리 보내지만, 잃어버리거나 섞여도 별 말 안하는 프로토콜&amp;rdquo;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장단점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;빠르다(오버헤드가 적음)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;연결 설정(3-way handshake 등)이 없다.&lt;/li&gt;
&lt;li&gt;헤더가 작고, 추가 기능이 적어서 처리 비용이 낮다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;지연에 민감한 애플리케이션에 유리&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;비연결형(Connectionless)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트-서버 간에 연결상태를 따로 유지하지 않음&lt;/li&gt;
&lt;li&gt;서버 입장에서는 많은 클라이언트를 상대할 때도 상태를 덜 들고 있어서 리소스 부담이 적음.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;브로드캐스트, 멀티캐스트 지원
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;동일 네트워크에서 여러 호스트에게 한 번에 데이터를 보내기 좋음&lt;/li&gt;
&lt;li&gt;예시: 게임 서버의 위치 정보 방송, 스트리밍, 로컬 네트워크 서비스 검색 등&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션에서 자유로운 제어 가능
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;신뢰성, 재전송, 순서 보장 등을 애플리케이션 레벨에서 직접 구현할 수 있음&lt;/li&gt;
&lt;li&gt;TCP가 너무 무겁다, 우리 서브시에 맞는 규칙으로 커스텀하고 싶다고 할 때 선택할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;실시간 서비스에 적합
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;약간의 패킷 손실은 괜찮고 지연이 더 치명적인 서비스에 적합
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인터넷 전화&lt;/li&gt;
&lt;li&gt;화상 회의&lt;/li&gt;
&lt;li&gt;온라인 게임&lt;/li&gt;
&lt;li&gt;실시간 스트리밍&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;신뢰성이 없다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;패킷이 유실될 수 있음&lt;/li&gt;
&lt;li&gt;패킷이 중복해서 도착할 수 있음&lt;/li&gt;
&lt;li&gt;패킷이 순서가 뒤바뀌어 도착할 수 있다.&lt;/li&gt;
&lt;li&gt;이걸 보장해주는 건 애플리케이션이 직접 처리해야 함.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;혼잡 제어/ 흐름 제어 없음
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TCP는 네트워크가 혼잡하면 전송 속도를 조절하지만, UDP는 그런게 없음.&lt;/li&gt;
&lt;li&gt;잘못 쓰면 네트워크를 과부하 상태로 만들 수 있음.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;세션 개념이 없음
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;연결 상태를 유지하지 않으니 누가 언제 연결하고 상태가 어떤지 프로토콜 차원에서 알 수 없음&lt;/li&gt;
&lt;li&gt;로그인/세션/상태 관리는 모두 애플리케이션 쪽에서 구현해야 함&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;체크섬&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UDP는 checksum 헤더를 통해 수신된 데이터에 변조되지 않았는지 여부만 판단할 수 있다.(네트워크로 송신된 데이터는 네트워크의 물리적 구성요소에 의해 비트 오류가 발생할 수 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UDP 세그먼트를 word(16비트)단위로 나눈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나누어진 각 word를 모두 더한 다음 1의 보수 연산을 수행하여 checksum을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UDP 세그먼트 헤더의 checksum에 만든 checksum을 넣고 세그먼트를 송신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수신측에서 세그먼트를 수신하면 그 세그먼트에 대해 checksum을 다시 만든 다음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수신된 checksum과 비교하면 오류 여부를 판단할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떤 서비스에 활용?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UDP는 빠르기 때문에 DNS와 SNMP등의 프로토콜에서 활용된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DNS의 목적은 단지 도메인 네임에 대한 IP주소를 얻는 것이다. 따라서 오버헤드가 적고 빠른 UDP 위에서 동작하는 것이 적합하다.&lt;/li&gt;
&lt;li&gt;SNMP는 주기적으로 네트워크 장비에 대한 정보를 수집하고 관리하는 프로토콜이다. 네트워크 장비는 수백대 수천대가 될 수 있다. 따라서 오버헤드가 적고 빠른 UDP가 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UDP는 인터넷 전화 또는 스트리밍 서비스에 적합하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷 전화 또는 스트리밍 서비스는 최소한의 전송률이 보장되면서 약간의 데이터 손실을 허용한다.&lt;/li&gt;
&lt;li&gt;따라서 속도가 빠르지만 신뢰적인 데이터 전송을 보장하지 못하는 UDP가 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;신뢰적 데이터 전송의 원리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전송 후 대기 프로토콜(Stop and Wait Protocol)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pseSz/dJMcagcXImv/q2HxToaiMPt1IHVEDuWE9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pseSz/dJMcagcXImv/q2HxToaiMPt1IHVEDuWE9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pseSz/dJMcagcXImv/q2HxToaiMPt1IHVEDuWE9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpseSz%2FdJMcagcXImv%2Fq2HxToaiMPt1IHVEDuWE9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;303&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전송 후 대기 프로토콜은 데이터를 전송한 후 수신자로부터 확인(ACK)을 받을 때까지 대기하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로토콜은 송신자가 한 개의 프레임을 전송하고, 수신자로부터 응답을 받을 때까지 기다리는 방식으로 동작한다. 수신자는 전송받은 프레임에 대해 ACK 또는 NAK(부정) 신호를 송신자에게 되돌려 보낸다. 송신자는 ACK 신호를 받으면 다음 프레임을 전송하고 NAK 신호를 받으면 같은 프레임을 재전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 접근 방식은 안정적인 데이터 전송을 보장하는 데 도움이 되지만 특히 발신자와 수신자 사이에 상당한 왕복 시간이 있는 경우 속도가 느려지고 네트워크 링크 활용도가 낮아질 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육용, 개념 설명용, 단순 무선 통신 시스템에서만 사용됨.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이프라인 프로토콜(Pipelined Protocol)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 알아봤듯이 신뢰적 데이터 전송은 Stop-and-wait 프로토콜을 통해 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 데이터를 전송한 뒤 수신자로부터 데이터 전송에 대한 결과를 피드백 받은 후 다음 데이터를 전송하는 방식이다. 하지만 이러한 Stop-and-wait 방식은 송신자가 패킷을 전송하는데 오랜 시간이 걸린다. 송신자는 패킷이 올바르게 전송되었다는 수신자의 피드백(ACK)을 받고나서 다음 패킷을 전송하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 성능 이슈를 파이프라이닝(pipelining)을 통해 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라이닝은 전송된 패킷에 대해 피드백을 받지 않고도 다음 패킷들을 전송하도록 허용하여 링크 이용률을 높이는 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라이닝에서도 패킷 손상과 손실, 순서가 맞지 않는 전송이 발생하는데, 이러한 오류에 대해 두 가지 기본적인 접근 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 GBN(Go-Back-N)과 SR(Selective Repeat)이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP 파이프라이닝&lt;/b&gt; &amp;sub; &lt;b&gt;파이프라인 프로토콜&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; HTTP에서 파이프라인 기법을 쓴 &lt;b&gt;구체적인 기능 이름&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &amp;ldquo;HTTP는 파이프라인 프로토콜이 될 수 있다&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP는 Sliding Window + 파이프라이닝 + ACK 누적 방식을 사용한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SR(Selective Repeat)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Selective Repeat 프로토콜은 손실되거나 손상된 패킷에 대해서만 재전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GBN처럼 윈도우 크기만큼 패킷들을 전송하지 않는다. 즉, 불필요한 재전송을 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SR 수신자는 누적확인 응답을 하지 않는다. 순서번호가 앞서는 패킷이 도착하면 그대로 수신한다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;송신자의 행동&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;패킷 송신하기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상위 계층에서 데이터를 받으면, 먼저 윈도우가 가득 찼는지 확인한다.&lt;/li&gt;
&lt;li&gt;윈도우가 가득 차지 않았다면, 다음 순서 번호에 해당하는 패킷이 생성되고 전송된다.&lt;/li&gt;
&lt;li&gt;윈도우가 가득 차 있다면, 상위 계층으로부터 받은 데이터를 반환하며 윈도우가 가득차서 전송을 할 수 없음을 나타낸다.(송신자는 나중에 다시 데이터를 보내서 전송을 시도할 것이다.)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;ACK 수신하기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;ACK가 수신되었을 때, SR 송신자는 그 ACK가 윈도우에 있다면, 그 패킷을 수신된 것(초록색)으로 표기한다.&lt;/li&gt;
&lt;li&gt;수신된 패킷의 순서번호가 윈도우의 send_base와 같다면&lt;/li&gt;
&lt;li&gt;윈도우는 가장 작은 순서번호를 가지면서, 아직 확인응답이 되지 않은 패킷의 순서번호로 이동한다.&lt;/li&gt;
&lt;li&gt;윈도우가 이동한 후 윈도우 내에 아직 전송되지 않은 패킷이 있다면 전송한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;타이머 동작시키기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 패킷마다 논리 타이머가 존재한다.(하나의 하드웨어 타이머는 여러 개의 논리 타이머를 구현한다.)&lt;/li&gt;
&lt;li&gt;특정 패킷의 타이머가 타임아웃되면 해당 패킷은 개별적으로, 선택적으로 재전송된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수신자의 행동&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;rcv_base ~ rcv_base + N - 1 범위(수신 윈도우 범위)의 순서번호를 가지는 패킷이 수신되는 경우
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;처음 수신한 패킷이라면 수신 버퍼에 저장되고, ACK를 보낸다.&lt;/li&gt;
&lt;li&gt;만약 수신한 패킷의 순서번호가 rcv_base라면, 이 패킷부터 연속된 패킷들을 상위 계층에 보낸다.&lt;/li&gt;
&lt;li&gt;윈도우는 앞으로 이동한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;rcv_base -N ~ rcv_base - 1 범위(수신 윈도우 이전 범위)의 순서번호를 가지는 패킷이 수신되는 경우
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이전에 확인 응답한 것이라도 ACK를 보낸다.(송신 측 윈도우 이동을 위해)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;이외의 경우(수신 윈도우 이후 범위)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수신된 패킷을 무시한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SR 동작 예시&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;1075&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJPiVZ/dJMcabipQbd/aARiYHRWNIS1nFakRoiSTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJPiVZ/dJMcabipQbd/aARiYHRWNIS1nFakRoiSTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJPiVZ/dJMcabipQbd/aARiYHRWNIS1nFakRoiSTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJPiVZ%2FdJMcabipQbd%2FaARiYHRWNIS1nFakRoiSTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;482&quot; height=&quot;446&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;1075&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;송신, 수신 윈도우 크기가 4인 SR 프로토콜의 동작 예시다.&lt;/li&gt;
&lt;li&gt;위 그림에서 수신자는 0, 1번 패킷을 수신했고, 2번 패킷을 수신하지 못했고, 3, 4, 5번 패킷을 수신했다.&lt;/li&gt;
&lt;li&gt;2번 패킷에 대한 타임아웃이 발생하고 2번 패킷만 재전송된다.&lt;/li&gt;
&lt;li&gt;수신자는 재전송된 2번 패킷을 수신하고 ACK2을 송신자에게 보낸다. 이때 2번 패킷은 수신 윈도우의 rcv_base와 일치하고&lt;/li&gt;
&lt;li&gt;2번 패킷부터 연달아 수신이 된 패킷들을 상위 계층에 전송하고, 수신측 윈도우를 슬라이딩한다.&lt;/li&gt;
&lt;li&gt;이제 수신 윈도우는 6,7,8,9번의 범위를 갖게된다.&lt;/li&gt;
&lt;li&gt;수신자는 send_base에 해당하는 2번 패킷이 올바르게 수신했음을 ACK2로부터 알아내고, 송신 윈도우를 슬라이딩한다.&lt;/li&gt;
&lt;li&gt;위 그림을 보면 송신자의 윈도우와 수신자의 윈도우가 항상 같지 않음을 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GBN(Go-Back-N)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GBN에서 송신자는 송신한 패킷에 대한 확인 응답 없이, 최대 N개의 패킷을 전송할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 크기가 N인 윈도우로 표현한다.(N은 흐름제어와 혼잡제어에 의해 조정된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;송신한 패킷이 올바르게 수신 측에 도착하여 확인 응답을 받으면, 윈도우는 앞으로 이동하고 다음 패킷을 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 슬라이딩 윈도우 프로토콜(sliding-window protocol)이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2SWGG/dJMcahJHYlm/TFdYtc915EYQ5ljz8gyiM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2SWGG/dJMcahJHYlm/TFdYtc915EYQ5ljz8gyiM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2SWGG/dJMcahJHYlm/TFdYtc915EYQ5ljz8gyiM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2SWGG%2FdJMcahJHYlm%2FTFdYtc915EYQ5ljz8gyiM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;457&quot; height=&quot;98&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GBN 송신자의 행동&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;패킷 송신하기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상위 계층에서 데이터를 받으면, 먼저 윈도우가 가득 찼는지 확인한다.&lt;/li&gt;
&lt;li&gt;윈도우가 가득차지 않았다면, 다음 순서 번호에 해당하는 패킷이 생성되고 전송된다.&lt;/li&gt;
&lt;li&gt;윈도우가 가득 차 있다면, 상위 계층으로부터 받은 데이터를 반환하며 윈도우가 가득 차서 전송할 수 없음을 나타낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;ACK 수신하기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GBN 프로토콜에서 순서번호 N을 가진 패킷에 대한 확인 응답은 N까지의 순서번호를 가진 모든 패킷들에 대한 확인 응답이다. &amp;rArr; 누적 확인 응답(cumulative acknowledgement)&lt;/li&gt;
&lt;li&gt;예를들어 송신자가 수신자로부터 ACK 7을 받았다면 7번 패킷까지 올바르게 전송되었음을 의미한다.&lt;/li&gt;
&lt;li&gt;확인 후 윈도우를 앞으로 이동시키고, 다음 패킷을 전송한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;타이머 동작시키기
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;패킷이 손실되거나 매우 긴 전송 지연으로 타임아웃이 발생한다면 해당 패킷부터 윈도우 크기 N만큼 패킷을 재전송한다. 그래서 Go back N이다.&lt;/li&gt;
&lt;li&gt;송신자는 전송되었지만 아직 확인을 받지 못한 패킷 중 가장 오래된 것에 대한 단일 타이머를 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GBN 수신자의 행동&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수신된 패킷의 순서가 올바른 경우
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;직전 수신한 패킷의 번호가 n-1이고 이번에 수신한 패킷의 번호가 n이라면 수신자는 n번 패킷에 대한 ACK를 송신자에게 전송하고 상위 계층에 n번 패킷의 데이터를 전달한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;수신된 패킷의 순서가 올바르지 않은 경우
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수신자는 순서가 잘못된 패킷들을 버린다.&lt;/li&gt;
&lt;li&gt;그리고 가장 최근에 올바르게 수신된 패킷에 대해 ACK를 보낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GBN 동작 예제&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DLVSw/dJMcaioikZb/RXkQgIHkvzLjdH4C39NTTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DLVSw/dJMcaioikZb/RXkQgIHkvzLjdH4C39NTTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DLVSw/dJMcaioikZb/RXkQgIHkvzLjdH4C39NTTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDLVSw%2FdJMcaioikZb%2FRXkQgIHkvzLjdH4C39NTTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;362&quot; height=&quot;447&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 윈도우 크기가 4인 경우에 대한 GBN 프로토콜의 동작과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우의 크기가 4이기 때문에 송신자는 0, 1, 2, 3번 패킷을 확인 응답받지 않고 연속적으로 송신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전송한 각 패킷에 대한 ACK을 받으면, 윈도우는 앞으로 이동하고 송신자는 다음 순서를 갖는 패킷을 송신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 2번 패킷은 손실되었다. 수신자는 2번 패킷을 못받은 채로 3, 4, 5번 패킷을 수신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 수신자는 2번 패킷을 못받은 상태이므로 올바르게 수신한 3,4,5번 패킷을 버리고 ACK1을 송신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 송신 측을 보면 타이머가 Time out 되는 것을 볼 수 있다. GBN에서 Time out이 되면 수신 확인이 되지 못한 가장 오래전에 전송한 패킷부터 N만큼 재전송한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GBN의 장단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GBN은 파이프라이닝을 가능하게 하여, 링크 이용률을 높여준다는 장점이 있다.&lt;/li&gt;
&lt;li&gt;하지만, GBN에서 패킷이 손실되면 N개 만큼의 패킷을 불필요하게 재전송하는 단점이 있다.&lt;/li&gt;
&lt;li&gt;네트워크에 손실이 많이 발생할수록 네트워크 파이프 라인(링크)는 불필요한 재전송 패킷으로 채워진다는 단점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;TCP&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuR11I/dJMcahiCT0X/wOVBJSGQxpjsVakcuiJeTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuR11I/dJMcahiCT0X/wOVBJSGQxpjsVakcuiJeTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuR11I/dJMcahiCT0X/wOVBJSGQxpjsVakcuiJeTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuR11I%2FdJMcahiCT0X%2FwOVBJSGQxpjsVakcuiJeTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;375&quot; height=&quot;204&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TCP란?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP는 신뢰적인 데이터 전송을 보장한다.&lt;/li&gt;
&lt;li&gt;연결지향형이다.(Connection-oriented)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3 way handshake(연결 설정)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwFP4/dJMcaihxvMQ/Y47RoAlfJAdPGxJNMKL1Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwFP4/dJMcaihxvMQ/Y47RoAlfJAdPGxJNMKL1Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwFP4/dJMcaihxvMQ/Y47RoAlfJAdPGxJNMKL1Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmwFP4%2FdJMcaihxvMQ%2FY47RoAlfJAdPGxJNMKL1Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;393&quot; height=&quot;269&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-way-handshake는 신뢰적인 데이터 전송을 보장하기 위해 3개의 패킷을 주고 받으며 사전 연결 설정을 수립하는 과정이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: 클라이언트 &amp;rarr; 서버(SYN)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 서버에게 연결 요청 메시지를 전송하는 단계다.&lt;/li&gt;
&lt;li&gt;SYN 플래그 비트를 1로 설정하고, 랜덤으로 Sequence Number를 지정한 세그먼트를 전송한다.&lt;/li&gt;
&lt;li&gt;1단계 종료 후 포트 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트: CLOSED(포트가 닫힌 상태)&lt;/li&gt;
&lt;li&gt;서버: LISTEN(포트가 열린 상태로 연결 요청 대기 중)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: 서버 &amp;rarr; 클라이언트(SYN + ACK)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 클라이언트의 연결 요청을 수락하여, 클라이언트의 포트도 열어 달라는 메시지를 전송하는 단계다.&lt;/li&gt;
&lt;li&gt;SYN 플래그 비트를 1로 설정하고, ACK 플래그 비트를 1로 설정하고, Acknowledgement Number 필드를 &amp;ldquo;Sequence Number + 1&amp;rdquo;로 지정한 세그먼트를 전송한다.&lt;/li&gt;
&lt;li&gt;클라이언트는 서버의 ACK를 통해 현재 서버가 alive 임을 확인한다.&lt;/li&gt;
&lt;li&gt;2단계 종료 후 포트 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트: CLOSED &amp;rarr; ESTABLISHED(포트 연결 상태)&lt;/li&gt;
&lt;li&gt;서버: LISTEN &amp;rarr; SYN_RCV(SYN 요청을 받고 상대방의 응답을 기다리는 중)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: 클라이언트 &amp;rarr; 서버(ACK)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마지막으로 호스트 클라이언트가 서버에게 확인 메시지를 전송함으로써 서버가 클라이언트를 확인하여 연결을 확립하는 단계다.&lt;/li&gt;
&lt;li&gt;이때 전송할 데이터가 있으면 이 단계에서 데이터를 전송할 수 있다.&lt;/li&gt;
&lt;li&gt;서버는 클라이언트의 ACK를 통해 현재 클라이언트가 alive 임을 확인한다.&lt;/li&gt;
&lt;li&gt;3단계 종료 후 포트 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트: ESTABLISHED&lt;/li&gt;
&lt;li&gt;서버: ESTABLISHED&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4 way handshake(연결 해제)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1iRj/dJMcafZpCSN/pUAUZmVa32KC07QG0avyIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1iRj/dJMcafZpCSN/pUAUZmVa32KC07QG0avyIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1iRj/dJMcafZpCSN/pUAUZmVa32KC07QG0avyIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1iRj%2FdJMcafZpCSN%2FpUAUZmVa32KC07QG0avyIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;298&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4개의 특별한 패킷을 주고 받으며 TCP 연결을 해제하는 방법이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: 클라이언트 &amp;rarr; 서버(FIN)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 서버에게 연결을 종료하겠다는 FIN 플래그를 1로 설정한 세그먼트를 보낸다.&lt;/li&gt;
&lt;li&gt;포트 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트: FIN_WAIT_1&lt;/li&gt;
&lt;li&gt;서버: CLOSE_WAIT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: 서버 &amp;rarr; 클라이언트(ACK)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 클라이언트에게 ACK를 보낸다. ACK를 받은 클라이언트는 FIN_WAIT_2로 포트 상태를 바꾼 뒤 서버의 FIN 패킷을 기다린다.&lt;/li&gt;
&lt;li&gt;포트 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트: FIN_WAIT_2&lt;/li&gt;
&lt;li&gt;서버: CLOSE_WAIT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: 서버 &amp;rarr; 클라이언트(FIN)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버도 클라이언트에게 연결을 종료하겠다는 FIN 플래그를 1로 설정한 세그먼트를 보낸다.&lt;/li&gt;
&lt;li&gt;포트 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트: TIME_WAIT&lt;/li&gt;
&lt;li&gt;서버: LAST_ACK&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4단계: 클라이언트 &amp;rarr; 서버(ACK)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 서버의 종료 요청에 응답하며 마지막 ACK를 보낸다.&lt;/li&gt;
&lt;li&gt;마지막 ACK를 수신한 서버는 포트를 닫는다.&lt;/li&gt;
&lt;li&gt;포트 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트: CLOSED&lt;/li&gt;
&lt;li&gt;서버: CLOSED&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TCP 빠른 재전송&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUMLFg/dJMcaaKzqKM/xPVjKEuxtDpRVCxO3rk6F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUMLFg/dJMcaaKzqKM/xPVjKEuxtDpRVCxO3rk6F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUMLFg/dJMcaaKzqKM/xPVjKEuxtDpRVCxO3rk6F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUMLFg%2FdJMcaaKzqKM%2FxPVjKEuxtDpRVCxO3rk6F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;348&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임아웃에 의한 재전송의 문제점은 타임아웃의 주기가 때때로 길다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 송신자는 중복 ACK 수신을 통해 타임아웃이 일어나기 전에 송신된 패킷이 손실되었음을 인지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수신자는 순서가 올바르지 않은 세그먼트를 수신하면, 마지막으로 올바르게 수신된 세그먼트에 대한 ACK를 송신측에 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;송신자&lt;/b&gt;가 만약 &lt;b&gt;같은 세그먼트에 대해 3개의 중복된 ACK(원본과 합치면 총 4개)를 수신&lt;/b&gt;하게 되면 해당 ACK에 해당하는 세그먼트가 손실되었음을 인지하게 재전송을 하게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이는 타임아웃에 상관없이 이루어지므로 빠른 재전송이라고 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Congestion Control(혼잡 제어)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크에 흘러들어오는 트래픽 양이, 네트워크가 처리할 수 있는 용량을 초과한 상태.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 혼잡을 줄이기 위해 패킷 송신 속도 즉, 송신 측의 윈도우 크기를 조절하는 것을 말한다.&lt;/li&gt;
&lt;li&gt;네트워크가 혼잡해지면 지연이 커지고 패킷 손실이 발생한다.&lt;/li&gt;
&lt;li&gt;패킷이 지연되거나 손실되면 재전송이 이루어지는데, 이렇게 되면 네트워크는 더욱 혼잡해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Flow Control(흐름 제어)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;송신자가 너무 빠르게 데이터를 보내서 수신자의 TCP 수신 버퍼가 넘쳐 흐르는 상황(버퍼 오버플로우)을 막기 위한 매커니즘&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수신자는 자신의 빈 버퍼 크기를 ACK에 실어 송신자에게 알려준다.&lt;/li&gt;
&lt;li&gt;송신자는 이 값에 맞춰 전송 속도를 조절하여 수신 버퍼가 감당할 수 있는 만큼만 데이터를 전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떤 통신에 사용될까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP는 신뢰성이 중요하고, 순서가 보장되야 하고, 데이터가 유실되면 안되는 통신에 사용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 전송(FTP, HTTP/HTTPS 다운로드)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일은 1바이트도 틀리면 안됨&lt;/li&gt;
&lt;li&gt;중간에 패킷이 날아가면 재전송 필요&lt;/li&gt;
&lt;li&gt;순서 보장 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;웹 브라우징(HTTP/HTTPS)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네이버 접속, 유튜브 페이지 로딩 등&lt;/li&gt;
&lt;li&gt;HTML/CSS/JS 파일이 깨지면 페이지가 망가진다.&lt;/li&gt;
&lt;li&gt;HTTPS는 TLS Handshake 때문에 TCP 기반&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터베이스 통신
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 응답이 유실되면 안됨&lt;/li&gt;
&lt;li&gt;순서도 매우 중요&lt;/li&gt;
&lt;li&gt;트랜잭션 처리 때문에 패킷 재전송 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이메일&lt;/li&gt;
&lt;li&gt;메신저 / 채팅
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만 일부 메신저는 알림/실시간 기능은 UDP도 함께 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SSH/원격 연결&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>네트워크</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/32</guid>
      <comments>https://icecupregular.tistory.com/32#entry32comment</comments>
      <pubDate>Tue, 9 Dec 2025 16:58:15 +0900</pubDate>
    </item>
    <item>
      <title>[컴퓨터 네트워크] 애플리케이션 계층 -2- (보안, JWT, 프록시, REST..)</title>
      <link>https://icecupregular.tistory.com/31</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿠키와 세션의 차이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 프로토콜의 특징이자 약점을 보완하기 위해 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP는 Connectionless 즉, 비연결형 프로토콜이다. 클라이언트가 서버에 요청을 했을 때, 그 요청에 맞는 응답을 보낸 후 &lt;b&gt;연결을 끊는&lt;/b&gt; 처리 방식이다. HTTP가 TCP(TCP:연결 지향) 위에서 구현되었기 때문에 연결지향이라는 논란이 있지만, 서버 측에서 비연결 지향적인 특성으로 커넥션 관리에 대한 비용을 줄이는 것이 명확한 장점으로 보기 때문에 비연결 지향으로 생각하자.&lt;/li&gt;
&lt;li&gt;HTTP는 Stateless 프로토콜 커넥션을 끊는 순간 클라이언트와 서버의 통신이 끝나며 &lt;b&gt;상태 정보는 유지하지 않는 특성&lt;/b&gt;이 있다. 하지만, 실제로는 데이터 유지가 필요한 경우가 있다. &amp;rarr; &lt;b&gt;따라서, Stateful 경우를 대처하기 위해 쿠키와 세션을 사용한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버와 클라이언트가 통신을 할 때 통신이 연속적으로 이어지지 않고 한 번 통신이 되면 끊어진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿠키(Cookie)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠키는 사용자가 웹사이트를 방문할 때, 웹 사이트가 사용자의 브라우저에 저장하는 작은 텍스트 파일.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠키 특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 도메인 당 20개의 쿠키를 가질 수 있다.&lt;/li&gt;
&lt;li&gt;하나의 쿠키는 4KB까지 저장 가능하다&lt;/li&gt;
&lt;li&gt;클라이언트에 총 300개의 쿠키를 저장할 수 있다.&lt;/li&gt;
&lt;li&gt;이름, 값, 만료일, 경로 정보로 구성되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동작 순서
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 페이지를 요청한다.(사용자가 웹사이트에 접근)&lt;/li&gt;
&lt;li&gt;웹 서버는 쿠키를 생성한다.&lt;/li&gt;
&lt;li&gt;생성한 쿠키에 정보를 담아 HTTP 화면을 돌려줄 때, 같이 클라이언트에게 돌려준다.&lt;/li&gt;
&lt;li&gt;받은 쿠키는 클라이언트 PC에 저장했다가 다시 서버 요청할 때 요청과 함께 쿠키를 전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용 예시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방문 사이트에서 로그인 시, &amp;ldquo;아이디와 비밀번호를 저장하시겠습니까?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;팝업창을 통해 &amp;ldquo;오늘 이 창 다시 보지 않기&amp;rdquo;체크&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세션(Session)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정 시간 동안 같은 사용자(브라우저)로 부터 들어오는 일련의 요구를 하나의 상태로 보고, 그 상태를 유지시키는 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방문자가 웹 서버에 접속해 있는 상태를 하나의 단위로 보고 그것을 세션이라고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 서버에 웹 컨테이너의 상태를 유지하기 위한 정보를 저장한다.&lt;/li&gt;
&lt;li&gt;웹 서버에 저장되는 쿠키&lt;/li&gt;
&lt;li&gt;브라우저를 닫거나, 서버에서 세션을 삭제했을 때만 삭제가 되므로, 쿠키보다 비교적 보안이 좋다.&lt;/li&gt;
&lt;li&gt;저장 데이터에 제한이 없다.(서버 용량 한에서)&lt;/li&gt;
&lt;li&gt;각 클라이언트에 고유 Session ID를 부여한다.(세션ID로 클라이언트를 구분)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동작 순서
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 페이지를 서버에 요청한다.&lt;/li&gt;
&lt;li&gt;서버는 접근한 클라이언트의 헤더 필드인 Cookie를 확인하여, 클라이언트가 Session-id를 보냈는지 확인한다.&lt;/li&gt;
&lt;li&gt;session-id가 존재하지 않는다면 서버는 session-id를 생성해 클라이언트에게 넘겨준다.&lt;/li&gt;
&lt;li&gt;클라이언트는 서버로부터 받은 session-id를 &lt;b&gt;쿠키에 저장&lt;/b&gt;한다&lt;/li&gt;
&lt;li&gt;클라이언트는 서버에 요청 시 이 쿠키의 session-id 값을 서버에 전달한다.&lt;/li&gt;
&lt;li&gt;서버는 전달받은 session-id로 session에 있는 클라이언트 정보를 가지고 요청을 처리 후 응답한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면을 이동해도 로그인이 풀리지 않고 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿠키와 세션의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션도 결국 쿠키를 사용하는 방법이기 때문에 쿠키와 세션은 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘의 가장 큰 차이점은 사용자의 정보가 저장되는 위치다. 쿠키는 서버의 자원을 전혀 사용하지 않으며, 세션은 서버의 자원을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 면에서 세션이 더 우수하며 쿠키는 로컬에서 변환될 가능성이 있어 보안에 취약하지만 세션은 쿠키를 이용해서 session-id만 저장하고 그것으로 구분하여 서버에서 처리하기 때문에 비교적 보안성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이프사이클은 쿠키의 유지기간 설정 유무에 따라 유지여부가 달라진다. 쿠키 유지시간 없이 기본적으로 브라우저를 닫으면 쿠키는 사라진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q. 세션을 사용하면 좋아보이는데 쿠키를 왜 사용할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션이 쿠키에 비해 보안성이 높지만 서버 자원을 소모하게 되고 세션을 서버에서 인증하고 처리해야하기 때문에 속도 면에서 쿠키가 훨씬 빠르다. 웹 사이트의 속도를 고려하여 자원 낭비를 방지하기 위해 기능을 선택할 수 있어야한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 토큰&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JWT는 일반적으로 클라이언트-서버, 서비스-서비스 간 통신 시 권한 인가(Authorization)를 위해 사용하는 토큰이다.&lt;/li&gt;
&lt;li&gt;문자열로 구성되어 있기 때문에 URL, HTTP 헤더 등 어디든 위치할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HEADER.PAYLOAD.SIGNATURE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는 헤더, 페이로드, 서명으로 이루어져 있고 점(.)으로 구분된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;헤더(Header)&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
		&quot;alg&quot;: &quot;ES256&quot;,
		&quot;kid&quot;: &quot;Key ID&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;헤더는 JWT를 어떻게 검증할지에 대한 내용을 담고 있다.&lt;/li&gt;
&lt;li&gt;alg는 서명 시 사용하는 알고리즘, kid는 서명 시 사용하는 키를 식별하는 값이다.&lt;/li&gt;
&lt;li&gt;인코딩 시 아래와 같이 헤더를 생성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;Base64URLSafe(UTF-8('{&quot;alg&quot;: &quot;ES256&quot;,&quot;kid&quot;: &quot;Key ID&quot;}')) -&amp;gt; eyJhbGciOiJFUzI1NiIsImtpZCI6IktleSBJRCJ9
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;페이로드(Payload)&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
		&quot;iss&quot;: &quot;jinho.shin&quot;,
		&quot;iat&quot;: &quot;151413132&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이로드는 JWT의 내용이다. 페이로드에 있는 속성들을 클레임 셋이라 부른다.&lt;/li&gt;
&lt;li&gt;클레임 셋은 jwt에 대한 내용이나 클라이언트와 서버 간 주고받기 위한 값들로 구성된다.&lt;/li&gt;
&lt;li&gt;위 JSON 객체로 아래와 같이 페이로드로 인코딩할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Base64URLSafe('{&quot;iss&quot;: &quot;jinho.shin&quot;,&quot;iat&quot;: &quot;1586364327&quot;}') -&amp;gt; eyJpYXQiOjE1ODYzNjQzMjcsImlzcyI6ImppbmhvLnNoaW4ifQ
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서명(Signature)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;점(.)을 구분자로 해서 헤더와 페이로드를 합친 문자열을 서명한 값이다.&lt;/li&gt;
&lt;li&gt;서명은 헤더의 alg에 정의된 알고리즘과 비밀키를 사용해 생성하고 인코딩한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;Base64URLSafe(Sign('ES256', '${PRIVATE_KEY}',
'eyJhbGciOiJFUzI1NiIsImtpZCI6IktleSBJRCJ9.eyJpYXQiOjE1ODYzNjQzMjcsImlzcyI6ImppbmhvLnNoaW4ifQ'))) -&amp;gt;
MEQCIBSOVBBsCeZ_8vHulOvspJVFU3GADhyCHyzMiBFVyS3qAiB7Tm_MEXi2kLusOBpanIrcs2NVq24uuVDgH71M_fIQGg
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JWT 생성과 검증&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 사용자에 대해 인증(Authentication)을 수행한다.&lt;/li&gt;
&lt;li&gt;인증이 되면, 비밀키와 공개키를 생성하고 헤더와 페이로드를 인코딩한 다음 둘을 합친 문자열을 비밀키로 서명해서 JWT를 만들어 클라이언트에게 전달한다.&lt;/li&gt;
&lt;li&gt;서버는 JWT의 서명을 공개키로 검증한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT토큰과 세션의 차이&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에 인증 정보를 저장하는 세션과 달리 JWT는 서버에 인증 정보를 저장하지 않는다. 따라서 JWT는 클라이언트의 요청마다 인증을 위해 DB를 탐색하는 과정이 불필요하고, 저장 공간도 필요하지 않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리프레시 토큰이 사용되는 이유&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token만 쓸 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰 만료 전까지 아무나 마음대로 내 서비스에 들어올 수 있다&lt;/li&gt;
&lt;li&gt;시간이 짧으면 사용자가 계속 로그인 해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Refresh Token을 쓸 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token을 짧게 쓰고 Refresh Token으로 새 엑세스 토큰을 쉽게 발급받게 해서 보안과 편의를 챙길 수 있음. 안전장치가 하나 더 생긴 것&lt;/li&gt;
&lt;li&gt;토큰 재사용 방지 가능. 이미 사용된 리프레쉬 토큰으로 요청이 오면 해킹 의심하여 리프레시 토큰 강제 만료와 사용자 강제 로그아웃이 가능하게 할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 Refresh Token을 저장하는 이유는 검증 때문이 아니라 추가 조치를 하기 위해 저장하는 것이다. 즉, 리프레쉬 토큰이 있으면 탈취 감지와 회수가 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리프레시 토큰과 세션의 차이점?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프레시 토큰의 정보가 서버에 저장되면 마찬가지로 서버에 세션 정보를 저장하는 세션 방식과 어떤 차이가 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하지만 차이가 존재한다. 세션은 매 요청마다 세션 아이디로 DB속 유저의 정보에 접근해야 하지만 리프레시 토큰은 재발급 시점에만 유저의 정보를 접근하면 되기 때문에 DB의존 시점을 줄일 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SOP란&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSLhVn/dJMcab3KZUN/wM8q5OsjHHGCaeKYGv9OmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSLhVn/dJMcab3KZUN/wM8q5OsjHHGCaeKYGv9OmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSLhVn/dJMcab3KZUN/wM8q5OsjHHGCaeKYGv9OmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSLhVn%2FdJMcab3KZUN%2FwM8q5OsjHHGCaeKYGv9OmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;558&quot; height=&quot;213&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Same-Origin Policy 은 자바스크립트 엔진 표준 스펙에 존재하는 보안 규칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Origin(Protocol + Host + Port)가 모두 같은 겨우에만, 서로의 중요한 데이터에 접근할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;브라우저에서 다른 Origin에 대해 JS가 할 수 있는 행동을 강하게 제한한다는 뜻&lt;/li&gt;
&lt;li&gt;왜 SOP가 필요할까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XSS 등으로 탈취된 스크립트가 다른 사이트의 쿠키, 토큰 등을 훔쳐가는 것을 막기 위해.&lt;/li&gt;
&lt;li&gt;JS가 브라우저 안에서 네이버, 구글 같은 탭의 쿠키 / localStorage / 응답 데이터에 마음대로 접근 가능하다면 로그인 세션, JWT, 민감 데이터를 다 탈취당할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CORS란&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cross Origin Resource Sharing&lt;/li&gt;
&lt;li&gt;CORS는 현재 접속한 도메인 말고 다른 도메인에 접근할 수 있도록 처리 해주는 웹 브라우저 표준 기술이다.(원래는 SOP 때문에 안된다.)&lt;/li&gt;
&lt;li&gt;만약 프론트엔드와 백엔드가 도메인이 다르다면 SOP 정책에 의해 프론트엔드는 백엔드의 API를 호출할 수 없을 것이다. 이 경우 CORS를 사용해서 해결할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백엔드에서 응답 HTTP 헤더에 Access-Controll-Allow-Origin을 추가한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Rest API(Representational State Transfer)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REST는 2000년도 Roy Fielding의 박사 논문에서 최초로 소개된 소프트웨어 아키텍처다.&lt;/li&gt;
&lt;li&gt;자원의 표현을 가지고 상태를 전달한다는 뜻이다.&lt;/li&gt;
&lt;li&gt;자원의 표현: HTTP URI&lt;/li&gt;
&lt;li&gt;상태: HTTP Status code&lt;/li&gt;
&lt;li&gt;전달: HTTP Method&lt;/li&gt;
&lt;li&gt;REST는 자원을 HTTP URI로 표현하고 HTTP METHOD를 통해 자원의 상태를 주고 받는 아키텍처&lt;/li&gt;
&lt;li&gt;RESTful: REST 아키텍처 스타일의 제약조건을 모두 만족하는 시스템을 뜻한다.&lt;/li&gt;
&lt;li&gt;REST API: REST 스타일의 API를 뜻한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;REST 제약 조건&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REST 아키텍처에 적용되는 6가지 제약 조건&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REST는 아래 6가지 스타일을 반드시 지켜야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Client-Server(클라이언트 - 서버 구조) REST 서버는 API 제공, 클라이언트는 컨텍스트 등을 직접 관리하는 구조로 되어 있어 Server와 Client의 역할이 명확히 나뉘기 때문에 각 필드에서 개발해야할 점이 명확해지고 서로 간의 의존성이 줄어든다.&lt;/li&gt;
&lt;li&gt;Stateless(무상태) HTTP는 stateless 프로토콜이므로 REST 역시 stateless다. 즉, HttpSession과 같은 컨텍스트 저장소에 상태 정보를 따로 저장하고 관리하지 않고, API 서버는 들어오는 요청만을 단순 처리하면 된다. 세션과 같은 컨텍스트 정보를 신경 쓸 필요가 없어 구현이 단순해진다.&lt;/li&gt;
&lt;li&gt;Cacheable(캐시 처리 가능) REST는 웹 표준인 HTTP를 그대로 사용하기 때문에, 웹에서 사용하는 기존 인프라를 그대로 사용할 수 있다. 따라서 HTTP가 가진 캐싱 기능을 적용할 수 있다.&lt;/li&gt;
&lt;li&gt;Layered System(계층화) REST 서버는 다중 계층으로 구성될 수 있으며, 보안, 로드 밸런싱, 암호화 계층 등을 추가해 구조상의 유연성을 둘 수 있고, PROXY, 게이트웨이와 같은 네트워크 기반의 중간매체를 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;Uniform Interface(인터페이스 일관성)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;resource는 URL로 식별된다.&lt;/li&gt;
&lt;li&gt;resource를 생성, 수정, 추가하고자 할 때 HTTP 메시지에 표현해야 한다.&lt;/li&gt;
&lt;li&gt;메시지는 스스로 설명할 수 있어야 한다.(self descriptive message)&lt;/li&gt;
&lt;li&gt;애플리케이션의 상태는 하이퍼링크를 이용해 전이되어야 한다.(HATEOAS)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Code on demand 다른 조건들과 달리 꼭 지켜야 하는 조건은 아님.(선택적 제약 조건) 서버가 실행 가능한 코드를 클라이언트로 내려보내서, 클라이언트의 기능을 동적으로 확장할 수 있게 하는 방식&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;URL, URI, URN&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;735&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7uhVT/dJMcaaKxLxj/B8orMtmARSVmkt9b4PI1fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7uhVT/dJMcaaKxLxj/B8orMtmARSVmkt9b4PI1fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7uhVT/dJMcaaKxLxj/B8orMtmARSVmkt9b4PI1fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7uhVT%2FdJMcaaKxLxj%2FB8orMtmARSVmkt9b4PI1fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;326&quot; height=&quot;371&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;735&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URI(Uniform Resource Identifier)는 인터넷 상에서 특정 자원을 가리키는 식별자다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL과 URN을 포함하는 개념&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;URL은 인터넷 상에서 특정 시점에서의 자원의 구체적인 위치다.&lt;/li&gt;
&lt;li&gt;URN은 인터넷 상에서 자원을 가리키는 영속적이고 고유한 식별자다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;URL과 URN&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL은 특정 시점에서 자원의 위치를 나타낸다.&lt;/li&gt;
&lt;li&gt;URL은 나중에 자원의 위치가 바뀌게 되면 기존의 URL로는 식별할 수 없게된다.&lt;/li&gt;
&lt;li&gt;URN은 자원에 대해 영속적이고 고유한 이름을 부여한다.&lt;/li&gt;
&lt;li&gt;URN은 다음과 같은 구조를 가진다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;urn:isbn:978321313&lt;/li&gt;
&lt;li&gt;URN은 urn으로 시작하고 콜론으로 구분되어 표현된다.&lt;/li&gt;
&lt;li&gt;ISBN은 국제 표준 도서번호를 뜻한다.&lt;/li&gt;
&lt;li&gt;URN은 해당 자원을 고유하게 식별한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;XSS(Cross-Site Scripting)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 브라우저에서 악성 스크립트가 실행되도록 만드는 공격&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 입력한 값(댓글, 게시글, 검색어 등)이 그대로 HTML에 삽입되고, 브라우저가 그걸 자바스크립트로 실행하면서 문제 발생.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 세션 탈취&lt;/li&gt;
&lt;li&gt;쿠키/로컬스토리지에 저장된 정보 탈취&lt;/li&gt;
&lt;li&gt;피싱, 가짜 화면 표시, 임의 요청 전송 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;방어 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML 컨텍스트: &amp;lt; , &amp;gt;, &quot;, ', &amp;amp; 등을 HTML엔티티로 변환 &amp;rarr; &amp;lt;script&amp;gt; &amp;rarr; &amp;lt;script&amp;gt;&lt;/li&gt;
&lt;li&gt;쿠키에 HttpOnly 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바스크립트로 document.cookie 접근 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSRF(Cross-Site Request Forgery)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 로그인된 상태라는 점을 악용해서 공격자가 만든 페이지에서 피해 사이트로 사용자 몰래 요청을 보내는 공격.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트 주입 없이, 사용자의 인증 상태(쿠키)를 이용해서 다른 사이트로 요청을 보내게 하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방어 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CSRF 토큰 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 사용자의 세션/스토리지에 랜덤 토큰을 저장.&lt;/li&gt;
&lt;li&gt;스프링 시큐리티같은 프레임워크는 CSRF 토큰 자동 지원.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Samsite 쿠키 옵션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 도메인에서 온 요청에 쿠키 자동 전송을 차단.&lt;/li&gt;
&lt;li&gt;요즘 브라우저는 기본이 일부 GET링크를 허용하는 Lax에 가까움.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SQL Injection&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 입력을 그대로 SQL 문자열에 붙여서 실행할 때, 공격자가 SQL 문법을 끼워 넣어 쿼리의 의도를 바꾸는 공격&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;SELECT * FROM users WHERE username = '입력값' AND password = '입력값';
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 username에 1=1 같은걸 넣으면, WHERE 조건을 항상 참으로 만들어 로그인 우회 같은게 가능.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방어 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Parameterized Query 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL과 파라미터를 완전히 분리해서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;String sql = &quot;SELECT * FROM users WHERE username = ? AND password = ?&quot;;
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, password);
ResultSet rs = ps.executeQuery();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;? 자리는 단순 데이터로 처리되고, SQL 문법으로 해석되지 않아서 Injection 방지&lt;/li&gt;
&lt;li&gt;JPA, MyBatis 등 ORM/매퍼에서도 바인딩 파라미터(?, :name)을 사용하는 방식이 바로 이것.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 캐시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 캐시는 자주 쓰이는 자원의 사본을 자동으로 보관하는 장치이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 요청이 캐시에 도착했을 때 캐시된 사본이 존재한다면 해당 자원은 origin 서버가 아닌 캐시로부터 제공된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불필요한 데이터 전송을 줄여준다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;origin 서버의 부하를 줄여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;네트워크 병목을 줄여준다.&lt;/li&gt;
&lt;li&gt;갑작스러운 트래픽에 대처할 수 있다.&lt;/li&gt;
&lt;li&gt;거리로 인한 지연을 줄일 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트와 서버의 거리가 멀수록 지연이 커진다.(빛의 속도는 300000km/s)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cache hit 와 cache miss&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cache hit
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시에 요청이 도착했을 때, 요청에 대응하는 사본이 존재한다면, 해당 요청을 처리할 수 있다. 이를 캐시 적중이라 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cache miss
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 대응하는 사본이 없다면 origin 서버로 요청이 전달된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;캐시 재검사 적중
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 대응하는 사본이 존재하는데, 유효기간이 만료돼서 재검사를 했을 때, 해당 사본이 최신인 경우를 캐시 재검사 적중이라 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;재검사&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;origin 서버 콘텐츠는 변경될 수 있기 때문에 캐시는 갖고 있는 사본이 최신인지 서버를 통해 점검해야 한다.&lt;/li&gt;
&lt;li&gt;서버에 보내는 GET 요청에 If-Modified-Since 헤더를 추가하면 캐싱된 시간 이후에 사본이 변경되었을 경우만 사본을 보내달라는 의미이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 서버 자원이 변경되지 않았다면, HTTP 304 Not Modified 응답을 보낸다.&lt;/li&gt;
&lt;li&gt;만약 서버 자원이 캐시된 사본과 다르다면, 서버는 자원과 함께 HTTP 200 OK 응답을 보낸다.&lt;/li&gt;
&lt;li&gt;만약 서버 객체가 삭제되었다면, 서버는 404 NOT FOUND 응답을 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;재검사를 보내는 기준
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP는 Cache-Control 과 Expire라는 특별한 헤더들을 이용해서 origin 서버가 각 자원에 유효기간을 붙일 수 있게 한다.&lt;/li&gt;
&lt;li&gt;이 유효기간이 만료되면, 캐시는 반드시 서버와 문서에 변경된 것이 있는지 검사해 보고, 최신의 사본을 얻어와야 한다.(새로운 유효기간과 함께)&lt;/li&gt;
&lt;li&gt;Cache-Controll : max-age
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;max-age는 자원의 최대 나이다.초단위 시간값이다.&lt;/li&gt;
&lt;li&gt;ex) Cache-Controll: max-age=4842000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Expires
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유효기간을 날짜로 명시한다.&lt;/li&gt;
&lt;li&gt;ex) Expires: Fri, 05 Jul 2002, 05:00:00 GMT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;종류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개인 전용 캐시(private cache)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 명의 사용자에게만 할당된 캐시다.&lt;/li&gt;
&lt;li&gt;웹 브라우저는 개인 전용 캐시를 내장하고 있다.&lt;/li&gt;
&lt;li&gt;대부분의 브라우저는 자주 쓰이는 자원을 개인용 컴퓨터의 디스크와 메모리에 캐싱해 놓는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공용 프록시 캐시(public cache)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 캐시는 로컬 캐시에서 자원을 제공한다.&lt;/li&gt;
&lt;li&gt;사용자의 입장에서 서버에 접근하기도 한다.&lt;/li&gt;
&lt;li&gt;공용 캐시는 여러 사용자가 접근하기 때문에 불필요한 트래픽을 더 많이 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프록시 서버&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/da4j28/dJMcafkMFT3/rZxiNNUKnT2Z9fLrNfGBf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/da4j28/dJMcafkMFT3/rZxiNNUKnT2Z9fLrNfGBf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/da4j28/dJMcafkMFT3/rZxiNNUKnT2Z9fLrNfGBf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fda4j28%2FdJMcafkMFT3%2FrZxiNNUKnT2Z9fLrNfGBf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;194&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 프록시 서버는 클라이언트 입장에서 트랜잭션을 대신 수행해준다.&lt;/li&gt;
&lt;li&gt;HTTP 프록시 서버는 웹 서버이기도 하고, 웹 클라이언트기도 하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개인 프록시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 클라이언트만을 위한 프록시를 개인 프록시라 부른다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공유 프록시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 프록시는 공유 프록시다. 중앙 집중형 프록시를 관리하는게 더 비용효율이 높고 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 프록시 서버와 같은 프록시는 사용자가 많을 수록 유리하다.(여러 사용자들의 공통된 요청에서 이득을 취할 수 있기 때문)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;게이트웨이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시는 같은 프로토콜을 사용하는 둘 이상의 애플리케이션을 연결하고, 게이트웨이는 서로 다른 프로토콜을 사용하는 둘 이상을 연결한다.&lt;/li&gt;
&lt;li&gt;게이트웨이는 클라이언트와 서버가 서로 다른 프로토콜로 말하더라도 서로 간 트랜잭션을 완료할 수 있도록 해주는 프로토콜 변환기처럼 동작한다.&lt;/li&gt;
&lt;li&gt;실질적으로 프락시와 게이트웨이의 차이점은 모호하다. 브라우저와 서버의 HTTP 버전이 다를 경우 프록시는 약간의 프로토콜 변환을 하기도 하고 상용 프록시 서버는 게이트웨이 기능을 구현하기도 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프락시 사용 예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어린이 성인 사이트 필터
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초등학교는 어린이들에게 교육 사이트를 제공하면서 동시에 성인 콘텐츠를 차단하려고 필터링 프록시를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문서 접근 제어자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 서버는 많은 웹 서버들과 웹 리소스에 대한 접근을 제어할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;보안 방화벽
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 보안 엔지니어는 보안을 강화하기 위해 프록시 서버를 사용한다.&lt;/li&gt;
&lt;li&gt;프록시 서버는 조직 안에 들어오거나 나가는 응용 레벨 프로토콜의 흐름을 네트워크의 한 지점에서 통제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;웹 캐시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 캐시는 자주 사용되는 자원의 사본을 관리하고 해당 자원에 대한 요청이 오면 빠르게 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대리 프록시&lt;/li&gt;
&lt;li&gt;콘텐츠 라우터&lt;/li&gt;
&lt;li&gt;트랜스 코더&lt;/li&gt;
&lt;li&gt;익명화 프록시&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;포워드 프록시&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLjUuJ/dJMcabvU0GH/dsKP44yMENqbKrBo7GHITK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLjUuJ/dJMcabvU0GH/dsKP44yMENqbKrBo7GHITK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLjUuJ/dJMcabvU0GH/dsKP44yMENqbKrBo7GHITK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLjUuJ%2FdJMcabvU0GH%2FdsKP44yMENqbKrBo7GHITK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;338&quot; height=&quot;259&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Forward Proxy는 클라이언트의 요청을 대신 서버에게 보내준다.&lt;/li&gt;
&lt;li&gt;Forward Proxy에 클라이언트의 요청한 내용을 캐싱할 수 있다.&lt;/li&gt;
&lt;li&gt;Forward Proxy는 클라이언트가 보낸 요청을 익명으로 서버에게 전달할 수 있다. 서버는 요청으로 들어온 클라이언트의 IP 주소를 알아내지 못하고, Proxy 서버의 IP주소를 알 수 있을 뿐이다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리버스 프록시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAGtc/dJMcaiaJZd1/z5RPi4J0u8YW4SYmjhyD31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAGtc/dJMcaiaJZd1/z5RPi4J0u8YW4SYmjhyD31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAGtc/dJMcaiaJZd1/z5RPi4J0u8YW4SYmjhyD31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAGtc%2FdJMcaiaJZd1%2Fz5RPi4J0u8YW4SYmjhyD31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;187&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Proxy는 클라이언트 편에서 동작한다. 클라이언트가 누구인지 감추고 서버로 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 리버스 Porxy는 서버 편에서 동작한다. 서버가 누구인지 감추고 클라이언트에게 응답한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 방향이 반대라서 리버스 프록시이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Reverse Proxy는 서버의 응답을 대신 클라이언트에 전달한다.&lt;/li&gt;
&lt;li&gt;Reverse Proxy는 클라이언트의 요청을 캐싱할 수 있다.&lt;/li&gt;
&lt;li&gt;Reverse Proxy는 실제 서버를 노출하지 않게 해준다. 클라이언트는 Reverse Proxy를 실제 서버라고 생각하고 Reverse Proxy에 요청을 보낸다.&lt;/li&gt;
&lt;li&gt;Reverse Proxy를 활용하여 로드 밸런싱을 할 수 있다.(부하 분산)&lt;/li&gt;
&lt;li&gt;Reverse Proxy를 한 대 두고 뒤에 여러 개의 WAS를 두는 구조에서 Reverse Proxy는 들어오는 요청의 부하를 여러 WAS로 분산 시켜줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커넥션 타임아웃&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버와의 TCP 연결을 맺는데 최대 얼마까지 기다릴 것인가를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 서버 IP/포트로 TCP연결을 시도할 때, 서버가 죽거나 네트워크에 문제가 있거나 방화벽에 막힌다면 연결이 아예 안 맺어질 수 있다. 이때 얼마나 기다리다가 포기할지를 정하는 값이 커넥션 타임아웃.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값 설정 시 고려할 점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;너무 크면 연결 시도 중에 많은 연결 시도가 묶이게 되어 스레드/커넥션 고갈로 서버 장애로 이어질 수 있다.&lt;/li&gt;
&lt;li&gt;너무 작으면 잠깐 네트워크가 느려졌을 때도 타임아웃이 자주 발생한다.&lt;/li&gt;
&lt;li&gt;내부망 서비스는 0.5~3초로 설정하고 모니터링 하면서 조정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리드 타임아웃&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결은 성공했는데 서버 응답 데이터를 읽을 때 최대 얼마까지 기다릴 것인가를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;readTimeout=5000ms 는 응답 바디를 읽기 시작하고서 5초 동안 데이터가 안 들어오면 타임아웃.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값 설정 시 고려할 점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;너무 크면 트래픽 많은 서비스에서 높처럼 스레드가 빠져나오지 못해 전체 장애를 부를 수 있다.&lt;/li&gt;
&lt;li&gt;너무 작으면 조금만 응답이 느려도 바로 예외가 발생한다.&lt;/li&gt;
&lt;li&gt;실무에서의 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부 API: 1~5초&lt;/li&gt;
&lt;li&gt;외부:3~10초&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>네트워크</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/31</guid>
      <comments>https://icecupregular.tistory.com/31#entry31comment</comments>
      <pubDate>Thu, 4 Dec 2025 21:09:35 +0900</pubDate>
    </item>
    <item>
      <title>[컴퓨터 네트워크]애플리케이션 레이어</title>
      <link>https://icecupregular.tistory.com/30</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTP&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP 프로토콜이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP(Hypertext Transfer Protocol)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP는 World Wide Web 에서 정보를 주고 받을 수 있게 해주는 프로토콜이다.&lt;/li&gt;
&lt;li&gt;웹에서 송수신되는 정보는 HTML, CSS, JS, 이미지 등이 있다.&lt;/li&gt;
&lt;li&gt;HTTP는 TCP 기반에서 동작하며, 80번 포트를 사용한다.(HTTP3는 UDP 기반에서 동작한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP의 요청/응답 모델에 대해&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1239&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQgGN2/dJMcacuMmTC/hKFQXHqniIUiMlzbOOfzh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQgGN2/dJMcacuMmTC/hKFQXHqniIUiMlzbOOfzh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQgGN2/dJMcacuMmTC/hKFQXHqniIUiMlzbOOfzh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQgGN2%2FdJMcacuMmTC%2FhKFQXHqniIUiMlzbOOfzh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;223&quot; data-origin-width=&quot;1239&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUEST(요청)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 요청은 클라이언트가 서버에 보내는 메시지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Start line&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 요소
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP method를 나타낸다.&lt;/li&gt;
&lt;li&gt;HTTP method를 수행할 작업(GET, PUT, POST 등)이나 방식(HEAD, OPTIONS)을 설명한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;두 번째 요소
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 대상(일반적으로 URL 이나 URI) 또는 프로토콜, 포트, 도메인의 절대 경로는 요청 컨텍스트에 작성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP 메소드 중 GET과 POST의 차이점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET: 서버에게 데이터를 요청하는 것.&lt;/li&gt;
&lt;li&gt;POST: 클라이언트에서 서버로 데이터를 전송하는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP 메소드 중 PUT과 PATCH의 차이점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PUT: 서버 데이터를 수정하는 요청. 수정 요청한 컬럼을 제외한 ROW의 다른 값들 모두 NULL로 바꿈&lt;/li&gt;
&lt;li&gt;PATCH: 서버 데이터를 수정하는 요청. PUT 요청과 다르게 정확히 수정 요청한 값만 수정.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP 상태 코드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 HTTP 응답 메시지는 HTTP 상태 코드와 함께 반환된다.&lt;/li&gt;
&lt;li&gt;상태 코드는 클라이언트에게 요청이 성공했는지, 실패했는지, 추가 조치가 필요한지 등을 알려주는 3자리 숫자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 상태 코드 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;200 (ok)&lt;/td&gt;
&lt;td&gt;요청 성공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;201 (Moved Parmanently)&lt;/td&gt;
&lt;td&gt;요청 객체가 영원히 이동됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400 (Bad Request)&lt;/td&gt;
&lt;td&gt;서버가 요청을 이해할 수 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404 (Not Found)&lt;/td&gt;
&lt;td&gt;서버에서 요청받은 리소스를 찾을 수 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;505 (HTTP Version Not Supported)&lt;/td&gt;
&lt;td&gt;요청 HTTP 프로토콜 버전을 서버가 지원하지 않는다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP 헤더란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 헤더는 클라이언트와 서버가 &lt;b&gt;요청 또는 응답으로 부가적인 정보를 전송&lt;/b&gt;할 수 있도록 해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d41WWG/dJMcajtQVk0/3CVXXrowGILWSVmi3hxCf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d41WWG/dJMcajtQVk0/3CVXXrowGILWSVmi3hxCf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d41WWG/dJMcajtQVk0/3CVXXrowGILWSVmi3hxCf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd41WWG%2FdJMcajtQVk0%2F3CVXXrowGILWSVmi3hxCf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;431&quot; height=&quot;358&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/1.1 표준 기준으로 4가지로 분류됨&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;General Header: 요청과 응답에서 사용할 수 있는 공통 헤더&lt;/li&gt;
&lt;li&gt;Request Header: 페치될 리소스나 클라이언트 자체에 대한 자세한 정보를 포함하는 헤더.&lt;/li&gt;
&lt;li&gt;Response Header: 서버 &amp;rarr; 클라이언트 정보 전달 헤더
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Server:서버 소프트웨어 정보&lt;/li&gt;
&lt;li&gt;Set-Cookie: 쿠키 생성/수정&lt;/li&gt;
&lt;li&gt;WWW-Authenticate: 인증 필요 시 인증 방식 알림&lt;/li&gt;
&lt;li&gt;Location: 리다이렉트 주소(3xx 응답에서 사용)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Entity Header: body의 내용을 설명하는 헤더&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP의 무상태성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 는 서버가 클라이언트의 상태를 보존하지 않는 무상태 프로토콜&lt;/li&gt;
&lt;li&gt;장점: 서버 확장성 높음. 무한한 서버 증설 가능(스케일 아웃)&lt;/li&gt;
&lt;li&gt;단점: 클라이언트가 추가 데이터를 전송해야 함(서버에 저장되어 있지 않으므로)&lt;/li&gt;
&lt;li&gt;단순한 서비스 소개 화면 같은 경우엔 무상태로 설계할 수 있지만, 로그인 등 유저의 상태를 유지해야 하는 서비스라면 브라우저 쿠키, 서버 세션, 토큰 등을 이용해 상태를 유지해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP Keep-Alive 란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 는 Connectionless 방식으로 연결을 매번 끊고 새로 생성하는 구조이다. 이는 Network 비용 측면에서 최초 연결을 하기 위해 많은 비용을 소비한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 프로토콜의 Keep-alive 기능은 클라이언트와 서버 간 요청 및 응답 과정을 효율적으로 유지하기 위해 사용된다. Keep-Alive 를 활성화하면 하나의 TCP 연결을 여러번 재사용하며 응답과 요청을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 프로토콜에서 클라이언트와 서버 간 여러 요청을 단일 TCP 연결을 재사용하는 방식으로 처리하는 기능을 말한다. HTTP/1.1 프로토콜부터 도입됐다. 이 기능을 활성화하면 여러 HTTP 요청 및 응답 과정에서 발생하는 네트워크 오버헤드를 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keep-alive를 사용하는 경우 HTTP 요청 헤더에 Connection: Keep-Alive 라는 값을 포함시킨다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Connection: Keep-Alive
...

(body)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용 상 이점&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keep-alive가 없으면 클라이언트와 서버는 각 요청과 응답에 대해 매번 새로운 TCP 연결을 생성하고 닫아야 한다. 이 방식은 네트워크 리소스가 비효율적으로 사용된다. 반면 keep-alive를 사용하면 단일 TCP 연결에서 여러 요청과 응답이 이루어지기 때문에 네트워크 지연 시간이 줄어들고 웹 사이트 성능이 좋아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 HTTP/1.0 프로토콜에서는 각 HTTP 요청에 대해 매번 TCP 연결을 새로 생성해야 했기 때문에 위에서 언급한 문제들이 존재했다. 이를 해결하기 위해 HTTP/1.1 프로토콜에 해당 기능이 도입됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkUmL0/dJMcahXaaMr/8M1lpuT22lcLe80rc5sRK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkUmL0/dJMcahXaaMr/8M1lpuT22lcLe80rc5sRK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkUmL0/dJMcahXaaMr/8M1lpuT22lcLe80rc5sRK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkUmL0%2FdJMcahXaaMr%2F8M1lpuT22lcLe80rc5sRK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;547&quot; height=&quot;400&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지는 4개의 커넥션이 생성되어 각각 처리하는 방식과 하나의 커넥션으로만 처리하는 방식을 나타낸 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keep-Alive에서는 커넥션을 맺고 끊는 데 작업이 없어졌기 때문에 시간이 단축된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP 파이프라이닝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라이닝(Pipelining)은 주로 네트워크 프로토콜에서 사용되는 기법으로, 여러 요청을 동시에 보내고, 응답을 기다리지 않고 계속해서 추가적인 요청을 보내는 방식이다. 이를 통해 대기 시간을 줄이고 네트워크 효율성을 높일 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이프라이닝의 동작 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로, 파이프라이닝은 클라이언트가 서버로 여러 요청을 연속적으로 보내는 방식을 의미한다. 이를 통해 클라이언트는 첫 번째 요청에 대한 응답을 기다리지 않고, 다음 요청들을 전송할 수 있습니다. 서버는 이러한 요청들을 차례대로 처리한 후, 응답을 클라이언트에게 순차적으로 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 네트워크 대기 시간을 줄이는 데 도움이 됩니다. 일반적인 HTTP 요청/응답 사이클에서는 요청을 보내고 응답을 받은 후 다음 요청을 보낼 수 있지만, 파이프라이닝을 사용하면 응답을 기다릴 필요 없이 여러 요청을 한 번에 보내므로 전체 응답 시간을 줄일 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP/1.1에서의 파이프라이닝
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP/1.1은 기본적으로 파이프라이닝을 지원합니다. 클라이언트는 같은 연결에서 여러 HTTP 요청을 연속적으로 전송할 수 있습니다.이때, 클라이언트는 첫 번째 요청에 대한 응답을 기다리지 않고 두 번째, 세 번째 요청을 계속 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파이프라이닝의 한계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순차적인 응답 처리: 파이프라이닝은 여러 요청을 보내더라도 &lt;b&gt;응답은 순차적으로&lt;/b&gt; 처리된다. 즉, 첫 번째 요청의 응답이 완료되어야 두 번째 요청의 응답을 처리할 수 있다. 만약 첫 번째 요청이 지연되면 그 뒤의 요청들도 응답이 지연되는 문제가 발생하는데, 이를 Head-of-Line-Blocking 이라고 한다.&lt;/li&gt;
&lt;li&gt;서버 지원 부족: 파이프라이닝은 HTTP/1.1에서 지원되지만, 모든 서버나 프록시가 이를 완벽하게 지원하지 않으며, 클라이언트 측에서도 제한적으로 사용된다. HOL 블로킹 문제나 서버 호환성 문제로 인해 파이프라이닝의 실질적인 사용지 제한되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파이프라이닝 vs 멀티플렉싱&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/2 는 파이프라이닝의 문제를 해결하기 위해 등장한 프로토콜입니다. HTTP/2에서는 멀티플렉싱 기법을 도입하여, 여러 요청과 응답을 동시에 처리할 수 있다. 멀티플렉싱은 파이프라이닝과 달리 요청과 응답을 병렬로 처리하기 때문에, 특정 요청의 지연이 다른 요청에 영향을 주지 않는다.따라서 HOL 블로킹이 발생하지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP/1.1, HTTP/2, HTTP/3 각각의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP/1.1
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;텍스트 기반 프로토콜&lt;/li&gt;
&lt;li&gt;keep-alive 로 연결 재사용&lt;/li&gt;
&lt;li&gt;파이프라이닝 지원하지만 Head-of-Line-Blocking, 호환성 문제로 실질적 활용 적음&lt;/li&gt;
&lt;li&gt;브라우저는 병렬성을 위해 TCP 연결 여러 개 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTP/2
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바이너리 프레이밍 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바이너리 프레임 단위로 쪼개서 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;멀티플렉싱&lt;/li&gt;
&lt;li&gt;하나의 TCP 연결에서 여러 스트림 동시 처리&lt;/li&gt;
&lt;li&gt;TCP 기반이라 전송 계층 HOL Blocking 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTP/3
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP 대신 QUIC(UDP 기반) 사용&lt;/li&gt;
&lt;li&gt;이미 브라우저/대형 서비스에서 점진적으로 채택 중&lt;/li&gt;
&lt;li&gt;스트림 단위 손실 처리로 전송 계층 HoL Blocking 완화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTPS&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTPS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS는 HTTP에 TLS라는 암호화 계층을 덧씌운 것이다. https:// 와 기본 포트 443을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lQaWq/dJMcabvRW7k/mfkV8n3vCvdzabhHHiCKFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lQaWq/dJMcabvRW7k/mfkV8n3vCvdzabhHHiCKFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lQaWq/dJMcabvRW7k/mfkV8n3vCvdzabhHHiCKFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlQaWq%2FdJMcabvRW7k%2FmfkV8n3vCvdzabhHHiCKFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;398&quot; height=&quot;154&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP는 평문으로 메시지를 교환하기 때문에 도청이 가능하다.&lt;/li&gt;
&lt;li&gt;HTTP를 사용하면서 통신 보안이 필요하다면 HTTPS를 사용하면 된다.&lt;/li&gt;
&lt;li&gt;HTTPS는 HTTP의 보안 버전으로 TLS 위에서 동작하는 HTTP다.&lt;/li&gt;
&lt;li&gt;HTTPS를 통해 전송되는 모든 HTTP 메시지는 SSL 계층을 거쳐 암호화, 복호화 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSL/TLS&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSL(Secure Sockets Layer)은 컴퓨터 네트워크에 통신 보안을 제공하는 프로토콜이다.&lt;/li&gt;
&lt;li&gt;TLS(Transport Layer Security)라는 이름은 SSL이 표준화 되면서 바뀐 이름이다.&lt;/li&gt;
&lt;li&gt;SSL은 클라이언트가 서버와 주고 받는 통신 데이터에 대한 도청, 간섭, 위조를 방지해준다.&lt;/li&gt;
&lt;li&gt;또한 데이터를 암호화 해주기 때문에 클라이언트 서버가 안전한 통신을 할 수 있게 해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대칭키 암호화 방식(symmetric-key algorithm)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대칭키 암호화 기법에서는 암호화, 복호화할 때 사용되는 키가 같다.&lt;/li&gt;
&lt;li&gt;같은 키를 사용하기 때문에 &amp;lsquo;대칭키&amp;rsquo;라고 부른다.&lt;/li&gt;
&lt;li&gt;대칭키 암호화 기법에서 송신자와 수신자는 대칭키를 공유한다.&lt;/li&gt;
&lt;li&gt;송신자는 대칭키를 사용해서 데이터를 암호화해서 전송하고&lt;/li&gt;
&lt;li&gt;수신자는 수신한 데이터를 대칭키를 사용해서 복호화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공개키 암호화(public key cryptosystem, 비대칭키 암호화)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공개키 암호화 기법에서는 암호화, 복호화할 때 사용되는 키가 다르다.&lt;/li&gt;
&lt;li&gt;공개키는 모두에게 공개된 키&lt;/li&gt;
&lt;li&gt;개인키는 개인이 비밀스럽게 갖고 있는 키&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편지함 비유&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;열쇠로 잠긴 편지함에&lt;/li&gt;
&lt;li&gt;누구나 편지를 넣을 수 있다.&lt;/li&gt;
&lt;li&gt;하지만 개인키를 가진 사람만이 편지함을 열 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전자 서명&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPWQYa/dJMcafrvhcQ/JicToKHnJTxVIrUxHKTre1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPWQYa/dJMcafrvhcQ/JicToKHnJTxVIrUxHKTre1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPWQYa/dJMcafrvhcQ/JicToKHnJTxVIrUxHKTre1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPWQYa%2FdJMcafrvhcQ%2FJicToKHnJTxVIrUxHKTre1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;292&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인키로 데이터를 암호화하고 공개키로 복호화 하는 방식&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터를 복호화하는 키는 모두 공개하고 데이터를 암호화하는 키는 개인만 갖게 하는 방식
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;특정 개인이 자신을 인증하는 용도로 사용할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;A라는 특정 개인은 어떤 데이터를 개인키로 암호화한다. 그리고 공개키는 모두에게 공개한다.&lt;/li&gt;
&lt;li&gt;공개키를 가진 누군가는 A로부터 받은 암호화 데이터를 공개키로 복호화 해본다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;만약 복호화 된다면, 그 데이터는 A로부터 보내진 것이다.(A가 데이터를 보낸 쪽 임이 확실)&lt;/li&gt;
&lt;li&gt;데이터가 복호화되지 않는다면, 그 데이터는 A로부터 보내진 데이터가 아니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;공개키를 가진 누군가는 A로부터 받은 암호화된 데이터는 인증을 위한 서명으로 사용되는 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 작성한 저자가 누구인지 알려준다.&lt;/li&gt;
&lt;li&gt;전자 서명은 오직 저자의 개인키를 통해서만 접근할 수 있기 때문에 저자의 개인 &amp;lsquo;서명&amp;rsquo;처럼 동작한다.&lt;/li&gt;
&lt;li&gt;전자 서명은 메시지 위조를 방지한다.&lt;/li&gt;
&lt;li&gt;만약 누군가 송신중인 메시지를 수정했다면 메시지에 대한 체크섬은 전자서명(원본의 체크섬)과 다를 것이다.&lt;/li&gt;
&lt;li&gt;체크섬 또한 저자의 개인키로 암호화되어 있기 때문에 위조할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편지 봉투 비유&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전자 서명은 인장으로 편지 봉투를 봉인하는 것에 비유할 수 있다.&lt;/li&gt;
&lt;li&gt;인장으로 봉인된 편지는 누구나 인장을 뜯어서 열어볼 수 있다&lt;/li&gt;
&lt;li&gt;하지만 인장 확인을 통해 인장을 소유한 발신자가 편지를 보냈음을 증명할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTPS 암호화 과정(SSL Handshake 동작 과정)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOsYiJ/dJMcafSzI3A/Ln682duvHhxsbmVqpW9Xu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOsYiJ/dJMcafSzI3A/Ln682duvHhxsbmVqpW9Xu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOsYiJ/dJMcafSzI3A/Ln682duvHhxsbmVqpW9Xu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOsYiJ%2FdJMcafSzI3A%2FLn682duvHhxsbmVqpW9Xu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;483&quot; height=&quot;416&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSL 통신 과정은 &amp;ldquo;SSL 핸드셰이크 &amp;rarr; Session &amp;rarr; Session 종료&amp;rdquo; 순으로 진행된다.&lt;/li&gt;
&lt;li&gt;HTTPS 통신 과정을 그림과 함께 차례대로 살펴보자.&lt;/li&gt;
&lt;li&gt;SSL 핸드셰이크&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 서버와 클라이언트는 어떤 암호화 알고리즘을 사용할지 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정이 됐으면, 암호화 알고리즘을 사용해서 대칭키를 만들고 서로 나누어 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1번: Client Hello&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 서버에게 클라이언트 측에서 생성한 랜덤 데이터, 클라이언트가 사용 가능한 암호화 알고리즘 후보들을 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이전에 서버와 SSL 핸드셰이킹을 진행했다면, 기존의 세션을 재활용하기 위해 세션 키를 전송한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2번: Server Hello&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 보낸 암호화 알고리즘 후보들 중 사용 가능한 알고리즘을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 클라이언트에게 서버 측에서 생성한 랜덤 데이터, 선택한 암호화 알고리즘, 인증서를 발송한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3번: 인증서 검증 및 대칭키 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 서버로부터 받은 인증서(전자서명)가 CA에 의해 발급된 것인지 확인하기 위해 클라이언트 측에 저장된 CA의 공개키로 인증서를 검증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증이 성공했다면, 해당 서버가 CA로부터 인증 받았다는 것이 보장된다. 서버를 믿을 수 있게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 클라이언트 측에서 생성한 랜덤 데이터와 서버 측에서 생성한 랜덤 데이터를 사용해서 pre master secret라는 대칭키를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pre master secret는 클라이언트와 서버의 암호화 통신에 사용될 대칭키다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;4번: pre master secret를 서버에게 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 pre master secret을 안전하게 암호화해서 서버에게 전달해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서에 들어있던 서버의 공개키로 암호화해서 전달하는 것.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;5번: pre master secret 수신&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 자신의 개인키로 pre master secret을 복호화해서 얻어낸다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;6번: master secret 및 session key 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버와 클라이언트는 일련의 과정을 거쳐서 pre master secret를 master secret으로 만들고 master secret으로 session key를 생성한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DNS&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DNS란&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DNS는 호스트의 Domain Name을 IP주소로 바꿔주는 애플리케이션 계층 프로토콜이다.&lt;/li&gt;
&lt;li&gt;DNS는 DNS 서버들이 계층구조로 구현된 분산 데이터베이스다. DNS 전세계적으로 수많은 호스트에 의해 사용되므로 여러 개의 서로 나누어서 구현하는 것이 효율적이고 확장성을 높여준다.&lt;/li&gt;
&lt;li&gt;DNS는 UDP위에서 동작하고 포트번호는 53번이다.&lt;/li&gt;
&lt;li&gt;DNS는 하나의 도메인 네임에 여러개의 IP주소를 대응할 수 있다. 이렇게 함으로써 부하를 분산하는 효과를 줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DNS 작동 방식(동작 과정)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저에 URI(&lt;a href=&quot;http://www.naver.com/index.html&quot;&gt;www.naver.com/index.html&lt;/a&gt; 를 입력하고 엔터&lt;/li&gt;
&lt;li&gt;브라우저는 URI로부터 호스트 네임(&lt;a href=&quot;http://www.naver.com&quot;&gt;www.naver.com&lt;/a&gt;)을 추출하고 DNS 클라이언트로 넘긴다.&lt;/li&gt;
&lt;li&gt;DNS 클라이언트는 DNS 서버로 호스트 네임을 전달한다.&lt;/li&gt;
&lt;li&gt;DNS 서버는 호스트 네임에 대한 IP주소를 DNS 클라이언트에게 응답한다.&lt;/li&gt;
&lt;li&gt;브라우저가 DNS로부터 IP주소를 받으면, 브라우저는 그 IP 주소와 그 주소의 80번 포트에 위치하는 HTTP 서버 프로세스로 TCP 연결을 초기화한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DNS 질의 종류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재귀적 질의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 DNS 서버에게 보내는 요청&lt;/li&gt;
&lt;li&gt;클라이언트는 서버에게 도메인 이름에 대한 IP주소를 알려달라고 요청. 리졸버는 최종 IP 주소를 찾을 때까지 다른 DNS 서버들과의 통신을 책임지고 수행하며, 클라이언트는 최종 IP만 받는다.&lt;/li&gt;
&lt;li&gt;사용 주체: 일반 사용자 컴퓨터(스텁 리졸버)가 로컬 DNS 서버(리졸버)에 요청할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;반복적 질의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DNS 리졸버가 다른 DNS 서버에게 보내는 요청&lt;/li&gt;
&lt;li&gt;요청을 받은 서버는 최종 IP 주소를 알고 있다면 알려주지만, 모른다면 다음단계의 DNS 서버 주소를 응답으로 돌려준다. 그러면 리졸버는 새로운 주소로 요청하는 과정을 반복. 즉, IP 주소를 찾아가는 과정을 리졸버 자신이 책임지고 반복하는 방식&lt;/li&gt;
&lt;li&gt;사용 주체: DNS 리졸버가 루트 서버, TLD 서버, 권한 있는 네임 서버와 통신할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DNS 서버에게 IP주소를 요청할 때, 왜 UDP를 사용할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UDP는 빠르기 때문에 DNS에 활용된다. DNS의 목적은 단지 도메인 네임에 대한 IP주소를 얻는 것이다. 따라서 오버헤드가 적고 빠른 UDP에서 동작하는 것이 적합하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DNS 레코드란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 이름을 IP 주소와 연결하는 데 사용되는 일련의 지침. 인터넷상의 &amp;lsquo;전화번호부&amp;rsquo;와 같아서, 사람이 기억하기 쉬운 도메인 이름을 컴퓨터가 통신에서 사용하는 IP로 변환하는 역할.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 레코드의 종류 및 기능&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 레코드(Address Record): 도메인 이름 또는 호스트 이름을 IPv4주소에 매핑한다. 웹사이트 접속 시 가장 기본적으로 사용됨&lt;/li&gt;
&lt;li&gt;AAAA 레코드(IPv6 Address Record): A레코드와 동일한 역할을 하지만, IPv6 주소에 매핑&lt;/li&gt;
&lt;li&gt;CNAME 레코드(Canoncial Name Record): 하나의 도메인 이름을 다른 도메인 이름으로 연결한다. 예를 들어, blog.example.com을 example.com과 연결&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>네트워크</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/30</guid>
      <comments>https://icecupregular.tistory.com/30#entry30comment</comments>
      <pubDate>Wed, 26 Nov 2025 17:47:27 +0900</pubDate>
    </item>
    <item>
      <title>[우아한 테크코스 8기 백엔드]프리코스 4~6주차 회고 -오픈 미션-</title>
      <link>https://icecupregular.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우아한 테크코스 4-5주차 미션이 끝났습니다. 8기에는 지난 기수들과 다르게 마지막 미션이 한 주가 더 늘어난 2주간의 오픈 미션으로 진행되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대해 프리코스를 끝내며 미션에 대한 회고를 적어보려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/LEEJaeHyeok97/digging-into-db&quot;&gt;https://github.com/LEEJaeHyeok97/digging-into-db&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763995760240&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - LEEJaeHyeok97/digging-into-db: 데이터베이스 프로그램을 자바코드로 직접 만들기&quot; data-og-description=&quot;데이터베이스 프로그램을 자바코드로 직접 만들기. Contribute to LEEJaeHyeok97/digging-into-db development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/LEEJaeHyeok97/digging-into-db&quot; data-og-url=&quot;https://github.com/LEEJaeHyeok97/digging-into-db&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/deNkyl/hyZOoMyvYT/Ur7FcH4stD7M9rfTNtXr3k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cfZBw7/hyZOIqqHo6/RKNLXBrrpSgQreh4pQg4y1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/LEEJaeHyeok97/digging-into-db&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/LEEJaeHyeok97/digging-into-db&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/deNkyl/hyZOoMyvYT/Ur7FcH4stD7M9rfTNtXr3k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cfZBw7/hyZOIqqHo6/RKNLXBrrpSgQreh4pQg4y1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - LEEJaeHyeok97/digging-into-db: 데이터베이스 프로그램을 자바코드로 직접 만들기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스 프로그램을 자바코드로 직접 만들기. Contribute to LEEJaeHyeok97/digging-into-db development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈 미션의 첫 인상은 &amp;lsquo;충격&amp;rsquo;이었습니다. 이전 기수에는 편의점 문제가 나왔었는데 저는 사실 잘 풀지 못했었습니다. 그래서, 굉장한 각오를 하고 맞닥드린 4주차의 오픈 미션은 &amp;lsquo;자유&amp;rsquo; 그 자체였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1,2,3주차의 프리코스 미션에서 배운 것들을 잘 적용했냐고 물어본다면 부끄러울 정도로 적용하지 못한 코드를 제출했습니다. 그래도 한가지 변명이라면 DB라는 row level의 프로그램을 만드는 것과 더 하이 레벨의 애플리케이션 프로그램을 만드는 것은 코드가 분명 달랐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 반성을 하기 위해 오픈 미션을 진행하면서 느낀점을 적어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 새로운 방식의 오픈 미션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈 미션이 지난 기수들 처럼 기준이나 가이드가 있길 바랬는데 심지어 바리스타에 도전하는 것 까지도 된다는 설명에 우테코는 정말 무엇이든 좋으니 우리가 &amp;lsquo;도전&amp;rsquo;에 뛰어들길 원하는구나 생각됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근까지의 저의 가장 큰 도전은 &amp;lsquo;데이터베이스&amp;rsquo;를 직접 자바 코드로 만드는 일이었습니다. 이전에는 시간이 없다는 생각으로, 아직 데이터베이스에 대한 지식들이 부족하다는 등의 이유로 개발을 초기에 중단했던 프로그램이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우테코가 만들어 준 오픈 미션 시간 동안 근래 최고의 도전인 &amp;lsquo;데이터베이스 프로그램 자바로 만들기&amp;rsquo;에 다시 도전하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 우테코가 제시한 객체지향 원칙들을 지키지 못한 아쉬움&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명, 1~3주차 까지의 우테코는 객체지향을 지킬 것을 제안했고 이번 우테코의 핵심 키워드는 역설적이게도 &amp;lsquo;도전&amp;rsquo;이었습니다. 현재까지의 세이프존(safe zone)을 버리고 정말 큰 &amp;lsquo;도전&amp;rsquo;을 한다면 역설적이게도 지금까지 지켜온 객체지향 원칙이나 테스트 코드 작성 등을 오히려 더 지키기 어렵게 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네, 그래서 저도 객체지향 원칙은 거의 지키지 못했고 그나마 MVC패턴으로 클래스를 분리하고 도메인을 나눈 것 정도가 전부고 테스트 코드는 한 클래스에 여러 절차적 코드가 섞여 차마 테스트 하기 어려운 그런 코드가 완성됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 실제 데이터베이스인 MySQL의 코드를 오픈 소스에서 살펴보니 대부분이 switch문으로 이루어져 있었습니다. switch문을 쓰는게 row level 프로그램에서는 성능상 이점이 있기 때문이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 우리는 지금까지 객체지향 원칙을 지키며 switch문을 지양해야 했습니다. 이런 부분들에서 많은 고민이 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 정답은 없을텐데 &amp;ldquo;앞이 안보이면 우선 &lt;b&gt;돌아가는 쓰레기&lt;/b&gt;라도 만들어보자&amp;rdquo;는 마음으로 매일매일 학습한 내용과 구현한 내용을 기록하며 미션을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 데이터베이스 프로그램 설계의 아쉬움&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스가 정말 다양한 기능을 포함하고 있고 복잡한 설계가 들어간 소프트웨어라는 것을 직접 만들어보며 제대로 느낄 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 새로운 주요 기능을 구현할 때면 이전 코드들에도 많은 수정이 필요했습니다. 이런 부분이 MySQL의 코드의 많은 부분이 절차적으로 짜여진 이유가 아닐까 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, 먼저 데이터베이스 크래쉬 상황에서 recovery를 위한 redo log를 저장하는 기능이 있고 이를 트랜잭션끼리의 격리를 보장하기 위해 undo log처럼 쓰는 상황에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 데이터베이스 크래시 상황에서 복구를 위한 &lt;b&gt;redo log&lt;/b&gt;를 남기는 기능만을 생각하고 설계를 시작했습니다. 그런데 이후에 트랜잭션 간의 격리를 보장하기 위해 &lt;b&gt;undo log&lt;/b&gt;와 비슷한 역할을 하는 기능이 필요해졌을 때, 이미 작성해 둔 로그 모듈이 &amp;ldquo;크래시 복구&amp;rdquo;에만 초점이 맞춰져 있어서 그대로 재사용하기가 어려웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;어차피 로그니까 하나의 구조로 같이 다루면 되지 않을까?&amp;rdquo; 하고 단순하게 접근했다가, 나중에 가서는 redo와 undo의 목적, 쓰기 타이밍, 저장해야 하는 정보 구조가 서로 다르다는 것을 뒤늦게 체감했고, 그때부터는 기존 코드를 우회하는 식의 수정이 많아졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀점&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/98KwN/dJMcacuLFDD/q4ElHzBf2RarxcyEkBJLbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/98KwN/dJMcacuLFDD/q4ElHzBf2RarxcyEkBJLbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/98KwN/dJMcacuLFDD/q4ElHzBf2RarxcyEkBJLbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F98KwN%2FdJMcacuLFDD%2Fq4ElHzBf2RarxcyEkBJLbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;98&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자유로움 속에서 방향을 잡느라 헤어나지 못한 3주가 된 것 같습니다. 미션이 끝나고 다시 코드를 봤을 때 아쉬운 점이 많이 보였고, 다시 설계한다면 저렇게 안할것 같은데.. 라는 생각이 들기도 했습니다. 가장 아쉬운 점은 객체지향적으로 1,2,3주차 때 배운 원칙을 지킨 코드를 끝까지 원칙을 지키며 작성하지 못했다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 아쉬움과 후회도 드는 오픈 미션이었습니다. 이런 과정 또한 성장의 발판으로 생각하며 최종 코딩테스트를 기다리며 다시 달리려고 합니다. 4주차 간 특별한 미션을 준비해주신 우아한 테크코스 운영진 여러분과 지원자 분들 너무 고생 많으셨습니다!&lt;/p&gt;</description>
      <category>우아한 테크코스 프리코스</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/29</guid>
      <comments>https://icecupregular.tistory.com/29#entry29comment</comments>
      <pubDate>Tue, 25 Nov 2025 00:00:05 +0900</pubDate>
    </item>
    <item>
      <title>MySQL의 Using filesort와 Using temporary</title>
      <link>https://icecupregular.tistory.com/28</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;MySQL의 filesort&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 8.0 버전 기반으로 공식문서에는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;filesort is the algorithm MySQL uses to sort results when it cannot use an index to retrieve the rows in sorted order.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL 8.0 Reference Manual, 8.2.1.14 How MySQL Optimizes ORDER BY&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 인덱스를 통해 정렬된 순서로 가져올 수 없을 때 MySQL이 사용하는 정렬 알고리즘을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;파일&amp;rdquo;이라는 단어 때문에 디스크에 쓰는 정렬이라고 오해하기 쉽지만, filesort는 &amp;lsquo;정렬 알고리즘&amp;rsquo;이며, 반드시 파일(디스크)를 쓰는 것은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MySQL의 정렬 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL이 조회한 데이터를 정렬하는 방식은 크게 두 가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 사용하는 방식과 filesort 를 사용하는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스를 이용하여 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회 쿼리의 결과가 인덱스를 통해 정렬할 수 있는 상태라면 데이터를 인덱스 순서대로 내려주면 되므로 &lt;b&gt;별도로 정렬할 필요가 없습니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인덱스 칼럼의 순서와 어긋나는 정렬이 요구되는 인덱스를 사용할 수 없는 상태라면 재정렬을 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, (post_id, user_id, created_at)의 순서로 복합 인덱스가 걸려있다고 가정해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;ORDER BY (post_id, user_id, created_at); // -&amp;gt; 정렬 칼럼이 인덱스 칼럼 순서와 일치하므로 인덱스 이용가능
ORDER BY (post_id, user_id); // -&amp;gt; 정렬 칼럼이 인덱스의 선행 칼럼을 포함한 부분집합이고 순서 동일이므로 인덱스 이용가능
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 복합인덱스의 부분집합 중 선행 칼럼들의 순서가 같은 정렬은 인덱스를 사용할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 인덱스 사용이 불가능한 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;ORDER BY user_id, post_id, created_at; // 순서가 다름
ORDER BY user_id, created_at; // 선행 칼럼 누락
ORDER BY post_id, created_at; // 2번째 칼럼 누락
ORDER BY post_id, user_id, created_at, updated_at; // 인덱스에 포함되지 않은 칼럼 존재
ORDER BY post_id DESC, user_id, created_at; // 정렬 방식이 다름
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 모든 칼럼이 ORDER BY 에만 명시될 필요는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;where 에 명시되어도 충분히 사용 가능하며, 아래는 그 예시입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;where 로 선행 칼럼이 고정된 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select * from posts where post_id = 10 order by user_id, created_at;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;post_id = 10으로 선행 칼럼이 고정되었기 때문에, 인덱스 입장에서는 사실상 (user_id, created_at) 으로 정렬된 구간을 스캔하는 것과 같으므로&lt;/li&gt;
&lt;li&gt;따라서, 인덱스를 활용한 정렬이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select * from posts where post_id = 10 and user_id = 882837 order by created_at;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;post_id와 user_id 둘 다 동등 조건.&lt;/li&gt;
&lt;li&gt;남는 정렬 키는 created_at 뿐인데, 인덱스 상에서 post_id=10,user_id=882837 구간은 이미 created_at 기준으로 정렬되어 있습니다. 이 경우도 인덱스를 사용한 정렬이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스의 선행 칼럼들이 where 에서 동등조건으로 완전히 고정되어 있으면, order by에서 그 이후 칼럼들을 써도 인덱스 정렬을 그대로 활용할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. where + order by 를 함께 사용할 때의 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select * from posts where post_id=10 order by user_id, created_at;
// -&amp;gt; 인덱스 활용 가능

select * from posts where post_id=10 and user_id=773277 order by created_at;
// -&amp;gt; 이것도 가능

select * from posts where post_id = 10 order by post_id, user_id, created_at;
// -&amp;gt; OK post_id는 어차피 전부 10이라 의미가 없지만 인덱스 정렬 순서는 일치
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 인덱스 정렬을 깨는 대표적인 예시로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선행 칼럼에 범위 조건(&amp;gt;, &amp;lt;, between, like)을 사용하면 인덱스 정렬을 깨게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select * from posts where post_id &amp;gt; 10 order by user_id, created_at;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;post_id 는 범위 조건이기 때문에, 인덱스는 post_id 까지는 정렬/탐색에 사용하지만 그 이후에는 보통 filesort가 발생합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동등 비교시에는 한개 구간만 보면 됐지만, 범위 조건 시 여러 연속 구간을 봐야하고 각, 구간 안에서는 정렬이 되어 있지만 모든 구간을 통틀어 봤을 때는 전혀 정렬되어 있지 않습니다. 따라서, 인덱스 정렬이 안되는 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. where 절에서 조건의 나열순서는 상관이 없나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;where 절에 SQL 문법상 어떤 순서로 적어놨는지는 전혀 중요하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 엔진은 where 절을 파싱해서 논리적인 조건으로만 바라보고 정렬 사용 가능성도 동등하게 평가합니다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;-- 1
WHERE post_id = 10
  AND user_id = 20
  AND created_at &amp;gt;= '2025-01-01'

-- 2
WHERE created_at &amp;gt;= '2025-01-01'
  AND user_id = 20
  AND post_id = 10

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1의 쿼리와 2의 쿼리가 완전히 동등합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;filesort를 이용한 정렬 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 사용할 수 없는 경우는 내부적으로 조회된 데이터에 정렬을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 방법이 바로 filesort 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filesort 가 필요한 상황이 발생하면 MySQL 내부에서는 테이블을 Sort Buffer 에 옮겨 정렬 작업을 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filesort 를 이용하는 방식은 두 가지로 나뉘는데, 이것이 Using temporary 와 Using filesort 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Using filesort&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Using filesort 는 쿼리에서 첫 번째로 조회하는 테이블(드라이빙 테이블)에 대해서만 정렬이 필요한 경우 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL은 조인을 할 때 어떤 테이블을 먼저 읽을지(=드라이빙 테이블) 을 정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음에 그 테이블의 각 row를 기준으로 나머지 테이블들을 붙여 나가는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, posts와 comment 를 조인할 때 posts에 대해서만 정렬을 수행하면 되므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Using filesort는 쿼리에서 첫 번째로 읽는 테이블에 대해서만 정렬이 필요한 경우라고 표현합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT  p.*, c.*
FROM    posts p       -- 드라이빙 테이블
LEFT JOIN comments c  -- 드리븐 테이블
       ON c.post_id = p.id
WHERE   p.is_deleted = 0
ORDER BY p.created_at DESC;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 ORDER BY에 조인된 다른 테이블의 컬럼이 같이 있는 경우를 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT  p.*, c.*
FROM    posts p
LEFT JOIN comments c
       ON c.post_id = p.id
WHERE   p.is_deleted = 0
ORDER BY c.created_at DESC;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 정렬 기준이 드리븐 테이블인 comments가 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이빙 테이블(posts) 만 정렬한다고 해도 comments.created_at 기준 순서를 보장할 수가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 그래서 MySQL은 posts + comments 를 조인해서 한 덩어리의 결과를 만들고 그 결과를 다시 정렬해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 조인 결과를 임시 테이블에 쌓고(Using temporary) 그 임시 테이블을 다시 정렬하면서 Using filesort 가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대한 filesort 가 발생하는 상황을 없앨 수 있도록 쿼리를 튜닝하는 시도가 필요하며, 불가피하다면 해당 부분을 애플리케이션 서버 단에 맡기는 것 또한 충분히 합리적인 고려대상이 될 수 있습니다.&lt;/p&gt;</description>
      <category>DB</category>
      <category>filesort</category>
      <category>MySQL</category>
      <category>Using temporary</category>
      <category>정렬방식</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/28</guid>
      <comments>https://icecupregular.tistory.com/28#entry28comment</comments>
      <pubDate>Sun, 23 Nov 2025 13:00:45 +0900</pubDate>
    </item>
    <item>
      <title>[이음] 프로젝트에 쿼리의 실행계획을 분석하며 최적화 해보기</title>
      <link>https://icecupregular.tistory.com/27</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1479&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A7WDC/dJMcacPZEaf/C9YGipB1lV54KKIhEeg6T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A7WDC/dJMcacPZEaf/C9YGipB1lV54KKIhEeg6T0/img.png&quot; data-alt=&quot;핵심 축약 표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A7WDC/dJMcacPZEaf/C9YGipB1lV54KKIhEeg6T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA7WDC%2FdJMcacPZEaf%2FC9YGipB1lV54KKIhEeg6T0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1479&quot; height=&quot;156&quot; data-origin-width=&quot;1479&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;핵심 축약 표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;게시글의 댓글 조회&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IkzPN/dJMcaaqax2f/4V1haNQ5g2Dw0bZjKMRmSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IkzPN/dJMcaaqax2f/4V1haNQ5g2Dw0bZjKMRmSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IkzPN/dJMcaaqax2f/4V1haNQ5g2Dw0bZjKMRmSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIkzPN%2FdJMcaaqax2f%2F4V1haNQ5g2Dw0bZjKMRmSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;82&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 쿼리는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT c.* from comments c WHERE c.post_id = :postId and c.is_deleted = false ORDER BY c.created_at asc 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;댓글을 조회할 게시글의 id 값으로 한번 필터링한 후 삭제 여부가 false인 것들을 조회해 생성일자 오름차순으로 조회했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;757&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/InqB5/dJMcaacDiSj/f8Kkj1yNgXIPcLOS1R9uC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/InqB5/dJMcaacDiSj/f8Kkj1yNgXIPcLOS1R9uC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/InqB5/dJMcaacDiSj/f8Kkj1yNgXIPcLOS1R9uC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FInqB5%2FdJMcaacDiSj%2Ff8Kkj1yNgXIPcLOS1R9uC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;674&quot; height=&quot;359&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;757&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;explain으로 실행계획을 분석했을 때 개선할 수 있는 점을 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 칼럼별로 간단히 결과를 하나하나 분석해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id select_type table partitions type possible_keys key key_len ref rows filtered Extra&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JKe6B/dJMcaa4LYU3/g0MOdh5E4HVZh6U5npKOm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JKe6B/dJMcaa4LYU3/g0MOdh5E4HVZh6U5npKOm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JKe6B/dJMcaa4LYU3/g0MOdh5E4HVZh6U5npKOm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJKe6B%2FdJMcaa4LYU3%2Fg0MOdh5E4HVZh6U5npKOm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;691&quot; height=&quot;147&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id: 쿼리 내부의 SELECT 단위 번호.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;select_type: select 문이 어떤 형태인지 나타냄&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SIMPLE: 서브쿼리 없는 단일 SELECT&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;table: 이 row가 설명하는 테이블 이름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;partitions: 어떤 파티션을 사용했는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type(★): 테이블 접근 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ALL = 풀 테이블 스캔&lt;/li&gt;
&lt;li&gt;INDEX=인덱스 풀 스캔&lt;/li&gt;
&lt;li&gt;ref, eq_ref=key lookup&lt;/li&gt;
&lt;li&gt;range=범위 스캔&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;possible_keys: 옵티마이저가 사용할 수 있다고 판단한 후보 인덱스 목록&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null: 인덱스를 전혀 사용할 수 없는 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key: 실제로 선택된 인덱스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key_len: 사용한 인덱스의 바이트 길이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref: 인덱스 조회에 사용된 값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rows: 옵티마이저가 예측한 스캔 row 수&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제가 아니라 추정치 기반이라 통계 기반 의존&lt;/li&gt;
&lt;li&gt;rows가 높으면 느린 쿼리 가능성 높음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filtered: 조건을 통과한 row 비율&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Extra(★): 쿼리가 어떤 추가 작업을 수행하는지 보여주는 매우 중요한 정보&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Using where: WHERE 조건을 통해 row 필터링 중&lt;/li&gt;
&lt;li&gt;Using index: 커버링 인덱스 &amp;rarr; 테이블 접근 필요X&lt;/li&gt;
&lt;li&gt;Using temporary: 임시 테이블 생성&lt;/li&gt;
&lt;li&gt;Using filesort: ORDER BY 수행 시 파일 정렬&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;explain 을 통해 현재 해당 쿼리는 인덱스를 전혀 타지 못하고, ORDER BY 문 때문에 내부적으로 filesort 를 진행중인 매우 비효율적인 쿼리를 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 개선해봅시다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 인덱스가 전혀 없던 Comment 엔터티에 인덱스를 먼저 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF0W3s/dJMcacarT4G/rKEJZ0loMkCKFR1ONK9EjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF0W3s/dJMcacarT4G/rKEJZ0loMkCKFR1ONK9EjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF0W3s/dJMcacarT4G/rKEJZ0loMkCKFR1ONK9EjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF0W3s%2FdJMcacarT4G%2FrKEJZ0loMkCKFR1ONK9EjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;280&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 where문과 orderby 에서 사용하는 오름차순 순서대로 선행 칼럼을 postId로 뒤이어 isDeleted와 createdAt 오름차순으로 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cva9pH/dJMcacIibFr/miZmKA6krhDcPKXvadRkiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cva9pH/dJMcacIibFr/miZmKA6krhDcPKXvadRkiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cva9pH/dJMcacIibFr/miZmKA6krhDcPKXvadRkiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcva9pH%2FdJMcacIibFr%2FmiZmKA6krhDcPKXvadRkiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;270&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 filesort 도 진행하지 않고 새로 생성한 idx_post_id_is_deleted_created_at 인덱스도 key 커럼을 통해 잘 타고 있음을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;explain을 통해 프로젝트의 기존의 슬로우 쿼리를 발견하고 인덱스를 타도록 개선할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;게시글의 좋아요 여부 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5vyXo/dJMcafSybVT/E2cqwEkkn4JV35i0sOVlQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5vyXo/dJMcafSybVT/E2cqwEkkn4JV35i0sOVlQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5vyXo/dJMcafSybVT/E2cqwEkkn4JV35i0sOVlQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5vyXo%2FdJMcafSybVT%2FE2cqwEkkn4JV35i0sOVlQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;232&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 쿼리는 Like 엔터티가 Unique 제약 조건으로 인덱스가 걸려 있기 때문에 const type으로 조회가 완료되어 조회 성능은 좋은 편으로 조회에는 문제가 없는 쿼리로 생각되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;현재 상태가 진행중인 게시글, 완료 상태인 게시글 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HqEpy/dJMcafLMpQN/OqErdcMzIPPbYLaepkkpyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HqEpy/dJMcafLMpQN/OqErdcMzIPPbYLaepkkpyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HqEpy/dJMcafLMpQN/OqErdcMzIPPbYLaepkkpyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHqEpy%2FdJMcafLMpQN%2FOqErdcMzIPPbYLaepkkpyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;106&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1405&quot; data-origin-height=&quot;45&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOzkXC/dJMcaaRe31q/hFlAPj34t7pStG69WNGdYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOzkXC/dJMcaaRe31q/hFlAPj34t7pStG69WNGdYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOzkXC/dJMcaaRe31q/hFlAPj34t7pStG69WNGdYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOzkXC%2FdJMcaaRe31q%2FhFlAPj34t7pStG69WNGdYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;24&quot; data-origin-width=&quot;1405&quot; data-origin-height=&quot;45&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;select * from posts p where p.is_completed = false and p.is_deleted = false order by p.created_at desc limit ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행계획을 출력해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/du77gD/dJMcajgiao6/3KUQsxGiomm91AUaKZ8Tn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/du77gD/dJMcajgiao6/3KUQsxGiomm91AUaKZ8Tn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/du77gD/dJMcajgiao6/3KUQsxGiomm91AUaKZ8Tn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdu77gD%2FdJMcajgiao6%2F3KUQsxGiomm91AUaKZ8Tn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;791&quot; height=&quot;265&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 풀스캔을 하고 있고 idx_created_at 이라는 인덱스를 타고 있는걸 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Posts 엔터티에는 생성일 내림차순으로 인덱스가 생성되어 있기 때문에 이걸 옵티마이저가 선택한 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LMvRV/dJMcadNUyJ1/qViQdgKbnktCW1l2K09NoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LMvRV/dJMcadNUyJ1/qViQdgKbnktCW1l2K09NoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LMvRV/dJMcadNUyJ1/qViQdgKbnktCW1l2K09NoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLMvRV%2FdJMcadNUyJ1%2FqViQdgKbnktCW1l2K09NoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;761&quot; height=&quot;153&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 개선할 수 있는 부분이 없는지 수행 시간을 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP1BVf/dJMcagRr8VB/oe6nARiNUgD5yK7uGMn7t1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP1BVf/dJMcagRr8VB/oe6nARiNUgD5yK7uGMn7t1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP1BVf/dJMcagRr8VB/oe6nARiNUgD5yK7uGMn7t1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP1BVf%2FdJMcagRr8VB%2Foe6nARiNUgD5yK7uGMn7t1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;45&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 걸린 시간은 0.8초 정도 이대로는 사용하기 어렵습니다. 더미 데이터가 적은데도 이정도 시간이라면 실제 운영환경에서는 더 느려질 것이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선할 수 있는 부분으로 인덱스 풀 스캔을 범위 스캔을 하도록 개선해볼 수 있다고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범위 스캔을 위해 인덱스를 수정 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/97nYs/dJMcacnZjaD/n1VuZki4q3AbdAChdNDpzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/97nYs/dJMcacnZjaD/n1VuZki4q3AbdAChdNDpzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/97nYs/dJMcacnZjaD/n1VuZki4q3AbdAChdNDpzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F97nYs%2FdJMcacnZjaD%2Fn1VuZki4q3AbdAChdNDpzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;94&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 type이 index 던 것을 ref로 동등 비교를 통해 여러 non-unique 인덱스를 읽는 방식으로 개선할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHyAf3/dJMcahQmz9o/eA6jKVSJx4JoYYOwSEoq1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHyAf3/dJMcahQmz9o/eA6jKVSJx4JoYYOwSEoq1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHyAf3/dJMcahQmz9o/eA6jKVSJx4JoYYOwSEoq1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHyAf3%2FdJMcahQmz9o%2FeA6jKVSJx4JoYYOwSEoq1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;709&quot; height=&quot;47&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 응답 속도도 0.8초대 이던 것을 0.09초대로 개선할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB0uJn/dJMcahQmz9v/lJg5GS2wdj8DZh7PJUkUU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB0uJn/dJMcahQmz9v/lJg5GS2wdj8DZh7PJUkUU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB0uJn/dJMcahQmz9v/lJg5GS2wdj8DZh7PJUkUU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB0uJn%2FdJMcahQmz9v%2FlJg5GS2wdj8DZh7PJUkUU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;30&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;b&gt;완료상태인 게시글 조회&lt;/b&gt; 쿼리에 대해서도 실행계획을 확인해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 select * from posts p where p.id&amp;nbsp;&amp;lt; 810000000000000029 and p.is_completed = true and p.is_deleted = false order by p.created_at desc limit 10;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 무한스크롤을 위한 쿼리였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZpT5l/dJMcabWUF8F/2jumFGTt4f0us7nuRfrLuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZpT5l/dJMcabWUF8F/2jumFGTt4f0us7nuRfrLuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZpT5l/dJMcabWUF8F/2jumFGTt4f0us7nuRfrLuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZpT5l%2FdJMcabWUF8F%2F2jumFGTt4f0us7nuRfrLuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;173&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범위 스캔을 잘 타고 있고 key = primary로 클러스터링 인덱스를 활용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 Using filesort 로 내부적으로 정렬을 수행하고 있으므로 이 부분을 병목지점으로 생각하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제거해보기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;created_at 에서 생성일 순으로 정렬된 인덱스가 없기 때문에 내부적으로 filesort 가 일어난 것으로 보였고, 완료 상태인 게시글로 먼저 필터링을 해주는게 수 만건의 id로 필터링 하는 것 보다 효과적으로 쿼리할 수 있을 것이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 보조 인덱스를 is_completed를 선행 칼럼으로 is_completed, id, created_at 순서로 생성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE 절에 컬럼을 명시하더라도, ORDER BY 에서 컬럼의 순서가 잘못되거나 특정 컬럼이 누락되면 동일한 이유로 정렬에 인덱스를 사용할 수 없다.(WHERE 절에서는 당연히 컬럼 순서가 바뀌어도 무관하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, WHERE 절이 동등비교가 아닐 때에도 정렬 시 인덱스를 사용할 수 없으므로, 그럴 땐 차라리 ORDER BY 조건에 명시적으로 해당 칼럼을 넣어주는 것이 더 나을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용을 기억하고 쿼리문을 수정해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;// 이전 쿼리
explain select * from posts p where p.id &amp;lt; 810000000000000029 and p.is_completed = true and p.is_deleted = false order by p.created_at desc limit 10;
// 수정한 쿼리
explain select * from posts p where p.id &amp;lt; 810000000000000029 and p.is_completed = true and p.is_deleted = false order by p.id, p.created_at desc limit 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnnT6S/dJMcacarT7E/ZpqLKkxgemlXbzBmkMCZWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnnT6S/dJMcacarT7E/ZpqLKkxgemlXbzBmkMCZWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnnT6S/dJMcacarT7E/ZpqLKkxgemlXbzBmkMCZWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnnT6S%2FdJMcacarT7E%2FZpqLKkxgemlXbzBmkMCZWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;150&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id값을&lt;/p&gt;
&lt;div&gt;&lt;span style=&quot;background-color: #000000; color: #eb5757;&quot; data-token-index=&quot;1&quot;&gt;ORDER BY&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서 명시적으로 컬럼을 넣어줌으로 써 정렬된 인덱스를 사용할 수 있으므로&lt;/p&gt;
&lt;div&gt;&lt;span style=&quot;background-color: #000000; color: #eb5757;&quot; data-token-index=&quot;3&quot;&gt;Using filesort&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 제거할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mmesr/dJMcagKGJkd/XhyPb0LKYMnGk2PciEIDXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mmesr/dJMcagKGJkd/XhyPb0LKYMnGk2PciEIDXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mmesr/dJMcagKGJkd/XhyPb0LKYMnGk2PciEIDXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMmesr%2FdJMcagKGJkd%2FXhyPb0LKYMnGk2PciEIDXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;142&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 전 0.09초가 걸리던 쿼리를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1391&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EyuAo/dJMcaacDiXW/0BwyJdUIHkbpMAILFCBwU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EyuAo/dJMcaacDiXW/0BwyJdUIHkbpMAILFCBwU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EyuAo/dJMcaacDiXW/0BwyJdUIHkbpMAILFCBwU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEyuAo%2FdJMcaacDiXW%2F0BwyJdUIHkbpMAILFCBwU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;143&quot; data-origin-width=&quot;1391&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 튜닝 후 0.06초 까지 단축할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠깐 범위 스캔에 대해 알아 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RANGE가 나온 순간, 그 뒤 컬럼(c)은 ORDER BY에 절대 쓸 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서도 이것 때문에 정렬이 깨지고 filesort가 터지게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 동등 조건은 인덱스를 계속 이어서 탈 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 범위 조건은 해당 컬럼까지만 인덱스를 사용하고 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ 그 뒤 컬럼은 인덱스 정렬 최적화 불가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ ORDER BY는 RANGE가 나오기 전 컬럼까지만 인덱스로 정렬 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;넓은 범위&quot;가 왜 정렬 최적화를 못하게 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 인덱스는 이렇게 정렬되어 있습니다:&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;(A ASC, B ASC, C ASC)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 A 기준으로 group&lt;/li&gt;
&lt;li&gt;그 안에서 B 기준으로 정렬&lt;/li&gt;
&lt;li&gt;그 안에서 C 기준으로 정렬&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 RANGE는 이렇게 넓습니다&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;B &amp;lt; 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; B 값 1,2,3, ..., 99 전부 포함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 B 그룹 내부에만 C가 정렬됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;B 전체를 통합하면 C 정렬 순서가 깨짐&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;B=1 &amp;rarr; C 순서: 1,2,10
B=2 &amp;rarr; C 순서: 3,4,8
B=3 &amp;rarr; C 순서: 0,9,11
...

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값들을 통째로 가져오면 C는 전혀 정렬되어 있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MySQL은 이렇게 판단합니다:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이건 ORDER BY C를 인덱스 정렬로 해결할 수 없네&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; filesort 수행&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 MySQL 내부 규칙&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 공식 문서에 다음 규칙이 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 인덱스에서 RANGE 조건이 등장하면, 그 컬럼 이후는 정렬 최적화(ORDER BY)를 할 수 없음.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;스캔&amp;rdquo;(next pointer로 읽기)은 가능&lt;/li&gt;
&lt;li&gt;&amp;ldquo;정렬된 순서 유지&amp;rdquo;는 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내가 댓글 단 게시글 조회&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbSesj/dJMcagjCqC3/fiP8KXV9AQYfBFz142bpS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbSesj/dJMcagjCqC3/fiP8KXV9AQYfBFz142bpS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbSesj/dJMcagjCqC3/fiP8KXV9AQYfBFz142bpS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbSesj%2FdJMcagjCqC3%2FfiP8KXV9AQYfBFz142bpS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;70&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1mtkG/dJMcajtPlPp/LezjYFVaRrQuTCdv4So4W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1mtkG/dJMcajtPlPp/LezjYFVaRrQuTCdv4So4W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1mtkG/dJMcajtPlPp/LezjYFVaRrQuTCdv4So4W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1mtkG%2FdJMcajtPlPp%2FLezjYFVaRrQuTCdv4So4W0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;125&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 쿼리는 게시글과 댓글을 조인하는 쿼리기 때문에 2가지 쿼리 시퀀스가 나왔는데, 게시글을 가져오는 쿼리는 PRIMARY 로 클러스터드 인덱스를 타서 성능에 문제가 없을것으로 판단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 인덱스를 전혀 타지 못하고 내부적으로 임시 테이블을 생성하고 filesort 를 수행하는 comment 쪽이 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리는 다음과 같았습니다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select distinct p.* from posts p left join comments c on p.id = c.post_id where c.user_id = 773227704805082357 and p.is_deleted = false order by p.created_at desc;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qYFWm/dJMcachdsaO/PPt0zKSC776SO4wIZSZH8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qYFWm/dJMcachdsaO/PPt0zKSC776SO4wIZSZH8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qYFWm/dJMcachdsaO/PPt0zKSC776SO4wIZSZH8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqYFWm%2FdJMcachdsaO%2FPPt0zKSC776SO4wIZSZH8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;133&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수행 시간 또한 1.86초 라는 프로덕트에 사용할 수 없는 시간대가 걸렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 조인이 되는 곳에는 인덱스를 활용해주는 것이 좋다는 것을 조인 쿼리를 분석할 때 마다 느낄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 대상이 &amp;ldquo;JOIN 결과물&amp;rdquo;이기 때문에 거의 항상 filesort가 발생하고 있었습니다. 게다가 distinct 는 내부적으로 Using temporary 를 사용하게될 문제점도 있어서 가능한 distinct 키워드는 제거하고 쿼리를 수정하는 쪽으로 방향을 잡았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리가 문제인 이유는 다음과 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;comments를 where 절에서 user_id값으로 먼저 탐색하기 때문에 posts의 순서대로 정렬을 다시 거쳐야 했기 때문에 Using filesort 와 Using temporary 가 발생했던 것 이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 개선하기위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;posts 먼저 읽기&lt;/li&gt;
&lt;li&gt;인덱스 (is_deleted, created_at DESC, id)로 스캔&lt;/li&gt;
&lt;li&gt;그 순서대로 comments 조인&lt;/li&gt;
&lt;li&gt;추가 정렬 없이 바로 리턴&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 수정한 쿼리는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;post_id + isDeleted + userId 복합 인덱스를 추가해서 댓글 필터링 조건 조회 시 인덱스를 탈 수 있도록 수정했습니다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select p.* from posts p left join comments c on p.id = c.post_id where c.user_id = 773227704805082357 and c.is_deleted = false and p.is_deleted = false order by p.created_at desc;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5cNxI/dJMcahv3QEn/4tiVZoWxBkd4g9qkR3E3Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5cNxI/dJMcahv3QEn/4tiVZoWxBkd4g9qkR3E3Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5cNxI/dJMcahv3QEn/4tiVZoWxBkd4g9qkR3E3Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5cNxI%2FdJMcahv3QEn%2F4tiVZoWxBkd4g9qkR3E3Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;126&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수행 시간도 이전 1.86초에서 0.05초로 수행 시간이 짧아진 것을 확인할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이 쿼리는 조금 더 개선이 필요할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 filesort 를 진행하고 있는데 더 최적화 할 수 있는 부분에 대해 찾아봐야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글 읽어주셔서 감사합니다!&lt;/p&gt;</description>
      <category>DB</category>
      <author>dev_noonoo</author>
      <guid isPermaLink="true">https://icecupregular.tistory.com/27</guid>
      <comments>https://icecupregular.tistory.com/27#entry27comment</comments>
      <pubDate>Sat, 22 Nov 2025 16:49:21 +0900</pubDate>
    </item>
  </channel>
</rss>