<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>지나공 : 지식을 나누는 공간</title>
    <link>https://eocoding.tistory.com/</link>
    <description>지식을 나누는 공간</description>
    <language>ko</language>
    <pubDate>Thu, 30 Jul 2026 00:24:11 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>해리리_</managingEditor>
    <item>
      <title>String -&amp;gt; 음수 hashCode -&amp;gt; Hex -&amp;gt; Long 변환 시 NumberFormatException 트러블슈팅</title>
      <link>https://eocoding.tistory.com/140</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;타 팀에서 Kafka 메시지를 발행하면 NiFi를 거쳐 우리팀 Kafka Consumer가 해당 메시지를 수신하는 프로세스가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에는 아래 단계가 포함되는데, 가끔 길이가 긴 문자열로 된 Kafka Key가 들어왔을 때 3번 단계에서 NumberFormatException 이 발생했었다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;String인 Kafka Key 를 hashCode 로 전환&lt;/li&gt;
&lt;li&gt;hashCode를 Hex Encoding 함.&lt;/li&gt;
&lt;li&gt;Hex Encoding 된 값을 fromRadix(16) 메소드를 활용해 Long 타입 숫자로 변환함. (parseLong)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인을 정확하게 확인하지 못하고 넘어갔었는데 최근에 간단한 내용이었음을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사전 지식&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;길이가 긴 문자열을 hashCode(int) 전환 시 음수가 되는 케이스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열을 hashCode (int) 로 전환하면 아래와 같은 식을 거치다보니, 문자열 길이가 길어지면 int 범위를 초과하여 오버플로가 발생하면서 음수 hashCode로 전환될 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]&amp;nbsp;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtXkNE/dJMcafr5g0W/Up16iTrhhPfrg40elfZG6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtXkNE/dJMcafr5g0W/Up16iTrhhPfrg40elfZG6k/img.png&quot; data-alt=&quot;hashCode 메소드 내부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtXkNE/dJMcafr5g0W/Up16iTrhhPfrg40elfZG6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtXkNE%2FdJMcafr5g0W%2FUp16iTrhhPfrg40elfZG6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;371&quot; height=&quot;274&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hashCode 메소드 내부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Hex Encoding된 값을 parseLong 시도 시 양수/음수 판단이 제대로 이뤄지지 않는 케이스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parseLong은 signedLong으로 해석하기 때문에 들어온 값이 양수인지 음수인지 판단해야하는데, 이때 &quot;-&quot; 라는 문자가 포함되었는지를 보고 음수인지를 판단한다. 즉&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #172b4d;&quot;&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt;FFFFFFFFBA44B6E1와 같은 음수인 16진수 값을 제대로 음수로 취급하지 못할 수 있다. 따라서 음수로 취급해야할 값을 양수로 취급하면서 값이 매우 커지고 signed long 범위를 벗어날 수 있다. paseUnsignedLong 메소드에서는 범위 내에 속하여 정상적으로 변환됐다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS1Uc7/dJMcagYPpSs/NMxYiEml7Wl2Bqa8lKNynK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS1Uc7/dJMcagYPpSs/NMxYiEml7Wl2Bqa8lKNynK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS1Uc7/dJMcagYPpSs/NMxYiEml7Wl2Bqa8lKNynK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS1Uc7%2FdJMcagYPpSs%2FNMxYiEml7Wl2Bqa8lKNynK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;362&quot; height=&quot;287&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정상 케이스) 문자열 길이가 짧고 양수 hashCode로 전환될 때&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&quot;02715&quot; 라는 적당한 길이의 문자열&lt;/li&gt;
&lt;li&gt;hashCode (int) 로 변환 시 결과는 &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;45872985&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;위 값을 Hex Encoding 해서 16진수로 만든 결과는 &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;0000000002BBF759&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;16진수 값을 parseLong 하여 숫자로 변환 시 &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;45872985 로 정상 변환되어 2번의 값을 동일하게 얻는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;실패 케이스) 문자열 길이가 짧고 양수 hashCode로 전환될 때&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&quot;0659506596&quot; 라는 조금 더 긴 길이의 문자열&lt;/li&gt;
&lt;li&gt;hashCode (int) 로 변환 시 결과는&lt;span&gt; &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;-1169901855. 오버플로 발생하면서 음수로 나옴.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt; 위 값을 Hex Encoding 해서 16진수로 만든 결과는 &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;FFFFFFFFBA44B6E1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;16진수 값을 parseLong 하여 숫자로 변환 시&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt; 위 사전지식에서 말한 것처럼 signed long으로 해석을 시도하는데, &quot;-&quot; 라는 문자열이 없으니 양수로 해석했고, 이때&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt;18446744072554600161 와 같이 큰 값으로 변환되면서 sigend long 범위를 벗어나서 NumberFormatException 이 발생한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt;&lt;span style=&quot;color: #172b4d; text-align: left;&quot;&gt;parseUnsignedLong 으로 하면 정상적으로 다시 &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;-1169901855 를 얻을 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771754264313&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class Test {
    /**
     * 정상 케이스: 문자열이 양수 hashCode 로 전환되는 경우
     */
    @Test
    fun positiveHashFlow() {
        val str = &quot;02715&quot;

        // 1. String -&amp;gt; hashCode()
        val hashCode = str.hashCode()
        println(&quot;1. hashcode 결과 (int): $hashCode&quot;)
        assertEquals(45872985, hashCode)

        // 2. hashCode -&amp;gt; Hex Encoding
        val hexString = java.lang.Long.toHexString(hashCode.toLong())
        println(&quot;2. hex 변환 (String): $hexString&quot;)
        assertEquals(&quot;2bbf759&quot;, hexString)

        val padded = &quot;%016X&quot;.format(hashCode.toLong())
        println(&quot;2. padded hex: $padded&quot;)
        assertEquals(&quot;0000000002BBF759&quot;, padded)

        // 3. Hex &amp;rarr; Long
        val parsedLong = java.lang.Long.parseLong(hexString, 16)
        println(&quot;3. toLong(16) 변환 (long): $parsedLong&quot;)
        assertEquals(45872985, parsedLong)

        val parsedUnsignedLong = java.lang.Long.parseUnsignedLong(hexString, 16)
        println(&quot;3. unsignedLong: $parsedUnsignedLong&quot;)
        assertEquals(45872985, parsedUnsignedLong)
    }

    /**
     * 실패 케이스: 문자열이 음수 hashCode 로 전환되는 경우
     */
    @Test
    fun negativeHashFlow() {
        val str = &quot;0659506596&quot;

        // 1. String -&amp;gt; hashCode()
        val hashCode = str.hashCode()
        println(&quot;1. hashcode 결과 (int): $hashCode&quot;)
        assertEquals(-1169901855, hashCode)

        // 2. hashCode -&amp;gt; Hex Encoding
        val hashCodeLong = hashCode.toLong()
        val hexString = java.lang.Long.toHexString(hashCodeLong)
        println(&quot;2. hex 변환 (String): $hexString&quot;)
        assertEquals(&quot;ffffffffba44b6e1&quot;, hexString)

        val padded = &quot;%016X&quot;.format(hashCode.toLong())
        println(&quot;2. padded hex: $padded&quot;)
        assertEquals(&quot;FFFFFFFFBA44B6E1&quot;, padded)

        // 3. Hex &amp;rarr; Long
        assertThrows(NumberFormatException::class.java) {
            java.lang.Long.parseLong(hexString, 16)
        }

        val parsedUnsignedLong = java.lang.Long.parseUnsignedLong(hexString, 16)
        println(&quot;3. unsignedLong: $parsedUnsignedLong&quot;)
        assertEquals(-1169901855, parsedUnsignedLong)
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>우당탕탕 삽질기</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/140</guid>
      <comments>https://eocoding.tistory.com/140#entry140comment</comments>
      <pubDate>Sun, 22 Feb 2026 19:21:07 +0900</pubDate>
    </item>
    <item>
      <title>Elasticsearch 와 Lucene</title>
      <link>https://eocoding.tistory.com/139</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지금 opensearch 를 운영 중인데 쿼리나 운영이 서툴러서 쿼리를 좀 기록하려 했다가... 클러스터 내용을 빠뜨리고 바로 쿼리에 대해 쓰기가 좀 아쉬워서 결국 chapter 1 같은 내용부터 쓴다. 전에 한참 elasticsearch를 전혀 모르고 책의 딱 챕터1만 읽었던 시절, 루씬과 엘라스틱서치가 여기저기 나와서 이게 뭔가 싶었다. 근데 시니어 분이 '그냥 Lucene을 감쌌을 뿐이다.' 라고 하셨는데, 그 말이 무슨 말인지 알겠다..ㅎ&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 style=&quot;color: #000000;&quot;&gt;ElasticSearch 와 Opensearch&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elastic 에서 Apache Lucene 을 기반으로 만든 &lt;u&gt;검색 엔진&lt;/u&gt; 으로, 여러 서버에 &lt;u&gt;분산&lt;/u&gt;해서 데이터를 저장하고 노드 추가를 통한 수평 확장이 가능하다. 7.11 버전부터는 오픈소스가 아니고 상용 라이선스로 제공된다. Opensearch 는 Elasticsearch 와 Kibana의 오픈소스 포크고, 회사에서 현재는 opensearch를 사용하고 있다.&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 style=&quot;color: #000000;&quot;&gt;Lucene 과 Elasticsearch&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Elasticsearch를 알려면 Lucene 을 알아야 한다. Lucene 은 elasticsearch와 opensearch 의 코어 라이브러리이자 오픈소스 검색 라이브러리이다. 루씬 자체는 단일 노드에서만 작동하고, 분산 처리를 지원하지 않고, 클러스터링 기능도 없지만 엘라스틱서치는 Lucene을 기반으로 분산, 클러스터링, RESTful API 등을 추가해서 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bExdZ0/btsMMBFGBH6/C9hg4eAU7KPNaBTLfZ74hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bExdZ0/btsMMBFGBH6/C9hg4eAU7KPNaBTLfZ74hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bExdZ0/btsMMBFGBH6/C9hg4eAU7KPNaBTLfZ74hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbExdZ0%2FbtsMMBFGBH6%2FC9hg4eAU7KPNaBTLfZ74hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;339&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;루씬의 기본적인 동작은 인덱싱과 검색이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;835&quot; data-start=&quot;727&quot;&gt;&lt;b&gt;인덱싱&lt;/b&gt;: 데이터를 &lt;b&gt;역인덱스(reverse index)&lt;/b&gt; 형식으로 변환하여 저장한다. 각 문서(Document)는 키워드와 매핑되는 &lt;b&gt;단어의 위치&lt;/b&gt;를 포함한 정보로 저장된다.&lt;/li&gt;
&lt;li data-end=&quot;908&quot; data-start=&quot;836&quot;&gt;&lt;b&gt;검색&lt;/b&gt;: 사용자가 검색어를 입력하면, Lucene은 역인덱스를 검색하여 해당 키워드와 일치하는 문서들을 찾아 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch 동작은 Lucene 에 기반한 분산환경 동작이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1127&quot; data-start=&quot;1027&quot;&gt;&lt;b&gt;클러스터&lt;/b&gt;: Elasticsearch는 여러 대의 서버에서 &lt;b&gt;클러스터&lt;/b&gt;를 구성하여 동작한다. 각 서버는 &lt;b&gt;노드&lt;/b&gt;로 구성되며, 데이터를 분산하여 저장하고 처리한다.&lt;/li&gt;
&lt;li data-end=&quot;1240&quot; data-start=&quot;1128&quot;&gt;&lt;b&gt;샤딩과 리플리카&lt;/b&gt;: 데이터는 &lt;b&gt;샤드&lt;/b&gt;(Shard)로 분할되고, 각 샤드는 여러 노드에 분산되어 저장된다. 또한, &lt;b&gt;리플리카&lt;/b&gt; 샤드가 다른 노드에 복제되어 데이터의 고가용성을 보장한다.&lt;/li&gt;
&lt;li data-end=&quot;1335&quot; data-start=&quot;1241&quot;&gt;&lt;b&gt;인덱싱&lt;/b&gt;: Elasticsearch에서 데이터를 인덱싱하면, Elasticsearch는 이 데이터를 Lucene의 &lt;b&gt;역인덱스&lt;/b&gt; 형식으로 변환하여 저장한다.&lt;/li&gt;
&lt;li data-end=&quot;1420&quot; data-start=&quot;1336&quot;&gt;&lt;b&gt;검색&lt;/b&gt;: Elasticsearch는 사용자가 보낸 검색 쿼리를 여러 노드에 분산시켜 검색을 수행하고, 그 결과를 합쳐서 사용자에게 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Elasticsearch 와 Lucene 의 상호 동작 흐름&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터 인덱싱:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Lucene 이 데이터를 역색인하여 단일 노드에 저장한다.&lt;/li&gt;
&lt;li&gt;Elasticsearch 는 데이터를 여러 샤드로 나누어서 여러 노드에 저장하고, 각 샤드는 내부적으로 Lucene 역색인을 사용해서 데이터를 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;쿼리 처리:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Lucene 은 쿼리를 처리할 때 단일 노드에서 역색인을 검색해서 일치하는 문서를 변환한다.&lt;/li&gt;
&lt;li&gt;Elasticsearch는 쿼리를 여러 노드에 분배하여 병렬로 검색을 진행한 뒤, 각 노드의 결과를 통합해서 최종 결과를 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;확장성 / 고가용성
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Lucene은 단일 노드라 확장성과 고가용성이 제한된다.&lt;/li&gt;
&lt;li&gt;ElasticSearch는 데이터를 샤드로 분할하고, 여러 노드에 분산 저장해서 수평적 확장성을 제공한다. 그리고 리플리카를 통해 복제 샤드를 여러 노드에 분산해두기 때문에, 고가용성을 보장하고 장애 발생 시 자동 복구도 가능하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;결론
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Lucene 은 검색 및 인덱싱을 위한 저수준 라이브러리로 단일 노드에서 동작함.&lt;/li&gt;
&lt;li&gt;Elasticsearch 는 Lucene을 기반으로 분산 시스템 기능과 대규모 데이터 처리 및 고속 검색을 지원한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 이 단일 노드를 넘어, 여러 노드가 모인 하나의 Elasticsearch 클러스터를 보자.&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 style=&quot;color: #000000;&quot;&gt;Elaticsearch 클러스터&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJySw/btsMLqZFKg4/MIvVzK46K1v8b5rGtLSLD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJySw/btsMLqZFKg4/MIvVzK46K1v8b5rGtLSLD0/img.png&quot; data-alt=&quot;각 샤드 별로 주샤드와 복제본 1개인 상태 (샤드 0,1,2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJySw/btsMLqZFKg4/MIvVzK46K1v8b5rGtLSLD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJySw%2FbtsMLqZFKg4%2FMIvVzK46K1v8b5rGtLSLD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;307&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;각 샤드 별로 주샤드와 복제본 1개인 상태 (샤드 0,1,2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스: 문서의 집합. DB Table 같은 개념.&lt;/li&gt;
&lt;li&gt;샤드: 인덱스는 그 내용을 여러 샤드로 분산해서 저장한다. 그리고 고가용성을 위해 1개 주샤드에 N개 복제샤드를 구성하여 복제한다.&lt;/li&gt;
&lt;li&gt;노드: N개의 샤드가 모이면 하나의 노드가 된다. 엘라스틱서치는 고가용성을 제공하기 위해 같은 종류의 샤드를 다른 노드에 배치한다. 즉, 0번 샤드의 주 샤드와 복제 샤드가 있을 텐데, 이 둘을 같은 노드에 배치하지 않고, 0번 샤드의 복제본이 두개라면 이 둘도 서로 다른 노드로 배치한다.
&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;ingest 노드 : 데이터가 색인되기 전에 전처리를 수행하는 노드.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클러스터: 엘라스틱서치 노드 여러개가 모여 하나의 클러스터를 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 지정 예시&lt;/p&gt;
&lt;pre id=&quot;code_1742134719543&quot; class=&quot;crmsh&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;node.master: true  # 이 노드를 마스터 노드로 지정
node.data: false   # 이 노드는 데이터를 저장하지 않음
node.ingest: false # 이 노드는 데이터를 처리하지 않음
cluster.name: my-cluster
node.name: node-1
network.host: 192.168.1.10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터의 검색과 저장 흐름&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 상세로 돌아가서, Elasticsearch에서 데이터가 디스크에 어떻게 저장되고 검색되는지 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmmTuj/btsMK5uuTV7/2owO5kT6j01OFdd1225Zb0/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmmTuj/btsMK5uuTV7/2owO5kT6j01OFdd1225Zb0/tfile.svg&quot; data-alt=&quot;루씬과 Elasticsearch 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmmTuj/btsMK5uuTV7/2owO5kT6j01OFdd1225Zb0/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmmTuj%2FbtsMK5uuTV7%2F2owO5kT6j01OFdd1225Zb0%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;301&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;루씬과 Elasticsearch 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세그먼트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lucene
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lucene의 인덱스 구조에서 가장 기본적인 데이터 저장 단위. &lt;b&gt;immutable&lt;/b&gt; 하기 때문에 데이터가 인덱스에 추가될 때마다 &lt;b&gt;새로운 세그먼트&lt;/b&gt;가 생성되고 기존 문서가 삭제되면 삭제 플래그만 표시해둔다. 업데이트가 되면 삭제 플래그 표시 후 새 세그먼트를 생성한다.&lt;/li&gt;
&lt;li&gt;세그먼트가 불변이기 때문에 무작정 늘어나면 데이터 저장 공간이 부족하다. 따라서 루씬이 적당히 세그먼트의 &lt;b&gt;병합&lt;/b&gt;을 수행해서 디스크 공간을 효율화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Elasticsearch
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;각 샤드가 독립적으로 세그먼트를 관리&lt;/b&gt;하고 자기만의 세그먼트를 갖고 있다. (각 샤드 = 루씬 동작 범위..)&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lucene
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인메모리 버퍼는 문서가 디스크로 flush 되기 전에 메모리 상에서 임시로 저장되는 공간이다. 이 버퍼에 문서들이 쌓이다가 일정량이 되면 디스크로 플러시 되면서 새로운 세그먼트가 생성된다.&lt;/li&gt;
&lt;li&gt;버퍼가 일정 용량에 도달하면 해당 데이터를 &lt;b&gt;세그먼트로 플러시&lt;/b&gt; 해서 디스크에 &lt;b&gt;영구적&lt;/b&gt;으로 저장한다.&lt;/li&gt;
&lt;li&gt;자동 플러시가 주기적으로 이뤄진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Elasticsearch
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인메모리 버퍼도 &lt;b&gt;각 샤드별로 관리&lt;/b&gt;하고, 주기적으로 데이터를 &lt;b&gt;디스크로 플러시&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;플러시 (Flush)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인메모리버퍼에 있는 데이터를 &lt;b&gt;디스크에 저장&lt;/b&gt;하는 과정이고, 이를 통해 &lt;b&gt;세그먼트&lt;/b&gt;가 디스크에 생긴다.&lt;/li&gt;
&lt;li&gt;데이터가 메모리에만 존재하면 장애 발생 시 손실될 수 있고, 플러시 과정을 거친 뒤에야 디스크에 영구 저장된다.&lt;/li&gt;
&lt;li&gt;플러시가 발생하면 세그먼트 생성과 함께 디스크에 쓰여지고, 이후 해당 문서는 &lt;b&gt;검색 가능한 상태로 변환&lt;/b&gt;된다. 그러나 즉시 반영이 되지는 않는다. &lt;b&gt;커밋이 되어야만 쿼리에 반영이 되고 플러시만 된 상태에서는 검색할 수 없다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커밋 (Commit)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flush 이후 디스크에 기록된 데이터를 &lt;b&gt;최종적으로 검색 가능한 상태&lt;/b&gt;로, 즉 &lt;b&gt;실제로 확인 가능한 상태&lt;/b&gt;로 만드는 작업이다. 플러시 후 커밋이 되면 해당 데이터는 검색 쿼리에서 사용할 수 있도록 보장된다.&lt;/li&gt;
&lt;li&gt;커밋은 &lt;b&gt;쓰기 작업의 안전성을 보장&lt;/b&gt;한다. 장애가 발생하면 마지막 커밋 이후의 데이터를 복구할 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXYQHe/btsMMcMS0kd/QbFPcj02rUaz2QHNrIwCO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXYQHe/btsMMcMS0kd/QbFPcj02rUaz2QHNrIwCO0/img.png&quot; data-alt=&quot;플러시 동작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXYQHe/btsMMcMS0kd/QbFPcj02rUaz2QHNrIwCO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXYQHe%2FbtsMMcMS0kd%2FQbFPcj02rUaz2QHNrIwCO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;252&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;플러시 동작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch 의 refersh와 Lucene 에서의 동작&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch에서 refersh 는 &lt;b&gt;검색 가능한 상태로 만들기 위한 작업&lt;/b&gt;을 의미한다. 즉, 새로 인덱싱된 문서나 수정된 문서가 검색에 반영되도록 하는 중요한 작업이다. Elasticsearch 는 주기적으로 refesh 를 수행하는데, 디폴트는 1초이고, 이 주기는 index.refersh_interval 을 통해 조절할 수 있다. 실시간을 요구하는게 아니라면 좀 늘리는게 성능 상 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 elasticsearch에서 refresh가 일어날 때 어떻게 동작이 될까?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;문서 인덱싱
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 문서가 인덱싱 되면 Elasticsearch는 &lt;b&gt;메모리 버퍼&lt;/b&gt;에 데이터를 저장한다.&lt;/li&gt;
&lt;li&gt;이 시점에는 문서가 검색되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Flush
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정 주기 후, 또는 수동 flush 하면, &lt;b&gt;메모리 버퍼의 데이터가 디스크로 기록&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lucene 은 새 세그먼트를 검색 인덱스에 포함&lt;/b&gt;시켜, 새로 추가된 데이터를 &lt;b&gt;검색 가능 상태로 만든다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;인덱싱된 새 문서는 &lt;b&gt;검색 쿼리에 포함되어 반영&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/47003336/elasticsearch-index-sharding-explanation&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/47003336/elasticsearch-index-sharding-explanation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.expertrec.com/elasticsearch-vs-lucene-understanding-the-differences/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.expertrec.com/elasticsearch-vs-lucene-understanding-the-differences/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.google.com/search?q=lucene+elasticsearch+&amp;amp;sca_esv=a0f6929b78caa0c6&amp;amp;udm=2&amp;amp;biw=1920&amp;amp;bih=934&amp;amp;ei=IeLWZ5uhK8CF2roPgaK_oAE&amp;amp;ved=0ahUKEwjbtfvf6Y6MAxXAglYBHQHRDxQQ4dUDCBE&amp;amp;uact=5&amp;amp;oq=lucene+elasticsearch+&amp;amp;gs_lp=EgNpbWciFWx1Y2VuZSBlbGFzdGljc2VhcmNoIDIHEAAYgAQYEzIGEAAYExgeMgYQABgTGB4yBhAAGBMYHjIIEAAYExgIGB4yCBAAGBMYCBgeMggQABgTGAgYHjIIEAAYExgFGB4yCBAAGBMYCBgeMggQABgTGAgYHkitClCPBViDCHABeACQAQCYAXagAc8DqgEDMC40uAEDyAEA-AEBmAICoAL0AcICBhAAGAgYHpgDAIgGAZIHAzAuMqAHnQeyBwMwLjK4B_QB&amp;amp;sclient=img#vhid=iqopm3xUcICu5M&amp;amp;vssid=mosaic&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.google.com/search?q=lucene+elasticsearch+&amp;amp;sca_esv=a0f6929b78caa0c6&amp;amp;udm=2&amp;amp;biw=1920&amp;amp;bih=934&amp;amp;ei=IeLWZ5uhK8CF2roPgaK_oAE&amp;amp;ved=0ahUKEwjbtfvf6Y6MAxXAglYBHQHRDxQQ4dUDCBE&amp;amp;uact=5&amp;amp;oq=lucene+elasticsearch+&amp;amp;gs_lp=EgNpbWciFWx1Y2VuZSBlbGFzdGljc2VhcmNoIDIHEAAYgAQYEzIGEAAYExgeMgYQABgTGB4yBhAAGBMYHjIIEAAYExgIGB4yCBAAGBMYCBgeMggQABgTGAgYHjIIEAAYExgFGB4yCBAAGBMYCBgeMggQABgTGAgYHkitClCPBViDCHABeACQAQCYAXagAc8DqgEDMC40uAEDyAEA-AEBmAICoAL0AcICBhAAGAgYHpgDAIgGAZIHAzAuMqAHnQeyBwMwLjK4B_QB&amp;amp;sclient=img#vhid=iqopm3xUcICu5M&amp;amp;vssid=mosaic&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/DB</category>
      <category>Elasticsearch</category>
      <category>elasticsearch lucene 관계</category>
      <category>Lucene</category>
      <category>루씬</category>
      <category>엘라스틱서치</category>
      <category>클러스터</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/139</guid>
      <comments>https://eocoding.tistory.com/139#entry139comment</comments>
      <pubDate>Sun, 16 Mar 2025 23:55:08 +0900</pubDate>
    </item>
    <item>
      <title>Context Switching 이 비용이 드는 이유</title>
      <link>https://eocoding.tistory.com/138</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;들어가는 말&lt;/span&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 컨텍스트 스위칭이 비용이 드는 건 아는데, 왜 비용이 드는건지를 설명해보라는 질문에 간단명료하게 답하지 못했다. 학부 때나 취준하면서 CS를 보기는 했지만&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; OS, 네트워크 보다도 나도 모르게 프로젝트 과정에 대한 질문에 치우쳐서 학습했던 것 같다. 이를 반성하며... 오래된 OS 책을 다시 꺼냈다. 지금 보니 느낌이 좀 다른데 아&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;무튼 컨텍스트 스위칭에 필요한 배경지식부터 간단하게 리마인드 해본다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로세스의 구조&lt;/span&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 실행 중인 프로그램이고 디스크에 있던 프로그램이 메모리에 로드되면 프로세스가 된다. 이는 Context와 Memory로 개념을 나눌 수 있고, OS는 Context를 저장해서 실행 상태를 관리하고, Memory 구조를 통해 코드, 데이터, 스택, 힙을 분리해서 프로그램을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;하드웨어 Context - 레지스터, PC(Program Counter), SP(Stack Pointer)&lt;/li&gt;
&lt;li&gt;PCB(Process Control Block) - PID, 상태, 우선순위&lt;/li&gt;
&lt;li&gt;메모리 Context - 코드, 데이터, 힙, 스택&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Memory
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스택&lt;/li&gt;
&lt;li&gt;힙&lt;/li&gt;
&lt;li&gt;데이터&lt;/li&gt;
&lt;li&gt;코드&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lYnxa/btsL2nBSpx5/4dqTFzjYcxYyEK69nNUtok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lYnxa/btsL2nBSpx5/4dqTFzjYcxYyEK69nNUtok/img.png&quot; data-alt=&quot;프로세스 메모리 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lYnxa/btsL2nBSpx5/4dqTFzjYcxYyEK69nNUtok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlYnxa%2FbtsL2nBSpx5%2F4dqTFzjYcxYyEK69nNUtok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;192&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로세스 메모리 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로세스 Context: 1) 하드웨어 Context&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;레지스터&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스터는 CPU가 프로세스를 처리할 때 필요한 정보를 임시 저장하여 연산 속도를 높이는 초고속 메모리다. 실행할 명령어, 즉 코드 부분의 주소를 가리키기도 하고 연산 결과의 상태를 저장하기도 하고 여러가지 목적에 따라 종류가 나뉜다.&lt;/p&gt;
&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;PC (Program Counter)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 다음에 수행될 명령어의 주소를 저장하는 레지스터다. 명령어가 실행될 때마다 PC가 증가되어 다음 명령어 주소로 업데이트 된다.&lt;/p&gt;
&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;SP (Stack Pointer)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 스택의 최상단을 가리키는 레지스터로, 스택 메모리에서 가장 최근에 저장된 데이터의 주소를 갖는다. 스택이 함수 호출이나 지역 변수, 리턴 주소 등을 저장하는 메모리 영역이므로, SP는 이에 따라 데이터가 추가되거나 변경될 때마다 업데이트된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 아래 그림에서처럼 스택은 높은 주소에서 시작했다가 데이터가 추가될 때마다 낮은 주소로 이동하여 주소값이 감소한다. 반면 힙은 커질 때 낮은 주소에서 높은 주소로 이동한다. 이처럼 스택이 특이하게도(?) 데이터가 추가될 때 더 낮은 주소로 이동하는 이유를 알아보니 데이터 증가 시 힙과 메모리 충돌을 방지하기 위함이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lh2CL/btsL1iA95yl/HCuBzFLXuEhehhkChPK2i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lh2CL/btsL1iA95yl/HCuBzFLXuEhehhkChPK2i0/img.png&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;410&quot; data-is-animation=&quot;false&quot; style=&quot;width: 38.2043%; margin-right: 10px;&quot; data-widthpercent=&quot;38.65&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lh2CL/btsL1iA95yl/HCuBzFLXuEhehhkChPK2i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLh2CL%2FbtsL1iA95yl%2FHCuBzFLXuEhehhkChPK2i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;704&quot; height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OwEwv/btsL2HmAKEL/Lqpet94QzP0HxUd9bDDIVk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OwEwv/btsL2HmAKEL/Lqpet94QzP0HxUd9bDDIVk/img.jpg&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;211&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;61.35&quot; style=&quot;width: 60.6329%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OwEwv/btsL2HmAKEL/Lqpet94QzP0HxUd9bDDIVk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOwEwv%2FbtsL2HmAKEL%2FLqpet94QzP0HxUd9bDDIVk%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;575&quot; height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;메모리 구조2 (높은주소 ~ 낮은주소)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5pohG/btsL3kdlmPB/kz707W8AxQSppjnd5ulGpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5pohG/btsL3kdlmPB/kz707W8AxQSppjnd5ulGpk/img.png&quot; data-alt=&quot;SP 증가 방향&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5pohG/btsL3kdlmPB/kz707W8AxQSppjnd5ulGpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5pohG%2FbtsL3kdlmPB%2Fkz707W8AxQSppjnd5ulGpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;347&quot; height=&quot;225&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SP 증가 방향&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;프로세스 Context: 2) PCB&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PCB&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제가 각 프로세스를 관리하기 위해 사용하는 데이터 구조로, 아래 정보를 포함한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PID : 프로세스 고유 식별번호&lt;/li&gt;
&lt;li&gt;프로세스 상태: Running, Waiting, Ready 등&lt;/li&gt;
&lt;li&gt;우선순위: 스케줄링에 적용되어야 할 프로세스 우선순위&lt;/li&gt;
&lt;li&gt;CPU 문맥: 레지스터, PC, SP 등&lt;/li&gt;
&lt;li&gt;메모리 정보: 코드, 데이터, 힙, 스택 등의 메모리 영역&lt;/li&gt;
&lt;li&gt;입출력 정보: 열린 파일 목록&lt;/li&gt;
&lt;li&gt;부모 프로세스 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1738161879043&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// PCB 예시
PID: 1234
상태: Ready
PC: 0x100A
SP: 0x2000
열린 파일: /home/user/file.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;프로세스 Context: 3) Memory Context&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 사용하는 메모리 구조에 대한 정보를 의미한다. 메모리 내에서 낮은 주소 ~ 높은 주소에 위치한 영역 순으로 작성하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lh2CL/btsL1iA95yl/HCuBzFLXuEhehhkChPK2i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lh2CL/btsL1iA95yl/HCuBzFLXuEhehhkChPK2i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lh2CL/btsL1iA95yl/HCuBzFLXuEhehhkChPK2i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLh2CL%2FbtsL1iA95yl%2FHCuBzFLXuEhehhkChPK2i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;704&quot; height=&quot;410&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 영역: 실행할 명령어를 저장&lt;/li&gt;
&lt;li&gt;데이터 영역: 전역변수, static 변수 저장&lt;/li&gt;
&lt;li&gt;힙 영역: 동적 할당된 메모리&lt;/li&gt;
&lt;li&gt;스택 영역: 지역변수, 함수 호출 정보 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;프로세스 Context - 4) File Context&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열려 있는 파일 목록이나, 파일 내에서 현재 읽고 있는 위치, 파일에 대한 읽고 쓰는 권한 등을 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MMU (Memory Management Unit) 과 가상메모리&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가상 메모리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 자신만의 독립적인 메모리 공간을 가지는 것처럼 보이게 하는 기술이다. 실제 물리 메모리는 한정적이지만 프로세스에게 가상 메모리 주소를 주고 독립적인 메모리 공간을 갖도록 하여, 프로세스 간의 간섭을 방지한다. 가상 메모리 시스템을 통해 동적으로 할당하거나 회수하면서 메모리를 관리할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE8lMW/btsL1EEkark/jwWajrmBL2GkVbY7lqxcSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE8lMW/btsL1EEkark/jwWajrmBL2GkVbY7lqxcSk/img.png&quot; data-alt=&quot;가상 메모리 - 물리 메모리 변환&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE8lMW/btsL1EEkark/jwWajrmBL2GkVbY7lqxcSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE8lMW%2FbtsL1EEkark%2FjwWajrmBL2GkVbY7lqxcSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;455&quot; height=&quot;352&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가상 메모리 - 물리 메모리 변환&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MMU&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 메모리 시스템을 물리 메모리 주소로 변환하는 장치다. 프로세스가 가상 주소를 사용해서 메모리를 참조하면, MMU는 이를 물리 주소로 변환한다. 이때 Page Table 이라고 불리는 가상주소와 물리주소를 연결하는 매핑 테이블을 통해 물리주소로 변환한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PageTable: 물리주소 - 가상주소 매핑 테이블이고, 각 프로세스가 자신만의 Page Table을 독립적으로 갖는다. 페이징이 아니라 세그멘테이션 메모리 할당 방식이라면 세그먼트 테이블이라고 불리는데 주요 역할이 매핑이라는 점은 동일하다.&lt;/li&gt;
&lt;li&gt;TLB (Traslation Lookaside Buffer) : MMU 내에 존재하는 캐시 메모리로, 최근에 변환된 주소 정보를 빠르게 조회해서 주소 변환 속도를 높인다.&lt;/li&gt;
&lt;li&gt;페이지 폴트 처리 - CPU가 접근하려는 페이지가 메모리에 없어 page table에서 valid bit 가 0이라면 page fault가 발생하는데, 이때 CPU에 인터럽트 신호를 보내어 디스크를 탐색하게 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpG2MR/btsL1FcdfhU/oHKC1syAE4IDIjvZDryY30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpG2MR/btsL1FcdfhU/oHKC1syAE4IDIjvZDryY30/img.png&quot; data-alt=&quot;메모리 부족 시 하드 디스크의 페이지를 메인 메모리로 로드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpG2MR/btsL1FcdfhU/oHKC1syAE4IDIjvZDryY30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpG2MR%2FbtsL1FcdfhU%2FoHKC1syAE4IDIjvZDryY30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;332&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메모리 부족 시 하드 디스크의 페이지를 메인 메모리로 로드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가상 메모리, MMU 사용 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 격리 : 프로세스가 각자의 페이지 테이블을 통해 각자의 메모리 공간을 갖도록 하여 프로세스 간 간섭을 방지한다.&lt;/li&gt;
&lt;li&gt;메모리 보호: 각 프로세스가 다른 프로세스의 메모리를 침범하지 않도록 보호한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Context Switching 과정, 비용 발생 이유&lt;/span&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context Switching은 CPU가 실행중인 프로세스의 상태, 즉 Context를 저장하고, 다른 프로세스로 전환하는 과정이다.&amp;nbsp;&lt;/p&gt;
&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;Context Switching 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 실행 중인 프로세스의 context 저장
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;PC (Progrom Counter) 저장&lt;/li&gt;
&lt;li&gt;Register 저장&lt;/li&gt;
&lt;li&gt;SP (Stack Pointer) 저장&lt;/li&gt;
&lt;li&gt;MMU 페이지 테이블 정보 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;새로운 프로세스로 전환
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CPU 스케줄러가 다음 실행할 프로세스를 선택&lt;/li&gt;
&lt;li&gt;MMU 페이지 테이블 교체. 새 프로세스의 페이지 테이블로 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;새로운 프로세스의 Context 복원
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;새로운 프로세스의 PC, 레지스터, SP, MMU 설정 등을 복원&lt;/li&gt;
&lt;li&gt;새 프로세스의 실행 재개&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, CPU 의 레지스터를 저장하고 복구해야 하고, 새 프로세스가 실행될 때 MMU 페이지 테이블도 다시 로드해야 하며, 이에 따라 TLB 캐시도 무효화되어 기존보다 성능 저하가 발생한다. 이전 프로세스의 정보가 캐시에 남아있지만 모두 무효화 되므로 속도 저하가 발생한다. 또한, Context Switching은 커널 모드에서 수행되기 때문에 유저모드 -&amp;gt; 커널모드 전환 -&amp;gt; 유저모드로 다시 전환 되므로 이 점에서도 비용이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Context Switching 을 줄이려면&lt;/span&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스레드를 활용한다면, 스레드는 같은 프로세스 내에서 실행되므로 MMU 페이지 테이블 변경이나 위에서 말한 context 저장 및 복원 과정이 필요 없어서 더 효율적이다.&lt;/li&gt;
&lt;li&gt;CPU 작업과 I/O 작업을 잘 배분하여 불필요한 프로세스 전환을 방지해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 운영체제 내부 구조 및 설계 원리 제 8판&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://hyem2.tistory.com/entry/%EC%8A%A4%ED%83%9D%ED%8F%AC%EC%9D%B8%ED%84%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hyem2.tistory.com/entry/%EC%8A%A4%ED%83%9D%ED%8F%AC%EC%9D%B8%ED%84%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://sigridjin.medium.com/%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%A7%EC%9D%80-%EB%A9%94%EB%AA%A8-9aa54c546f04&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sigridjin.medium.com/%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%A7%EC%9D%80-%EB%A9%94%EB%AA%A8-9aa54c546f04&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/OS</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/138</guid>
      <comments>https://eocoding.tistory.com/138#entry138comment</comments>
      <pubDate>Thu, 30 Jan 2025 00:59:50 +0900</pubDate>
    </item>
    <item>
      <title>2024년 돌아보기</title>
      <link>https://eocoding.tistory.com/137</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;유독 올해는 연말이 온 걸 인지하지 못한 것 같다. 연예대상을 누가 탔다는 뉴스기사 제목을 보고 벌써 연예대상을 하나? 했는데 날짜 보고 놀람...ㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 블로그에 썼던 23년 상반기 회고에서는 신입으로 팀에 배정된지 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;1. 첫 자취 + 다른 지역&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학이나 기존 회사 모두 집에서 갈만한 거리였어서 자취할 일이 없었는데, 이번에는 좀 많이 멀어져서 결국 입사 한달만에 자취를 시작했다. 1년만 계약한거라 다음 집을 알아보거나 본가에 들어갈텐데 이건 아직 어떻게 할지 고민 중이다. 작은 집이지만 집들이한다고 여러번 사람들을 초대하고 오붓하게 대화도 나누고... 색다른 경험이었다! 방명록은 영원히 간직해야지~ 그리고 서울과 매우 가깝지만 어쨌든 다른 지역에 살아본 것도 색다른 경험이었다. 광역버스는 줄 서는 위치가 정해져있다는 것을 몰랐어서 왜 여기 안서지?? 하며 엉뚱한 행동도 했고... 시내버스 타던 습관처럼 내리기 전에 살짝 일어나려했는데 아무도 안 일어나셔서 그것도 처음엔 신기했다. ㅋㅋ 서울을 벗어나 버스 색도 다르고 광역버스로 오가고 하는 것들을 계속 하다보니 뭔가 생활반경이 전보다 넓어진 기분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 첫 진짜 운전&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면허만 있고 실제로 연수를 받아보거나 운전해서 나가본 적은 없었는데 최근에 조금씩 집 근처 백화점에도 가고 부모님하고 같이 탈 때는 근교까지 나가보고 있다. 너무 많은 사람들이 운전할 줄 알다보니 운전이 이렇게 많은 노력을 요하는 일인지 몰랐는데 이번에 알게 됐다. 주차연습도, 주유도, 주행연습 뭐 도로 위에서의 온갖 상황들을 다 겪어보고 유연하게 대처하시는 분들이 이 세상에 이렇게나 많다니 다들 너무 대단한 분들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 첫 야구. 문화생활 음악콘서트, 연극&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워낙 문화생활을 좋아해서 음악 콘서트는 종종 갔는데 올해는 야구 관람이 좀 특별했다. 한번도 야구를 본 적 없는데 전직장 동료분들 덕분에 입문해서 올해만 한 세번을 갔다. 응원문화도 너무 재밌고 음식도 맛있음.ㅋㅎㅋㅎ 연극은 음악콘서트처럼 예전에 종종 봤는데 올해 아주 오랜만에 진짜 뭐 n년만에 관람한거라 기억에 남는다.&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;4. 첫 건강검진 소견..&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에는 아무 소견이 없었고 그 중에 혈당이 정상수치지만 내 눈에 띄어서 올해 목표로 혈당 낮추기를 생각했다. 실제로 조금이라도 낮추는 데에는 성공했다. 그러나 다른 부분에서 이상수치가 나타나 처음으로 재검이 필요하다는 소견을 받았다. 작년에 비해 눈에 띄게 달라진 부분이... 내 생각에는 불규칙적인 식사 시간인 것 같은데. 이 부분을 잘 챙겨봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 첫 이직&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 변화를 많이 유발한 것은 이직이다. 낯선 사람, 낯선 문화... 평소 성향 자체가 익숙한 사람들과 익숙한 장소에서 익숙한 음식 먹으며 보내는 편이다보니 낯선환경이 많이 힘들었다.ㅠㅠ 약간의 차이가 있는 잘 모르는 지역에 출근하러 간 것도 은근한 영향이 있던 것 같다. 새로운 곳에 놀러가는 것은 재밌는 일이지만 사람이나 생활환경이 새로워지는 건 좀 다른 영역인 듯 하다. 어떤게 힘드냐고 물으면 딱히 뭐라고 답할 말은 없는데 시간이 약이라는걸 알고 있고 하루하루 나아지고 있기 때문에 앞으로 더 좋아질거라는 믿음으로 지내고 있다. 추가로, 동기들 중에 이직한 사람이 없었는데 최근 다른 동기들이 이직하고 적응을 위해 노력해가는 모습을 보니 나도 전보다 더 힘을 얻는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라나 FE, 그외에도 내가 다루지 않았던 것들에 대해서는 좀 무지한 편이었는데, 여전히 좀 어렵지만 전보다 알아가고 있다. 과거에는 부끄럽지만 다양한 것에 관심을 갖지 않았다. 근데 어떤 회사에서는 이 기술을, 이 일을 내가 직접 담당할 수도 있겠구나 라는 생각이 들었다. 내가 1인 사업자거나 스타트업이었으면 더 다양한 영역을 작업하는게 그리 놀랄 일이 아닌데, 역할이 세분화되어 분담된 만큼 극히 작은 영역에만 관심을 두지 않았나 싶다. 이직한 이후로는 실제 다루는게 더 넓어진 것도 맞지만 행동보다도 그냥 생각이 좀 달라졌다. 다 관심 갖고 바라봐야하는구나... 내 일이다 남의 일이다 따질 게 없고 모두 다 하나의 목적을 이룬다는 점에서 다 관심 갖고 바라봐야하는구나,,, 하는. 근데 쉽지는 않다ㅋㅋㅋㅋ 내년엔 또 생각이 달라질 수 있음 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기존 팀도 지금 팀도 모두 버티컬이라는 것을 다루고 있지만 그 내막은 조금 다르다는걸 최근에 깨달았다. (지금의 일과 비슷한 일을 하고 있는 전 직장 동기들의 생각을 듣다보니 이해에 도움이 됐다.) 기존에는 버티컬이라고 불리는 서비스들 뭐 예를 들어 오늘의 집, 여기어때, 무신사 이런 것들 중에 하나의 회사에 종사해서, 한가지 특정 버티컬에 특화된 서비스를 딥하게 다루는 느낌이었는데 지금은 여러가지 버티컬을 다양하게 다룰 줄 아는 전시 영역의 능력치도 함께 요구되는 것 같다. 기존 팀은 과제가 많지 않았고 한가지 영역을 오픈하면 한동안은 동일 이슈에 대해 운영할 일이 많았는데, 이번엔 프로젝트 스위칭이나 멀티태스킹이 좀 요구된다. 내년에는 여러가지를 챙겨야 할 일이 오더라도 우왕좌왕이 아닌 안정된 자세가 목표다. 정신 똑디 차리는 내년이 되길. 제발...! ㅎㅎㅎ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;/p&gt;</description>
      <category>경험들/후기와 회고</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/137</guid>
      <comments>https://eocoding.tistory.com/137#entry137comment</comments>
      <pubDate>Sun, 22 Dec 2024 23:58:20 +0900</pubDate>
    </item>
    <item>
      <title>RDB Query Plan 확인 (+ Vitess Query Plan)</title>
      <link>https://eocoding.tistory.com/136</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Explain은 select, delete, insert, replace, update 연산에 대해 수행 가능하다.&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;h4 data-ke-size=&quot;size20&quot;&gt;Explain 결과 해석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과에 나오는 테이블은 MySQL 이 처리할 때 읽는 순서로 노출된다. 이 말은 MySQL이 첫 테이블로부터 row를 하나 읽고, 두번째 테이블로부터 이 row에 매핑된 row를 찾고, 세번째 테이블에서도 반복한다는 말이다. 모든 테이블이 처리되면, MySQL는 선택된 columns를 출력하고 일치하는 행이 더 있는 테이블을 찾을 때까지 테이블 list를 통해 백트래킹한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v8ClI/btsKVAnJPXy/IusTRaPsMx07sikUo6ehUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v8ClI/btsKVAnJPXy/IusTRaPsMx07sikUo6ehUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v8ClI/btsKVAnJPXy/IusTRaPsMx07sikUo6ehUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv8ClI%2FbtsKVAnJPXy%2FIusTRaPsMx07sikUo6ehUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1372&quot; height=&quot;224&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 id=&quot;QueryPlan-id&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;id&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 문이 수행되는 실행순서로, id 값이 작을수록 먼저 수행된거고 id 값이 같다면 두 테이블이 조인된 것.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조인이 수행된 이후에 서브쿼리가 수행되어서 아래처럼 조인 테이블은 모두 1, 서브쿼리 내 테이블은 2로 나옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;QueryPlan-select_type&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;select_type&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SELECT문 유형.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주요 유형 소개&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SIMPLE: union 이나 서브쿼리로 인식되는 것이 없는 경우에 나옴.&lt;/li&gt;
&lt;li&gt;PRIMARY: 서브쿼리가 포함된 쿼리에서 첫번재 select 문에 표시되는 값. 서브쿼리를 감싸는 외부 쿼리이거나 UNION 이 포함된 쿼리의 첫번째 select 문에 표시됨.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;SUBQUERY: 서브쿼리인데 바깥 쿼리에 영향받지 않고 독립적으로 수행 가능한 것 (DEPENDENT SUBQUERY와 비교 가능)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;DEPENDENT SUBQUERY: 서브쿼리인데 바깥 쿼리에 영향을 받는 것&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;br /&gt;DERIVED : 파생테이블&lt;/li&gt;
&lt;li&gt;DEPENDENT DERIVED: 다른 테이블에 영향 받는 파생 테이블&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&quot;main-content&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;
&lt;h4 id=&quot;QueryPlan-type&quot; style=&quot;color: #333333;&quot; data-ke-size=&quot;size20&quot;&gt;type&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;join type. 데이터를 어떻게 찾는지에 대한 정보고, 위에 적혀있을수록 성능이 좋고, 아래로 내려갈수록 성능 안 좋은 방식임.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;system: system table 이라 1개 row만 가진 경우. const join type 에 속하는 것중 하나의 특별 케이스.&lt;/li&gt;
&lt;li&gt;const: 고유 인덱스나 PK를 사용해서 단 1건의 데이터만 접근해서 얻은 경우. rows가 1로 나옴.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;
&lt;div id=&quot;expander-1322180481&quot; data-macro-name=&quot;expand&quot; data-hasbody=&quot;true&quot;&gt;
&lt;div id=&quot;expander-control-1322180481&quot;&gt;&lt;span style=&quot;color: #0052cc;&quot;&gt;여기를 클릭하여 펼치기...&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;eq_ref: 조인 시 driving 테이블이 driven 테이블에서 고유 인덱스나 PK를 이용해 단 1건의 데이터만 접근한 경우&lt;/li&gt;
&lt;li&gt;ref: 조인 시 driven 테이블에 접근하는 데이터 범위가 2개 이상일 경우&lt;/li&gt;
&lt;li&gt;ref_or_null: IS NULL 구문 수행 시 인덱스를 활용했을 경우&lt;/li&gt;
&lt;li&gt;range: 테이블 내 연속된 데이터를 조회한 경우. (비교연산이나 IS NULL, IN 등의 범위 스캔)&lt;/li&gt;
&lt;li&gt;fulltext: FULLTEXT 인덱스를 활용한 경우&lt;/li&gt;
&lt;li&gt;index_merge: 하나의 인덱스를 통해 데이터를 필터링하지 않고 여러 인덱스를 병합해서 데이터를 조회한 경우 (union 처럼 여러 인덱스를 사용해서 개별적으로 검색 후 합집합으로 결합)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ex) select&lt;span&gt;&amp;nbsp;&lt;/span&gt;from users where age = 30; 이면 age 인덱스 하나로 가능하지만 select&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;from users where age = 30 or city = 'Seoul'일 때 age,city 인덱스를 통해 결곽를 얻은 뒤 병합함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;index: 오직 인덱스 tree만 스캔되었을 때. (Extra 컬럼에도 Using Index라고 찍힘)&lt;/li&gt;
&lt;li&gt;all: 테이블을 처음부터 끝까지 검색해서 풀스캔한 케이스&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;possible keys&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;옵티마이저가 SQL문을 최적화하기 위해 사용할 수 있는 인덱스 목록을 출력함.&lt;/li&gt;
&lt;li&gt;다만 실제 사용한 인덱스가 아니라 사용할 수 있는 후보군을 보여주는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;key&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;옵티마이저가 SQL문을 최적화하기 위해 실제 사용한 PK 나 인덱스명&lt;/li&gt;
&lt;li&gt;아무것도 사용하지 않았거나 비효율적인 인덱스를 사용했다면 SQL을 튜닝해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&quot;likes-and-labels-container&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot;&gt;
&lt;div id=&quot;labels-section&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;key_len&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용한 인덱스의 바이트 수&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ref&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 조인 시 어떤 조건으로 해당 테이블에 접근했는지에 대한 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;rows&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 문을 수행하고자 접근하는 모든 데이터 row 수를 나타내는 항목. (예상값)&lt;/li&gt;
&lt;li&gt;디스크에서 데이터 파일을 읽고 메모리에서 처리해야할 행 수를 예상하는 값임.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL문의 최종 결과 건수랑 비교해서 row가 크게 차이난다면 불필요하게 많은 데이터를 읽었다는 말이므로 SQL 튜닝이 필요하다고 해석할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;filtered&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어느 비율로&amp;nbsp; 데이터를 제거했는지.&lt;/li&gt;
&lt;li&gt;예를 들어 DB 엔진으로 100건의 데이터를 가져왔다면 이후 where 절의 emp_no between 1 and 10 조건으로 100건의 데이터가 10으로 필터링됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Vitess Query Plan&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 쿼리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; vexplain plan select * from corder join commerce.product as prod on corder.sku = prod.sku;&lt;/p&gt;
&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_1733141123050&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;OperatorType&quot;: &quot;Join&quot;,
  &quot;Variant&quot;: &quot;Join&quot;,
  &quot;JoinColumnIndexes&quot;: &quot;L:1,L:2,L:3,L:4,R:0,R:1,R:2&quot;,
  &quot;JoinVars&quot;: {
    &quot;corder_sku&quot;: 0
  },
  &quot;TableName&quot;: &quot;corder_product&quot;,
  &quot;Inputs&quot;: [
    {
      &quot;OperatorType&quot;: &quot;Route&quot;,
      &quot;Variant&quot;: &quot;Scatter&quot;,
      &quot;Keyspace&quot;: {
        &quot;Name&quot;: &quot;customer&quot;,
        &quot;Sharded&quot;: true
      },
      &quot;FieldQuery&quot;: &quot;select corder.sku, corder.order_id as order_id, corder.customer_id as customer_id, corder.sku as sku, corder.price as price from corder where 1 != 1&quot;,
      &quot;Query&quot;: &quot;select corder.sku, corder.order_id as order_id, corder.customer_id as customer_id, corder.sku as sku, corder.price as price from corder&quot;,
      &quot;Table&quot;: &quot;corder&quot;
    },
    {
      &quot;OperatorType&quot;: &quot;Route&quot;,
      &quot;Variant&quot;: &quot;Unsharded&quot;,
      &quot;Keyspace&quot;: {
        &quot;Name&quot;: &quot;commerce&quot;,
        &quot;Sharded&quot;: false
      },
      &quot;FieldQuery&quot;: &quot;select prod.sku as sku, prod.description as description, prod.price as price from product as prod where 1 != 1&quot;,
      &quot;Query&quot;: &quot;select prod.sku as sku, prod.description as description, prod.price as price from product as prod where prod.sku = :corder_sku&quot;,
      &quot;Table&quot;: &quot;product&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;br /&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/explain-output.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.mysql.com/doc/refman/8.4/en/explain-output.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/using-explain.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.mysql.com/doc/refman/8.4/en/using-explain.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/DB</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/136</guid>
      <comments>https://eocoding.tistory.com/136#entry136comment</comments>
      <pubDate>Sun, 24 Nov 2024 23:53:48 +0900</pubDate>
    </item>
    <item>
      <title>Vitess 용어</title>
      <link>https://eocoding.tistory.com/135</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;일반적으로 MySQL은 단일서버에서 운영되고 필요에 따라 여러 서버를 구축해서 샤딩할 수 있다. 하지만 자동 샤딩 기능은 없기 때문에 데이터가 커진다면 애플리케이션 레벨에서 수동으로 샤딩 로직을 구성해야 한다. 그런데 Vitess에선 이걸 자동으로 해준다. 마치 MySQL 을 쓰는 것처럼 클라이언트에서 사용하면 되고, 데이터만 확장해서 샤딩 구조를 사용할 수 있다. 즉 MySQL 호환 버전의 클러스터링 시스템이 vitess 라고 보면 된다.&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 style=&quot;color: #000000;&quot;&gt;Vitess 란&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #323232;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;컨테이너 환경에서 제공되는 확장과 관리가 용이한 MySQL 기반의 분산 데이터베이스.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scalable. Reliable. MySQL-compatible. Cloud-native. Database.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docs 에서 말하는 주요 기능은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;built-in 자동 샤딩 기능을 통해 확장 가능&lt;/li&gt;
&lt;li&gt;데이터 안정성 보장 (HA, 장애복구)&lt;/li&gt;
&lt;li&gt;MySQL 호환. 기존 MySQL 드라이버를 그대로 사용하면 되고, vanilla MySQL에 접속하기 위해서 mysqld 에 connect 하던 것 &amp;rarr; vtgate 서비스에 connect 하도록 바꿔주기만 하면 됨.&lt;/li&gt;
&lt;li&gt;Cloud Native DB 로, 클라우드 환경에서 배포,관리, 확장 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL 과 Vitess의 차이&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;경량 커넥션: MySQL 은 많은 메모리와 CPU 를 사용하지만, Vitess 는 connection pooling을 통해 경량의 connect를 소수의 MySQL connection pool로 매핑해서 리소스 효율을 높임&lt;/li&gt;
&lt;li&gt;쿼리 성능 보호: MySQL은 잘못된 쿼리로 DB 성능에 영향 끼칠 수 있으나, Vitess는 성능 저해 위험이 있는 쿼리를 rewite 함.&lt;/li&gt;
&lt;li&gt;샤딩 지원: MySQL은 수동으로 샤딩 코드를 작성하고 애플리케이션에 샤딩 로직을 작성해야 하지만, Vitess는 자동으로 샤딩 지원함.&lt;/li&gt;
&lt;li&gt;HA/replication: MySQL 은 단일서버 기반의 DB로, MySQL cluster의 경우 수동으로 복제 같은 기능을 적용해야 하는데 Vitess에서는 DB 서버의 lifecycle을 자동 관리해준다.&lt;/li&gt;
&lt;li&gt;쿼리 라우팅 기능 지원함.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Vitess의 구조&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/td7Ou/btsKlrlj7nP/KzEmh6p4z8JB5rPPyyE1iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/td7Ou/btsKlrlj7nP/KzEmh6p4z8JB5rPPyyE1iK/img.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;392&quot; data-is-animation=&quot;false&quot; style=&quot;width: 39.9593%; margin-right: 10px;&quot; data-widthpercent=&quot;40.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/td7Ou/btsKlrlj7nP/KzEmh6p4z8JB5rPPyyE1iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftd7Ou%2FbtsKlrlj7nP%2FKzEmh6p4z8JB5rPPyyE1iK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bctOcM/btsKl5viKe6/4ckxoopm9Ti9BRufWIcdik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bctOcM/btsKl5viKe6/4ckxoopm9Ti9BRufWIcdik/img.png&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;588&quot; data-is-animation=&quot;false&quot; style=&quot;width: 58.8779%;&quot; data-widthpercent=&quot;59.57&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bctOcM/btsKl5viKe6/4ckxoopm9Ti9BRufWIcdik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbctOcM%2FbtsKl5viKe6%2F4ckxoopm9Ti9BRufWIcdik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1304&quot; height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Keyspace&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Logical 한 데이터베이스를 의미&lt;/li&gt;
&lt;li&gt;샤딩을 할 수도 있고 안할 수도 있는데, 샤딩을 한다면 해당 keyspace는 여러 MySQL DB에 매핑된다.&lt;/li&gt;
&lt;li&gt;애플리케이션단에서는 하나의 DB 로 보인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Shard&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;keyspace의 하위 집합으로, keyspace 는 항상 하나 이상의 shard를 갖고 있다.&lt;/li&gt;
&lt;li&gt;샤드는 일반적으로 하나의 MySQL Primary 및 Replicas가 포함된다.&lt;/li&gt;
&lt;li&gt;단일샤드라면 MySQL 사용하듯이 사용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VTGate&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VTTablet 의 상위에서 프록시 서버 역할을 하고, 적절한 VTTablet으로 쿼리를 전달한다.&lt;/li&gt;
&lt;li&gt;VTGate 를 통해, 애플리케이션에서 하나의 데이터베이스로 보여지게 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VTTablet&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mysqld 에 붙어서 mysqld를 컨트롤한다.&lt;/li&gt;
&lt;li&gt;하나의 Pod으로 띄워져 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VSchema&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 논리적인 DB를 어떻게 물리적으로 샤드할 것인지 표현한다.&lt;/li&gt;
&lt;li&gt;샤드가 필요 없다면 정의하지 않아도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;KeyspaceID&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분산 DB의 샤딩키와 동일한 개념&lt;/li&gt;
&lt;li&gt;DB에 저장되지 않고 런타임에 계산되는 값으로, 별도 컬럼 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Vindex&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레코드에 Keyspace ID를 찾아낼 수 있는 방법을 제공한다.&lt;/li&gt;
&lt;li&gt;질의를 어떤 샤드로 라우팅해야하는지 결정하는 데 사용된다.&lt;/li&gt;
&lt;li&gt;한번 정해지면 변경할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Primary Vindex&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;primary vindex 는 unique한 keyspace-id를 생성하고, 이를 위한 function이 존재한다. (ex. xxhash)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lookup Vindex&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개념적으로 RDBMS 의 secondary Index 에 해당된다.&lt;/li&gt;
&lt;li&gt;primary vindex로 처리할 수 없는 쿼리에 대해 최적화한다.&lt;/li&gt;
&lt;li&gt;새로운 row가 들어오면 vitess가 내부적으로 lookup vindex를 변경해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Vitess Workflow&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vitess 내부에서 VReplication 이라는 내부 복제 시스템을 이용해서, 데이터를 다른 위치로 복제하거나 이동시키는 시스템&lt;/li&gt;
&lt;li&gt;리샤딩, 마이그레이션, 복제 작업 등에 사용됨.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;workflow type
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MoveTables: 테이블을 한 keyspace &amp;rarr; 다른 keyspace 로 옮길 때 사용&lt;/li&gt;
&lt;li&gt;Reshard : 샤드 사이에서 데이터를 분할하거나 합칠 때 사용&lt;/li&gt;
&lt;li&gt;Materialize: 다른 데이터베이스가 샤드로 데이터를 복제해서 사용 가능한 상태로 만들때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Workload Mode&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vitess에서 DB 작업을 처리하기 위해 최적화된 설정 모드고, 이 값에 따라 성능 최적화와 리소스 관리를 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OLTP (Online Transaction Processing) : default 모드.&amp;nbsp;빠른 응답 속도를 위해 최적화된 모드&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;oltp 모드 하에서는 모든 쿼리 결과가 10만건, 트랜잭션 최대 허용 시간이 60초로 제한됨. (row count exceed error 발생함)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OLAP (Online Analytical Processing): 분석이나 복잡한 조회 작업 처리에 최적화된 모드.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;olap 모드로 변경하면 더 큰 결과, 더 오래 걸리는 쿼리 수행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Execution Plans&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vitess는 쿼리를 최선의 방법을 측정하기 위해 VTGate와 VTTablet 계층 모두에서 파싱한다.&lt;/li&gt;
&lt;li&gt;실행계획은 쿼리와 VSchema 따라 달라진다. Vitess의 기본 목적은 가능한 한 많은 작업을 아래의 MySQL 인스턴스로 push down 하는 것이고, 이게 불가능할 경우 vitess는 여러 소스로부터 입력을 수집하고 쿼리 결과를 병합하여 올바른 쿼리 결과를 생성하는 계획을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Reference Table&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개 샤드에 sync 해서 동일한 복제본을 만들 수 있게 하는 개념으로, 모든 샤드에 복제해둠으로써 cross-keyspace 할 필요 없이 효과적으로 쿼리 결과를 제공할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;크기가 작지만 공통적으로 앱에서 사용되는 look-up 용 테이블에 유용한 개념이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://vitess.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://vitess.io/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/DB</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/135</guid>
      <comments>https://eocoding.tistory.com/135#entry135comment</comments>
      <pubDate>Sun, 27 Oct 2024 22:54:02 +0900</pubDate>
    </item>
    <item>
      <title>Exposed 란? (+ JDBC, ORM)</title>
      <link>https://eocoding.tistory.com/133</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;JDBC란?&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Java Database Connectivity. 자바에서 DB에 접속하고 SQL 쿼리를 실행하며 결과를 처리할 수 있게 하는 표준 API로, JDBC 드라이버를 통해 특정 데이터베이스(MySQL, Postgre SQL 등)와 자바 애플리케이션 간의 통신을 가능하게 한다. 커넥션을 얻어 DB에 연결하고 명령어를 실행해서 결과를 처리한 뒤, Statement, ResultSet, Connection 객체를 닫아서 자원을 해제한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728835427253&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 연결
Connection conn = DriverManager.getConnection(
    &quot;jdbc:mysql://localhost:3306/mydb&quot;,
    &quot;username&quot;,
    &quot;password&quot;
);

// 명령 실행
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(&quot;SELECT * FROM Users&quot;);

// 자원 해제
rs.close();
stmt.close();
conn.close();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;장점은,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표준화된 API라 다양한 데이터베이스에 대해 호환 가능한 것&lt;/li&gt;
&lt;li&gt;쿼리를 직접 작성해서 세밀하게 제어할 수 있는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단점은,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연결 설정이나 자원 해제 같이 반복적인 코드 작성이 필요함.&lt;/li&gt;
&lt;li&gt;ORM 이 지원되지 않아 객체지향적 설계에 부합하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;객체 지향적 설계에 부합하지 않는다?&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 문장을 조금 더 보충설명하자면, 객체지향프로그래밍과 관계형DB 간 불일치하는 데에서 오는 문제점이라고 보면 된다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터 표현 방식이 다름.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OOP는 객체를 통해 데이터와 메소드를 정의하지만 DB는 row와 column으로 표현&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;연관관계 처리 방식이 다름.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OOP는 객체 간 관계를 한 객체가 다른 객체를 필드로 가지는 방식으로 표현하지만,&lt;/li&gt;
&lt;li&gt;관계형DB는 테이블 간 관계를 외래키를 통해서 표현한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ORM은 위와 같은 문제를 해결해준다. 예를 들어 User와 Order가 1:N 관계라고 할 때, User는 List&amp;lt;Order&amp;gt;를 필드로 가져서 참조할 수 있게 하고, 이 필드 위에 어노테이션 @OneToMany를 선언해두면 객체 간의 참조관계를 RDB에서의 테이블 관계로 자동 매핑해준다. 이 기능을 통해 데이터베이스 의존성을 감소시키면서 DB 테이블과 객체 간 매핑 작업을 자동으로 해준다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;ORM 이란?&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ORM(Object Relational Mapping)은 객체 지향 프로그래밍 언어의 객체와 데이터베이스 테이블 간의 매핑을 의미한다. ORM 프레임워크는 DB와 상호작용할 때 SQL을 직접 작성하는 대신 객체지향적으로 데이터를 다룰 수 있게 해주는 도구다. 주요 ORM 프레임워크로는 Hibernate, JPA, Django ORM, Exposed 등이 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Exposed란?&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin 용 JDBC 드라이버 위에 구축된 경량의 SQL 라이브러리다. DB 접근을 위해 두가지 방식을 제공하는데, 하나는 도메인 특화 언어(DSL)고, 하나는 데이터 접근 객체(DAO)다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DSL (Domain Specific Language) API:&lt;br /&gt;SQL 과 유사한 형태로 쿼리를 작성할 수 있어서 익숙한 SQL 개념을 사용하면서도 kotlin 이 제공하는 타입 안정성의 이점을 얻을 수 있다.&lt;/li&gt;
&lt;li&gt;DAO (Data Access Object) API:&lt;br /&gt;전통적인 ORM 프레임워크인 Hibernate와 유사하게 객체지향적 접근 방식을 제공한다.객체 중심으로 DB 엔티티를 다루고, 테이블을 클래스로 표현하므로 DB 레코드를 직접 객체로 매핑할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1728835427254&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// DSL 방식
val users = UsersTable.select { UsersTable.age greater 20 }

// DAO 방식
val user = User.findById(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 Exposed는 프로젝트의 요구사항에 가장 적합한 방식을 선택할 수 있는 유연한 라이브러리다... 라고 docs에서 말한다. ㅎㅎ DSL API를 쓰면 SQL의 직접적 제어가 가능하고, DAO API를 사용하면 더 높은 수준의 추상화를 통해 간편하게 DB와 상호작용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Exposed 만의 차별화된 특징을 생각해보면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kotlin 타입 활용 가능하여 컴파일 시점에 타입 오류를 알 수 있다.&lt;/li&gt;
&lt;li&gt;DSL, DAO 를 모두 제공해서 용도에 맞게 적합한 것을 선택할 수 있다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;코루틴 호환성: 비동기 프로그래밍을 지원해서 블로킹 작업을 효율적으로 처리할 수 있다. (이건 다음 포스팅에 더 자세히..)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Exposed는 내부적으로 JDBC를 사용해서 DB와 통신하고, JDBC는 전통적인 blocking API 이므로 DB 작업을 수행할 때 해당 작업이 완료될 때까지 스레드가 블로킹된다. 따라서 Exposed 자체는 non-blocking API를 제공하지 않지만 코루틴과 함께 사용할 경우 코루틴의 Dispatcher를 활용해서 블로킹 작업을 적절한 스레드 풀에서 실행하도록 관리할 수 있고, 이를 통해 메인 스레드를 차단하지 않고도 DB 작업을 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Tech</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/133</guid>
      <comments>https://eocoding.tistory.com/133#entry133comment</comments>
      <pubDate>Sun, 13 Oct 2024 23:54:27 +0900</pubDate>
    </item>
    <item>
      <title>Response already committed. broken pipe.</title>
      <link>https://eocoding.tistory.com/132</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Response already committed / broken pipe&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 상황이 발생하는 통신 과정에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트가 서버에 요청&lt;/li&gt;
&lt;li&gt;서버는 클라이언트의 요청을 수신해서 필요한 작업을 수행&lt;/li&gt;
&lt;li&gt;응답 준비
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 메시지 형태의 응답을 구성하여 준비한다. header, body 로 이루어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;응답 전송:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 준비한 응답을 클라이언트에게 전송하는데, 이때 header가 먼저 전송되고 body가 뒤따라 전송된다.&lt;/li&gt;
&lt;li&gt;응답 header를 클라이언트에 전송하고 나서 응답 body를 보내려는 시점을 &lt;b&gt;commit&lt;/b&gt; 이라고 한다. 이 시점부터는 header를 변경하거나 새로운 응답을 보내는 것이 불가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 전송 중 클라이언트에서 연결 종료
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 응답을 받는 도중에 연결을 종료할 수도 있다. 사용자가 중간에 브라우저를 닫거나, 다운로드 중이던 파일의 다운로드를 취소하거나, 처리 중인 요청을 사용자가 기다리지 않고 새로고침을 여러번 실행하는 사례가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Broken Pipe&lt;/b&gt; 오류 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 연결을 종류한 후인데도 서버가 데이터 전송을 시도하면 broken pipe가 발생한다. 서버가 데이터를 더이상 전송할 수 없음을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 서버에는 응답을 쓰기 위한 고정 크기(일반적으로 2KB - 10KB) 버퍼가 있다. 서버가 응답 데이터를 버퍼에 채우고, 버퍼가 가득 차면 이걸 클라이언트로 전송(flush)한다. 즉, 응답 데이터가 커서 버퍼 크기를 초과하면 버퍼가 여러번 flush 되면서 데이터가 부분적으로 클라이언트에 전송된다. 이건 근데 응답이 클 때 얘기고... 응답이 크지 않더라도 요청 당시의 정보가 사라지면 서버 측에서는 작업의 결과를 전달할 곳이 없어지면서 Broken Pipe가 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문제를 해결하는 방법 중 하나는 서버가 보내는 응답 중 가장 큰 크기만큼 버퍼의 크기를 크게 구성하는 것이 있다. tomcat에서 bufferSize 속성으로 이걸 조절할 수는 있지만 이 설정을 통해 응답 출력 스트림에서의 flush호출을 막을 수 있는 건 아니다. 아니면 이 exception을 그냥 무시하기도 한다. 클라이언트가 요청을 닫은 것을 서버 측에서 어찌할 수 없기도 하고, 서비스 영향도 안 받기 때문이다. 아니면 여러번 중복 요청 되는 것을 막기 위해 짧은 시간 여러번 버튼 클릭되는 경우를 제어하는 것도 방법이라고 한다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 정리하면 already committed는 이미 commit된 응답인데 이걸 또 요청할 때 발생하고, broken pipe는 최초 요청했던 곳으로 응답하고 싶은데 그곳이 사라지니 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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;서버가 클라어인트로 일부 데이터를 전송해서 커밋까지 이뤄진 시점인데 여기서 사용자가 브라우저를 닫으면 Broken Pipe 가 발생한다. 이렇게 예외가 발생한 경우에 예외처리 과정에서 서버가 응답을 변경하거나 새로운 응답을 생성해서 클라이언트로 보낼 수도 있는데, 이때 커밋을 또 시도하게 되니까 response already committed가 발생한다고 한다. 서버에서 응답을 동적으로 조작하는 경우, 이렇게 broken pipe가 발생했을 때에 추가적으로 응답 변경을 시도할 수 있고, commit 이후에는 응답 변경이 안되니까 already commited가 발생한다.&amp;nbsp;&lt;/p&gt;
&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 style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; href=&quot;https://stackoverflow.com/questions/14392361/what-exactly-does-response-already-committed-mean-how-to-handle-exceptions-th&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/14392361/what-exactly-does-response-already-committed-mean-how-to-handle-exceptions-th&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lookingfor.tistory.com/entry/%EC%9E%90%EB%B0%94-ExceptionBroken-pipe-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95#google_vignette&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://lookingfor.tistory.com/entry/%EC%9E%90%EB%B0%94-ExceptionBroken-pipe-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95#google_vignette&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/132</guid>
      <comments>https://eocoding.tistory.com/132#entry132comment</comments>
      <pubDate>Sun, 26 May 2024 19:07:00 +0900</pubDate>
    </item>
    <item>
      <title>MongoDB 쿼리 플랜 (간단하게!)</title>
      <link>https://eocoding.tistory.com/131</link>
      <description>&lt;h1&gt;Query Explain Plan&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/ko-kr/docs/rapid/tutorial/analyze-query-plan/&quot;&gt;https://www.mongodb.com/ko-kr/docs/rapid/tutorial/analyze-query-plan/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;explain result를 보고 query에 대한 아래 정보를 얻을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 수행에 걸리는 시간&lt;/li&gt;
&lt;li&gt;쿼리가 인덱스를 사용하는지&lt;/li&gt;
&lt;li&gt;쿼리 수행을 위해 스캔되는 문서나 인덱스 키의 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;쿼리 성능에 대한 통계를 제공하는 메소드&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[cursor.explain(&quot;executionStats&quot;)] &amp;amp;&amp;nbsp;[db.collection.explain(&quot;executionStats&quot;)]&lt;/li&gt;
&lt;li&gt;쿼리가 인덱스를 사용하는지 여부와 방법을 측정할 때 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 document가 들어있다고 가정하자.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;{ &quot;_id&quot; : 1, &quot;item&quot; : &quot;f1&quot;, type: &quot;food&quot;, quantity: 500 }
{ &quot;_id&quot; : 2, &quot;item&quot; : &quot;f2&quot;, type: &quot;food&quot;, quantity: 100 }
{ &quot;_id&quot; : 3, &quot;item&quot; : &quot;p1&quot;, type: &quot;paper&quot;, quantity: 200 }
{ &quot;_id&quot; : 4, &quot;item&quot; : &quot;p2&quot;, type: &quot;paper&quot;, quantity: 150 }
{ &quot;_id&quot; : 5, &quot;item&quot; : &quot;f3&quot;, type: &quot;food&quot;, quantity: 300 }
{ &quot;_id&quot; : 6, &quot;item&quot; : &quot;t1&quot;, type: &quot;toys&quot;, quantity: 500 }
{ &quot;_id&quot; : 7, &quot;item&quot; : &quot;a1&quot;, type: &quot;apparel&quot;, quantity: 250 }
{ &quot;_id&quot; : 8, &quot;item&quot; : &quot;a2&quot;, type: &quot;apparel&quot;, quantity: 400 }
{ &quot;_id&quot; : 9, &quot;item&quot; : &quot;t2&quot;, type: &quot;toys&quot;, quantity: 50 }
{ &quot;_id&quot; : 10, &quot;item&quot; : &quot;f4&quot;, type: &quot;food&quot;, quantity: 75 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스가 없는 쿼리&lt;/h2&gt;
&lt;pre id=&quot;code_1711702589604&quot; class=&quot;css&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;db.inventory.find( { quantity: { $gte: 100, $lte: 200 } } )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 쿼리의 결과는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{ &quot;_id&quot; : 2, &quot;item&quot; : &quot;f2&quot;, &quot;type&quot; : &quot;food&quot;, &quot;quantity&quot; : 100 }
{ &quot;_id&quot; : 3, &quot;item&quot; : &quot;p1&quot;, &quot;type&quot; : &quot;paper&quot;, &quot;quantity&quot; : 200 }
{ &quot;_id&quot; : 4, &quot;item&quot; : &quot;p2&quot;, &quot;type&quot; : &quot;paper&quot;, &quot;quantity&quot; : 150 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 쿼리에 대한 explain을 보려면&lt;/p&gt;
&lt;pre id=&quot;code_1711702599300&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.inventory.find({ quantity: { $gte: 100, $lte: 200 } }).explain(&quot;executionStats&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과 같이 뒤에 explain을 붙여주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래 결과를 반환한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;{
   queryPlanner: {
         ...
         winningPlan: {
            queryPlan: {
               stage: 'COLLSCAN',
               ...
            }
         }
   },
   executionStats: {
      executionSuccess: true,
      nReturned: 3,
      executionTimeMillis: 0,
      totalKeysExamined: 0,
      totalDocsExamined: 10,
      executionStages: {
         stage: 'COLLSCAN',
         ...
      },
      ...
   },
   ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;queryPlanner.winningPlan.queryPlan.stage : COLLSCAN
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컬렉션을 스캔했다는 뜻. 즉 일반적으로 비용이 많이 드는 작업이므로 쿼리 속도가 느려질 수 있다는 말&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;executionStats.nReturned : 3
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3개의 document를 반환했다는 뜻&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;executionStats.totalKeysExamined : 0
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리가 인덱스를 사용하지 않고 있음을 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;executionStats.totalDocsExamined: 10
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;몽고디비가 3개의 document를 찾기 위해 10개의 document를 스캔해야 했다는 말임. 즉, 현재로써는 전체 document를 스캔했다는 말.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;index를 적용해보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재까지 quantity를 기준으로 필터링해서 document를 조회했다. 이제 quantity 필드에 인덱스를 적용해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711702623594&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.inventory.createIndex( { quantity: 1 } )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 다시 쿼리 플랜을 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711702637694&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.inventory.find({ quantity: { $gte: 100, $lte: 200 } }).explain(&quot;executionStats&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;{
   queryPlanner: {
         ...
         winningPlan: {
            queryPlan: {
               stage: 'FETCH',
               inputStage: {
                  stage: 'IXSCAN',
                  keyPattern: {
                     quantity: 1
                  },
                  ...
               }
            }
         },
         rejectedPlans: [ ]
   },
   executionStats: {
         executionSuccess: true,
         nReturned: 3,
         executionTimeMillis: 0,
         totalKeysExamined: 3,
         totalDocsExamined: 3,
         executionStages: {
            ...
         },
         ...
   },
   ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;queryPlanner.winningPlan.queryPlan.stage : IXSCAN
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스 사용했단 뜻&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;executionStats.nReturned : 3
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3개의 document를 반환했다는 뜻&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;executionStats.totalKeysExamined : 3
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세 개의 인덱스 항목을 스캔했다는 뜻. key 수는 반환된 document 수와 일치한다.&lt;/li&gt;
&lt;li&gt;즉, 결과를 반환하기 위해 모든 문서를 스캔할 필요 없이 index key만 검사했다는 말이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;executionStats.totalDocsExamined: 3
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3개의 문서를 스캔했단 뜻.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 없으면 쿼리는 전체 10개 도큐먼트를 검색해서 일치하는 3개 도큐먼트를 반환한다. 또한 쿼리는 각 문서를 전체 스캔해서 잠재적으로 해당 문서를 메모리로 가져와야 했다. 이로 인해 비용이 많이 들고 쿼리 작업이 느려질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스와 함께 실행하면 3개의 도큐먼트를 찾기 위해 쿼리가 3개의 인덱스 항목과 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;다양한 stage 종류들은 아래에서 확인!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/gharriso/MongoDBPerformanceTuningBook/blob/master/ExplainPlanSteps.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/gharriso/MongoDBPerformanceTuningBook/blob/master/ExplainPlanSteps.md&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/DB</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/131</guid>
      <comments>https://eocoding.tistory.com/131#entry131comment</comments>
      <pubDate>Fri, 29 Mar 2024 17:57:55 +0900</pubDate>
    </item>
    <item>
      <title>DB Index (RDBMS)</title>
      <link>https://eocoding.tistory.com/130</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우테코의 테코톡에서 잘 설명해줘서 두고 두고 보려고 기록하는 글.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Index와 DB Index&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는, 쉽게 찾아볼 수 있도록 &lt;u&gt;일정한 순서&lt;/u&gt;에 따라 놓은 목록을 말한다. 현재 데이터가 아무 기준 없이 저장된 상태라면 뒤죽박죽이라서, 특정 키워드를 찾으려 하면 모든 데이터를 순차적으로 탐색해야 한다. 그러나 데이터가 특정 기준으로 &lt;u&gt;정렬&lt;/u&gt;되어 있다면 검색을 빠르게 할 수 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;DB 인덱스는, 데이터베이스 테이블에 대한 검색 성능을 향상시키는 자료구조로, WHERE절 등을 통해서 활용된다.&lt;br /&gt;이메일을 인덱스로 정한다면, 이메일 컬럼에 대한 값을 복사해서 이걸 abc 순으로 정렬한 데이터가 생긴다. 여기서&quot;SELECT * FROM member WHERE email = 'ehl3288@naver.com'을 실행하면 email로 정렬된 데이터(인덱스 적용된 대상)를 검색하고, &quot;SELECT *FROM member&quot; 라고 where 절을 빼고 실행하면 인덱스가 사용되지 않는다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이외에도 아래와 같은 특징을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스는 항상 최신의 정렬상태를 유지한다.&lt;/li&gt;
&lt;li&gt;인덱스도 하나의 데이터베이스 객체다.&lt;/li&gt;
&lt;li&gt;데이터베이스 크기의 약 10% 정도의 저장공간이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Index 알고리즘&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 : 데이터가 저장되는 단위로, MySQL 기준으로 16Kbyte 정도다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Full Table Scan&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 순차적으로 탐색하는 것으로, 접근 비용이 적다.&lt;br /&gt;아래 그림에서는 PPP를 찾는데 3개 페이지에서 12번의 검색이 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3mBY5/btsFimDJMH7/jEAkykgQyYafkfbDzYuVg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3mBY5/btsFimDJMH7/jEAkykgQyYafkfbDzYuVg0/img.png&quot; data-alt=&quot;Full Table Scan&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3mBY5/btsFimDJMH7/jEAkykgQyYafkfbDzYuVg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3mBY5%2FbtsFimDJMH7%2FjEAkykgQyYafkfbDzYuVg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;193&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Full Table Scan&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Full Scan이 이뤄지는 경우는 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;적용 가능한 인덱스가 없는 경우&lt;/li&gt;
&lt;li&gt;인덱스 처리 범위가 넓은 경우 - DB가 인덱스를 적용해도 성능상 별 이점이 없다고 판단했을 때 Full Table Scan한다.&lt;/li&gt;
&lt;li&gt;크기가 작은 테이블에 엑세스하는 경우 - 2와 동일&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Binary Search Tree (이진 탐색 트리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CGQzn/btsFjW5KYsc/2YAVWPsNEuCNQm2SFIrgz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CGQzn/btsFjW5KYsc/2YAVWPsNEuCNQm2SFIrgz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CGQzn/btsFjW5KYsc/2YAVWPsNEuCNQm2SFIrgz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCGQzn%2FbtsFjW5KYsc%2F2YAVWPsNEuCNQm2SFIrgz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;181&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이진탐색트리는 균형이 있다면 O(logN)으로 빠르게 탐색 가능하지만, 균형이 없을 때는 O(N)일 때도 있어서 이점을 얻지 못하는 경우가 있다. 이걸 극복하려고 나온게 B-Tree다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B-Tree (Balanced Tree)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;트리의 높이가 같다.&lt;/li&gt;
&lt;li&gt;자식 노드를 2개 이상 가질 수 있다. 이진트리는 자식 노드가 최대 2개지만 B-tree는 최대 자식 수에 따라서 N차 B-tree라고 부른다.&lt;/li&gt;
&lt;li&gt;기본 데이터베이스의 인덱스 구조다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csJLGa/btsFlRwaCiK/XfGnuQMu5MVjJIzijKHFV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csJLGa/btsFlRwaCiK/XfGnuQMu5MVjJIzijKHFV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csJLGa/btsFlRwaCiK/XfGnuQMu5MVjJIzijKHFV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsJLGa%2FbtsFlRwaCiK%2FXfGnuQMu5MVjJIzijKHFV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;343&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Full Table Scan과 동일한 데이터를 B-Tree로 만든 다음 찾아보면 위처럼 7번 검색해서 찾을 수 있다. 즉 SELECT의 성능이 향상되는걸 볼 수 있다. 균일하지 못하고 한쪽에 노드가 다 치우진 이진트리에서 검색 시간복잡도가 O(N)으로 나올 수도 있는 것에 반해, &lt;span style=&quot;color: #333333;&quot;&gt;B-tree는&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;어떤 값이라도 같은 시간에 결과를 얻을 수 있다는 균일성을 보장하는 균형 트리 개념이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그러나 B-tree도 생성 당시는 균형 트리지만 테이블 갱신(insert, update, delete)을 반복하면 서서히 균형이 깨지고 성능도 약화된다. 어느정도 자동으로 균형을 회복하는 기능이 있지만, 갱신 빈도가 높은 테이블에 작성되는 인덱스의 경우 인덱스 재구성을 통해 트리의 균형을 되찾는 작업이 필요하다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Index에 의한 SELECT 성능, 그리고 INSERT, UPDATE, DELETE에 대해&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baab0E/btsFoHzjYql/4TaJg8Obw5glpsKPIyk3n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baab0E/btsFoHzjYql/4TaJg8Obw5glpsKPIyk3n1/img.png&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;778&quot; style=&quot;width: 48.0763%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baab0E/btsFoHzjYql/4TaJg8Obw5glpsKPIyk3n1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbaab0E%2FbtsFoHzjYql%2F4TaJg8Obw5glpsKPIyk3n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1256&quot; height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LQJEo/btsFm4aJC9y/xlOhYJMxqFbb5xsGyTcwwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LQJEo/btsFm4aJC9y/xlOhYJMxqFbb5xsGyTcwwK/img.png&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;792&quot; style=&quot;width: 50.7609%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LQJEo/btsFm4aJC9y/xlOhYJMxqFbb5xsGyTcwwK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLQJEo%2FbtsFm4aJC9y%2FxlOhYJMxqFbb5xsGyTcwwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1350&quot; height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;먼저 INSERT에 대해 알아보자.&lt;br /&gt;ZZZ를 삽입할 페이지가 이미 꽉 찼다면 빈 페이지를 하나 확보해서 거기에 넣고, 꽉 찼던 그 페이지의 데이터를 빈 페이지와 함께 공평하게 나눠서 저장한다. 이렇게 &lt;u&gt;페이지 분할(&lt;/u&gt;페이지에 새로운 데이터를 채울 공간이 없어서 페이지에 무언가 변화가 생기는 것)이 발생하는 것은 부담이 되는 작업이기 때문에 DB가 느려지고 성능에 영향을 준다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;DELETE의 경우,&lt;br /&gt;인덱스 데이터를 실제로 지우는 것이 아니라 사용안함 표시만 한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;UPDATE의 경우,&lt;br /&gt;변경하려는 기존 값을 사용안함으로 표시하고, 변경된 새 값을 삽입한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UPDATE, DELETE도 WHERE절을 사용하면(Index를 태우면) 더 빨리지는가?&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 점&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WHERE 절로 처리할 대상을 찾아야 하는데 그 조회 성능은 향상된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;나쁜 점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스 데이터를 지우는 게 아니라 사용안함 표시만 하고 데이터 자체는 계속해서 추가하니까 페이지를 낭비하게 되고 인덱스 조각화도 심해지며, 삭제 없이 지속적으로 추가하는 과정에서 페이지 분할도 많아질테니 성능이 저하된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 인덱스는 SELECT 성능은 향상되지만 INSERT, UPDATE, DELETE는 성능이 저하된다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스의 종류&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클러스터링 인덱스&lt;/li&gt;
&lt;li&gt;논-클러스터링 인덱스 (보조 인덱스, 세컨더리 인덱스)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터링은 &lt;b&gt;무리&lt;/b&gt;를 의미하는 것으로, 클러스터링 인덱스라고 하면 &lt;u&gt;실제 데이터와 같은 무리의 인덱스&lt;/u&gt;를 말한다. 실제 데이터 자체가 정렬되어 있는 사전 같은 게 클러스터링 인덱스에 해당된다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;논-클러스터링은 &lt;b&gt;무리가 아니라는 말&lt;/b&gt;로, 논-클러슽어링 인덱스라고 하면 &lt;u&gt;실제 데이터와 다른 무리의 별도 인덱스&lt;/u&gt;를 말한다. 책 뒤에 별도로 찾아보기 페이지가 제공되는 경우가 논클러스터링 인덱스에 해당된다.&lt;br /&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;1196&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJ6vX/btsFiohdTZA/N3m3qsHCTCITfDOQ3JzUHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJ6vX/btsFiohdTZA/N3m3qsHCTCITfDOQ3JzUHk/img.png&quot; data-alt=&quot;Primary Key는 클러스터링, unique는 논-클러스터링 인덱스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJ6vX/btsFiohdTZA/N3m3qsHCTCITfDOQ3JzUHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJ6vX%2FbtsFiohdTZA%2FN3m3qsHCTCITfDOQ3JzUHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;526&quot; height=&quot;154&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Primary Key는 클러스터링, unique는 논-클러스터링 인덱스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클러스터링 인덱스를 만들면 어떻게 되는지&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터링 인덱스는 Primary Key로 지정하거나 Not Null, Unique 제약조건을 걸 때 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blHlQE/btsFlQD2tHi/V8hccoAP5M9oODxb1h4KY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blHlQE/btsFlQD2tHi/V8hccoAP5M9oODxb1h4KY0/img.png&quot; data-alt=&quot;클러스터링 인덱스 생성 방법: Primary Key 또는 Primary Key 없이 Not Null + UNIQUE 제약 조건 걸기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blHlQE/btsFlQD2tHi/V8hccoAP5M9oODxb1h4KY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblHlQE%2FbtsFlQD2tHi%2FV8hccoAP5M9oODxb1h4KY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;693&quot; height=&quot;237&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;클러스터링 인덱스 생성 방법: Primary Key 또는 Primary Key 없이 Not Null + UNIQUE 제약 조건 걸기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;생성이 되면 아래와 같이 B-Tree가 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ViNj/btsFjJekKoH/Wp0d181hkyfyLq08nQRMsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ViNj/btsFjJekKoH/Wp0d181hkyfyLq08nQRMsK/img.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;350&quot; style=&quot;width: 48.9217%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ViNj/btsFjJekKoH/Wp0d181hkyfyLq08nQRMsK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ViNj%2FbtsFjJekKoH%2FWp0d181hkyfyLq08nQRMsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1146&quot; height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccxY3k/btsFkWR18T2/fKrreAD25yt3FIou9nWGlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccxY3k/btsFkWR18T2/fKrreAD25yt3FIou9nWGlk/img.png&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;696&quot; style=&quot;width: 28.4656%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccxY3k/btsFkWR18T2/fKrreAD25yt3FIou9nWGlk/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccxY3k%2FbtsFkWR18T2%2FfKrreAD25yt3FIou9nWGlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1326&quot; height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caFWzg/btsFmeLvNSy/VRrF21uz5pdl21r3dhqHL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caFWzg/btsFmeLvNSy/VRrF21uz5pdl21r3dhqHL0/img.png&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;654&quot; style=&quot;width: 20.2871%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caFWzg/btsFmeLvNSy/VRrF21uz5pdl21r3dhqHL0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaFWzg%2FbtsFmeLvNSy%2FVRrF21uz5pdl21r3dhqHL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;888&quot; height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;클러스터링 인덱스 구성 후의 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;루트페이지가 자식페이지를 가지고 있고, 위 그림처럼 리프페이지에는 실제 데이터를 가지는 데이터 페이지가 위치한다. 클러스터링 인덱스의 특징은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;실제 데이터 자체가 정렬되어 있다. ID 컬럼 기준으로 정렬되어 있었음.&lt;/li&gt;
&lt;li&gt;테이블당 &lt;u&gt;1개만&lt;/u&gt; 존재 가능하다.&lt;/li&gt;
&lt;li&gt;B-Tree의 리프페이지가 '데이터 페이지'다. (실제 데이터 페이지가 리프에 담겨있다.)&lt;/li&gt;
&lt;li&gt;Primary Key를 걸거나, Unique + Not Null로 제약조건을 걸면 자동으로 클러스터링 인덱스가 생성된다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;둘이 같이 있으면 primary key가 우선순위를 가져가서 primary key로 인덱스가 생성된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;논클러스터링 인덱스를 만들면 어떻게 되는지&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논클러스터링 인덱스를 만들려면 아래와 같은 방법이 있는데, 방법3처럼 default INDEX를 만들면 중복을 허용하는 인덱스가 만들어진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/budenA/btsFkG9BNzj/iTZJKY0X0GeDTgaHwTy7U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/budenA/btsFkG9BNzj/iTZJKY0X0GeDTgaHwTy7U1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/budenA/btsFkG9BNzj/iTZJKY0X0GeDTgaHwTy7U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbudenA%2FbtsFkG9BNzj%2FiTZJKY0X0GeDTgaHwTy7U1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;290&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wluBt/btsFp9P0jAX/6cGY7bfJCoLUIwMCxASoK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wluBt/btsFp9P0jAX/6cGY7bfJCoLUIwMCxASoK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wluBt/btsFp9P0jAX/6cGY7bfJCoLUIwMCxASoK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwluBt%2FbtsFp9P0jAX%2F6cGY7bfJCoLUIwMCxASoK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;385&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;클러스터링 인덱스는 데이터를 가진 데이터 페이지 자체가 B-Tree의 리프페이지가 되고 얘네들에 대해 루트 페이지가 만들어졌었는데, 논클러스터링 인덱스는 데이터 페이지는 그대로 있고 각 name에 대해 그 name을 가진 데이터 페이지를 찾을 수 있는 별도의 페이지가 리프 페이지로 추가된다. 이 name 을 가진 데이터가 '어느 페이지에 있는지(1002), 그리고 그 안에서는 어떤 주소에 있는지(#3)'를 가지는 B-Tree가 생긴다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;여기서 '라라'를 찾는다면, '라라'는 도리와 스컬 사이에 있을 테니 100번이라는 리프 페이지로 이동하면 되고, 거기서 '라라'에 대한 정보가 1001번 * #2에 위치한다는 것을 알게 되므로 실제 데이터 페이지에 가서 1001번의 두번째 주소로 가면 데이터를 얻을 수 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;논클러스터링 인덱스의 특징은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;실제 데이터 페이지는 그대로&lt;/li&gt;
&lt;li&gt;별도의 인덱스 페이지 생성해야 하므로 추가 공간이 필요하다.&lt;/li&gt;
&lt;li&gt;테이블 당 여러 개가 존재할 수 있다.&lt;/li&gt;
&lt;li&gt;B-Tree의 리프 페이지에는 실제 데이터 페이지가 아니라 &lt;s&gt;데이터 페이지의 '주소'&lt;/s&gt;를 담고 있다. (뒤에 가면 달라진다. 실제론 주소를 들고 있는게 아님)&lt;/li&gt;
&lt;li&gt;한 컬럼에 unique 제약 조건을 적용하면 자동으로 논클러스터링 인덱스가 생성되고, 직접 index를 생성하는 경우에도 논클러스터링 인덱스가 생성된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클러스터링인덱스 (ID) + 논클러스터링인덱스(name에 대한 별도 인덱스)&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT * FROM MEMBER WHERE name = '라라';&lt;br /&gt;를 한다고 할 때 아래 그림에서 왼쪽은 우리가 지금까지 본 예상한 그림이고, 실제 모습은 우측과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mda9M/btsFiuBLToj/NqpAMxkkir8JlnhwnIiOS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mda9M/btsFiuBLToj/NqpAMxkkir8JlnhwnIiOS0/img.png&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;690&quot; style=&quot;width: 49.4665%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mda9M/btsFiuBLToj/NqpAMxkkir8JlnhwnIiOS0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmda9M%2FbtsFiuBLToj%2FNqpAMxkkir8JlnhwnIiOS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1221&quot; height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qIuoZ/btsFnWQ9PLZ/7IKEK8oVNbNKcDlK5DQak0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qIuoZ/btsFnWQ9PLZ/7IKEK8oVNbNKcDlK5DQak0/img.png&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;697&quot; style=&quot;width: 49.3707%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qIuoZ/btsFnWQ9PLZ/7IKEK8oVNbNKcDlK5DQak0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqIuoZ%2FbtsFnWQ9PLZ%2F7IKEK8oVNbNKcDlK5DQak0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1231&quot; height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;지금까지 예상한 그림 -&amp;amp;amp;gt; 실제 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상으로는 name이 라라인 것에 대한 name에서의 별도 인덱스에서 리프 페이지를 통해 실제 데이터의 주소를 얻고, 그 주소에 가서 데이터 페이지를 찾지 않을까 했지만........&lt;br /&gt;&amp;nbsp;&lt;br /&gt;실제로는 name에 대한 인덱스들은 리프 페이지에 데이터가 들어있는 주소가 아니라 ID 값을 가지고 있다. 그래서 name에 대한 인덱스에서 B-Tree의 리프에서 ID를 얻고, 그 ID에 대해 클러스터링 인덱스 B-Tree를 타서 리프 페이지에 도착하는데, 여긴 리프 페이지 자체가 실제 데이터 페이지니까 이렇게 도달할 수 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;u&gt;&lt;b&gt;즉, 논클러스터링 인덱스는 리프 페이지에서 데이터 페이지의 주소를 가진게 아니라 ID를 가지고 있는 것이다.&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 논클러스터링 인덱스가 데이터 페이지의 주소를 가지고 있지 않는걸까?&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;위에서 Index의 성능에서 언급했듯이, Insert나 Update에서(사용안함표시 때문에?) 값을 새로 추가하는데 여기서 페이지 분할이 일어난다고 했다. 그러면 데이터의 주소도 바뀌게 되니까 논클러스터링 인덱스가 리프 페이지에서 이 주소를 가지고 있어버리면 매번 주소 변경이 일어나야 한다. &lt;u&gt;&lt;b&gt;이걸 막기 위해 논클러스터링 인덱스는 리프 페이지에서 클러스터링 인덱스가 적용된 컬럼의 실제 값 (Primary Key가 있다면 그 값, ID 같은 것)을 가지고 있다.&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어느 컬럼에 인덱스를 적용해야 할까?&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 카디널리티( == 그룹 내 요소의 개수)가 높은 것. 즉, 중복도가 낮은 컬럼에 적용하는 게 좋다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1123&quot; data-origin-height=&quot;661&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLh4i6/btsFn9bP6Sr/q8odACfKvshVtTpWVa0HQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLh4i6/btsFn9bP6Sr/q8odACfKvshVtTpWVa0HQk/img.png&quot; data-alt=&quot;카디널리티는 ID, 이메일, 주민번호가 낮다. 중복이 별로 없어서.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLh4i6/btsFn9bP6Sr/q8odACfKvshVtTpWVa0HQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLh4i6%2FbtsFn9bP6Sr%2Fq8odACfKvshVtTpWVa0HQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;659&quot; height=&quot;388&quot; data-origin-width=&quot;1123&quot; data-origin-height=&quot;661&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;카디널리티는 ID, 이메일, 주민번호가 낮다. 중복이 별로 없어서.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;그 외에도&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카디널리티가 높은 컬럼. == 중복도가 낮은 컬럼&lt;/li&gt;
&lt;li&gt;where, join, order by 절에 자주 사용되는 컬럼 (인덱스는 조건 절이 있어야만 쓰이니까)&lt;/li&gt;
&lt;li&gt;insert, update, delete 가 자주 발생하지 않는 컬럼. (성능 저하 영향 때문에.)&lt;/li&gt;
&lt;li&gt;규모가 작지 않은 테이블. (규모가 작으면 인덱스 효과가 미미하니까)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스 보는 법&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID를 기본키로, email은 unique 제약 조건을 걸어서 인덱스를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPeCcZ/btsFkG9Cpme/dmtUBKo1vMc5Ji9eeHz4V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPeCcZ/btsFkG9Cpme/dmtUBKo1vMc5Ji9eeHz4V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPeCcZ/btsFkG9Cpme/dmtUBKo1vMc5Ji9eeHz4V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPeCcZ%2FbtsFkG9Cpme%2FdmtUBKo1vMc5Ji9eeHz4V1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1064&quot; height=&quot;154&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Non_Unique : 중복 허용이면 1, 허용 안하면 0.&lt;/li&gt;
&lt;li&gt;key name은 인덱스 이름. 기본키면 primary로 표시함.&lt;/li&gt;
&lt;li&gt;카디널리티: 현재는 아무 데이터 없이 create table만 해놔서 0임.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잘 활용되지 않는 인덱스는 과감히 제거하자.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Where 절에 쓰더라도 자주 사용되어야 가치가 있음.&lt;/li&gt;
&lt;li&gt;불필요한 인덱스로 성능 저하가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 중복도가 높은 컬럼은 인덱스 효과가 적다.&lt;/li&gt;
&lt;li&gt;자주 사용되더라도 insert, update, delete가 자주 일어나지는 않는지 고려해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 웹서비스 같은 온라인 트랜잭션 환경에서 쓰기와 읽기 비율은 2:8 또는 1:9다.&lt;/li&gt;
&lt;li&gt;조금 느린 쓰기를 감수하고 빠른 읽기를 선택하는 것도 하나의 방법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;출처&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=edpYzFgHbqs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=edpYzFgHbqs&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/DB</category>
      <author>해리리_</author>
      <guid isPermaLink="true">https://eocoding.tistory.com/130</guid>
      <comments>https://eocoding.tistory.com/130#entry130comment</comments>
      <pubDate>Fri, 1 Mar 2024 22:08:06 +0900</pubDate>
    </item>
  </channel>
</rss>