<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dev-seonho 님의 블로그</title>
    <link>https://dev-sunny.tistory.com/</link>
    <description>dev-seonho 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 10 Sep 2026 18:26:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>dev-seonho</managingEditor>
    <item>
      <title>@Cacheable 사용해서 속도 개선</title>
      <link>https://dev-sunny.tistory.com/93</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱 적용 전 &lt;b&gt;25,887ms&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱 적용 후 &lt;b&gt;6,834ms&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;74% 성능 개선 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Cacheable 사용해 캐싱한 적은 처음이라.. 시행착오가 있었다.&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;1. 역직렬화 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis에 저장은 잘 되었는데 값을 꺼낼 때 타입을 제대로 판단하지 못해 오류가 났었다.&lt;/p&gt;
&lt;pre id=&quot;code_1765431156636&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Unexpected token (START_OBJECT), expected VALUE_STRING:
need String, Number or Boolean that contains type id
...
class java.util.LinkedHashMap cannot be cast to HolidayCacheDto&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;redis에는 json으로 들어가 있는데 꺼낼 때 jaskson이 어떤 타입으로 역질력화해야하는지 알 수 없어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lis로 반환해야하는데.. LinkedHashMap으로 들어감&lt;/p&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;blockquote style=&quot;color: #0e0e0e;&quot; data-ke-style=&quot;style1&quot;&gt;GenericJackson2JsonRedisSerializer&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입정보가 약해서 리스트를 직렬화/역직렬화할 때 타입을 잃는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 해결&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) wrapper class&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트를 바로 캐싱하는 대시 wrapper 클래스로 감싸서 redis가 정확한 타입을 인식하도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1765431307217&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Setter
@NoArgsConstructor
public class HolidayCacheWrapper {
    private List&amp;lt;HolidayCacheDto&amp;gt; list;

    @Builder
    public HolidayCacheWrapper(List&amp;lt;HolidayCacheDto&amp;gt; list) {
        this.list = list;
    }
}


// sevice
@Cacheable(cacheNames = &quot;activeHolidays&quot;)
public HolidayCacheWrapper getActiveHolidays() {
    List&amp;lt;HolidayCacheDto&amp;gt; list = ...;

    return HolidayCacheWrapper.builder()
            .list(list)
            .build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) Jackson2JsonRedisSerializer 사용 시 타입 인식 문제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wrapper class를 적용해도 여전히 오류가 났다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 사용하는 캐시가 HolidayCacheWrapper 타입인 걸 redis에 명확하게 알려주기 위해&lt;/p&gt;
&lt;pre id=&quot;code_1765431426183&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// activeHolidays 전용
    Jackson2JsonRedisSerializer&amp;lt;HolidayCacheWrapper&amp;gt; holidaySerializer =
            new Jackson2JsonRedisSerializer&amp;lt;&amp;gt;(objectMapper, HolidayCacheWrapper.class);

    RedisCacheConfiguration holidayConfig = RedisCacheConfiguration.defaultCacheConfig()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(holidaySerializer))
            .computePrefixWith(CacheKeyPrefix.simple());

    Map&amp;lt;String, RedisCacheConfiguration&amp;gt; configs = new HashMap&amp;lt;&amp;gt;();
    configs.put(&quot;activeHolidays&quot;, holidayConfig);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 적용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/93</guid>
      <comments>https://dev-sunny.tistory.com/93#entry93comment</comments>
      <pubDate>Thu, 11 Dec 2025 14:49:10 +0900</pubDate>
    </item>
    <item>
      <title>토큰 기반 인증 정리 &amp;mdash; Access, Refresh, JWT, Bearer</title>
      <link>https://dev-sunny.tistory.com/92</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 토큰이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버와 클라이언트를 오고 가면서 사용자 인증을 해주는 문자열이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 사용자가 로그인에 성공하면 사용자에게 토큰을 발급하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 클라이언트는 이후 api 요청마다 이 토큰을 함께 보내면서 사용자 인증을 한다.&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;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;Access Token vs Refresh Token&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;access 토큰은 유효기간이 짧다. 5~10분으로 localStorage나 memory 등에 저장한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;refresh 토큰은 유효기간이 길다. 보통 7일에서 30일까지 저장하고 javascript가 읽지 못하게 하기 위해 httpOnly Cookie에 저장하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;access 토큰이 만료되면 refresh 토큰을 서버에 보내 access 토큰을 발급 받는 용도로 사용한다.&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;3. JWT(JSON WEB TOKEN)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰의 종류로 하나의 토큰 문자열 안에 세가지 구조로 이루어져 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1753170192617&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;헤더.내용.서명
Header.Payload.Signature&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;Payload : 사용자 정보, 권한, 만료 시간 등이 담긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signature :&amp;nbsp; JWT의 서명으로 누가 내용을 바꿔도 서버가 위조된 토큰이라고 판단할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디코딩은 가능하지만, 변조는 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Bearer Toekn&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;access token을 api 요청 시 어떻게 보내느냐의 방식 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1753170288659&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Authorization: Bearer &amp;lt;access_token&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Authorization : 인증 정보를 담는 HTTP 표준 헤더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bearer : 토큰 기반 인증이라는 걸 나타내는 접두사 그냥 붙이는거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;access_token&amp;gt; : 실제 토큰 문자열이 들어간다. 보통 JWT&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;5. 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 웹/모바일 인증은ㄴ JWT + Access/Refresh + Bearer 방식 조합으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안, 성능, 편의성을 모두 고려한 표준적인 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Refresh Token은 httpOnly 쿠키에 보관하고, Access Token은 매 요청마다 Bearer로 보내는 것이 가장 보편적인 설계입니다.&lt;/p&gt;</description>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/92</guid>
      <comments>https://dev-sunny.tistory.com/92#entry92comment</comments>
      <pubDate>Tue, 22 Jul 2025 16:50:55 +0900</pubDate>
    </item>
    <item>
      <title>Entity ID 타입, long vs Long - 무엇을 써야할까?</title>
      <link>https://dev-sunny.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;JPA로 엔티티를 설계할 때 ID 타입에 long을 쓸지 Long을 쓸지 고민한적이 있나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity ID는 Long(Wrapper 타입)을 사용하는 것이 표준이고 가장 안전한 방법입니다.&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;1. JPA는 id == null 상태를 가정합니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA에서는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;엔티티를 저장할 때&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;id가 비어있는 null 상태를 기준&lt;/span&gt;으로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;새 엔티티인지 판단&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 id 필드가 primitive long 타입이라면 기본값은 0이 자동으로 들어갑니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745760980073&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private long id; // 기본값은 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;아직 저장되지 않은 엔티티이지만 저장된 엔티티인지 JPA가 헷갈릴 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Long 타입은 기본값이 null 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745761034883&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Long id; // 기본값은 null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;id가 null이면 아직 저장되지 않은 새 엔티티로 명확하게 판단할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. @GeneratedValue 전략은 null을 기대합니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@GeneratedValue(strategy = GenerationType.IDENTITY)같은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;ID 자동 생성 전략을 사용할 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hibernate는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;엔티티의 id가 null임을 전제로 동작&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;id가 0이면 Hibernate는 이를 이미 존재하는 엔티티로 오해&lt;/span&gt;할 수도 있고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;저장이나 업데이트가 꼬일 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;id가 null인 상태에서 persist 되어야 정상적으로 자동 ID 생성&lt;/span&gt;이 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 불변성, 가독성, 유지보수성 측면에서도 Long이 유리합니다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불변성 : ID를 합 번 설정하면 수정하지 않도록 하는게 좋습니다.&lt;/li&gt;
&lt;li&gt;Optional 처리 : Optional&amp;lt;Long&amp;gt;과 같이 null을 안전하게 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;조건문 : id !=null로 간결하게 새 엔티티 여부를 판별할 수 있습니다.&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;4. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity ID는 무조건 Long을 사용하자!&lt;/p&gt;</description>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/89</guid>
      <comments>https://dev-sunny.tistory.com/89#entry89comment</comments>
      <pubDate>Sun, 27 Apr 2025 22:47:30 +0900</pubDate>
    </item>
    <item>
      <title>GC(Garbage Collection)</title>
      <link>https://dev-sunny.tistory.com/85</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. GC란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC는 JVM이 더이상 사용되지 않는 객체를 자동으로 탐지해서 메모리에서 제거하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 명시적으로 메모리 해제를 하지 않아도 되며 GC가 이를 대신 처리해 메모리 누수와 시스템 불안정을 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 Heap 메모리 대상으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 GC 메모리 정리를 해주시기 때문에 메모리를 직접 해제하지 않아도 되는 언어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 new로 객체를 생성만 하면되고 제거는 GC가 알아서 처리해준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. GC의 목적&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더이상 사용되지 않는 객체를 제거해서 메모리 회수&lt;/li&gt;
&lt;li&gt;메모리 누수 방지&lt;/li&gt;
&lt;li&gt;앱의 안전성 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. GC 동작 방식&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) Minor GC&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Yong Generation만 대상으로 수행&lt;/li&gt;
&lt;li&gt;빠르고 자주 일어남&lt;/li&gt;
&lt;li&gt;대부분의 객체는 이때 제거된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) Major GC(Full GC)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Old Generation + Yong + Metaspace까지 대상이다.&lt;/li&gt;
&lt;li&gt;STW 발생해 -&amp;gt; 느리고 무겁다.&lt;/li&gt;
&lt;li&gt;모든 영역에 도달하지 못하는 객체를 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*Stop-The-World : GC 수행 중 모든 애플리케이션 스레드를 멈춤&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빌생하는 상황(OutOfMemoryError가 발생하는 상황)&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;old영역이 꽉 찼을 때 : Young GC 후에도 살아남은 객체가 Old로 이동 &amp;rarr; Old 영역 부족&lt;/li&gt;
&lt;li&gt;Metaspace가 넘친경우&lt;/li&gt;
&lt;li&gt;주로 ClassLoader 메모리 누수 또는 동적 클래스 생성이 많을 때 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Full GC가 자주 발생한다면 햐결 방법&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;GC 로그 분석&lt;/li&gt;
&lt;li&gt;Old 영역 크기 조정&lt;/li&gt;
&lt;li&gt;클래스 로더 누수 감지 및 제거&lt;/li&gt;
&lt;li&gt;G1 GC, ZGC 같은 저지연 GC 사용&lt;/li&gt;
&lt;li&gt;애플리케이션에서 불픽요한 객체 생성을 줄인다.(캐시, 불변 객체 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. GC 종류(알고리즘 )&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 149px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 27.7907%; height: 19px;&quot;&gt;GC 이름&lt;/td&gt;
&lt;td style=&quot;width: 23.9535%; height: 19px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 24.6511%; height: 19px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%; height: 19px;&quot;&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 27.7907%; height: 19px;&quot;&gt;Serial GC&lt;/td&gt;
&lt;td style=&quot;width: 23.9535%; height: 19px;&quot;&gt;단일 스레드&lt;/td&gt;
&lt;td style=&quot;width: 24.6511%; height: 19px;&quot;&gt;단순, 소형 서비스&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%; height: 19px;&quot;&gt;STW가 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 27.7907%; height: 19px;&quot;&gt;Parallel GC&lt;/td&gt;
&lt;td style=&quot;width: 23.9535%; height: 19px;&quot;&gt;다중 스레드&lt;/td&gt;
&lt;td style=&quot;width: 24.6511%; height: 19px;&quot;&gt;Throughput(처리량) 우수&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%; height: 19px;&quot;&gt;STW는 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;width: 27.7907%; height: 35px;&quot;&gt;CMS(&lt;span&gt;Concurrent Mark-Sweep&lt;/span&gt;)&lt;/td&gt;
&lt;td style=&quot;width: 23.9535%; height: 35px;&quot;&gt;Old를 동시 수집&lt;/td&gt;
&lt;td style=&quot;width: 24.6511%; height: 35px;&quot;&gt;짧은 지연 시간&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%; height: 35px;&quot;&gt;&lt;span&gt;Fragmentation, Full GC 발생 가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 27.7907%; height: 19px;&quot;&gt;G1 GC(java9부터 디폴드 GC)&lt;/td&gt;
&lt;td style=&quot;width: 23.9535%; height: 19px;&quot;&gt;&lt;span&gt;Region 기반&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.6511%; height: 19px;&quot;&gt;&lt;span&gt;예측 가능한 STW&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%; height: 19px;&quot;&gt;&lt;span&gt;설정이 복잡함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 27.7907%; height: 19px;&quot;&gt;&lt;span&gt;ZGC(초저지연용)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.9535%; height: 19px;&quot;&gt;&lt;span&gt;저지연 GC&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.6511%; height: 19px;&quot;&gt;&lt;span&gt;STW 거의 없음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%; height: 19px;&quot;&gt;&lt;span&gt;최신 JVM 필요&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 27.7907%; height: 19px;&quot;&gt;&lt;span&gt;Shenandoah&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.9535%; height: 19px;&quot;&gt;&lt;span&gt;저지연, Red Hat 중심&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.6511%; height: 19px;&quot;&gt;&lt;span&gt;낮은 지연시간&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%; height: 19px;&quot;&gt;&lt;span&gt;완성도는 G1보다 낮음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) Serial GC&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 스레드로 GC 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Yong/Old 모두 GC할 때 STW 발생(STW 매우 큼)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;구조가 단순해서 -&amp;gt; RAM이 적은 환경에서 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) Parallel GC (Throughput GC)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC를 다중 스레드로 병렬 처리해서 처리 속도가 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STW는 존재하지만 병렬로 빨리 끝낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java8의 기본 GC다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) CMS GC (Java 14부터 제거, Derepacated)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Old 영역을 대부분 Concurrent로 실행돼 지연시간이 거의 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;CMS GC는 &lt;/span&gt;죽은 객체만 정리하고&amp;nbsp; 살아있는 객체는 그대로 둬서 중간중간 빈칸이 생긴다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;공간은 있지만 연속된 큰 공간이 없기 때문에 이런 상황이 발생하면 Full GC가 발생하고 OOME가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*단편화 : 메모리의 빈 공간이 조가조가 흩어져서 연속된 공간이 부족한 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*CMS에서 단편화가 생기는 이유는 죽은 객체만 정리하고&amp;nbsp; 살아있는 객체는 그대로 둬서 중간중간 빈칸이 생긴다. 공간은 있지만 연속된 큰 공간이 없기 때문에 이런 상황이 발생하면 Full GC가 발생하고 OOME가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*Compaction(압축)이 없는 상태 : CMS는 객체를 제거할 뿐 객체를 이동 시켜 빈공간을 압축하지 않는 상태를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4) G1 GC(Garbage First) : 저지연 + 단편화 방지 + 예측 가능한 성능&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CMS의 단점을 보완해서 나온 GC이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙을 고정된 크기의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Region&lt;/span&gt;으로 나눠서 가장 많은 가비지가 모인 곳부터 수집하는 전략을 사용하는 GC다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집을 애플리케이션과 동시에 처리하는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Concurrent와 Parallel&lt;/span&gt; GC이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단편화를 방지하기 위해 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;압축&lt;/span&gt;도 지원하고 STW 시간을 설정 가능해서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;예측 가능한 저지연 환경&lt;/span&gt;을 지원하는 GC이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바9부터 기본 GC다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5) ZGC(Z Garbage Collector) : 초저지연 GC&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 일시 중단 시간을 거의 0에 가깝게 만드는 것을 목표로 설계된 고성능, 초저지연 GC이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시 처리하고 객체 참조에 색상을 입해 GC 상태를 구분해 객체를 이동하면서도 참조 안전성을 유지하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 이동도 동시에 수행한다. 대용량 힙 환경에서도 지연 없이 안정적으로 동작할 수 있는 GC이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java11부터 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*concurrent(동시) : GC가 애플리케이션 실행을 멈추지 않고 백그라운드에서 GC 작업을 병행하는 것이다. 소프트웨어 관점에서 동시는 둘 이상의 작업이 겉보기에는 동시에 실행되는 것처럼 보이는 상태를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6) Shenandoah GC(쉐너도어 GC, RedHat 주도)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RedHat 주도로 개발된 초저지연 GC다. ZGC처럼 중단 시간을 짧게 유지하는데 초점을 맞춘 GC이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC의 모든 단계를 애플리케이션 실행과 동시에 수행하는 초저지연 GC이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;G1처럼 지역 단위로 관리하면서 압축도 동시로 수행해 정지 시간을 짧게 유지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*Red Hat : 리눅스 기반의 클라우드, 미들웨어, 컨테이너 기술을 제공하는 미국의 오픈소스 소프트웨어 회사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. GC Root&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GC가 참조 탐색을 시작하는 기준점이다. 대표적인 GC Root에는 다음이 포함된다.&lt;/li&gt;
&lt;li&gt;스레드 스택 변수, static 필드, JNI 참조&lt;/li&gt;
&lt;li&gt;GC는 Root에서부터 연결된 객체는 살아있다고 판단하며 연결되지 않는 객체를 제거한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>JAVA</category>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/85</guid>
      <comments>https://dev-sunny.tistory.com/85#entry85comment</comments>
      <pubDate>Wed, 23 Apr 2025 11:00:25 +0900</pubDate>
    </item>
    <item>
      <title>끝난&amp;nbsp;PermGen,&amp;nbsp;시작된&amp;nbsp;Metaspace:&amp;nbsp;자바&amp;nbsp;메타데이터&amp;nbsp;관리의&amp;nbsp;진화</title>
      <link>https://dev-sunny.tistory.com/84</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. PermGen의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PermGen은 자바7까지 JVM에 메서드 영역을 구현한 방식으로 JVM 프로세스 내부 메모리 공간을 사용했고 JVM 시작 시 ‑XX:MaxPermSize로 크기를 미리 정해야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기가 고정되어 Spring 같은 프레임워크가 클래스를 동적으로 많이 로드하면 공간이 고갈돼 OutOfMemoryError가 자주 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC 알고리즘마다 PermGen을 별도로 처리해야하는 코드가 필요해서 구현이 복잡해지고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC 대상을 알기 위한 Permgen 수집 때문에 Stop-The-World 동안 처리해야해서 정지 시간이 길어지는 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*정지시간이 길어지는 이유 : Full GC가 되면 프로그램을 전부 멈추고 PermGen을 검사, 정리해야했기 떄문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*PermGen에서의 GC는? : PermGen은 Heap과 관리방식이 달랐기 때문에 GC가 Heap을 수집할 때처럼 간단히 처리할 수 없었다. 예를 들면 SerialGC, Parallerl GC, CMS GC 등 GC 알고리즘 마다 힙과 PermGen 수집 로직을 각각 구현해야 했고 PermGen 수집은 일반 객체 수집처럼 단순하지않아 GC알고리즘이 복잡해졌었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*Stop-The-World : GC나 특정 내부 작업을 수행할 때 모든 애플리케이션 스레드를 멈추는 이벤트이다. 이때는 애플리케이션 코드가 전혀 실행되지 않고 오진 JVM 내부 작업만 수행된다.&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;2. Metaspace의 등장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바8부터는 PermGen의 구조적 한계를 해결하기 위해 Metaspace가 도입되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Metaspace는 OS의 네이티브(user-space) 메모리를 사용해 필요할 때 메모리를 자동으로 확장할 수 있다.(필요에 따라 상한선 제한도 가능) 따라서 GC는 힙만 집중하면 되는구조가 되었다. (메타스페이스에서는 GC가 더이상 참조되지 않는 클래스만 수거한다. 따라서 메모리 누수가 방지되고 GC가 메타스페이스를 점진적으로 정리가 가능하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 16 이후에 엘라스틱 메타스페이스(Elastic Metaspace(JEP 387))가 미사용 데이터를 OS로 바로 uncommit(반납)하여 운영 메모리 효율이 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;메타스페이스의 한계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 클래스 로더 누수는 여전히 발생&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 로더가 계속 참조되면 -&amp;gt; 해당 클래스 메타데이터도 계속 메타스페이스에 유지해 -&amp;gt; 결국 OOME 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*클래스로더가 계속 참조되는 경우 : &lt;b&gt;static&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;필드에 객체 저장, Threadlocal 사용 후 clear() 사용 안함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 무제한 확장 시 OS 메모리 고갈&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값이 무제한이라, 제한 없이 운영 시 시스템 전체 메모리를 잠식할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 관찰 및 튜닝이 어려운 경우가 존재한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 힙이 아닌 네이티브 메모리를 사용하기 떄문에 일부 메모리 분석 도구에서느 분석이 까다로울 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*언로드 : 쓰지 않는 클래스 메타데이터를 지우는 일&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*uncommit : 실제 메모리를 OS에 돌려주는 행위&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*네이티브 영역 : 운영체제에게 직접 요청해 확보한 유저스페이스 메모리&lt;/p&gt;</description>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/84</guid>
      <comments>https://dev-sunny.tistory.com/84#entry84comment</comments>
      <pubDate>Tue, 22 Apr 2025 20:21:52 +0900</pubDate>
    </item>
    <item>
      <title>복합 인덱스 정리</title>
      <link>https://dev-sunny.tistory.com/83</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 복합 인덱스란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개 이상의 컬럼으로 구성된 단일 인덱스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예: &lt;/span&gt;CREATE INDEX idx_user_created ON orders(user_id, created_at);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합인덱스는 두 컬럼을 묶어서 하나의 정렬 기준으로 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 B+Tree 구조에 따라 왼쪽 컬럼 부터 오름차순 -&amp;gt; 두번째 컬럼은 동일한 첫번재 컬럼 내에서 오름차순으로 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 복합 인덱스 사용 목적&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 컬럼을 조건에서 사용하는 WHERE, JOIN, ORDER BY, GROUP BY에서 성능이 향상된다.&lt;/li&gt;
&lt;li&gt;다중 조건을 하나의 인덱스로 처리해 디스크 접근 횟수를 줄인다.&lt;/li&gt;
&lt;li&gt;단일 인덱스보다 커버 범위가 넓고 효율적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 내부 구조 - B+Tree 기반 정렬 방식&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 인덱스도 내부적으로 B+Tree 구조를 사용해 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 컬럼 기준으로 먼저 정렬하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 값 안에서 두번째 컬럼 정렬하며 세번째, 네번째도 동일하게 정렬을 따른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Leftmost prefix rule(왼쪽부터 매칭 원칙)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Leftmost prefix rule는 왼쪽부터 매칭 원칙으로 복합 인덱스의 핵심 규칙이자 성능 활용의 기준이 되는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 인덱스는 왼쪽부터 순차적으로 사용하는 조건에만 효율이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 인덱스(a, b, c)가 있을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문 내 a가 없고 b만 있으면 a가 빠졌기 때문에 인덱스 범위 사용을 할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문에 범위 조건이 나오면 인덱스 탐색이 끊긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 인덱스는 여러 컬럼을 B+Tree 인덱스로 묶은 것으로&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;잘 설계된 복합 인덱스는 WHERE + ORDER BY + SELECT까지 모두 처리 가능한 고성능 도구이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 인덱스 (user_id, status, created_at)&lt;span&gt; 는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE user_id = ? AND status = ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY created_at&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT 인덱스 포함 컬럼만&lt;span&gt; 일 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;b&gt;완전한 인덱스 기반 처리&lt;/b&gt;&lt;span&gt;가 가능해진다!&lt;/span&gt;&lt;/p&gt;</description>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/83</guid>
      <comments>https://dev-sunny.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 21 Apr 2025 18:22:39 +0900</pubDate>
    </item>
    <item>
      <title>실행계획이란? 옵티마이저 작동 원리부터 힌트까지 정리</title>
      <link>https://dev-sunny.tistory.com/82</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 실행계획(Execution Plan)이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL을 실행할 때 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DBMS가 데이터를 어떤 순서와 방식으로 읽고&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;처리할지 결정한 실행 로드맵&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 SELECT,JOIN, WHERE, ORDER BY 등 SQL을 작성하면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB는 내부적으로 여러 가지 실행 전략을 시물레이션한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 가장 효율적인 실행 경로(plan)을 선택하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 결과가 실행 계획이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행계획은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;단 하나만 선택되며&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;옵티마이저는 가능한 여러 계획을 시물레이션해&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;예상 비용이 가장 낮은 경로를 선택&lt;/span&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상 비용은 디스크 I/O, 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;쿼리를 날리면 DB는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 어떤 인덱스를 읽을지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 어떤 테이블을 먼저 읽을지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 조인은 어떤 방식을 사용할지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 실행 전략을 옵티마이저가 정하고 그 결과를 실행계획으로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 옵티마이저&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저는 SQL을 실행할 때&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;가장 효율적으로 실행하기 위한 실행 계획을 선택&lt;/span&gt;하는 DB 내부 컴포넌트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 날리면 옵티마이저가 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;가장 빠른 방법을 골라서 실행&lt;/span&gt;해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저는 쿼리를 실행하는데 드는 예상 비용이 가장 낮은 실행계획을 예측해 선택한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;옵티마이저 실행 순서&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;우리가 SQL을 실행하면&lt;/li&gt;
&lt;li&gt;DBMS는 그걸 파싱해서 -&amp;gt; 옵티마이저에게 넘기고&amp;nbsp;&lt;/li&gt;
&lt;li&gt;옵티마이저는 SQL 파싱 및 내부 구조를 분석한다.(파서 단계)&lt;/li&gt;
&lt;li&gt;가능한 실행 계획들을 조합한다.&lt;/li&gt;
&lt;li&gt;각 실행 계획 비용을 계산해서&lt;/li&gt;
&lt;li&gt;비용이 가장 낮은 최종 실행 계획을 선택한다.&lt;/li&gt;
&lt;li&gt;선택된 계획을 기반으로 실제 실행한다.&lt;/li&gt;
&lt;/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;b&gt;옵티마이저의 주요 역할&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;인덱스 사용할지 말지 결정&lt;/li&gt;
&lt;li&gt;테이블 스캔 방식(Index Scan, Full Scan 등) 결정&lt;/li&gt;
&lt;li&gt;조인 순서 결정&lt;/li&gt;
&lt;li&gt;조인 방식 결정&lt;/li&gt;
&lt;li&gt;서브 쿼리 vs JOIN 구조 재작성&lt;/li&gt;
&lt;li&gt;쿼리 리라이팅으로 내부적으로 SQL 재구성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;옵티마이저 종료&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;옵티마이저에는 &lt;/span&gt;&lt;b&gt;Rule-based Optimizer (RBO)&lt;/b&gt;&lt;span&gt; 와 &lt;/span&gt;&lt;b&gt;Cost-based Optimizer (CBO)&lt;/b&gt;&lt;span&gt; 가 있고,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;대부분의 현대 DBMS는 CBO 방식으로, 비용(cost)을 계산해 최적 경로를 선택한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: Oracle은 과거에 RBO와 CBO를 병행하다 현재는 CBO만 사용. MySQL은 기본적으로 CBO.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 쿼리 익스플레인(Query Explain)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 옵티마이저가 생성한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;실행계획을 개발자에게 보여주는 기능&lt;/span&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;SQL을 실행하며 어떤 방식으로 처리할지 예측된 경로를 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 목적&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리 속도가 느려 성능 튜닝을 할 때&lt;/li&gt;
&lt;li&gt;인덱스가 제대로 쓰이고 있는지 확인이 필요할 때&lt;/li&gt;
&lt;li&gt;조인 순서나 방식이 비효율적인지 확인해야할 때&lt;/li&gt;
&lt;li&gt;서브쿼리 -&amp;gt; JOIN이 적절한지 판단할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쿼리 익스플레인 보는 법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL&lt;/p&gt;
&lt;pre id=&quot;code_1745214732206&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPLAIN SELECT * FROM employees WHERE department_id = 10;

혹은 더 자세하게
EXPLAIN FORMAT=JSON SELECT * FROM employees WHERE department_id = 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;explain 해석법&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 97.7901%; height: 340px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 19px;&quot;&gt;컬럼&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 19px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 76px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 76px;&quot;&gt;id&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 76px;&quot;&gt;id는 SELECT 쿼리 블록의 식별자이자 실행 순서를 의미한다. &lt;br /&gt;쿼리 내부에 SELECT가 하나만 있다면 모든 row의 id는 1로만 표시되고 서브쿼리나 파생 테이블 등 쿼리 안에 또 다른 SELECT 블록이 있으면 id는 늘어난다. &lt;br /&gt;일반적으로 숫자가 클수록 더 안쪽의 서브 쿼리이며 먼저 실행된다는 뜻이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 112px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 112px;&quot;&gt;select_type&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 112px;&quot;&gt;쿼리 유형(SIMPLE, PRIMARY, SUBQUERY 등)&lt;br /&gt;SIMPLE : 하나의 SELECT 문&lt;br /&gt;PRIMARY : 메인 쿼리&lt;br /&gt;SUBQUERY : WHERE 절 등에 포함된 서브 쿼리&lt;br /&gt;DERIVED : FROM 절에 있는 파생 테이블(서브 쿼리)&lt;br /&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 19px;&quot;&gt;type&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 19px;&quot;&gt;조인 유형(ALL, index, ref, const 등)-핵심 성능 지표&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 19px;&quot;&gt;possible_keys&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 19px;&quot;&gt;사용할 수 있는 인덱스 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 19px;&quot;&gt;key&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 19px;&quot;&gt;실제 사용된 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 19px;&quot;&gt;key_len&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 19px;&quot;&gt;인덱스에서 사용된 컬럼들의 자료형 크기(바이트)의 합계를나타낸다. VARCHAR처럼 가변 길이 문자열 타입은 1~2바이트를 포함해서 계산된다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 19px;&quot;&gt;ref&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 19px;&quot;&gt;조인이나 필터링할 때 인덱스를 탐색하기 위해 사용된 컬럼명 또는 상수값(const)을 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 19px;&quot;&gt;rows&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 19px;&quot;&gt;이 테이블에서 몇개의 행을 읽을 것 같다고 예측한 숫자다. rows가 많을수록 해당 테이블에서 더 많은 데이터를 읽는다는 뜻이다. 쿼리 성능에 직접적인 영향이 있다.&lt;br /&gt;작다(1~100) : 인덱스를 잘 타고 있음 -&amp;gt; 성능 좋을 가능성이 높음&lt;br /&gt;크다(10000+) : 조건이 잘 안걸린다. -&amp;gt; Full Table Scan 가능성이 높다.&lt;br /&gt;이건 예측한 수이기 때문에 실제 결과 수를 확인하려면 &lt;span&gt;EXPLAIN ANALYZE 같은 실측 도구로 확인해야한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20.1163%; height: 19px;&quot;&gt;Extra&lt;/td&gt;
&lt;td style=&quot;width: 89.1114%; height: 19px;&quot;&gt;쿼리를 실행할 때 개발자가 작성한게 아니라 옵티마이저가 추가로 한 특별한 작업들을 보여준다.&amp;nbsp;&lt;br /&gt;조건 필터링을 했다거나, 정렬을 따로 했다거나, 임시 테이블을 만들었는 등 보통 외에 추가로 무슨 일이 일어났는지 알려준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*파생 테이블 : FROM 절 안에 들어있는 SELECT 서브 쿼리다. 마치 테이블처럼 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;SELECT&amp;nbsp;*&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;FROM&amp;nbsp;(&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT&amp;nbsp;user_id,&amp;nbsp;COUNT(*)&amp;nbsp;AS&amp;nbsp;order_count&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM&amp;nbsp;orders&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;GROUP&amp;nbsp;BY&amp;nbsp;user_id&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;)&amp;nbsp;AS&amp;nbsp;sub&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;WHERE&amp;nbsp;order_count&amp;nbsp;&amp;gt;=&amp;nbsp;3;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*EXPLAIN ANALYZE : 실제 실행까지 수행해서 실제 걸린 시간, 읽은 row 수, 조인 방식을 확인할 수 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;MySQL 8+의 실측 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 좋은 조인 유형(type)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 67.6735%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3633%;&quot;&gt;type 값&lt;/td&gt;
&lt;td style=&quot;width: 33.9619%;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 17.3491%;&quot;&gt;&amp;nbsp;성능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3633%;&quot;&gt;const&lt;/td&gt;
&lt;td style=&quot;width: 33.9619%;&quot;&gt;PK로 한 건만 조회&lt;/td&gt;
&lt;td style=&quot;width: 17.3491%;&quot;&gt;최고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3633%;&quot;&gt;ref&lt;/td&gt;
&lt;td style=&quot;width: 33.9619%;&quot;&gt;인덱스로 일부 조회&lt;/td&gt;
&lt;td style=&quot;width: 17.3491%;&quot;&gt;좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3633%;&quot;&gt;range&lt;/td&gt;
&lt;td style=&quot;width: 33.9619%;&quot;&gt;범위 조건 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 17.3491%;&quot;&gt;좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3633%;&quot;&gt;index&lt;/td&gt;
&lt;td style=&quot;width: 33.9619%;&quot;&gt;인덱스 전체 스캔&lt;/td&gt;
&lt;td style=&quot;width: 17.3491%;&quot;&gt;쓸만함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3633%;&quot;&gt;ALL&lt;/td&gt;
&lt;td style=&quot;width: 33.9619%;&quot;&gt;테이블 전체 스캔(Full Scan)&lt;/td&gt;
&lt;td style=&quot;width: 17.3491%;&quot;&gt;느림&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL&lt;/p&gt;
&lt;pre id=&quot;code_1745214767387&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPLAIN SELECT * FROM employees WHERE department_id = 10;

// 실제 실행 기간 포함하려면
EXPLAIN ANALYZE SELECT * FROM employees WHERE department_id = 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;Oracle&lt;/p&gt;
&lt;pre id=&quot;code_1745214785052&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPLAIN PLAN FOR
SELECT * FROM employees WHERE department_id = 10;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Hint&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;옵티마이저의 판단을 개발자가 개입&lt;/span&gt;해서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;바꾸는 방식&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 옵티마이저에게 어떻게 실행할지 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DB에 강제로 말하는 것&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 옵티마이저에게 실행 계획을 선택할 때 특정 방향으로 유도하거나 강제하는 지시어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힌트는 옵티마이저가 정보를 잘못 판단해서 비효율적인 실행 계획을 선택할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 힌트를 사요해서 더 나은 실행 계획을 유도하거나 강제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 데이터 분포나 쿼리 조건이 바뀌면 힌트도 성능 저하 요인이 될 수 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힌트는 최후의 수단으로 신중하게 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힌트를 썼을 때도 성능이 떨어진다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저 통계가 오래되어 잘못돈 판단일 수 있다. 이때는 ANALYZE TABLE&lt;span&gt;를 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또는 쿼리자체를 리팩토링하거나 인덱스 구조를 개선해야할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;힌트는 단기 처방에 가깝다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;종류&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 옵티마이저 힌트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저에게 조인 순서, 조인 방식, 쿼리 전략을 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;STRAIGHT_JOIN : 테이블을 작성 순서대로 조인하게 강제한다. (MySQL 전용)&lt;/li&gt;
&lt;li&gt;JOIN_ORDER(t1, t2) : MySQL 8+에서 조인 순서 고정&lt;/li&gt;
&lt;li&gt;HASH_JOIN, NESTED_LOOP : 조인 &lt;span&gt;방식(Hash / Loop)&lt;/span&gt; 강제 (MySQL 8+)&lt;/li&gt;
&lt;li&gt;NO_ICP : Index Condition Pushdown 비활성화&lt;/li&gt;
&lt;li&gt;BKA_JOIN&lt;span&gt;, &lt;/span&gt;BNL_JOIN : 조인 전략(Block, Batched Access)&lt;span&gt; 강제&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*Index Condition Pushdown(ICP) : WHERE 절의 조건들 중 인덱스에 포함된 컬럼 조건을 인덱스 레별에서 미리 평가해 불필요한 테이블 접근을 줄이는 최적화 기술이다. MySQL 5.6 이상에서는 옵티마이저가 자동으로 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;예제&lt;/p&gt;
&lt;pre id=&quot;code_1745224201708&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT /*+ HASH_JOIN(u) */ o.id, u.name
FROM orders o
JOIN users u ON o.user_id = u.id;

-- users 테이블을 Hash Join 방식으로 처리하도록 강제.
-- 인덱스가 없거나 Full Scan이 불가피할 때 유리할 수 있어.&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;*Hash Join : 두 테이블 간 조인을 수행할 때 한 테이블의 데이터를 메모리에서 해시 테이블로 저장한 뒤 다른 테이블의 데이터를 순회하면서 해시 기반으로 빠르게 매칭하는 조인 알고리즘이다.&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;p data-ke-size=&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;USE INDEX : 해당 테이블에서 지정한 인덱스를 우선적으로 고려하라고 옵티마이저에게 제안하는 힌트이다. (강제는 아님)&lt;/li&gt;
&lt;li&gt;FORCE INDEX : 옵티마이저가 무조건 지정된 인덱스를 살용하게 강제한다.&lt;/li&gt;
&lt;li&gt;IGNORE INDEX : 특정 인덱스를 옵티마이저가 고려 대상에서 제외하도록 하는 힌트이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 : FORCE INDEX&lt;/p&gt;
&lt;pre id=&quot;code_1745224250007&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * 
FROM orders FORCE INDEX (idx_user_id) 
WHERE user_id = 3;

-- 옵티마이저가 Full Scan 하려고 할 때,
-- idx_user_id 인덱스를 무조건 타도록 강제할 수 있다.&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;예제 : IGNORE INDEX &amp;ndash; 인덱스 무시&lt;/p&gt;
&lt;pre id=&quot;code_1745224296889&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * 
FROM orders IGNORE INDEX (idx_status) 
WHERE status = 'PAID';

-- 옵티마이저가 자꾸 idx_status를 타서 성능이 나쁘다면
-- 그 인덱스를 제외하고 다른 전략을 선택하게 유도할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) 옵티마이저 힌트 vs 인덱스 힌트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저 힌트와 인덱스 힌트는 모두 DB 실행계획에 개입하는 수단이지만 적용되는 범위가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저 힌트는 조인 순서, 방식을 제어하는 것으로 이 쿼리를 어떤 순서와 방식으로 실행할지를 결정하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 힌트는 인덱스 사용을 제어하는 것으로 이 테이블을 어떤 방식으로 읽을지를 결정한다.&amp;nbsp;&lt;/p&gt;</description>
      <category>DB</category>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/82</guid>
      <comments>https://dev-sunny.tistory.com/82#entry82comment</comments>
      <pubDate>Mon, 21 Apr 2025 17:49:51 +0900</pubDate>
    </item>
    <item>
      <title>B-Tree vs B+ Tree 정리</title>
      <link>https://dev-sunny.tistory.com/80</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. B TREE 알고리즘(Balanced Tree)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;균형잡힌 M-ary(엠에어리) 트리구조이다.&lt;/li&gt;
&lt;li&gt;한 노드에 여러개의 key와 자식 노드를 가질 수 있다.&lt;/li&gt;
&lt;li&gt;모든 리프 노드는 같은 깊이에 위치한다.(균형 트리)&lt;/li&gt;
&lt;li&gt;데이터(key+value)는 모든 노드에 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745074057725&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        [30]
       /    \
 [10, 20]  [40, 50]&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;중간 노드(30)도 데이터를 가지고 있고&lt;/li&gt;
&lt;li&gt;자식 노드(10, 20), (40, 50)도 데이터를 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*M-ary 트리구조 : 하나의 노드가 최대 M개의 자식 노드를 가질 수 있는 트리구조다. M은 자식 노드의 최대 개수, ary는 다진트리(n-ary)라는 말에서 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;B-Tree가 이 트리구조인 이유는 한 노드로 여러 key를 저장할 수 있어야 효율적이고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 key 탐색이 빠르다.&lt;/li&gt;
&lt;li&gt;트리 높이가 낮게 유지되어 빠른 검색이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: 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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. B+ TREE 알고리즘&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree의 확장 구조로 데이터베이스 인덱스에 최적화된 트리 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터(key+value)는 리프 노드에만 저장된다.&lt;/li&gt;
&lt;li&gt;중간 노드는 key만 가지고 있는 탐색용 노드이다.&lt;/li&gt;
&lt;li&gt;리프 노드끼리는 왼-&amp;gt;오른쪽으로 연결된 Linked List 구조이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745074307578&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        [30]
       /    \
[10, 20] &amp;rarr; [30, 40, 50]
 &amp;uarr; 실제 데이터     &amp;uarr; 실제 데이터&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;중간 노드는 key만 있다.&lt;/li&gt;
&lt;li&gt;리프 노드끼리 연결되어 있어 범위 검색이 매우 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리프 노드에만 데이터를 저장해 -&amp;gt; 구조가 일관적이다.&lt;/li&gt;
&lt;li&gt;리프 연결로 범위 검색에 강하다.&lt;/li&gt;
&lt;li&gt;디스크 페이지에 더 많은 key를 담을 수 있어 -&amp;gt; I/O 효율이 높다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*디스크페이지 : 데이터베이스가 디스크에서 데이터를 읽고 쓸 때 사용하는 최소 단위 블록(예:16KB)이다. 하나의 인덱스 노드나 여러 행이 한 페이지 안에 들어가 있고 DB는 항상 페이지 단위로 데이터를 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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;단일 key 조회 성능은 B-Tree 보다 조금 낮다.(미미함)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기술면접 질문 대비&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Q1. &lt;span&gt;&amp;nbsp;&lt;/span&gt;B-Tree와 B+ Tree의 차이를 설명해보세요.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree는 데이터가 중간 노드와 리프 노드에 모두 저장되는 반면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B+-Tree는 모든 데이터가 리프 노드에만 저장된고 중간 노드는 key만 가진 탐색용 노드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 B+-Tree는 리프 노드끼리 연결되어 있어 범위 검색에 훨씬 유리하고 데이터베이스 인덱스 구조에 최적화 되어 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Q2. 데이터베이스 인덱스에 B+ Tree가 사용되는 이유는?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프 노드에만 데이터를 저장하고 리프끼리 연결되어 있어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬된 상태로 빠르게 범위 검색을 할 수 있기 떄문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 중간 노드에는 key만 저장해 더 많은 key를 한페이지에 담을 수 있어 디스크 I/O가 줄어들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대량의 데이터를 처리하는데 최적화된 구조이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Q3. B+ Tree에서 split이 일어나는 조건과 그 결과는?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 노드가 최개 key 개수를 초과하면 split이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 값을 기준으로 두 노드로 나누고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중간 값을 부모 노드로 올려 트리의 균형을 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 노드로 꽉 차면 다시 split이 일어나며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트까지 분활되면 트리의 depth가 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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Q4. B+ Tree는 삽입 순서에 따라 달라지나요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니요. 삽입 순서는 상관없고, 항상 정렬 상태를 유지하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 시 마다 트리를 동적으로 균형 있게 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key는 정렬된 상태로 삽입되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요시 split을 통해 트리 구조가 자동으로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Q5.&lt;/b&gt; &lt;b&gt;리프 노드끼리 연결된 이유는?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범위 검색(BETWEEN, LIKE, ORDER BY 등)을 빠르게 처리하기 위해서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 노드부터 찾은 key 이후에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프 노드를 옆으로 따라가면서 순차적으로 값을 조회할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Q5. B+ Tree는 왜 디스크 I/O 효율이 좋은가요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 노드는 key만 가지고 있으므로 한 페이지에 더 많은 key를 담을 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 블록 수가 줄어들어 트리 깊이가 낮아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 디스크 접근 횟수가 줄어들고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 데이터를 효율적으로 검색할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B+ Tree는 리프 노드에만 데이터를 저장하고, 중간 노드는 key만 가진 구조로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 탐색과 범위 검색을 가능하게 하는 인덱스 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서 기본 인덱스로 가장 널리 사용된다.&lt;/p&gt;</description>
      <category>DB</category>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/80</guid>
      <comments>https://dev-sunny.tistory.com/80#entry80comment</comments>
      <pubDate>Sun, 20 Apr 2025 00:12:10 +0900</pubDate>
    </item>
    <item>
      <title>CHAR vs VARCHAR, BLOB vs TEXT</title>
      <link>https://dev-sunny.tistory.com/79</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. CHAR&amp;nbsp;vs&amp;nbsp;VARCHAR&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 104px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;CHAR(n)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;VARCHAR(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;저장방식&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;고정 길이 문자&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;가변 길이 문자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;공간 낭비&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;짧은 문자열도 n만큼 공간을 차지한다.(공백으로 채움)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;실제 문자열 길이만큼만 공간 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;속도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;고정 길이라 비교적 빠르다.(계산 쉬움)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;길이가 달라지므로 약간 느릴 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;용도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;주민번호, 우편번호 등 항상 길이가 고정된 값&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;이름, 이메일, 주소처럼 길이가 유동적인 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;최대 길이(MySQL 기준)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;255자&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;최대 65.535 바이트(단, 행 전체 제한 존재)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 길이 값이면 CHAR&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변동 길이 값이면 VARCHAR를 써야 공간 낭비 없이 효율적이다.&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;2. BLOB&amp;nbsp;vs&amp;nbsp;TEXT&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 87px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;BLOB&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;TEXT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;용도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;이진(bianry) 데이터 저장(이미지, 파일)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;문자(text) 데이터 자장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;내용 인코딩&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;바이너리 그대로 저장(인코딩 없음)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;문자셋(Charset) 기반 저장(예:UTF-8)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;정렬/검색&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;불가능하거나 제한적(LIKE 불가 등)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;문자열처럼 검색 가능(LIKE 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;데이터 크기&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;둘 다 4 단계 (TINY, MEDIUM, LONG) 존재&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;예시&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;프로필 사진, 첨부파일, 암호화된 값 등&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;블로그 본문, 댓글, 설명글 등 간 텍스트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BLOB = 파일 같은 바이너리 데이터 저장용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TEXT = 사람이 읽는 긴 문자열 데이터 저장용&lt;/p&gt;</description>
      <category>DB</category>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/79</guid>
      <comments>https://dev-sunny.tistory.com/79#entry79comment</comments>
      <pubDate>Sat, 19 Apr 2025 20:03:34 +0900</pubDate>
    </item>
    <item>
      <title>MySQL과 Oracle 차이점 정리: 기능, 성능, 사용 환경별 비교</title>
      <link>https://dev-sunny.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle과 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;1. 라이선스와 가격&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle은 상용 DB로 사용하려면 라이선스 비용이 발생, 보통 금융, 공공기관, 대기업 시스템에서 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL은 오픈 소스 DB로 누구나 무료로 사용 가능, 가볍고 빠른 설치 가능, 웹 서비스나 스타트업에서 사용된다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Oracle은 복잡하고 정교한 트랜잭션 처리 가능, 높은 동시성 제어, 대규모 시스템에서 안정성을 강조하는 환경에 좋음&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL은 상대적으로 단순한 구조와 빠른 속도를 제공하고 트랜잭션 복잡도가 낮은 서비스에 적합&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3. 기능 및 성능 튜닝&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Oracle은&amp;nbsp; SQL 표준 외 자체적으로 확장된 SQL문법과 강력한 성능 튜닝 도구들을 제공한다. HINT 구문이나 실행계획 제어, 복잡한 인덱스 전략, 다양한 파티셔닝 기능, 고급 Lock 처리 등이 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL은 표준 SQL 기반으로 동작, 성능 튜닝 가능하지만 Oracle 만큼 세밀한 제어는 못한다. 그래도 InnoDB 스토리지 앤지을 사용하면 트래잭션 등 대부분의 기본 기능은 지원된다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;4. 고가용성과 확장성&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Oracle은 RAC(Real Application Cluster), DataGuard 등 내장된 고가용성 및 장애복수 기능이 매우 강력하다. 그래서 데이터베이스의 중단 없이 서버를 추가하거나 복구하는데 유리하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL은 리플케이션, 클러스터, ProxySQL 같은 방식으로 확장할 수 있지만 이런 기능은 주로 외부 도구나 별도 설정을 통해 구현해야하면 Oracle 처럼 기본 기능에 없음&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;5. 백업과 복구&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Oracle은 RMAN 같은 전용 도구를 통해 고급 백업/복구 기능을 제공하며 트랜잭션 레별의 복구까지 정밀하게 수행할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL은 mysqldump 또는 binlog 복제를 통해 백업을 하지만 정밀도는 떨어진다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;6. 운영과 사용자 편의성&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Oracle은 GUI툴과, 세부적인 권한관리, 오디팅 기능 등 엔터프라이즈에 특화된 기능이 많다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL은 설치와 운영이 간편하고 GUI 툴도 있어 처음 접하는 개발자에게 진입 장벽이 났다.&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;7. 어떤 상황에서 쓰나?&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;사용 환경&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;추천 DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;복잡한 트랜잭션, 동시성, 고신뢰성이 필요한 시스템(금융, 보험, 공공)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Oracle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;빠른 개발, 쉬운 유지보수, 가벼운 서비스 구축(웹, 모바일)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;MySQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;대규모 클러스터 + 고가용성 필요&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;Oracle RAC or MySQL + ProxySQL or Vitess&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8. 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle은 상용 RDBMS로 복잡한 트랜잭션 처리와 고가용성 기능이 매우 강력하며, 대기업이나 금융기관 등에서 많이 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 MySQL은 오픈소스 DB로 가볍고 설치가 쉬우며, 스타트업이나 웹 서비스 같은 환경에서 많이 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle은 RMAN, RAC, 파티셔닝 등 고급 기능을 내장하고 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL은 InnoDB를 통해 기본 트랜잭션은 지원하지만 확장 기능은 제한적이며 외부 도구에 의존하는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*RMAN(recovery manger) : Oracle에서 제공하는 고급 백업 및 복구 도구로 트랜잭션 로드 기반의 정밀한 복구와 증분백업을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*RAC(Real Application Center) : 여러 서버가 하나의 DB처럼 동작하도록 구성하는 Oracle의 고가용성 클러스터링 기술로, 노드 간 동시 읽기/쓰기를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*InnoDB : MySQL의 기본 스토리지 앤지으로 트랜잭션, 외례 키, 충돌 제어, ACID를 지원하며 안전성과 데이터 정합성에 강하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*mysqldump : MySQL 데이터베이스를 SQL 형태로 백업(덤프)할 수 있는 명령어 도구로 간편하지만 대용량 데이터에는 느릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*binlog(Binary Log) : MySQL에서 데이터 변경(INSERT, UPDATE, DELETE) 내용을 기록한 이진 로그 파일로, 리플리케이션 및 포인트 복구에 사용된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>DB</category>
      <author>dev-seonho</author>
      <guid isPermaLink="true">https://dev-sunny.tistory.com/78</guid>
      <comments>https://dev-sunny.tistory.com/78#entry78comment</comments>
      <pubDate>Sat, 19 Apr 2025 19:52:58 +0900</pubDate>
    </item>
  </channel>
</rss>