<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기록은 희미해지지 않는다.</title>
    <link>https://neverfadeaway.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Apr 2026 07:34:24 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>cocoroocoo</managingEditor>
    <image>
      <title>기록은 희미해지지 않는다.</title>
      <url>https://tistory1.daumcdn.net/tistory/3491111/attach/f8c441047a5e4718a52818e756b5e648</url>
      <link>https://neverfadeaway.tistory.com</link>
    </image>
    <item>
      <title>1장 데이터 저장 구조 및 I/O 메커니즘</title>
      <link>https://neverfadeaway.tistory.com/83</link>
      <description>&lt;h1&gt;1. 인덱스 레인지 스캔 vs 풀스캔&lt;/h1&gt;
&lt;p&gt;풀스캔은 항상 느리다? 무조건 쿼리는 인덱스를 타야한다? -&amp;gt; NO&lt;br&gt;&amp;quot;인덱스 레인지 스캔은 큰 테이블에서 소량의 데이터를 읽을때 효율적이다. &amp;quot;  &lt;/p&gt;
&lt;h1&gt;2. SQL이 느리다면 십중팔구 I/O 때문&lt;/h1&gt;
&lt;p&gt;디스크 I/O 때문&lt;br&gt;디스크 I/O = SLEEP&lt;br&gt;디스크 I/O가 발생하는동안 프로세스는 wait 상태로 빠진다.  &lt;/p&gt;
&lt;h1&gt;3. 데이터베이스 저장 구조&lt;/h1&gt;
&lt;p&gt;테이블스페이스 : 세그먼트를 담는 콘테이너, 여러개의 데이터 파일로 구성&lt;br&gt;세그먼트 : 테이블, 인덱스 처럼 데이터 저장공간이 필요한 오브젝트&lt;br&gt;익스텐트 : 공간을 확장하는 단위&lt;br&gt;블록 : 사용자가 입력한 레코드를 실제로 저장하는 공간, 페이지  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;익스텐트 내 블록은 연속적인 공간&lt;/li&gt;
&lt;li&gt;익스텐트 끼리는 연속된 공간이 아니다.&lt;/li&gt;
&lt;li&gt;테이블 세그먼트 헤더에는 각 익스텐트의 첫번째 블록 주소가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 블록단위 I/O&lt;/h1&gt;
&lt;p&gt;블록단위 I/O : 오라클은 기본적으로 8KB 크기의 블록을 사용&lt;br&gt;테이블 뿐만아니라 인덱스도 블록단위로 읽고 쓴다.  &lt;/p&gt;
&lt;h1&gt;시퀀셜 액세스 VS 랜덤 액세스&lt;/h1&gt;
&lt;p&gt;시퀀셜 엑세스 : 논리적, 물리적으로 연결된 순서에 따라 차례대로 블록을 읽는 방식&lt;br&gt;랜덤 엑세스 : 레코드 하나를 읽기 위해 한 블록씩 접근 하는 방식  &lt;/p&gt;
&lt;p&gt;테이블 풀스캔 : 시퀀셜 엑세스&lt;br&gt;인덱스 리프노드 에서도 앞뒤로 : 시퀀셜 엑세스&lt;br&gt;인덱스에서 실제 테이블 조회 : 랜덤 액세스  &lt;/p&gt;
&lt;h1&gt;논리적 I/O VS 물리적 I/O&lt;/h1&gt;
&lt;p&gt;논리적 블록 I/O : 캐시 상관없이 실제 읽으려고 한 블록 수&lt;br&gt;물리적 블록 I/O : 캐시 상황도 고려되서 실제 발생한 총 블록 I/O  &lt;/p&gt;
&lt;p&gt;쿼리튜닝에서 논리적 I/O를  집중해야한다.&lt;br&gt;-&amp;gt; 물리적 I/O는 캐시 상황 등에 따라 그때 그때 달라질 수 있기 때문  &lt;/p&gt;
&lt;p&gt;Q. 쿼리실행시 적정 블록수는 얼마인가?&lt;br&gt;A. 쿼리 마다 다르다, 검색범위, 조인, 테이블 크기, 인덱스 구조 등에 따라 다름  &lt;/p&gt;
&lt;h2&gt;싱글 블록 I/O VS 멀티 블록 I/O&lt;/h2&gt;
&lt;p&gt;싱글 블록 I/O : 한번에 하나의 블록&lt;br&gt;멀티 블록 I/O : 한번에 여러 블록  &lt;/p&gt;
&lt;p&gt;MySQL 리드 어헤드 : 풀 테이블 스캔시 한번에 여러 블록을 백그라운드 스레드에서 미리 읽어놓음&lt;br&gt;&lt;a href=&quot;https://m.blog.naver.com/sqlmvp/221652490923&quot;&gt;https://m.blog.naver.com/sqlmvp/221652490923&lt;/a&gt; &lt;/p&gt;
&lt;h1&gt;캐시&lt;/h1&gt;
&lt;h2&gt;SQL 최적화 과정 캐싱 : 하드파싱 / 소프트파싱&lt;/h2&gt;
&lt;p&gt;소프트 파싱 : 쿼리 자체를 하나의 텍스트로 캐싱해서 해당 쿼리로 실행계획을 세우고 내부 프로시저를 생성하는 과정 자체를 재사용  &lt;/p&gt;
&lt;p&gt;하드 파싱 : 재사용 못하고 쿼리 실행 시 실행계획 세우고 내부 프로시저 생성까지 해야하는 경우  &lt;/p&gt;
&lt;p&gt;실행계획을 세우고 프로시저를 생성하는 과정은 CPU 바운드 작업 -&amp;gt; CPU 사용량이 높다면 과도한 하드파싱  &lt;/p&gt;
&lt;p&gt;자주 사용하는 쿼리인데 쿼리 텍스트가 매번 달라지면 소프트 파싱을 못함 -&amp;gt; 매번 하드파싱 -&amp;gt; CPU 사용량이 올라감 -&amp;gt; 그렇다면   무조건 바인드 변수를 사용하는게 좋을까?   &lt;/p&gt;
&lt;p&gt;System Global Area(SGA) : 여러 프로세스가 공통으로 엑세스 하는 메모리 공간&lt;br&gt;Library Cache : SQL 텍스트 자체를 캐싱 -&amp;gt; 소프트 파싱에 사용  &lt;/p&gt;
&lt;h2&gt;데이터 자체를 캐싱 : 버퍼 캐시&lt;/h2&gt;
&lt;p&gt;SGA 내 DB Buffer Cache에 데이터 자체를 캐싱  &lt;/p&gt;
&lt;p&gt;버퍼캐시 히트율 BCHR&lt;br&gt;= ( 캐시에서 읽은 블록 수 / 읽은 총 블록 수 ) x 100&lt;br&gt;= ( (논리적 I/O - 물리적 I/O) / 논리적 I/O ) x 100  &lt;/p&gt;
&lt;p&gt;Q. BCHR가 높으면 무조건 효율적인 SQL 일까?&lt;br&gt;효율적인 SQL = 딱 필요한 블록 수 만큼만 I/O를 수행하는 SQL  &lt;/p&gt;
&lt;p&gt;한번 읽은 블록을 반복적으로 다시 읽는 쿼리 -&amp;gt; BCHR 는 높겠지만 -&amp;gt; 과연 효율적인 쿼리 일까?  &lt;/p&gt;
&lt;p&gt;딱 필요한 블록 수 만큼만 I/O를 수행하는 SQL을 작성하기는 어렵다 = 경험의 영역&lt;br&gt;그래서 이렇게 캐싱을 먼저 고려하게 되는것일 수도...  &lt;/p&gt;
&lt;p&gt;쿼리가 느려? -&amp;gt; 인덱스 안타는거 아니야? -&amp;gt; 인덱스 있는데도 느리네 ??? -&amp;gt; 그냥 캐싱해두면 되잖아&lt;br&gt;이렇게 말이다...&lt;/p&gt;
&lt;h1&gt;1장 정리&lt;/h1&gt;
&lt;p&gt;인덱스 레인지 스캔이 풀스캔보다 더 느릴 수 있다.&lt;br&gt;어차피 대부분의 모든 블록 읽을거라면 멀티 블록 I/O 활용 해서 풀스캔으로 빠르게 처리하는게 나을수도.  &lt;/p&gt;
&lt;p&gt;오라클에서는 실행계획을 세우는거 자체를 캐싱하는구나  &lt;/p&gt;
&lt;p&gt;실행계획은 말 그대로 계획이지 그렇게 실행했다는게 아니구나  &lt;/p&gt;
&lt;p&gt;쿼리 최적화는 논리적으로 얼마나 블록을 읽을지를 줄여나가는 거구나 : 딱 필요한 만큼만 읽으면 best  &lt;/p&gt;
&lt;h1&gt;스터디를 진행하면서 나눈 이야기&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;책에서는 SQL이 느린 이유가 I/O 때문이고 I/O 작업시 프로세스가 waiting 상태로 빠지기 때문이라고 했는데, 이 디스크 I/O 자체가 느리기 때문이 아닐까? 책에서는 Sleep 이라는것에 더 중점을 두어 설명하고 있는것 같다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SQL을 한번 두번 세번 이렇게 반복적으로 실행할때마다 쿼리 속도는 빨라질것이라고 설명되어있는데 같은 쿼리라도 반복해서 실행하는것이 캐시상황에 도움이 될까? -&amp;gt; 이 부분은 일반적으로 쿼리를 실행하면 캐시상황이 좋아지기 때문에 히트율이 높아진다는 의미로 받아들이면 될것 같다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>✏️  스터디 모음집/친절한 SQL 튜닝</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/83</guid>
      <comments>https://neverfadeaway.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 15 Apr 2024 10:27:57 +0900</pubDate>
    </item>
    <item>
      <title>글또 8기 회고</title>
      <link>https://neverfadeaway.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;올해 1월 삶의 지도를 작성해보는 활동을 시작으로 벌써 8기 마지막 활동이 되었다. 그동안 작성한 글들을 보며 상반기 회고를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1689514675668&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[글또 8기] 삶의 지도&quot; data-og-description=&quot;삶의 지도 &amp;ldquo;10년 후에는 어떤 개발자가 되고 싶은가?&amp;rdquo;에 대한 질문을 받을 때가 있다. 나는 이 질문의 시점은 미래이지만, 핵심은 과거에 초점이 맞추어져 있다고 생각한다. 내가 어떤 목표를 &quot; data-og-host=&quot;neverfadeaway.tistory.com&quot; data-og-source-url=&quot;https://neverfadeaway.tistory.com/55&quot; data-og-url=&quot;https://neverfadeaway.tistory.com/55&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UgP7c/hyTlf9WSU6/HvjCt1IfP468wNhImNMRRK/img.png?width=800&amp;amp;height=566&amp;amp;face=0_0_800_566,https://scrap.kakaocdn.net/dn/oLEFK/hyTk4tQDxn/3beGYu48spqB3QtAsWBDpK/img.png?width=800&amp;amp;height=566&amp;amp;face=0_0_800_566,https://scrap.kakaocdn.net/dn/dfhD3M/hyTk2CLRA9/KClx9bmtou8IL4nsnVBNN1/img.png?width=1430&amp;amp;height=868&amp;amp;face=0_0_1430_868&quot;&gt;&lt;a href=&quot;https://neverfadeaway.tistory.com/55&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://neverfadeaway.tistory.com/55&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UgP7c/hyTlf9WSU6/HvjCt1IfP468wNhImNMRRK/img.png?width=800&amp;amp;height=566&amp;amp;face=0_0_800_566,https://scrap.kakaocdn.net/dn/oLEFK/hyTk4tQDxn/3beGYu48spqB3QtAsWBDpK/img.png?width=800&amp;amp;height=566&amp;amp;face=0_0_800_566,https://scrap.kakaocdn.net/dn/dfhD3M/hyTk2CLRA9/KClx9bmtou8IL4nsnVBNN1/img.png?width=1430&amp;amp;height=868&amp;amp;face=0_0_1430_868');&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;[글또 8기] 삶의 지도&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;삶의 지도 &amp;ldquo;10년 후에는 어떤 개발자가 되고 싶은가?&amp;rdquo;에 대한 질문을 받을 때가 있다. 나는 이 질문의 시점은 미래이지만, 핵심은 과거에 초점이 맞추어져 있다고 생각한다. 내가 어떤 목표를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;neverfadeaway.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 8기 활동을 하면서 그런 나의 부족했던 점을 많이 보완 할 수 있었던 것 같다. 관악구 Real MySQL 스터디 방장을 맡아 스터디원들을 모집할 때, 글또를 통해 함께 끝까지 공부 할 스터디원들을 만날 수 있었고, 중도에 빠지는 인원 없이 마지막 까지 스터디를 잘 마칠 수 있었다. 또 토스에서 진행하는 Node.js 스터디를 모집한다는 글을 통해 스터디에 지원해 현업에서 Node.js로 백엔드 업무를 진행하고 있는 다른 개발자 분들을 만나 Node.js 디자인 패턴들의 실무 적용방식에 대해 이야기 나눌 수 있었다. 그 결과 회사 프로젝트에 파일 처리 하는 부분을 stream 방식으로 처리하도록 수정해 파일 처리 요청의 동시성 문제를 개선했고, JS 프로젝트를 TS로 전환하면서 스터디에서 배운 어댑터 패턴을 적용해 유지보수성이 떨어지는 코드를 확장성 있는 코드로 정리 할 수 있었다.&lt;/p&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달간 매일 저녁마다 회고를 진행했는데, 그 과정에서 다른 개발자 분에게 데이터베이스의 이해의 중요성에 대해 이야기를 듣던 중 &quot;친절한 쿼리 튜닝 교육&quot;에 대해 함께 소개 받게 되었고, 바로 교육에 신청하여 이번 여름 쿼리 튜닝 교육을 잘 수료 할 수 있었다. 튜닝 교육을 통해 인덱스의 원리에 대해 다시 알게 되었고 회사에서 운영중인 Mongo DB 쿼리의 실행 속도를 개선하는데 큰 도움을 받을 수 있었다.&lt;/p&gt;
&lt;figure id=&quot;og_1689517523629&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;친절한 SQL 튜닝 교육과정 수료 후기 및 MongoDB에서 쿼리 최적화 도전&quot; data-og-description=&quot;최근 Real MySQL 스터디를 진행하면서 데이터베이스에 대해 더 관심을 갖게 되었는데, 그러던 중 글쓰기 모임에서 만난 한 개발자분이 해당 강의를 함께 듣자고 추천해주셨고 강의를 들은지 어느&quot; data-og-host=&quot;neverfadeaway.tistory.com&quot; data-og-source-url=&quot;https://neverfadeaway.tistory.com/78&quot; data-og-url=&quot;https://neverfadeaway.tistory.com/78&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bNskO7/hyTk9BXw8z/g2wQjcwzlmvtLAhUU9tyz0/img.png?width=692&amp;amp;height=692&amp;amp;face=0_0_692_692,https://scrap.kakaocdn.net/dn/upwza/hyTk0kHM36/JBVWeUXVK7jkN8j618PSN0/img.png?width=692&amp;amp;height=692&amp;amp;face=0_0_692_692,https://scrap.kakaocdn.net/dn/ClooN/hyTldqPbXV/Xl8B3AnBno8lSInSijZmE0/img.png?width=604&amp;amp;height=588&amp;amp;face=0_0_604_588&quot;&gt;&lt;a href=&quot;https://neverfadeaway.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://neverfadeaway.tistory.com/78&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bNskO7/hyTk9BXw8z/g2wQjcwzlmvtLAhUU9tyz0/img.png?width=692&amp;amp;height=692&amp;amp;face=0_0_692_692,https://scrap.kakaocdn.net/dn/upwza/hyTk0kHM36/JBVWeUXVK7jkN8j618PSN0/img.png?width=692&amp;amp;height=692&amp;amp;face=0_0_692_692,https://scrap.kakaocdn.net/dn/ClooN/hyTldqPbXV/Xl8B3AnBno8lSInSijZmE0/img.png?width=604&amp;amp;height=588&amp;amp;face=0_0_604_588');&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;친절한 SQL 튜닝 교육과정 수료 후기 및 MongoDB에서 쿼리 최적화 도전&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근 Real MySQL 스터디를 진행하면서 데이터베이스에 대해 더 관심을 갖게 되었는데, 그러던 중 글쓰기 모임에서 만난 한 개발자분이 해당 강의를 함께 듣자고 추천해주셨고 강의를 들은지 어느&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;neverfadeaway.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글또 활동을 통해 네트워킹은 점점 확장되는구나 하는 느낌을 참 많이 받은 것 같다. 관악구 Real MySQL 스터디를 마치고 스터디원들끼리 의견을 나눈 내용 중 책 1권 내용을 복습했으면 좋겠다는 피드백이 있었는데, 글또 활동을 통해 알게된 또 다른 모임에서 1권 부터 시작하는 스터디를 시작한다는 글을 기존 스터디원들에게 소개해 주었고, 관악구 Real MySQL가 끝나고 마지막 날에 기존 스터디원들과 함께 1권 부터 시작하는 스터디에 다시 또 참여하게 되었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 회사에서 최근 데이터 관련 업무를 진행하면서 데이터 엔지니어링, 분석에 관심을 가지게 되었는데, 글또에서 진행한 데이터 오프라인 모임을 통해 현업에서 데이터 업무를 진행하고 계신 분들을 만나 데이터를 적재 하는 방식, 데이터의 읽기 쓰기량을 모니터링 하는 방식 등 혼자 업무를 진행하면서 궁금했던 점을 물어보고, 생각지도 못한 새로운 시야를 얻을 수 있어서 기존 백엔드 업무 이외에 데이터 처리 업무를 새로 진행 하면서도 많은 도움을 받게 된것 같다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;# 상반기에는 데이터베이스, 데이터 파이프라인 구성에 빠져있었구나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8기 활동을 하면서 제출한 글들을 보니, 이번 상반기 동안은 데이터 처리, 데이터베이스 공부에 빠져있었다는 것을 알게 되었다. 그리고 글들을 읽어보며 그동안 삽질이나 공부를 했지만 미처 정리하지 못했던 부분들도 많아서 한편으론 아쉬운 마음이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;기록은 희미해 지지 않는다&quot; 라는 블로그 이름이 부끄럽지 않도록 꾸준히 더 많은 삽질을 하고 더 많이 기록으로 남겨야 겠다는 아쉬운 마음이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pass권 을 사용하지 않고 모든 글을 제출하려고 처음 마음을 가졌지만, 결국 pass를 2회나 사용했는데 ㅠㅠㅠ 나는 왜 무엇 때문에 처음 마음가짐과 다르게 글을 꾸준히 작성하지 못했을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*pass권 : 예치금 차감없이 글또 활동에서 해당 회차 글을 제출하지 않고 넘길 수 있다.&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;# 설마 꾸준한 글쓰기도 중꺾마?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 왜 무엇 때문에 처음 마음가짐과 다르게 글을 꾸준히 작성하지 못했을까? 왜 pass 권을 사용하지 않겠다는 다짐을 두번이나 져버렸을까?&amp;nbsp;&lt;/p&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일도 3일마다 다시 목표를 세워서 해나가면 된다는 말이 있듯이,  그동안 내가 작성한 글들을 보며 다음 기수 때는 pass권은 아예 없다고 생각하고! 글을 미루는 일 없이 다시 목표를 세워 꾸준하게 작성해 나가야 겠다는 다짐을 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서 이번달 부터는 매주 토요일은 아무 일정을 잡지 않고 비워놓았다. 일주일에 하루는 아무 일정이나 걱정없이 한주동안 고민 했던 것을 혼자 글로 정리하는 시간을 만들기 위해서이다.&amp;nbsp;&lt;/p&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기를 마지막으로 생각하고 계신다는 글을 읽고, 많은 생각이 들었다. 나는 인원 4명의 스터디를 운영하는것도 스스로 많이 부족하다고 생각했는데, 300명이 넘는 많은 인원이 포함된 커뮤니티를 8기 까지 운영해오신 운영진분들이 대단하다는것이 느껴졌다.&lt;/p&gt;</description>
      <category>  개인 회고</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/81</guid>
      <comments>https://neverfadeaway.tistory.com/81#entry81comment</comments>
      <pubDate>Sun, 16 Jul 2023 23:46:18 +0900</pubDate>
    </item>
    <item>
      <title>Docker-Compose로 InnoDB 클러스터 구축 실습 환경 구성하기</title>
      <link>https://neverfadeaway.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/blog-archive/docker-compose-setup-for-innodb-cluster/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dev.mysql.com/blog-archive/docker-compose-setup-for-innodb-cluster/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688302829238&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: Docker Compose Setup for InnoDB Cluster&quot; data-og-description=&quot;In the following we show how InnoDB cluster can be deployed in a container context. In the official documentation (Introducing InnoDB Cluster), InnoDB is described as: MySQL InnoDB cluster provides a complete high availability solution for MySQL. MySQL She&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/blog-archive/docker-compose-setup-for-innodb-cluster/&quot; data-og-url=&quot;https://dev.mysql.com/blog-archive/docker-compose-setup-for-innodb-cluster/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SPwV7/hyTcDilYN8/LcUxQdnxW4MrADXHuT68f1/img.jpg?width=629&amp;amp;height=560&amp;amp;face=0_0_629_560&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/blog-archive/docker-compose-setup-for-innodb-cluster/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/blog-archive/docker-compose-setup-for-innodb-cluster/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SPwV7/hyTcDilYN8/LcUxQdnxW4MrADXHuT68f1/img.jpg?width=629&amp;amp;height=560&amp;amp;face=0_0_629_560');&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;MySQL :: Docker Compose Setup for InnoDB Cluster&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In the following we show how InnoDB cluster can be deployed in a container context. In the official documentation (Introducing InnoDB Cluster), InnoDB is described as: MySQL InnoDB cluster provides a complete high availability solution for MySQL. MySQL She&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.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;RealMySQL 스터디를 진행하면서 InnoDB 클러스터를 직접 구축하고 테스트해보고 싶었지만, 실습용으로 사용할 분산된 서버환경을 어떻게 구성해야 할지 막막했다. 그러던 중 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;MySQL 공식 개발자 블로그 페이지에&lt;span&gt; &lt;/span&gt;&lt;/span&gt;실제 분산된 머신 상에서는 아니지만 Docker-Compose 를 통해 로컬에 InnoDB 클러스터를 구축하는 예제가 있어 해당 포스트를 보며 클러스터를 로컬에 구축해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 포스트에는 해당 예시는 도커라이징된 환경에서 어떻게 로컬에 InnoDB 클러스터링을 구성 할수 있을지에 대한 예시일 뿐, 각 MySQL 서버 보안 설정이나 네트워크 단의 설정이 빠져있기 때문에 실제 프로덕션 환경에 바로 적용하기에는 적합하지 않다는 설명이 나와있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2458&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj43w5/btsl2KWYP6V/eqa20XpMVtj7OM3jYZo0L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj43w5/btsl2KWYP6V/eqa20XpMVtj7OM3jYZo0L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj43w5/btsl2KWYP6V/eqa20XpMVtj7OM3jYZo0L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj43w5%2Fbtsl2KWYP6V%2Feqa20XpMVtj7OM3jYZo0L1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2458&quot; height=&quot;1012&quot; data-origin-width=&quot;2458&quot; data-origin-height=&quot;1012&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스터디에서 실습용으로 클러스터를 어떻게 구성하고 각 요소들이 어떻게 연결되는지 그리고 그룹 복제를 직접 테스트 해보기에는 적합 할 것 같아 해당 포스트를 보며 실습을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 진행 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Yesung-Han/docker-compose-innodb-cluster/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/Yesung-Han/docker-compose-innodb-cluster/tree/main&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688303678974&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 - Yesung-Han/docker-compose-innodb-cluster&quot; data-og-description=&quot;Contribute to Yesung-Han/docker-compose-innodb-cluster development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Yesung-Han/docker-compose-innodb-cluster/tree/main&quot; data-og-url=&quot;https://github.com/Yesung-Han/docker-compose-innodb-cluster&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AfXJQ/hyTaZ8qPtw/AZBkvtjDzHIDk3kekKtJK0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Yesung-Han/docker-compose-innodb-cluster/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Yesung-Han/docker-compose-innodb-cluster/tree/main&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AfXJQ/hyTaZ8qPtw/AZBkvtjDzHIDk3kekKtJK0/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 - Yesung-Han/docker-compose-innodb-cluster&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to Yesung-Han/docker-compose-innodb-cluster development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클러스터 구성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIeQC/btsl5iMk8Me/IlH8eAXRTTT9pVpOVbAq21/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIeQC/btsl5iMk8Me/IlH8eAXRTTT9pVpOVbAq21/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIeQC/btsl5iMk8Me/IlH8eAXRTTT9pVpOVbAq21/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIeQC%2Fbtsl5iMk8Me%2FIlH8eAXRTTT9pVpOVbAq21%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;560&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리&amp;nbsp;서버&amp;nbsp;:&amp;nbsp;mysql-server-1&lt;br /&gt;image:&amp;nbsp;mysql/mysql-server:8.0.12&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ports:&amp;nbsp;3301:3306&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨더리&amp;nbsp;서버&amp;nbsp;1&amp;nbsp;:&amp;nbsp;mysql-server-2&lt;br /&gt;image:&amp;nbsp;mysql/mysql-server:8.0.12&lt;br /&gt;ports:&amp;nbsp;3302:3306&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨더리&amp;nbsp;서버&amp;nbsp;2&amp;nbsp;:&amp;nbsp;mysql-server-3&lt;br /&gt;image:&amp;nbsp;mysql/mysql-server:8.0.12&lt;br /&gt;ports:&amp;nbsp;3303:3306&lt;/p&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 Router와 MySQL Shell 은 다음과 같다.&lt;/p&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-shell&lt;br /&gt;환경:&amp;nbsp;oraclelinux:7&lt;br /&gt;mysql-shell-8.0.13-1.el7.x86_64.rpm 설치 후 사용&lt;/p&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-router&lt;br /&gt;image:&amp;nbsp;mysql/mysql-router:8.0&lt;br /&gt;ports:&amp;nbsp;6446:6446&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;공식 예제에 사용되는 서버구성을 살펴보면 책에 나온 InnoDB 클러스터를 구성하기 위한 그룹 복제 요구사항 옵션들을 확인 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml 파일에 각 서버들의 실행 옵션이 다음과 같이 구성되어 있는데 이를 살펴 보면&lt;/p&gt;
&lt;pre id=&quot;code_1688303997377&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;command: [&quot;mysqld&quot;,&quot;--server_id=2&quot;,&quot;--binlog_checksum=NONE&quot;,&quot;--gtid_mode=ON&quot;,&quot;--enforce_gtid_consistency=ON&quot;,&quot;--log_bin&quot;,&quot;--log_slave_updates=ON&quot;,&quot;--master_info_repository=TABLE&quot;,&quot;--relay_log_info_repository=TABLE&quot;,&quot;--transaction_write_set_extraction=XXHASH64&quot;,&quot;--user=mysql&quot;,&quot;--skip-host-cache&quot;,&quot;--skip-name-resolve&quot;, &quot;--default_authentication_plugin=mysql_native_password&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그룹 복제 요구사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 각 서버는 고유한 서버아이디 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--server_id=2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp; 바이너리 로그가 활성화 되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--log_bin&lt;/p&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. ROW 포멧의 바이너리 로그 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--log_bin 일때 디폴트가 ROW 포멧&lt;/p&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. log_slave_updates 활성화 해야한다.&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;해당 옵션이 ON일 경우 마스터 서버에서 발생한 변경 사항을 복제(slave) 서버의 바이너리 로그에도 기록하게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--log_slave_updates=ON&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. MySQL 8.0.20 버전까지는 그룹복제에서 바이너리 로그 체크섬 기능을 지원하지 않으므로 binlog_checksum 값을 NONE으로 설정 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;binlog_checksum 값을 NONE으로 설정 할 경우 체크섬 검사를 비활성화하게 된다. 바이너리 로그 파일에는 체크섬이 생성되지 않고, 데이터의 무결성은 검사되지 않게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--binlog_checksum=NONE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 그룹복제는 GTID를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--gtid_mode=ON&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--enforce_gtid_consistency=ON&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# GTID (Global Transaction ID)란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GTID는 MySQL 복제(Replication)에서 사용되는 고유 식별자로, 그룹에 참여하는 멤버들끼리 전역적인 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;트랜잭션 아이디를&lt;span&gt; 만들어&lt;/span&gt;&lt;/span&gt; 사용하고 싶을 경우 설정한다. 이렇게 멤버들끼리 Global한 트랜잭션 아이디를 만들어 사용할 경우, 기존 바이너리 로그 파일의 위치 기반 복제보다 복제 내구성을 강화 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GTID는 마스터 서버에서 생성되며, 복제 서버는 이를 사용하여 정확한 트랜잭션 복제를 수행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-enforce-gtid-consistency&lt;/b&gt; 옵션을 사용하면 MySQL 서버가 실행되는 동안 GTID 일관성을 강제화한다. 이 옵션이 활성화되면 GTID 일관성을 검사하여&lt;b&gt; GTID가 일치하지 않는 트랜잭션을 거부&lt;/b&gt;하고, &lt;b&gt;복제의 일관성을 해칠 수 있는 쿼리를 허용하지 않음&lt;/b&gt;으로 일관성을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제의 일관성을 해칠 수 있는 쿼리유형으로는&amp;nbsp;소스 서버에서 레플리카 서버로 복제되어 적용될 때 &lt;b&gt;단일 트랜잭션 으로 처리되지 않을 수도 있는 것들&lt;/b&gt;이 있는데 다음과 같은 쿼리들은 안전하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트랜잭션을 지원하는 테이블과 지원하지 않는 테이블을 함께 변경하는 쿼리 혹은 트랜잭션&lt;br /&gt;- CREATE TABLE . . SELECT 구문&lt;br /&gt;- 트랜잭션 내에서 CREATE TEMPORARY TABLE, DROP TEMPORARY TABLE 구문 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일트랜잭션으로 처리되지 않을 수 있는 쿼리가 위험한 이유는 GTID가&amp;nbsp;트랜잭션&amp;nbsp;단위로&amp;nbsp;올바르게&amp;nbsp;할당돼야&amp;nbsp;복제가&amp;nbsp;정상적으로 원자적으로 동작하기&amp;nbsp;때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 소스서버에서 하나의 작업이 여러 트랜잭션으로 쪼개져 각각 다른 GTID가 생성 된다면, 원자적으로 이루어져야 하는 어떤 작업이 레플리카 서버에서는 원자적으로 이루어지지 않을 수 있다. 이러한 트랜잭션 쪼개짐은 곧 데이터의 일관성을 헤치는 원인이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 그룹복제에서 복제 관련 메타데이터는 데이터 일관성을 위해 테이블에 저장되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--master_info_repository=TABLE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 트랜잭션 WriteSet 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹복제에서 트랜잭션 충돌을 탐지 할때, 그 인증 단계에서 WriteSet이 사용되기 때문에 WriteSet을 추출하는 알고리즘을 지정해주어야 하는데 그룹복제에서는 XXHASH64 알고리즘을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--transaction_write_set_extraction=XXHASH64&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외의 서버 구성 요구사항들은 아래 공식 문서에서 확인이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688306090651&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: MySQL 8.0 Reference Manual :: 18.3.1 Group Replication Requirements&quot; data-og-description=&quot;MySQL 8.0 Reference Manual &amp;nbsp;/&amp;nbsp; ... &amp;nbsp;/&amp;nbsp; Group Replication &amp;nbsp;/&amp;nbsp; Requirements and Limitations &amp;nbsp;/&amp;nbsp; Group Replication Requirements 18.3.1&amp;nbsp;Group Replication Requirements Server instances that you want to use for Group Replication must satisfy the followi&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot; data-og-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL :: MySQL 8.0 Reference Manual :: 18.3.1 Group Replication Requirements&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MySQL 8.0 Reference Manual &amp;nbsp;/&amp;nbsp; ... &amp;nbsp;/&amp;nbsp; Group Replication &amp;nbsp;/&amp;nbsp; Requirements and Limitations &amp;nbsp;/&amp;nbsp; Group Replication Requirements 18.3.1&amp;nbsp;Group Replication Requirements Server instances that you want to use for Group Replication must satisfy the followi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;MySQL-Shell 에서 하는 일 살펴보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql-shell 컨테이너가 시작될때 실행되는 파일인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compose/mysql-shell/run.sh 파일을 보면&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688306215805&quot; class=&quot;nginx&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if [ &quot;$MYSQLSH_SCRIPT&quot; ]; then
mysqlsh &quot;$MYSQL_USER@$MYSQL_HOST:$MYSQL_PORT&quot; --dbpassword=&quot;$MYSQL_PASSWORD&quot; -f &quot;$MYSQLSH_SCRIPT&quot; || true
fi
if [ &quot;$MYSQL_SCRIPT&quot; ]; then
mysqlsh &quot;$MYSQL_USER@$MYSQL_HOST:$MYSQL_PORT&quot; --dbpassword=&quot;$MYSQL_PASSWORD&quot; --sql -f &quot;$MYSQL_SCRIPT&quot; || true
fi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MYSQLSH_SCRIPT 와 MYSQL_SCRIPT 로 등록된 파일을 설치한 mysqlsh로 실행하는것을 확인 할 수 있다.&lt;/p&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-shell.env 파일을 확인 해보면 각각 어떤 파일을 실행하는 지 확인 할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MYSQLSH_SCRIPT=/scripts/setupCluster.js&lt;br /&gt;MYSQL_SCRIPT=/scripts/db.sql&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/scripts/db.sql 는 어플리케이션 서버에서 사용할 DB와 유저를 만드는 SQL 이고,&lt;/p&gt;
&lt;pre id=&quot;code_1688306428388&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE DATABASE dbwebappdb;
CREATE USER 'dbwebapp'@'%' IDENTIFIED BY 'dbwebapp';
GRANT ALL PRIVILEGES ON dbwebappdb.* TO 'dbwebapp'@'%';&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;/scripts/setupCluster.js 가 바로 클러스터 구성을 시작하는 명령이다.&lt;/p&gt;
&lt;pre id=&quot;code_1688306461501&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var dbPass = &quot;mysql&quot;
var clusterName = &quot;devCluster&quot;

try {
  print('Setting up InnoDB cluster...\n');
  shell.connect('root@mysql-server-1:3306', dbPass)
  var cluster = dba.createCluster(clusterName);
  print('Adding instances to the cluster.');
  cluster.addInstance({user: &quot;root&quot;, host: &quot;mysql-server-2&quot;, password: dbPass})
  print('.');
  cluster.addInstance({user: &quot;root&quot;, host: &quot;mysql-server-3&quot;, password: dbPass})
  print('.\nInstances successfully added to the cluster.');
  print('\nInnoDB cluster deployed successfully.\n');
} catch(e) {
  print('\nThe InnoDB cluster could not be created.\n\nError: ' + e.message + '\n');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql-shell 에서는 이 JS 코드로 작성된 명령어를 실행해 클러스터 환경을 구축하게 되는데 각 과정을 살펴보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 프라이머리로 사용할 서버에 접속하기&lt;/h4&gt;
&lt;pre id=&quot;code_1688306565711&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var dbPass = &quot;mysql&quot;
shell.connect('root@mysql-server-1:3306', dbPass)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. createCluster 명령으로 클러스터링 시작하기&lt;/h4&gt;
&lt;pre id=&quot;code_1688306627231&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var clusterName = &quot;devCluster&quot;
var cluster = dba.createCluster(clusterName);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 InnoDB 클러스터를 구축하려면 MySQL 서버에 그룹 복제를 설정하는 등의 복잡한 작업이 필요한데 이러한 작업을 dba.createCluster() 메서드에서 모두 자동으로 처리해 준다.&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;그룹복제 분산 복구에서 사용될 DB 계성 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 클러스터는 기본적으로 싱글 프라이머리 모드로 생성되며 처음 클러스터 생성을 진행한 MySQL 서버가 프라이머리로 지정된다. mysql-server-1에 접속해 createCluster 명령을 수행했으므로 mysql-server-1이 프라이머리 서버가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. lnnoDB&amp;nbsp;클러스터&amp;nbsp;인스턴스&amp;nbsp;추가&lt;/h4&gt;
&lt;pre id=&quot;code_1688306825584&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var dbPass = &quot;mysql&quot;
cluster.addInstance({user: &quot;root&quot;, host: &quot;mysql-server-2&quot;, password: dbPass})
cluster.addInstance({user: &quot;root&quot;, host: &quot;mysql-server-3&quot;, password: dbPass})&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;addInstance() 메서드로 mysql-server-2 와 mysql-server-3을 클러스터의 멤버, 세컨더리 서버로 등록해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터에 이미 프라이머리 서버(mysql-server-1)가 존재하므로 이후 클러스터에 추가되는 서버(mysql-server-2, mysql-server-3)들은 모두 자동으로 읽기 전용으로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688307103877&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose-innodb-cluster-mysql-shell-1     | The instance 'root@mysql-server-2' was successfully added to the cluster.
docker-compose-innodb-cluster-mysql-shell-1     |
docker-compose-innodb-cluster-mysql-shell-1     | .A new instance will be added to the InnoDB cluster. Depending on the amount of
docker-compose-innodb-cluster-mysql-shell-1     | data on the cluster this might take from a few seconds to several hours.
docker-compose-innodb-cluster-mysql-shell-1     |
docker-compose-innodb-cluster-mysql-shell-1     | Adding instance to the cluster ...
docker-compose-innodb-cluster-mysql-shell-1     |
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 07:31:18 metadata_cache INFO [40b0ba3700] Potential changes detected in cluster after metadata refresh (view_id=0)
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 07:31:18 metadata_cache INFO [40b0ba3700] Metadata for cluster 'devCluster' has 2 member(s), single-primary:
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 07:31:18 metadata_cache INFO [40b0ba3700]     mysql-server-1:3306 / 33060 - mode=RW
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 07:31:18 metadata_cache INFO [40b0ba3700]     mysql-server-2:3306 / 33060 - mode=n/a
docker-compose-innodb-cluster-mysql-shell-1     | Validating instance at mysql-server-3:3306...
docker-compose-innodb-cluster-mysql-shell-1     |
docker-compose-innodb-cluster-mysql-shell-1     | This instance reports its own address as eac19e522bcc
docker-compose-innodb-cluster-mysql-server-2-1  | 2023-07-02T07:31:19.405408Z 19 [System] [MY-010597] [Repl] 'CHANGE MASTER TO FOR CHANNEL 'group_replication_recovery' executed'. Previous state master_host='1dc2b8a7be4a', master_port= 3306, master_log_file='', master_log_pos= 4, master_bind=''. New state master_host='&amp;lt;NULL&amp;gt;', master_port= 0, master_log_file='', master_log_pos= 4, master_bind=''.
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 07:31:20 metadata_cache INFO [40b0ba3700] Potential changes detected in cluster after metadata refresh (view_id=0)
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 07:31:20 metadata_cache INFO [40b0ba3700] Metadata for cluster 'devCluster' has 2 member(s), single-primary:
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 07:31:20 metadata_cache INFO [40b0ba3700]     mysql-server-1:3306 / 33060 - mode=RW
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 07:31:20 metadata_cache INFO [40b0ba3700]     mysql-server-2:3306 / 33060 - mode=RO
docker-compose-innodb-cluster-mysql-shell-1     |
docker-compose-innodb-cluster-mysql-shell-1     | Instance configuration is suitable.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.addInstance() 메서드가 실행된 후 로그를 살펴보면&lt;/p&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-server-2가 클러스터에 추가되면 메타데이터 캐시 로그에 mode=n/a 로 보여지는데 이는 아직 해당 인스턴스가 아직 그룹 복제에 참여하고 있지 않음을 나타낸다.&lt;/p&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-server-1:3306 / 33060 - mode=RW&lt;br /&gt;mysql-server-2:3306 / 33060 - mode=n/a&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 로그를 살펴보면 mode=RO 로 읽기 전용으로 설정 된것을 확인 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql-server-1:3306 / 33060 - mode=RW&lt;br /&gt;mysql-server-2:3306 / 33060 - mode=RO&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;MySQL-Router&lt;span&gt;&amp;nbsp;에서 하는 일 살펴보기&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;어플리케이션 서버 연결은 이제 각 DB 인스턴스가 아니라 MySQL-Router 에 연결한다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml 파일과 dbwebapp.env파일을 보면 예시 어플리케이션 서버인 dbwebapp가 어떻게 DB 와 커넥션을 맺고있는지 확인 할 수 있는데,&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688307878021&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose.yml

  dbwebapp:
    platform: linux/x86_64
    env_file:
      - dbwebapp.env
    image: neumayer/dbwebapp
    ports:
      - &quot;8080:8080&quot;
    depends_on:
      - mysql-router&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688307943272&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dbwebapp.env

DBUSER=dbwebapp
DBPASS=dbwebapp
DBNAME=dbwebappdb
DBHOST=mysql-router
DBPORT=6446&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;어플리케이션 서버는 각 인스턴스에 바로 연결 하는된것이 아니라 mysql-router에 연결되어있는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql-router는 어플리케이션 서버에 연결되어 쿼리 부하를 분산해주고 각 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;그렇기 때문에 어플리케이션 서버단에서는 MySQL 서버에 문제가 생기더라도 별도의 조치없이 정상적으로 쿼리를 실행 할수 있게된다.&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 data-ke-size=&quot;size16&quot;&gt;docker ps 로 프라이머리 서버 컨테이너를 강제 종료 시켜보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2942&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxDd7T/btsl2nAyKo0/s0NskBjMS3iKkKksuZXkh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxDd7T/btsl2nAyKo0/s0NskBjMS3iKkKksuZXkh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxDd7T/btsl2nAyKo0/s0NskBjMS3iKkKksuZXkh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxDd7T%2Fbtsl2nAyKo0%2Fs0NskBjMS3iKkKksuZXkh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2942&quot; height=&quot;424&quot; data-origin-width=&quot;2942&quot; data-origin-height=&quot;424&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;docker-compose 로그를 통해 어떤일이 일어나는 지 확인을 해보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1688310940758&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:46 metadata_cache WARNING [40b0ba3700] Failed connecting with Metadata Server mysql-server-1:3306: Lost connection to MySQL server at 'reading initial communication packet', system error: 104 (2013)
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:47 metadata_cache INFO [40b0ba3700] Connected with metadata server running on mysql-server-2:3306
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:47 metadata_cache WARNING [40b0ba3700] While updating metadata, could not establish a connection to cluster 'devCluster' through mysql-server-1:3306
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:48 metadata_cache WARNING [40b0ba3700] Failed connecting with Metadata Server mysql-server-1:3306: Can't connect to MySQL server on 'mysql-server-1:3306' (111) (2003)
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:48 routing INFO [4004a48700] Stop accepting connections for routing routing:bootstrap_rw listening on 6446
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:48.980603Z 0 [Warning] [MY-010909] [Server] /usr/sbin/mysqld: Forcing close of thread 38857  user: 'dbwebapp'.
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:48.981206Z 0 [Warning] [MY-010909] [Server] /usr/sbin/mysqld: Forcing close of thread 99  user: 'dbwebapp'.
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:48.985611Z 0 [Warning] [MY-010909] [Server] /usr/sbin/mysqld: Forcing close of thread 9085  user: 'dbwebapp'.
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:48.985786Z 0 [Warning] [MY-010909] [Server] /usr/sbin/mysqld: Forcing close of thread 5926  user: 'dbwebapp'.
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:48.985858Z 0 [Warning] [MY-010909] [Server] /usr/sbin/mysqld: Forcing close of thread 38855  user: 'dbwebapp'.
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:48.985984Z 0 [Warning] [MY-010909] [Server] /usr/sbin/mysqld: Forcing close of thread 9087  user: 'dbwebapp'.
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:48.995874Z 0 [Warning] [MY-010909] [Server] /usr/sbin/mysqld: Forcing close of thread 6671  user: 'root'.
docker-compose-innodb-cluster-mysql-server-2-1  | 2023-07-02T15:09:51.194827Z 0 [Warning] [MY-011499] [Repl] Plugin group_replication reported: 'Members removed from the group: 1dc2b8a7be4a:3306'
docker-compose-innodb-cluster-mysql-server-3-1  | 2023-07-02T15:09:51.195953Z 0 [Warning] [MY-011499] [Repl] Plugin group_replication reported: 'Members removed from the group: 1dc2b8a7be4a:3306'
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 metadata_cache WARNING [40b0ba3700] Member mysql-server-1:3306 (4a32401b-18aa-11ee-a01c-0242c0a8e004) defined in metadata not found in actual Group Replication
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 metadata_cache INFO [40b0ba3700] Potential changes detected in cluster after metadata refresh (view_id=0)
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 metadata_cache INFO [40b0ba3700] Metadata for cluster 'devCluster' has 3 member(s), single-primary:
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 metadata_cache INFO [40b0ba3700]     mysql-server-1:3306 / 33060 - mode=n/a
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 metadata_cache INFO [40b0ba3700]     mysql-server-2:3306 / 33060 - mode=RW
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 metadata_cache INFO [40b0ba3700]     mysql-server-3:3306 / 33060 - mode=RO
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 routing INFO [40b0ba3700] Disconnecting client 127.0.0.1:46658 from server 192.168.224.4:3306
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 routing INFO [40b0ba3700] Routing routing:bootstrap_rw listening on 6446 got request to disconnect 1 invalid connections: metadata change
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:09:51 routing INFO [40b0ba3700] Start accepting connections for routing routing:bootstrap_rw listening on 6446
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:53.789387Z 30 [ERROR] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Error pushing message into group communication engine.'
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:54.792949Z 30 [ERROR] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Error pushing message into group communication engine.'
docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T15:09:55.793411Z 30 [ERROR] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Error pushing message into group communication engine.'
docker-compose-innodb-cluster-mysql-router-1    | 2023-07-02 15:10:04 metadata_cache WARNING [40b0ba3700] Failed connecting with Metadata Server mysql-server-1:3306: Unknown MySQL server host 'mysql-server-1' (-2) (2005)
docker-compose-innodb-cluster-mysql-server-1-1 exited with code 137
docker-compose-innodb-cluster-mysql-server-1-1 exited with code 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql-router 에서 찍힌 로그만 살펴보면, mysql-server-1 에 연결할 수 없다는 것을 알게 되고 나서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql-server-1:3306&amp;nbsp;/&amp;nbsp;33060&amp;nbsp;-&amp;nbsp;mode=n/a&lt;br /&gt;mysql-server-2:3306 / 33060 - mode=RW&lt;br /&gt;mysql-server-3:3306 / 33060 - mode=RO&lt;/p&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-server-2 가&amp;nbsp; mode=RW로 설정되어 읽기, 쓰기 작업이 가능한 마스터 서버로 격상된 것을 확인 할 수 있었다.&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;그룹 복제 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dbwebapp.env 에 기입된 정보로 연결하여 테이블을 만들고 데이터를 추가했을때 다른 그룹 멤버들에게도 변경사항이 잘 반영되는지 확인해보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1688308166562&quot; class=&quot;makefile&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dbwebapp.env

DBUSER=dbwebapp
DBPASS=dbwebapp
DBNAME=dbwebappdb
DBHOST=mysql-router
DBPORT=6446&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688308338713&quot; class=&quot;sql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create table test (
    name varchar(30),
    age int
);


insert into test (name, age)
values (&quot;han&quot;, 12);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제상황&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 테이블을 만든 후 INSERT INTO로 데이터를 추가했는데 다음과 같은 오류가 발생했다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The&amp;nbsp;table&amp;nbsp;does&amp;nbsp;not&amp;nbsp;comply&amp;nbsp;with&amp;nbsp;the&amp;nbsp;requirements&amp;nbsp;by&amp;nbsp;an&amp;nbsp;external&amp;nbsp;plugin.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 docker-compose 로그를 확인해보니&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688308455373&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose-innodb-cluster-mysql-server-1-1  | 2023-07-02T14:31:47.099932Z 38855 [ERROR] [MY-011542] [Repl] Plugin group_replication reported: 'Table test does not have any PRIMARY KEY. This is not compatible with Group Replication.'&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;'Table&amp;nbsp;test&amp;nbsp;does&amp;nbsp;not&amp;nbsp;have&amp;nbsp;any&amp;nbsp;PRIMARY&amp;nbsp;KEY.&amp;nbsp;This&amp;nbsp;is&amp;nbsp;not&amp;nbsp;compatible&amp;nbsp;with&amp;nbsp;Group&amp;nbsp;Replication.'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;INSERT INTO 작업은 쓰기 작업이기 때문에 프라이머리 서버인 mysql-server-1에서 에러로그가 발생 되었고, 발생된 로그를 확인 해보니 PRIMARY KEY가 없다! 라는 에러가 나왔다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688308642904&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: MySQL 8.0 Reference Manual :: 18.3.1 Group Replication Requirements&quot; data-og-description=&quot;MySQL 8.0 Reference Manual &amp;nbsp;/&amp;nbsp; ... &amp;nbsp;/&amp;nbsp; Group Replication &amp;nbsp;/&amp;nbsp; Requirements and Limitations &amp;nbsp;/&amp;nbsp; Group Replication Requirements 18.3.1&amp;nbsp;Group Replication Requirements Server instances that you want to use for Group Replication must satisfy the followi&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot; data-og-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/group-replication-requirements.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL :: MySQL 8.0 Reference Manual :: 18.3.1 Group Replication Requirements&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MySQL 8.0 Reference Manual &amp;nbsp;/&amp;nbsp; ... &amp;nbsp;/&amp;nbsp; Group Replication &amp;nbsp;/&amp;nbsp; Requirements and Limitations &amp;nbsp;/&amp;nbsp; Group Replication Requirements 18.3.1&amp;nbsp;Group Replication Requirements Server instances that you want to use for Group Replication must satisfy the followi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.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;위 group replication requirement 문서에 나온 내용중 놓친 부분이 있었는데, 그룹 복제에 참여하는 테이블은 모두 명시적으로 프라이머리 키를 가지고 있어야 한다고 한다.(명시적으로 지정하지 않을 경우에는 NOT NULL 속성의 유니크 키라도 있어야 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹 복제에서는 이 키들이 테이블 내 모든 데이터에 대해 고유한 식별자 역할을 하고 이 식별자로 트랜잭션간 충돌을 감지한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pk를 지정해 주니까 제대로 INSERT INTO&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;1600&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KFqr9/btsl3i6Woex/khxkkjFf88zotkAtEymU71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KFqr9/btsl3i6Woex/khxkkjFf88zotkAtEymU71/img.png&quot; data-alt=&quot;mysql-router에 붙어서 해당 쿼리 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KFqr9/btsl3i6Woex/khxkkjFf88zotkAtEymU71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKFqr9%2Fbtsl3i6Woex%2FkhxkkjFf88zotkAtEymU71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;620&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;mysql-router에 붙어서 해당 쿼리 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3574&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m6w2y/btsl6UYLV9u/dm68zc7rD39Hz0ednJVstK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m6w2y/btsl6UYLV9u/dm68zc7rD39Hz0ednJVstK/img.png&quot; data-alt=&quot;세컨더리 서버에서 확인 했을때 그룹복제가 잘 연결되어 변경사항이 잘 보이는것을 확인 할 수 있었다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m6w2y/btsl6UYLV9u/dm68zc7rD39Hz0ednJVstK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm6w2y%2Fbtsl6UYLV9u%2Fdm68zc7rD39Hz0ednJVstK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3574&quot; height=&quot;1016&quot; data-origin-width=&quot;3574&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;세컨더리 서버에서 확인 했을때 그룹복제가 잘 연결되어 변경사항이 잘 보이는것을 확인 할 수 있었다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>✏️  스터디 모음집/RealMySQL 스터디</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/79</guid>
      <comments>https://neverfadeaway.tistory.com/79#entry79comment</comments>
      <pubDate>Sun, 2 Jul 2023 23:45:03 +0900</pubDate>
    </item>
    <item>
      <title>친절한 SQL 튜닝 교육과정 수료 후기 및 MongoDB에서 쿼리 최적화 도전</title>
      <link>https://neverfadeaway.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AoOTz/btskiABfMMR/sGKF21ClMTWdMiwkuFnlk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AoOTz/btskiABfMMR/sGKF21ClMTWdMiwkuFnlk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AoOTz/btskiABfMMR/sGKF21ClMTWdMiwkuFnlk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAoOTz%2FbtskiABfMMR%2FsGKF21ClMTWdMiwkuFnlk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;506&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;692&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;최근 Real MySQL 스터디를 진행하면서 데이터베이스에 대해 더 관심을 갖게 되었는데, 그러던 중 글쓰기 모임에서 만난 한 개발자분이 해당 강의를 함께 듣자고 추천해주셨고 강의를 들은지 어느덧 4일간의 교육과정이 끝나게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;32시간이라는 짧은 시간동안 쿼리 최적화 부분을 공부하면서 그동안 스스로 잘못 알고있었던 부분들이 많았구나 하는 것을 느꼈다. 그리고 당장 회사에가서 slow query를 찾아서 개선해봐야지! 하는 의욕도 생기게 되었다.  &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 data-ke-size=&quot;size16&quot;&gt;회사에서는 주로 OLTP용으로는 MongoDB와 MySQL을 사용하고, OLAP용으로는 BigQuery와 Snowflake를 사용한다. 그래서 처음 강의를 수강하기 전에 과연 강의에서 오라클에 대해 배운 내용을 실무에 적용시킬수있을까? 고민을했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강사님도 처음 강의때 오라클을 한번 배우면 왠만한 DB에 나오는 내용도 쉽게 이해 할 수 있을 것이다. 라고 말씀하셨는데, 강의를 수강하고 나서 어떤 이유에서 그렇게 말씀하셨는지 이해 할 수 있었다. 입문 강의인 만큼 데이터베이스의 구조, 인덱스 와 조인의 원리 등 원리에 기반해서 수업을 해주셨고, 그렇기 때문에 꼭 오라클이 아니니라도 전반적인 데이터베이스 시스템에 대해 원리적으로 이해하는데 큰 도움이 된것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;&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;서브쿼리 최적화&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;.&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 data-ke-size=&quot;size16&quot;&gt;그동안 혼자 공부하면서 내가 잘못생각하고 있는 부분들이 많았구나 하는것을 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 마음가짐이라고 표현한 부분이 있는데 바로 이 부분이다. 그동안 쿼리 최적화 작업을 할때 단순히 시간이 몇초에서 몇초로 빨라지는 결과만을 보고 최적화를 완료 했다고 생각한 적이 있었다. 또 실행계획을 확인할 때 cost가 적게 걸릴수록 단순히 좋다고 생각했는데, 강의를 수강하면서 실제로는 내가 짠 쿼리가 논리적으로 얼마나 많은 블록을 읽어들일지 기준으로 생각해야 한다는 것을 깨달았다. 그리고 그 과정이 머릿속에 그려질수있어야 겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 강의 때 강사분께서 말씀주신 어느 정도 데이터량이 대용량이고, 어느정도 데이터량이 소용량이냐? 하는 질문에 대해 해주신 대답이 인상 깊었다. 결국 캐싱 상황에 따라, 클러스터링 펙터에 따라 데이터 몇 천 건도 대용량일 수 있다. 라는 말이 위에서 말한 쿼리에 대한 마음가짐을 잘 나타내는 말 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;# 직접 해보면서 공부를 해보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회사에서 운용중인 웹페이지에서 조회시 발생되는 MongoDB쿼리 중 일부 유저에서 30초가 넘어가는 상황이 발생하여, 해당 부분을 직접 최적화 해보면서 공부를 해보려고 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 시스템의 DB는 MongoDB로 되어있었는데 MongoDB로 혼자 개발을 진행한 선임 개발자 분이 퇴사를 한 상황이라 팀 내에 물어볼 사람이 없었고 MongoDB를 사용해보지도 않았다. 데이터베이스의 원리는 같다는 마음가짐으로 내가 알고있는 부분이 MongoDB에도 있을까? 하는 마음가짐으로 최적화를 해보려고 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOFio7/btskuX3dKBA/EiRFJZLFU5yhuGjCQgbKGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOFio7/btskuX3dKBA/EiRFJZLFU5yhuGjCQgbKGk/img.png&quot; data-alt=&quot;MongoDB... 너무 어렵다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOFio7/btskuX3dKBA/EiRFJZLFU5yhuGjCQgbKGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOFio7%2FbtskuX3dKBA%2FEiRFJZLFU5yhuGjCQgbKGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;588&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MongoDB... 너무 어렵다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 33초 이상 걸리던 부분이 2.7초까지 줄어들긴 했지만 강의에서 배운대로 논리적으로 읽은 레코드 수, 테이블 접근 순서 등을 확인해보고 싶었지만, MongoDB에서의 실행계획에 익숙하지 않아서 그러지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 형식으로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;실행계획을 찍어보니&lt;/span&gt; 거의 1000 줄이 넘게 나왔다....&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvOYk/btsknvMXu8i/3UUKrEXaCEgkG4pxUGMuNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvOYk/btsknvMXu8i/3UUKrEXaCEgkG4pxUGMuNk/img.png&quot; data-alt=&quot;이거 어디서 부터 어떻게 분석해야 할까 ㅠㅠ  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvOYk/btsknvMXu8i/3UUKrEXaCEgkG4pxUGMuNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmvOYk%2FbtsknvMXu8i%2F3UUKrEXaCEgkG4pxUGMuNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;386&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이거 어디서 부터 어떻게 분석해야 할까 ㅠㅠ  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB를 처음부터 공부하기에는 무리라는 생각이 들었다. 그래서 이 실행계획의 모든 항목에 대해 너무 깊게 들어가기 보단, document read 수 정도 만 확인하고 어디에서 스캔이 오래걸릴지 확인해보자는 마음가짐을가지고 최적화를 수행해나가려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기존 방식대로 쿼리 수행시간과 실행계획에 표시되는 document 수를 기준으로 어떤 부분에 문제가 있을지 역추적 해나갔다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 profiler 옵션에서 slow query가 잡히도록 설정 한 후 원본 쿼리를 확인 해 보았다. &lt;br /&gt;&lt;br /&gt;그 결과 조인이 최대 3중으로 걸려있었고, 조인의 연결 조건에 존재하지 않는 컬럼이 alias로 지정되어 연결되어있는 부분도 있었고, 또 조인의 드리븐테이블에 활용할수있는 인덱스도 없는 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;드리븐테이블의 경우 인덱스 설계를 100점짜리로 해야한다&lt;/i&gt;&lt;/b&gt;는 강의에서 들은 말이 생각나서 일부 연결조건이 잘못된 부분을 제대로 다시 설정하고, 드리븐 테이블에 인덱스를 설정해주어었더니 쿼리속도가 33초에서 2.7초까지 줄어들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉬운점은 MongoDB에 익숙하지 않아서 실제로 어떤 스캔이 일어나는지, 얼마나 데이터를 읽으려고 했는지 이러한 부분을 직접 눈으로 확인하지 못하고 추측만 한 상태로 최적화를 수행 한 것이 아쉽다. 나중에 꼭 MongoDB에서 실행계획 보는 법, trace 분석을 걸수도 있는 지 등에 대한 부분도 숙지해서 더 최적화를 진행 해야겠다.&lt;/p&gt;</description>
      <category>  개인 회고</category>
      <category>쿼리 최적화</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/78</guid>
      <comments>https://neverfadeaway.tistory.com/78#entry78comment</comments>
      <pubDate>Sun, 18 Jun 2023 23:57:09 +0900</pubDate>
    </item>
    <item>
      <title>Real MySQL 8.0 - 9.3 인덱스 컨디션 푸쉬다운</title>
      <link>https://neverfadeaway.tistory.com/76</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwaZz1/btsgMbxIFgg/LVHx7EwyxgXlIpiXK3goz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwaZz1/btsgMbxIFgg/LVHx7EwyxgXlIpiXK3goz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwaZz1/btsgMbxIFgg/LVHx7EwyxgXlIpiXK3goz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwaZz1%2FbtsgMbxIFgg%2FLVHx7EwyxgXlIpiXK3goz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;930&quot; height=&quot;958&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림과 같이 인덱스 컨디션 푸쉬 다운을 활성화 하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합인덱스 테이블에서 인덱스를 활용하지 못하는 조건이라도 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;인덱스 테이블 내에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;모든 체크 조건을 처리할 수 있으면, 불필요한 랜덤 디스크 I/O 읽기를 수행 하지 않는다.&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;optimizer_switch 시스템 변수 내 index_condition_pushdown 설정을 통해 index_condition_pushdown을 사용할지 아닐지를 제어할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1684677354640&quot; class=&quot;sql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;set optimizer_switch='index_condition_pushdown=on';
show variables like 'optimizer_switch';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EOg94/btsgDKuJewb/GjHKksi8PGwWBiz82HqW5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EOg94/btsgDKuJewb/GjHKksi8PGwWBiz82HqW5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EOg94/btsgDKuJewb/GjHKksi8PGwWBiz82HqW5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEOg94%2FbtsgDKuJewb%2FGjHKksi8PGwWBiz82HqW5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1268&quot; height=&quot;122&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;122&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;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;테스트용 테이블 준비 - 더미 csv 파일 import&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #bdc6cf; text-align: start;&quot;&gt;&lt;a href=&quot;https://media.githubusercontent.com/media/datablist/sample-csv-files/main/files/people/people-10000.zip&quot;&gt;https://media.githubusercontent.com/media/datablist/sample-csv-files/main/files/people/people-10000.zip&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10,000 rows 준비&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;1529&quot; data-origin-height=&quot;695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pUZkV/btsgDJJlLdH/iNObDkWZK2FemZLKKUX1s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pUZkV/btsgDJJlLdH/iNObDkWZK2FemZLKKUX1s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pUZkV/btsgDJJlLdH/iNObDkWZK2FemZLKKUX1s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpUZkV%2FbtsgDJJlLdH%2FiNObDkWZK2FemZLKKUX1s1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1529&quot; height=&quot;695&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;695&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;last_name, first_name 순으로 복합 인덱스를 생성&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1684677510771&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE people ADD INDEX ix_lastname_firstname (last_name, first_name);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;index_condition_pushdown=off 결과&lt;/h4&gt;
&lt;pre id=&quot;code_1684678935572&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;set optimizer_switch='index_condition_pushdown=off';
explain select * from people where last_name = 'Barrera' and first_name LIKE '%en';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;75&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXm2Of/btsgThYtVvl/HiBkGVWVkVXcZkI3plTFD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXm2Of/btsgThYtVvl/HiBkGVWVkVXcZkI3plTFD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXm2Of/btsgThYtVvl/HiBkGVWVkVXcZkI3plTFD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXm2Of%2FbtsgThYtVvl%2FHiBkGVWVkVXcZkI3plTFD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1248&quot; height=&quot;75&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;75&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;index_condition_pushdown=on 결과&lt;/h4&gt;
&lt;pre id=&quot;code_1684679040798&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;set optimizer_switch='index_condition_pushdown=on';
explain select * from people where last_name = 'Barrera' and first_name LIKE '%en';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXIOXu/btsgUseBo7W/08tsp7Y2DDa0cJCCcdraCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXIOXu/btsgUseBo7W/08tsp7Y2DDa0cJCCcdraCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXIOXu/btsgUseBo7W/08tsp7Y2DDa0cJCCcdraCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXIOXu%2FbtsgUseBo7W%2F08tsp7Y2DDa0cJCCcdraCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;81&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 컨디션 푸쉬다운이 수행되었을 경우 실행계획의 Extra 컬럼에 Using index condition 이라는 항목이 표시됨을 확인 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&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;인덱스 컨디션 푸쉬다운과 랜덤 디스크 I/O&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpzCXT/btsgTjWin7N/h8zxw0ivJXhMguaDejZFzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpzCXT/btsgTjWin7N/h8zxw0ivJXhMguaDejZFzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpzCXT/btsgTjWin7N/h8zxw0ivJXhMguaDejZFzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpzCXT%2FbtsgTjWin7N%2Fh8zxw0ivJXhMguaDejZFzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;473&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 친절한 SQL 튜닝 강의를 수강 하면서,  쿼리 튜닝은 &quot;랜덤 I/O를 줄이는 작업&quot;이라는 말을 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 인덱스를 제대로 설정해도, 막상 실행계획을 확인해보면 옵티마이저가 풀스캔 계획을 세우는 경우가 종종 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 왜 인덱스를 타지 않고 풀스캔을 하는 걸까? 데이터가 많아서 B-tree를 타는것(수직 탐색)이 오히려 오버헤드로 작동하는 걸까? 라고 막연히 생각했었다.&lt;/p&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건 가져오는 작업은 랜덤 디스크 I/O고, 풀스캔의 경우 시퀀셜 I/O로 처음부터 끝까지 빠르게 가져온다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜덤 디스크 I/O의 경우 이전 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;결국 아래 그림처럼 레인지 스캔의 범위가 크고, 인덱스 테이블에 없는 컬럼을 조회 한 경우 나머지 컬럼 데이터를 디스크에서 일일히 랜덤하게 가져와야 하기 때문에 랜덤 I/O가 아닌 시퀀셜 I/O로 처리되는 풀스캔으로 데이터를 처음부터 끝까지 전부 가져와 필요한 부분만 걸러내는 것이 더 빠를거라 판단했기 때문이다.&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;898&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdqV1w/btsgGkBOhI3/PDdK4Q15vaEehWijFLzrFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdqV1w/btsgGkBOhI3/PDdK4Q15vaEehWijFLzrFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdqV1w/btsgGkBOhI3/PDdK4Q15vaEehWijFLzrFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdqV1w%2FbtsgGkBOhI3%2FPDdK4Q15vaEehWijFLzrFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;898&quot; height=&quot;542&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 관점에서 보았을때, 인덱스 컨디션 푸쉬다운의 경우도 마찬가지로 이 랜덤 디스크 I/O를 자체를 직접적으로 줄여주기 때문에 성능 향상에 엄청난 이점이 있을 것 같다.&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;단순 실행계획 뿐만아니라 실제적으로 랜덤 디스크 I/O가 얼마나 발생했는지 확인해보고 싶다. MySQL에서는 어떤 방식으로 디스크로 부터 읽어온 블록수를 트레이스를 할 수 있는지 더 조사해봐야겠다.&amp;nbsp;&lt;/p&gt;</description>
      <category>✏️  스터디 모음집/RealMySQL 스터디</category>
      <category>인덱스 컨디션 푸쉬 다운</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/76</guid>
      <comments>https://neverfadeaway.tistory.com/76#entry76comment</comments>
      <pubDate>Sun, 21 May 2023 23:42:04 +0900</pubDate>
    </item>
    <item>
      <title>2023년 3월 회고</title>
      <link>https://neverfadeaway.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;날씨가 아직은 추워서 아직도 겨울인것 같은데 벌써 3월이 지나갔다니 이제 계절에도 둔감해지는 기분이 든다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;fastAPI&amp;nbsp; + GraphQL 환경에서 테스트 코드 작성하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;fastAPI&amp;nbsp; + GraphQL 환경에서 e2e 테스트와 일부 계산 로직이 포함된 함수에 유닛 테스트를 작성했다. e2e 테스트 코드의 경우 테스트용 DB에 붙어서 수행되기 때문에 신경써야 할 부분들이 많은 것 같다.(테스트 DB 마이그레이션, 테스트용 더미 데이터 생성, 테스트 수행 속도 저하 등) 그래서 e2e테스트 코드는 각 API에 대해서만 작성을 하고, graphQL의 resolver 들에 대한 부분은 유닛테스트 코드를 작성하려고 했다. 그런데 현재는 graphQL의 각 resolver 함수에 대한 유닛테스트 코드를 작성하지 못했는데, db session 객체를 resolver에서 바로 import 해서 사용하는 방식으로 코드가 의존적으로 작성되어있기 때문이였다. 유닛테스트를 위해 이 부분을 어떻게 분리 할 수 있을지 고민 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SSR 환경이 아닌 상황에서 동적 open graph 적용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;회사 상품을 소개하는 웹사이트에서 상품의 디테일 페이지에서는 링크를 공유했을때 대표 이미지가 아닌 그 상품의 미리보기 이미지가 나왔으면 좋겠다는 이슈가 있었다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;프론트의 경우 Next.js와 같이 SSR을 적용할수있는 형태가 아니였다. 하지만&amp;nbsp;&lt;/span&gt;Vue 개발되어 빌드된 프론트 페이지가 백엔드 쪽에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/django-webpack/django-webpack-loader&quot;&gt;django webpack loader&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;를 통해 서비스 되고있는 상황이였기 때문에 Django 에서 상품 조회 URL로 라우팅 되었을때, 해당 template을 내려 줄 때에 view에서 DB에서 상품에 대한 정보를 조회 한 후 동적으로 템플릿에 open graph meta 태그 삽입해 내려주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 이슈를 처리하면서 &lt;a href=&quot;https://ngrok.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ngrok&lt;/a&gt;이라는 로컬환경을 외부에서 테스트 할 수 있게 해주는 툴을 처음 경험해 보았는데, 해당 과정을 정리하고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 구체화된 뷰 테이블을 활용한, snowflake 데이터 추출 업무&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;회사에서 출시한 앱의 로그 분석 업무를 진행하면서 snowflake의 구체화된 뷰(materalized view, mview)를 사용해보았다. 원본 로그 테이블의 크기가 너무 컷기 때문에(30분 소요 / 109.67GB 스캔) 쿼리를 수행하는데 너무 많은 시간이 소요되었는데, 구체화된 뷰를 구성하여 쿼리를 수행하니까 19초 만에 쿼리가 완료되었다. 이 작업을 진행하면서 snowflake에서 구체화된 뷰 테이블 갱신은 언제 일어나는지, 유지비용은 어느정도 드는지, 파티션 프루닝이란 무엇이고 왜 중요한지에 대해 알게 되었는데 알게된 내용을 블로그에 정리해야겠다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;redis 메모리 이슈&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;로그 처리를 하는 과정에서 1분동안 발생한 로그를 잠시 캐싱해두는 용도로 redis를사용하는데, 해당 redis의 메모리가 계속 증가하고 있는 것을 발견했다.&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;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ucS4w/btsbTU9adbW/KNF0D9FKPPXfHiGh9qxi5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ucS4w/btsbTU9adbW/KNF0D9FKPPXfHiGh9qxi5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ucS4w/btsbTU9adbW/KNF0D9FKPPXfHiGh9qxi5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FucS4w%2FbtsbTU9adbW%2FKNF0D9FKPPXfHiGh9qxi5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;587&quot; height=&quot;362&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3월에 갑자기 redis 메모리 사용율이 증가했는데, 그 원인은 2월과 3월에 한번씩 처리에 실패한 로그를 redis에 잠시 담아두도록 로그 처리 서버를 업데이트 했기 때문이였다. 그리고 redis 메모리 사용량도 너무 적게 설정 되어있었다.(300MB 수준) 일단은 redis 메모리 사용량을 올리고, 처리에 실패한 로그를 굳이 리소스를 잡아먹어가며 인메모리에 담아둘 필요는 없을 것 같아서 이부분도 S3와 같은 파일형태로 따로 저장하도록 로그 파이프라인을 수정했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;express JS -&amp;gt; Nest.js TS 로 리펙토링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;올해 초 원티드 프리온보딩 Node.js 과정을 수강하면서 새로 알게된 Port and Adaptor 아키텍쳐를 적용하면 좋을 프로젝트가 떠올라 기존 &lt;b&gt;express JS 로&lt;/b&gt; 만들어진 프로젝트를 Nest.js로 바꾸고 있다. 프로젝트를 진행하면서 고민 했던 내용들을 정리하고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Real MySQL 8.0 스터디 진행&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;RealMySQL 1권을 거의 마무리 하게 되었다. 개인적으로 스터디를 진행하면서 두가지 문제에 대해 봉착을 했는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;1. 비슷한 경험치의 구성원들만 모여 스터디를 하게 될 경우, 새로운 관점이나 경험에서 우러나온 지식을 얻기 힘들다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 내용과 흐름이 한눈에 안들어오는 경우 어떻게 정리를 할것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하는 문제였다. 1번 문제의 경우 지인을 통해 저자분이 운영하시는 오픈채팅방을 알게 되어 그 책의 저자분에게 질문을 바로 할 수 있게 되었다. 보통 스터디의 경우 &quot;교수님&quot;이라 불리우는 능력자 분이 있는 경우, 그 교수님의 도움을 받을 수 있는데, 만약 그런 상황이 아니라면 커뮤니티를 활용하는것도 많은 도움이 될 수 있다는 것을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Real MySQL 8.0 책의 경우, 뒤에 나올 내용이 갑자기 앞에 미리 나오기도 하고, 앞에 살짝 나왔던 내용이 뒤에 추가되어 나오기도 했다. 그러다보니 읽으면서 내용들이 한눈에 안들어오는 문제가 생겼는데 마인드맵을 그려보니 전체적인 흐름을 정리하는데 개인적으로 많은 도움이 되었던것같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2208&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pdHGO/btsbTuCKIX0/TinyFYykpKMHDAa3LT3Dc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pdHGO/btsbTuCKIX0/TinyFYykpKMHDAa3LT3Dc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pdHGO/btsbTuCKIX0/TinyFYykpKMHDAa3LT3Dc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpdHGO%2FbtsbTuCKIX0%2FTinyFYykpKMHDAa3LT3Dc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2208&quot; height=&quot;1162&quot; data-origin-width=&quot;2208&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;스터디를 하면서 다른 분이 자신이 쿼리 최적화 한 내용을 예시로 보여주셨는데, 마인드맵을 같이 보면서 아 이래서 임시테이블이 생성된것이였구나! 한눈에 파악 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;토스 스터디 클럽 지원&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://toss.im/career/toss-study-club?studyId=82236225003&quot;&gt;https://toss.im/career/toss-study-club?studyId=82236225003&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1682155622715&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;토스 스터디 클럽&quot; data-og-description=&quot;혼자 고민은 그만, 함께 이야기 나누며 해결해요&quot; data-og-host=&quot;toss.im&quot; data-og-source-url=&quot;https://toss.im/career/toss-study-club?studyId=82236225003&quot; data-og-url=&quot;https://toss.im/career/toss-study-club?studyId=82236225003&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jcAKb/hySlziJ25H/Oo2oDxhm1LKNbLqHr0iyG0/img.jpg?width=3250&amp;amp;height=1625&amp;amp;face=0_0_3250_1625,https://scrap.kakaocdn.net/dn/cer9Nx/hySm4alKf5/zy0DkJF48MKyLbEuvluCck/img.jpg?width=3250&amp;amp;height=1625&amp;amp;face=0_0_3250_1625,https://scrap.kakaocdn.net/dn/dpKGMd/hySlyRGJKH/xlaKLTl4wv2OSA4EeNkxT1/img.png?width=1041&amp;amp;height=520&amp;amp;face=0_0_1041_520&quot;&gt;&lt;a href=&quot;https://toss.im/career/toss-study-club?studyId=82236225003&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://toss.im/career/toss-study-club?studyId=82236225003&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jcAKb/hySlziJ25H/Oo2oDxhm1LKNbLqHr0iyG0/img.jpg?width=3250&amp;amp;height=1625&amp;amp;face=0_0_3250_1625,https://scrap.kakaocdn.net/dn/cer9Nx/hySm4alKf5/zy0DkJF48MKyLbEuvluCck/img.jpg?width=3250&amp;amp;height=1625&amp;amp;face=0_0_3250_1625,https://scrap.kakaocdn.net/dn/dpKGMd/hySlyRGJKH/xlaKLTl4wv2OSA4EeNkxT1/img.png?width=1041&amp;amp;height=520&amp;amp;face=0_0_1041_520');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;토스 스터디 클럽&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;혼자 고민은 그만, 함께 이야기 나누며 해결해요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;toss.im&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;토스 스터디 클럽 Node.js 분야에 지원했다.&lt;br /&gt;&lt;br /&gt;백엔드 관련 아키텍쳐나 클린코드 글들을 보면 대부분 Java로 쓰여져있는 부분들이 많았고, 나 또한 Java에 대한 경험이 없었기 때문에 원리를 이해해도 이걸 어떻게 JS에 적용할 수 있을까? 하는 2차적인 고민들이 어려웠다. 그런데 Node.js를 사용하는 백엔드 개발자 분들이 모여서 TS로 서로의 경험을 나눌수있는 자리라니!! 만약 스터디에 붙는다면 정말 너무 좋은 경험이 될것 같다.&lt;/p&gt;</description>
      <category>  개인 회고</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/74</guid>
      <comments>https://neverfadeaway.tistory.com/74#entry74comment</comments>
      <pubDate>Fri, 14 Apr 2023 21:44:26 +0900</pubDate>
    </item>
    <item>
      <title>redis 백업의 과정</title>
      <link>https://neverfadeaway.tistory.com/66</link>
      <description>&lt;h1&gt;문제 상황&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 이전에 관한 Job 정보들을 생성해 redis에 적재한 후, 테스트로 Job 단위로 작업을 수행 하던 중, 실수로 Redis 컨테이너를 중지 시켜버리는 문제가 발생 했고, 일부 Job 정보의 변경사항을 날리게 되었다.&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;2148&quot; data-origin-height=&quot;1210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOR8eo/btr9eB4oVio/uKaP7JkED0gaK2drMNMwT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOR8eo/btr9eB4oVio/uKaP7JkED0gaK2drMNMwT0/img.png&quot; data-alt=&quot;1번 작업 부터 1001번 작업까지 수행 하던 중 중간에 작업 진행 상황을 기록하던 Redis 컨테이너가 중지되어 버리는 상황 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOR8eo/btr9eB4oVio/uKaP7JkED0gaK2drMNMwT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOR8eo%2Fbtr9eB4oVio%2FuKaP7JkED0gaK2drMNMwT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2148&quot; height=&quot;1210&quot; data-origin-width=&quot;2148&quot; data-origin-height=&quot;1210&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1번 작업 부터 1001번 작업까지 수행 하던 중 중간에 작업 진행 상황을 기록하던 Redis 컨테이너가 중지되어 버리는 상황 발생&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2310&quot; data-origin-height=&quot;1232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C4nQe/btr8MpSuYXk/OKkrHZ6z269HmlsjtduhtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C4nQe/btr8MpSuYXk/OKkrHZ6z269HmlsjtduhtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C4nQe/btr8MpSuYXk/OKkrHZ6z269HmlsjtduhtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC4nQe%2Fbtr8MpSuYXk%2FOKkrHZ6z269HmlsjtduhtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2310&quot; height=&quot;1232&quot; data-origin-width=&quot;2310&quot; data-origin-height=&quot;1232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 테스트를 처음부터 실행하려고 한다면 작업 실행 정보를 다시 redis에 적재하는 초기화 작업을 수행해야 하는 상황이였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;1558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWRuZl/btr89198YwC/clKrpsmkHSpshFxGfywve0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWRuZl/btr89198YwC/clKrpsmkHSpshFxGfywve0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWRuZl/btr89198YwC/clKrpsmkHSpshFxGfywve0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWRuZl%2Fbtr89198YwC%2FclKrpsmkHSpshFxGfywve0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2140&quot; height=&quot;1558&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;1558&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;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;해결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 작업 시작 전 혹시 작업을 처음부터 다시 돌려야 하는 상황이 생긴다면, redis에 작업 정보들을 적재하는 작업부터 다시 하지 않도록 적재된 모든 작업 정보의 초기 상태를 백업 해 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;백업의 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BGSAVE 명령어로 /data 경로에 dump.rdb 백업 파일을 생성 한 후, 생성된 dump.rdb 파일이 작업 도중 자동으로 생성된 백업 파일로 인해 덮어씌워지지 않도록 initial_dump.rdb 파일로 이름을 변경 한 후 로컬에 따로 저장해두었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;1458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VBzXr/btr8LLBhG9J/Lgf9HlrxoJtv9olWwfJKO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VBzXr/btr8LLBhG9J/Lgf9HlrxoJtv9olWwfJKO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VBzXr/btr8LLBhG9J/Lgf9HlrxoJtv9olWwfJKO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVBzXr%2Fbtr8LLBhG9J%2FLgf9HlrxoJtv9olWwfJKO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2140&quot; height=&quot;1458&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;1458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업을 통해 다행히 테스트를 처음부터 다시 시작 할 수 있었다. 그러면서 Redis의 백업 방식에 궁금증이 생기게 되었고 관련된 내용을 정리해 보았다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;Redis 에서의 백업&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 인메모리 데이터베이스이기 때문에 휘발성 특성을 가진다. 따라서 위 상황처럼 갑자기 Redis 프로세스가 중지되는 순간 적재한 데이터는 유실되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis에서는 이 단점을 보완하기 위해 디스크에 데이터를 기록하는 영속성 기능을 지원해주는데 다음과 같은 방식들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1681050084136&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Redis persistence&quot; data-og-description=&quot;How Redis writes data to disk&quot; data-og-host=&quot;redis.io&quot; data-og-source-url=&quot;https://redis.io/docs/management/persistence/&quot; data-og-url=&quot;https://redis.io/docs/management/persistence/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://redis.io/docs/management/persistence/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://redis.io/docs/management/persistence/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Redis persistence&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How Redis writes data to disk&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;redis.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;1. RDB 스냅샷 방식&lt;/h1&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;- 백업 파일을 Amazon S3와 같이 외부 스토리지로 전송할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RDB는 AOF에 비해 큰 데이터 세트를 복구하기에 용이하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RDB 생성 주기를 짧게 설정하여 자주 저장하면 저장 작업을 완료하는 데 다소 시간이 걸릴 수 있으므로 Redis 성능에 영향을 줄 수 있다. 그렇기 때문에 Redis가 작동을 멈춘 경우(예: 정전 후) 데이터 손실 가능성을 최소화해야 하는 경우 RDB 방식은 좋지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 세트가 큰 경우 시간이 많이 소요될 수 있다. 그래서 백업을 수행하는 동안 Redis가 몇 밀리초 ~ 1초 동안 클라이언트 서비스에 영향을 줄 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RDB 자동 스냅샷&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Redis는 디스크에 일정 주기 마다 데이터 세트의 스냅샷을 dump.rdb 라는 이진 파일로 저장한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RDB 수동 스냅샷&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SAVE 또는 BGSAVE 명령어를 사용해서 수동으로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;dump.rdb&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;2. AOF 방식&lt;/h1&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;- RDB 방식보다 더 유실에 대해 안정적이 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RDB의 경우 백업 주기를 짧게 설정 할 경우 성능에 영향이 생길 수 있는 반면, AOF 싱크는 백그라운드 스레드에서 수행되기 때문에 기본 설정 값인 매 1초마다 동기화 작업을 수행해도 성능상 큰 문제가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- AOF 파일은 일반적으로 동일한 데이터 세트에 대한 RDB 파일보다 크다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 동기화 정책에 따라 속도가 RDB보다 느릴 수 있다.&lt;/p&gt;
&lt;h1&gt;RDB VS AOF&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;b&gt;PostgreSQL과 같은 상용 데이터베이스가 제공할 수 있는 것과 비슷한 수준의 데이터 안전을 원하는 경우, 두 지속성 방법을 모두 사용해야 하는 일반적&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 &lt;b&gt;데이터 양이 많고 재해 발생 시 몇 분의 데이터 손실을 감수할 수 있는 경우, RDB만 단독으로 사용하는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOF 와 RDB 스냅샷을 함께 설정 하는 것이 만약 AOF 엔진에 버그가 있는 상황에 대비할 수있다.&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;figure id=&quot;og_1681050521493&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Redis persistence&quot; data-og-description=&quot;How Redis writes data to disk&quot; data-og-host=&quot;redis.io&quot; data-og-source-url=&quot;https://redis.io/docs/management/persistence/#backing-up-redis-data&quot; data-og-url=&quot;https://redis.io/docs/management/persistence/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://redis.io/docs/management/persistence/#backing-up-redis-data&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://redis.io/docs/management/persistence/#backing-up-redis-data&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Redis persistence&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How Redis writes data to disk&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;redis.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1681050531061&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Redis의 백업(RDB, AOF) 알아보기&quot; data-og-description=&quot;Redis는 인메모리 데이터 저장소로 읽기 성능이 뛰어나 캐싱, 세션 저장소, RefreshToken 저장소 등 다양한 곳에 많이 사용됩니다.인메모리 데이터 저장소가 가지는 휘발성의 특성 때문에 종료되는 &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@pjh612/Redis%EC%9D%98-%EB%B0%B1%EC%97%85RDB-AOF-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&quot; data-og-url=&quot;https://velog.io/@pjh612/Redis의-백업RDB-AOF-알아보기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c0Urax/hySdv0KXqH/eDURo0uLWVagqUMWK2NW1K/img.png?width=1058&amp;amp;height=718&amp;amp;face=0_0_1058_718,https://scrap.kakaocdn.net/dn/dRcwCo/hySdlRnMDm/KtYYnSPI2BAcHQkPZsar4k/img.png?width=1058&amp;amp;height=718&amp;amp;face=0_0_1058_718,https://scrap.kakaocdn.net/dn/ecNzef/hySdmCLliN/peJ3CBjOhxkbBp8QTkkfUk/img.png?width=1058&amp;amp;height=718&amp;amp;face=0_0_1058_718&quot;&gt;&lt;a href=&quot;https://velog.io/@pjh612/Redis%EC%9D%98-%EB%B0%B1%EC%97%85RDB-AOF-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@pjh612/Redis%EC%9D%98-%EB%B0%B1%EC%97%85RDB-AOF-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c0Urax/hySdv0KXqH/eDURo0uLWVagqUMWK2NW1K/img.png?width=1058&amp;amp;height=718&amp;amp;face=0_0_1058_718,https://scrap.kakaocdn.net/dn/dRcwCo/hySdlRnMDm/KtYYnSPI2BAcHQkPZsar4k/img.png?width=1058&amp;amp;height=718&amp;amp;face=0_0_1058_718,https://scrap.kakaocdn.net/dn/ecNzef/hySdmCLliN/peJ3CBjOhxkbBp8QTkkfUk/img.png?width=1058&amp;amp;height=718&amp;amp;face=0_0_1058_718');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Redis의 백업(RDB, AOF) 알아보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Redis는 인메모리 데이터 저장소로 읽기 성능이 뛰어나 캐싱, 세션 저장소, RefreshToken 저장소 등 다양한 곳에 많이 사용됩니다.인메모리 데이터 저장소가 가지는 휘발성의 특성 때문에 종료되는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  삽질 이슈 기록</category>
      <category>Redis</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/66</guid>
      <comments>https://neverfadeaway.tistory.com/66#entry66comment</comments>
      <pubDate>Sun, 9 Apr 2023 23:27:51 +0900</pubDate>
    </item>
    <item>
      <title>Real MySQL 8.0 - 9.3 고급 최적화 - MySQL의 조인 방식들</title>
      <link>https://neverfadeaway.tistory.com/73</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 데이터 생성&lt;/h2&gt;
&lt;pre id=&quot;code_1679835799218&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DELIMITER $$
DROP PROCEDURE IF EXISTS gen_data$$
CREATE PROCEDURE gen_data(
IN v_rows INT
)
BEGIN
    DECLARE i INT DEFAULT 1;
    
    create table tb_test1(
    no bigint,
    col1 varchar(100),
    col2 varchar(100),
    col3 varchar(100)
    );
    WHILE i &amp;lt;= v_rows DO     
        INSERT INTO tb_test1 (no , col1, col2 , col3 )
          VALUES(concat(i), concat('col1-',i), concat('col2-',i), concat('col3-',i));
        SET i = i + 1;
    END WHILE;
END$$
DELIMITER ;

call gen_data(1000000);

create table tb_test2
   as
   select * from tb_test1;
   
CREATE INDEX idx_col1
ON tb_test1 (col1);

CREATE INDEX idx_col1
ON tb_test2 (col1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Nested Loop Join&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인의 연결 조건이 되는 컬럼에 모두 인덱스가 있는 경우 사용되는 조인 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩 for 문 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=pJWCwfv983Q&amp;amp;t=88s&quot;&gt;https://www.youtube.com/watch?v=pJWCwfv983Q&amp;amp;t=88s&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=pJWCwfv983Q&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/gE3HX/hyR2KqWTg5/JKKSMe61nnriqqtszFOO7K/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/pJWCwfv983Q&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;pre id=&quot;code_1679835864400&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;explain select * 
from tb_test1 a INNER JOIN tb_test2 b 
ON a.col1=b.col1 -- 두 테이블 모두 col1 에는 인덱스 설정 된 상태
where a.no &amp;gt; 1 and b.no &amp;gt; 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2252&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkisCF/btr5Qyx5DBS/l4c7cykMpxFammXoVeEK1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkisCF/btr5Qyx5DBS/l4c7cykMpxFammXoVeEK1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkisCF/btr5Qyx5DBS/l4c7cykMpxFammXoVeEK1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkisCF%2Fbtr5Qyx5DBS%2Fl4c7cykMpxFammXoVeEK1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2252&quot; height=&quot;217&quot; data-origin-width=&quot;2252&quot; data-origin-height=&quot;217&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nested Loop Join의 경우 실행계획의 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;1568&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D9q1h/btr59Z8z79N/yciGW7MmFNfWqS9pcYOEKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D9q1h/btr59Z8z79N/yciGW7MmFNfWqS9pcYOEKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D9q1h/btr59Z8z79N/yciGW7MmFNfWqS9pcYOEKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD9q1h%2Fbtr59Z8z79N%2FyciGW7MmFNfWqS9pcYOEKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1568&quot; height=&quot;826&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;826&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;&lt;span&gt; &lt;/span&gt;&amp;nbsp;Nested Loop Join에서 주의 할 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 테이블이 먼저 액세스 되느냐에 따라서 속도의 차이가 크게 날 수 있다. 그렇기 때문에 어느 테이블을 드라이빙 테이블로 설정 하는지, 드라이빙 테이블이 너무 많은 데이터를 읽는것은 아닌지 확인 하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1679835947404&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 테스트1 네스티드 루프조인, 드라이빙 조건에 걸리는 테이블 row 많은 경우 -- 38s
explain select * 
from tb_test1 a INNER JOIN tb_test2 b 
ON a.col1=b.col1 
where a.col1 &amp;gt;= 'col1-100' and b.col1 &amp;gt;= 'col1-100';

-- 테스트2 네스티드 루프조인, 드라이빙 테이블 row 적은 경우 -- 14ms
select * -- 35.6ms
from tb_test1 a INNER JOIN tb_test2 b 
ON a.col1=b.col1 
where a.col1 &amp;gt;= 'col1-999900' and b.col1 &amp;gt;= 'col1-100';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이빙 테이블의 검색 조건에 걸리는 데이터가 많은 경우(테스트1 약 999,900개) 적은 경우보다 처리에 속도가 오래 걸리는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;2. Block Nested Loop Join&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 드리븐 테이블의 연결 조건에 인덱스를 사용할 수 없는 경우 사용되는 조인 방식&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1696&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4dbAm/btr5R1GxbWO/dDz1Ik1gDoV6TTbnYvF4j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4dbAm/btr5R1GxbWO/dDz1Ik1gDoV6TTbnYvF4j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4dbAm/btr5R1GxbWO/dDz1Ik1gDoV6TTbnYvF4j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4dbAm%2Fbtr5R1GxbWO%2FdDz1Ik1gDoV6TTbnYvF4j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1696&quot; height=&quot;856&quot; data-origin-width=&quot;1696&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;드리븐 테이블 연결 조건에 인덱스를 사용할 수 없는 경우, Driving 테이블의 추출 건수만큼 Driven(Inner) 테이블 Full Scan 을 반복적으로 스캔을 해야하는 상황이 발생하는데 이러한 문제를 개선 하고자 내부적으로 내부적으로 메모리 버퍼를 활용하는 방식&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3AiN5/btr5QoCcs77/e8fuA56fJBXTilaYoEyN0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3AiN5/btr5QoCcs77/e8fuA56fJBXTilaYoEyN0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3AiN5/btr5QoCcs77/e8fuA56fJBXTilaYoEyN0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3AiN5%2Fbtr5QoCcs77%2Fe8fuA56fJBXTilaYoEyN0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1468&quot; height=&quot;1010&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;1010&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;드라이빙 테이블 row 여러개를 메모리에 저장해두고, 드리븐 테이블의 데이터를 한번 가지고 온 다음, 메모리에서 드라빙 테이블과 드리븐 테이블 데이터를 조인처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 스캔 횟수가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이빙 테이블 row 갯수 X 드리븐 테이블 row 갯수 당 조인 1번에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(드라이빙 테이블 row 갯수 X 드리븐 테이블 row 갯수) / 버퍼크기 당 1번으로 조인 횟수가 줄어들게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획 확인&lt;/p&gt;
&lt;pre id=&quot;code_1679836031343&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;explain select * 
from tb_test1 a JOIN tb_test2 b
on a.col2=b.col2 // 인덱스가 아닌 컬럼이 조인의 연결 조건으로 사용 되는 경우 작동
where a.no &amp;lt; 10 and b.no &amp;gt; 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;MySQL 5.7 버전의 경우&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2360&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0lKwt/btr5YVS9195/7D068zw1YokFrPSMa5Grn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0lKwt/btr5YVS9195/7D068zw1YokFrPSMa5Grn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0lKwt/btr5YVS9195/7D068zw1YokFrPSMa5Grn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0lKwt%2Fbtr5YVS9195%2F7D068zw1YokFrPSMa5Grn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2360&quot; height=&quot;182&quot; data-origin-width=&quot;2360&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 8.0.20 버전 부터 더이상 사용되지 않고, 해시조인 알고리즘이 대체되어 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2366&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bogp9F/btr5PRxMfp8/fkvSJASFMsNvADbp4YYqn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bogp9F/btr5PRxMfp8/fkvSJASFMsNvADbp4YYqn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bogp9F/btr5PRxMfp8/fkvSJASFMsNvADbp4YYqn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbogp9F%2Fbtr5PRxMfp8%2FfkvSJASFMsNvADbp4YYqn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2366&quot; height=&quot;194&quot; data-origin-width=&quot;2366&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;h1&gt;3. Hash Join&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 8.0.20 버전 부터 드리븐 테이블의 연결 조건에 인덱스를 사용할 수 없는 경우,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Block Nested Loop Join대신 사용되는 조인 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 테이블로 해시를 구성했는지 확인하기&lt;/p&gt;
&lt;pre id=&quot;code_1679836116172&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;explain format=tree
select * 
from tb_test1 a JOIN tb_test2 b
on a.col2=b.col2 // 인덱스가 아닌 컬럼이 조인의 연결 조건으로 사용 되는 경우 작동
where a.no &amp;lt; 10 and b.no &amp;gt; 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;explain format=tree 로 실행계획을 분석할때, 제일 하단에 명시된 테이블이 해시로 구성된 테이블이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해시로 구성되는 테이블을 &lt;b&gt;빌드테이블&lt;/b&gt;, 구성된 해시와 조인되는 테이블을 &lt;b&gt;프로브 테이블&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1679836169009&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-&amp;gt; Filter: (b.col2 = a.col2)  (cost=124311.64 rows=37278)
    -&amp;gt; Inner hash join (&amp;lt;hash&amp;gt;(b.col2)=&amp;lt;hash&amp;gt;(a.col2))  (cost=124311.64 rows=37278)
        -&amp;gt; Filter: (b.`no` &amp;gt; 10)  (cost=3.43 rows=334)
            -&amp;gt; Table scan on b  (cost=3.43 rows=10034)
        -&amp;gt; Hash
            -&amp;gt; Filter: (a.`no` &amp;lt; 10)  (cost=1027.65 rows=3344)
                -&amp;gt; Table scan on a  (cost=1027.65 rows=10034)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;1922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IXWUK/btr5PFR12z2/wwQxfIdHzUQ7UHTtnJ2kg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IXWUK/btr5PFR12z2/wwQxfIdHzUQ7UHTtnJ2kg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IXWUK/btr5PFR12z2/wwQxfIdHzUQ7UHTtnJ2kg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIXWUK%2Fbtr5PFR12z2%2FwwQxfIdHzUQ7UHTtnJ2kg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1578&quot; height=&quot;1922&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;1922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리(조인버퍼)의 크기가 조인처리해야할 데이터보다 작을 경우, 위 그림 처럼 빌드 테이블과 프로브 테이블의 데이터를 여러 청크를 구성해 나누어 조인 작업을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nested Loop Join과 성능 비교&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I4FoX/btr53Gg8h73/4j9H0WnpqMAju9TJcNhpkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I4FoX/btr53Gg8h73/4j9H0WnpqMAju9TJcNhpkk/img.png&quot; data-alt=&quot;위 그림에서 A 지점은 MySQL 서버가 첫번째 레코드를 찾아낸 시점, B는 마지막 레코드를 찾아낸 시점이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I4FoX/btr53Gg8h73/4j9H0WnpqMAju9TJcNhpkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI4FoX%2Fbtr53Gg8h73%2F4j9H0WnpqMAju9TJcNhpkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1570&quot; height=&quot;486&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위 그림에서 A 지점은 MySQL 서버가 첫번째 레코드를 찾아낸 시점, B는 마지막 레코드를 찾아낸 시점이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시조인의 경우 첫번째 지점을 찾기 까지는 오래걸리지만, 전체적인 실행시간이 Nested Loop Join 보다 적게 걸리는 것을 확인 할 수 있다.&lt;/p&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 서버는 조인 조건의 인덱스 컬럼이 없거나, 조인 대상 테이블 중 일부의 레코드 건수가 매우 적은 경우등에 대해서만 해시 조인 알고리즘을 사용하도록 설계 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 OLTP 환경의 경우 빠른 응답속도가 중요하기 때문에 해시조인이 더 빠르다고 옵티마이저 힌트를 사용해서 강제로 쿼리 실행계획을 해시 조인으로 유도하는 것은 좋지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인 조건 컬럼에 인덱스 있다면 인덱스네스티드 루프 조인 &amp;rarr; 인덱스 못쓰는 경우 &amp;ldquo;블록&amp;rdquo; 네스티드 루프조인 &amp;rarr; 더 빠른 해시 조인으로 대체&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;4. Batch Key Access JOIN(BKA JOIN)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multi Range Read 방식을 적용한 조인 방식&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;MMR 방식이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multi Range Read(MRR)는 메모리 버퍼를 사용하여 Random I/O를 Sequential I/O로 처리할 수 있도록 도와주는 기능&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxvrhA/btr5Q3qZ5pk/kmsAycsE2GRcxaackU1mt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxvrhA/btr5Q3qZ5pk/kmsAycsE2GRcxaackU1mt0/img.png&quot; data-alt=&quot;MMR의 경우 BKA 조인, Index range scan 에서 사용될 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxvrhA/btr5Q3qZ5pk/kmsAycsE2GRcxaackU1mt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxvrhA%2Fbtr5Q3qZ5pk%2FkmsAycsE2GRcxaackU1mt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;446&quot; height=&quot;417&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MMR의 경우 BKA 조인, Index range scan 에서 사용될 수 있다.&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;1. Index range scan 에서 MMR이 사용되는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Non-Clustered Index에서 Range Scan을 사용하여 테이블의 행을 읽게 되는 경우 테이블에 대한 랜덤 디스크 액세스가 많이 발생할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfEqIJ/btr6eTNKwGw/Rvd2PJFOmYkYnaleoMkETk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfEqIJ/btr6eTNKwGw/Rvd2PJFOmYkYnaleoMkETk/img.png&quot; data-alt=&quot;Index range scan에서 MMR이 적용되지 않은 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfEqIJ/btr6eTNKwGw/Rvd2PJFOmYkYnaleoMkETk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfEqIJ%2Fbtr6eTNKwGw%2FRvd2PJFOmYkYnaleoMkETk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;539&quot; height=&quot;438&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Index range scan에서 MMR이 적용되지 않은 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMR기능이 켜진 경우 버퍼 내에서 한번 데이터의 정렬이 일어나기 때문에 랜덤 I/O를 줄일 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI1GjH/btr5UlSiyIS/eoWkgNRaCOuKlYpB7LlTck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI1GjH/btr5UlSiyIS/eoWkgNRaCOuKlYpB7LlTck/img.png&quot; data-alt=&quot;Index range scan에서 MMR이 적용된 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI1GjH/btr5UlSiyIS/eoWkgNRaCOuKlYpB7LlTck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI1GjH%2Fbtr5UlSiyIS%2FeoWkgNRaCOuKlYpB7LlTck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1410&quot; height=&quot;754&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Index range scan에서 MMR이 적용된 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Index range scan 에서 MMR 기능 사용해 보기&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;[TEST1] MMR 사용, Non-Clustered Index에서 Range Scan&lt;/h3&gt;
&lt;pre id=&quot;code_1679836460108&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET profiling=1;

-- MMR 사용, Non-Clustered Index에서 Range Scan
-- 조회되는 건수에 따라서 MRR 동작 대신 Index Condition Pushdown 이 동작될 수 도 있는데 테스트를 위해 그 기능 off
set optimizer_switch='index_condition_pushdown=off';
set optimizer_switch='mrr_cost_based=off';

-- MMR on 설정
set optimizer_switch='mrr=on';

select * 
from tb_test1 force index(idx_col1)
where col1 between 'col1-1' and 'col1-900000';

SHOW profiles;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2370&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9PNxK/btr5PHoLkns/rEFXK2SR5jlbJmXQLANgTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9PNxK/btr5PHoLkns/rEFXK2SR5jlbJmXQLANgTK/img.png&quot; data-alt=&quot;MMR 옵션이 켜진 경우, Extra 컬럼에 정보가 표시 된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9PNxK/btr5PHoLkns/rEFXK2SR5jlbJmXQLANgTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9PNxK%2Fbtr5PHoLkns%2FrEFXK2SR5jlbJmXQLANgTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2370&quot; height=&quot;207&quot; data-origin-width=&quot;2370&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MMR 옵션이 켜진 경우, Extra 컬럼에 정보가 표시 된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 17초 소요&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;[TEST2] MRR 사용 X, Non-Clustered Index에서 Range Scan&lt;/h3&gt;
&lt;pre id=&quot;code_1679836520258&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET profiling=1;

-- MMR 사용 x, Non-Clustered Index에서 Range Scan
set optimizer_switch='index_condition_pushdown=on';
set optimizer_switch='mrr_cost_based=on';
set optimizer_switch='mrr=off';

explain
select * 
from tb_test1 force index(idx_col1)
where col1 between 'col1-1000' and 'col1-500000';

SHOW profiles;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2246&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2lRzs/btr5RgKHJMl/VlVGw6Um9Kr98dnXI0clJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2lRzs/btr5RgKHJMl/VlVGw6Um9Kr98dnXI0clJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2lRzs/btr5RgKHJMl/VlVGw6Um9Kr98dnXI0clJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2lRzs%2Fbtr5RgKHJMl%2FVlVGw6Um9Kr98dnXI0clJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2246&quot; height=&quot;354&quot; data-origin-width=&quot;2246&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 16초 소요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;?? 오히려 MMR 기능을 켰을때 더 시간이 걸렸는데, SSD라서 랜덤 읽기 I/O를 줄인다고 해도 별 성능 이점이 없었던 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Batch Key Access JOIN(BKA JOIN)에서 사용되는 경우&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgxy72/btr5UlkqbwN/2dEmoZKeqow81ecPd6KEkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgxy72/btr5UlkqbwN/2dEmoZKeqow81ecPd6KEkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgxy72/btr5UlkqbwN/2dEmoZKeqow81ecPd6KEkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdgxy72%2Fbtr5UlkqbwN%2F2dEmoZKeqow81ecPd6KEkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;578&quot; height=&quot;478&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;942&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;일반적인 Nested Loop 조인(NLJ) 의 경우 Driving Table(선행 테이블)에서 조회되는 값으로 조인되는 Driven Table(Inner 테이블,후행 테이블) 을 접근하게 되며 조인시 데이터 저장 순서대로 조회를 하는 것이 아니기 때문에 랜덤 액세스, 랜덤 I/O가 발생되게 된다.&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;1416&quot; data-origin-height=&quot;976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvaGtw/btr5ToIaPEQ/teN49NWKLtBD8WOr94FdHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvaGtw/btr5ToIaPEQ/teN49NWKLtBD8WOr94FdHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvaGtw/btr5ToIaPEQ/teN49NWKLtBD8WOr94FdHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvaGtw%2Fbtr5ToIaPEQ%2FteN49NWKLtBD8WOr94FdHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;498&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;976&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;BKA 조인의 경우 메모리에서 드라이빙 테이블에서 읽어온 데이터를 부가적으로 정렬하기 때문에 랜덤 I/O를 줄일 수 있다.&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;h1&gt;출처&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hoing.io/archives/24491&quot;&gt;https://hoing.io/archives/24491&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679836864044&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;MySQL - Block Nested-Loop and Batched Key Access Joins&quot; data-og-description=&quot; &quot; data-og-host=&quot;hoing.io&quot; data-og-source-url=&quot;https://hoing.io/archives/24491&quot; data-og-url=&quot;https://hoing.io/archives/24491&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d6OqBZ/hyR2RKomJ1/2E2XKtCZ9wAVbfZ4t8wCQk/img.png?width=759&amp;amp;height=500&amp;amp;face=0_0_759_500,https://scrap.kakaocdn.net/dn/c7H4r2/hyR38DLWqM/9KM9p9IrEOJnqwhp25dGK1/img.png?width=759&amp;amp;height=500&amp;amp;face=0_0_759_500,https://scrap.kakaocdn.net/dn/lbaIT/hyR2NgUmvr/Ck9WAX3HCJhuzp11wvSvTk/img.png?width=832&amp;amp;height=642&amp;amp;face=0_0_832_642&quot;&gt;&lt;a href=&quot;https://hoing.io/archives/24491&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hoing.io/archives/24491&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d6OqBZ/hyR2RKomJ1/2E2XKtCZ9wAVbfZ4t8wCQk/img.png?width=759&amp;amp;height=500&amp;amp;face=0_0_759_500,https://scrap.kakaocdn.net/dn/c7H4r2/hyR38DLWqM/9KM9p9IrEOJnqwhp25dGK1/img.png?width=759&amp;amp;height=500&amp;amp;face=0_0_759_500,https://scrap.kakaocdn.net/dn/lbaIT/hyR2NgUmvr/Ck9WAX3HCJhuzp11wvSvTk/img.png?width=832&amp;amp;height=642&amp;amp;face=0_0_832_642');&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;MySQL - Block Nested-Loop and Batched Key Access Joins&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hoing.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>✏️  스터디 모음집/RealMySQL 스터디</category>
      <category>RealMySQL8.0</category>
      <category>조인 방식</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/73</guid>
      <comments>https://neverfadeaway.tistory.com/73#entry73comment</comments>
      <pubDate>Sun, 26 Mar 2023 22:22:47 +0900</pubDate>
    </item>
    <item>
      <title>Real MySQL 8.0 8장 - 인덱스는 왼쪽이 중요하다.</title>
      <link>https://neverfadeaway.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 인덱스의 특징은 왼쪽 부터 오른쪽으로 값이 정렬되있다는 것이다.&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;LIKE 검색에서 왼쪽 부분이 정해 지지 않은 경우 인덱스 레인지 스캔이 불가능 하다. 한 컬럼 내의 데이터를 스캔 할 때 왼쪽 텍스트 부터 오른쪽 순서로 탐색이 이루어지기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCAp3m/btr5YU020LC/BHPJU4jZJPLS6AiEgjsj9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCAp3m/btr5YU020LC/BHPJU4jZJPLS6AiEgjsj9K/img.png&quot; data-alt=&quot;인덱스 테이블에서 컬럼내 데이터를 탐색 할 때 왼쪽 부터 오른쪽 순서로 읽는다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCAp3m/btr5YU020LC/BHPJU4jZJPLS6AiEgjsj9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCAp3m%2Fbtr5YU020LC%2FBHPJU4jZJPLS6AiEgjsj9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;284&quot; height=&quot;600&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인덱스 테이블에서 컬럼내 데이터를 탐색 할 때 왼쪽 부터 오른쪽 순서로 읽는다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LIKE 절에 왼쪽이 특정 된 경우&lt;/h3&gt;
&lt;pre id=&quot;code_1679834211303&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;explain select * from tb where name like 'a%';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sPdwP/btsbOXTguBh/klNEPSmKxvdI1jXi4VJvck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sPdwP/btsbOXTguBh/klNEPSmKxvdI1jXi4VJvck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sPdwP/btsbOXTguBh/klNEPSmKxvdI1jXi4VJvck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsPdwP%2FbtsbOXTguBh%2FklNEPSmKxvdI1jXi4VJvck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1952&quot; height=&quot;198&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type : range =&amp;gt; 인덱스 레인지 스캔&lt;br /&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;LIKE 절에 왼쪽이 특정 되지 않은 경우&lt;/h3&gt;
&lt;pre id=&quot;code_1679834477405&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;explain select * from tb where name like '%a';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WCrXD/btsbRziPmhD/CxrIKh4rpZf6CcPAUcRwQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WCrXD/btsbRziPmhD/CxrIKh4rpZf6CcPAUcRwQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WCrXD/btsbRziPmhD/CxrIKh4rpZf6CcPAUcRwQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWCrXD%2FbtsbRziPmhD%2FCxrIKh4rpZf6CcPAUcRwQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;288&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type: ALL =&amp;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;h2 data-ke-size=&quot;size26&quot;&gt;2. 다중 키 인덱스에서 왼쪽 컬럼이 범위 결정 조건으로 사용 되지 않은 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다중 키 인덱스를 생성 할 때는, 컬럼의 순서가 중요한데 왼쪽 컬럼부터 오른쪽 컬럼 순으로 인덱스 테이블에서 탐색이 일어나기 때문에 왼쪽 컬럼이 탐색의 범위를 결정시키는 조건으로 사용되지 않는다면 인덱스 스캔을 활용 할 수 없다.&lt;/p&gt;
&lt;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;502&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIqeFS/btr6eWRehqY/s8EQ44kr49VlZSvTFpvTzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIqeFS/btr6eWRehqY/s8EQ44kr49VlZSvTFpvTzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIqeFS/btr6eWRehqY/s8EQ44kr49VlZSvTFpvTzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIqeFS%2Fbtr6eWRehqY%2Fs8EQ44kr49VlZSvTFpvTzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;305&quot; height=&quot;662&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;1090&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;pre id=&quot;code_1679834924495&quot; class=&quot;oxygene&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM dept_emp WHERE dept_no &amp;gt;= 'd001' and emp_no &amp;gt;= 1044; // 인덱스 활용가능

SELECT * FROM dept_emp WHERE emp_no &amp;gt;= 1044; // 인덱스 활용 불가&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;cf. 조건이 인덱스의 범위 결정 조건(range)으로 활용하지 못하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. NOT EQUAL&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. LIKE '%abc'&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;4. 데이터 타입이 서로 다른 비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 문자열의 경우 콜레이션이 다른 경우&lt;/p&gt;</description>
      <category>✏️  스터디 모음집/RealMySQL 스터디</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/72</guid>
      <comments>https://neverfadeaway.tistory.com/72#entry72comment</comments>
      <pubDate>Sun, 26 Mar 2023 21:51:57 +0900</pubDate>
    </item>
    <item>
      <title>Real MySQL 8.0 8장 - 인덱스 정렬 및 스캔의 방향</title>
      <link>https://neverfadeaway.tistory.com/71</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스 생성시 기본적으론, 오름차순(ASC) 정렬로 인덱스가 생성된다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql 5.7 버전 이후의 버전에서 인덱스 생성 시, 인덱스를 구성하는 컬럼의 정렬 순서를 정 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.7 버전에서는 인덱스 생성시 아래 와 같이 ASC, DESC 키워드로 정렬 순서를 지정해 주어도, 실제로 내부적으로 오름차순(ASC)으로만 정렬된다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;CREAT INDEX ix_teamname ON employees (team_name DESC)
// 5.7 버전에서는 DESC로 생성해도 실제로는 ASC 로 정렬 됨.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8.0 버전 부터 복합 인덱스 생성시 각 컬럼마다 별개로 정렬 순서를 정할 수 있게 되었다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;CREAT INDEX ix_teamname_userscore ON employees (team_name ASC, user_score DESC)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스 정렬 방향과 스캔 방향의 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 어떤 인덱스가 오름차순으로 정렬되어있다면, 오름차순으로만 읽을 수 있는것은 아니다. 그 인덱스를 거꾸로 부터 읽으면 내림차순으로 정렬된 인덱스로도 사용될수 있기 때문이다.&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;902&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LuoB8/btr59ZHvzjA/CJlKDdSvlunTufYRVUAzgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LuoB8/btr59ZHvzjA/CJlKDdSvlunTufYRVUAzgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LuoB8/btr59ZHvzjA/CJlKDdSvlunTufYRVUAzgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLuoB8%2Fbtr59ZHvzjA%2FCJlKDdSvlunTufYRVUAzgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;490&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;956&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;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스 정순 스캔 VS 인덱스 역순 스캔&lt;/h2&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;1544&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5O0FV/btr5TrkBnfs/4LaK0V7YiUpDdxMZBjqN5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5O0FV/btr5TrkBnfs/4LaK0V7YiUpDdxMZBjqN5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5O0FV/btr5TrkBnfs/4LaK0V7YiUpDdxMZBjqN5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5O0FV%2Fbtr5TrkBnfs%2F4LaK0V7YiUpDdxMZBjqN5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1544&quot; height=&quot;588&quot; data-origin-width=&quot;1544&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;오름차순 인덱스 : 작인 값의 인덱스 키가 B-Tree 왼쪽 노드에 위치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내림차순 인덱스 : 큰인 값의 인덱스 키가 B-Tree 왼쪽 노드에 위치&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;만약 오름차순(ASC) 인덱스로 생성된 컬럼을 활용해서 Order by 절에서 오름차순(ASC)으로 읽는 다면 정순스캔 (DESC)로 읽는다면 역순 스캔이 된다. 반대로 내림차순(DESC) 인덱스로 생성된 컬럼(MySQL 8.0 부터 지원)을 활용해서 Order by 절에서 오름차순(DESC)으로 읽는 다면 정순스캔 (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;즉 &lt;b&gt;인덱스 생성시 정한 정렬 방향과 일치하는 방향대로 읽는 경우&lt;/b&gt;를 정순 스캔, &lt;b&gt;인덱스 생성시 정한 정렬 방향과 반대로 읽는 경우를 역순 스캔&lt;/b&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;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스 정순 스캔 과 인덱스 역순 스캔 성능 차이&lt;/h2&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;// 322597개 row가 있는 테이블에서 테스트, 오름차순(ASC) 인덱스 생성 된 BeginDate 컬럼 사용

// 인덱스 정순 스캔(ASC로 생성된 인덱스를 ASC로 읽음)
select * from `test_sales` ORDER BY BeginDate ASC LIMIT 322596,1; // 233ms

// 인덱스 역순 스캔(ASC로 생성된 인덱스를 DESC로 읽음)
select * from `test_sales` ORDER BY BeginDate DESC LIMIT 322596,1; // 391ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 역순 스캔의 경우 시간이 더 걸린것을 확인 할 수 있다. 보통 28.9% 정도 더 걸리는데 그 이유는 InnoDB 자체 내부에서 페이지 잠금 구조나, 페이지 내부에서 레코드들의 연결 &lt;b&gt;구조가 정순 스캔에 더 적합한 구조&lt;/b&gt;로 되어있기때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>✏️  스터디 모음집/RealMySQL 스터디</category>
      <category>RealMySQL8.0</category>
      <category>인덱스</category>
      <author>cocoroocoo</author>
      <guid isPermaLink="true">https://neverfadeaway.tistory.com/71</guid>
      <comments>https://neverfadeaway.tistory.com/71#entry71comment</comments>
      <pubDate>Sun, 26 Mar 2023 21:34:37 +0900</pubDate>
    </item>
  </channel>
</rss>