<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발세밝</title>
    <link>https://i-source.tistory.com/</link>
    <description>개발로 세상을 밝히자.(억지 맞음)</description>
    <language>ko</language>
    <pubDate>Tue, 19 May 2026 03:14:36 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>오늘은치킨이닭</managingEditor>
    <item>
      <title>언리얼 공부하면 좋아보이는것</title>
      <link>https://i-source.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gauntlet-automation-framework-overview-in-unreal-engine&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gauntlet-automation-framework-overview-in-unreal-engine&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774765299480&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;언리얼 엔진의 건틀릿 자동화 프레임워크 개요 | 언리얼 엔진 5.7 문서 | Epic Developer Community&quot; data-og-description=&quot;테스트를 수행하고 결과의 유효성을 검사하는 언리얼 엔진의 프로젝트 세션을 실행하는 프레임워크입니다.&quot; data-og-host=&quot;dev.epicgames.com&quot; data-og-source-url=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gauntlet-automation-framework-overview-in-unreal-engine&quot; data-og-url=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gauntlet-automation-framework-overview-in-unreal-engine&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kaUjx/dJMb83ksRkK/0xRkKjf34aiyDmjpHX0Vz0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/xfblI/dJMb9g5a58y/jSIouKSb9QmjCuFWA5SJc0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gauntlet-automation-framework-overview-in-unreal-engine&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gauntlet-automation-framework-overview-in-unreal-engine&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kaUjx/dJMb83ksRkK/0xRkKjf34aiyDmjpHX0Vz0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/xfblI/dJMb9g5a58y/jSIouKSb9QmjCuFWA5SJc0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진의 건틀릿 자동화 프레임워크 개요 | 언리얼 엔진 5.7 문서 | Epic Developer Community&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;테스트를 수행하고 결과의 유효성을 검사하는 언리얼 엔진의 프로젝트 세션을 실행하는 프레임워크입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.epicgames.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;건틀렛&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size26&quot;&gt;2. 주요 테스트 도구 및 방법&lt;/h2&gt;
&lt;h2 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;Gauntlet 자동화 프레임워크&lt;/h2&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진에서 E2E 테스트를 위해 가장 많이 사용하는 도구는 &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;9&quot;&gt;Gauntlet&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;역할:&lt;/b&gt; 게임 빌드를 실행하고, 여러 세션을 관리하며, 특정 시나리오를 수행한 뒤 결과를 보고합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;활용:&lt;/b&gt; &quot;게임 패키징 -&amp;gt; 서버/클라이언트 실행 -&amp;gt; 특정 맵 로드 -&amp;gt; 플레이어 행동 수행 -&amp;gt; 결과 확인&quot; 과정을 자동화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;멀티플레이어 테스트:&lt;/b&gt; 클라이언트 4명과 서버 1명을 동시에 띄워 네트워크 동기화를 테스트하는 데 매우 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;functional Testing (기능 테스트 매니저)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;에디터 내에서 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;12&quot;&gt;Functional Test 레벨&lt;/b&gt;을 만들어 테스트를 진행할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레벨 안에 FunctionalTest 액터를 배치하고, 블루프린트나 C++로 성공/실패 조건을 설정합니다.&lt;/li&gt;
&lt;li&gt;예: &quot;플레이어가 A 지점에서 B 지점으로 10초 안에 도착하는가?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 단위의 테스트만 실행하는 방향으로하고 세세한 조작감이나 인간이 개입이 필요한건 QA과정에서 하는걸 추천.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Area(일생동안 지속 유지하는 활동,마감X)/게임</category>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/93</guid>
      <comments>https://i-source.tistory.com/93#entry93comment</comments>
      <pubDate>Sun, 29 Mar 2026 15:25:37 +0900</pubDate>
    </item>
    <item>
      <title>B/B+트리</title>
      <link>https://i-source.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;B+ / B트리의 차이&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;1. B-Tree (Binary Tree의 확장)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;B-Tree는 모든 노드(루트, 중간, 리프)가 **데이터(Value)**와 **키(Key)**를 함께 가질 수 있는 구조입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;데이터 저장:&lt;/b&gt; 어느 노드에서든 키와 일치하는 데이터를 찾으면 즉시 탐색을 종료할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;중복 제거:&lt;/b&gt; 트리 전체에 걸쳐 키가 중복되지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0&quot;&gt;탐색 효율:&lt;/b&gt; 특정 데이터를 찾을 때 운 좋게 루트 노드나 중간 노드에 데이터가 있다면 리프 노드까지 내려가지 않아도 되므로 탐색 시간이 단축될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;5&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;2. B+Tree (B-Tree의 개선형)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;B+Tree는 실제 **데이터(Value)**를 오직 **리프 노드(Leaf Node)**에만 저장하고, 중간 노드들은 데이터를 찾기 위한 &lt;b data-index-in-node=&quot;78&quot; data-path-to-node=&quot;7&quot;&gt;가이드(Key)&lt;/b&gt; 역할만 수행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;데이터 저장:&lt;/b&gt; 실제 레코드나 데이터 포인터는 모두 리프 노드에만 존재합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;연결 리스트(Linked List):&lt;/b&gt; 모든 리프 노드들은 서로 연결 리스트 형태로 이어져 있습니다. 덕분에 **범위 스캔(Range Scan)**이나 전체 탐색을 할 때 트리 위아래를 왔다 갔다 할 필요 없이 리프 노드만 순차적으로 읽으면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;인덱스 노드의 효율:&lt;/b&gt; 중간 노드에 데이터를 담지 않기 때문에, 하나의 노드(페이지)에 더 많은 인덱스 키를 채울 수 있습니다. 이는 트리의 높이를 낮추어 대용량 데이터 처리 시 디스크 I/O 횟수를 줄여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;9&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;3. 주요 차이점 비교&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 126px;&quot; border=&quot;1&quot; data-path-to-node=&quot;11&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;B-Tree&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;B+Tree&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0,0&quot;&gt;데이터 저장 위치&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,1,1,0&quot;&gt;모든 노드에 저장 가능&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,1,2,0&quot;&gt;오직 리프 노드에만 저장&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0,0&quot;&gt;리프 노드 간 연결&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,2,1,0&quot;&gt;연결되지 않음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,2,2,0&quot;&gt;연결 리스트로 연결됨&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,3,0,0&quot;&gt;탐색 성능&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,3,1,0&quot;&gt;특정 키 탐색 시 빠를 수 있음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,3,2,0&quot;&gt;전체 탐색 및 범위 조회에 압도적 유리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,4,0,0&quot;&gt;트리의 높이&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,4,1,0&quot;&gt;상대적으로 높음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,4,2,0&quot;&gt;상대적으로 낮음 (더 많은 키 수용)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,5,0,0&quot;&gt;사용 사례&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,5,1,0&quot;&gt;일반적인 파일 시스템 등&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span data-path-to-node=&quot;11,5,2,0&quot;&gt;대부분의 관계형 DB(MySQL, Oracle 등) 인덱스&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;1. B-Tree: 모든 노드에 데이터가 있는 구조&lt;/h2&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;B-Tree는 검색 과정에서 중간 노드(Internal Node)에 찾는 키가 있으면 즉시 데이터를 반환합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwiZkvmutMSTAxUAAAAAHQAAAAAQcg&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Java&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public class BTreeNode {
    int[] keys;          // 부모/자식 노드를 구분하는 키
    Object[] data;       // 실제 데이터 (모든 노드가 데이터를 가짐)
    BTreeNode[] children; // 자식 노드 포인터
    int keyCount;
    boolean isLeaf;

    public Object search(int key) {
        int i = 0;
        while (i &amp;lt; keyCount &amp;amp;&amp;amp; key &amp;gt; keys[i]) {
            i++;
        }

        // 1. 현재 노드에서 키를 찾으면 즉시 데이터 반환
        if (i &amp;lt; keyCount &amp;amp;&amp;amp; keys[i] == key) {
            return data[i]; 
        }

        // 2. 리프 노드인데 못 찾았다면 데이터 없음
        if (isLeaf) {
            return null;
        }

        // 3. 자식 노드로 내려가서 계속 탐색
        return children[i].search(key);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;5&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;2. B+Tree: 리프 노드에만 데이터가 있고 서로 연결된 구조&lt;/h2&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;B+Tree는 인덱스 노드(Internal Node)에는 가이드용 키만 있고, 실제 데이터 탐색은 반드시 리프 노드까지 내려가야 완료됩니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwiZkvmutMSTAxUAAAAAHQAAAAAQcw&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Java&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public class BPlusTreeNode {
    // 인덱스 노드와 리프 노드를 구분하여 설계하는 것이 일반적입니다.
    
    // [인덱스 노드 역할]
    int[] keys; 
    BPlusTreeNode[] children; 

    // [리프 노드 역할 - 실제 데이터 저장소]
    Object[] data; 
    BPlusTreeNode next; // 다음 리프 노드를 가리키는 포인터 (연결 리스트)

    public Object search(int key) {
        BPlusTreeNode current = root;
        
        // 1. 무조건 리프 노드까지 내려감 (중간에 멈추지 않음)
        while (!current.isLeaf) {
            current = current.getChild(key);
        }

        // 2. 리프 노드에서만 실제 데이터를 확인
        for (int i = 0; i &amp;lt; current.keyCount; i++) {
            if (current.keys[i] == key) {
                return current.data[i];
            }
        }
        return null;
    }

    // 범위 검색 (Range Query) 시 장점
    public List&amp;lt;Object&amp;gt; searchRange(int start, int end) {
        List&amp;lt;Object&amp;gt; result = new ArrayList&amp;lt;&amp;gt;();
        BPlusTreeNode leaf = findLeafNode(start); // 시작점 리프 찾기

        // 연결 리스트를 따라가며 순차적으로 읽기만 하면 됨 (트리 재탐색 불필요)
        while (leaf != null) {
            for (Object d : leaf.data) {
                if (withinRange(d, start, end)) result.add(d);
            }
            leaf = leaf.next; 
        }
        return result;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;9&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;주요 차이점 요약 (코드 관점)&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;데이터 위치:&lt;/b&gt; B-Tree는 search 도중에 return data[i]가 가능하지만, B+Tree는 반드시 isLeaf가 true인 곳까지 도달해야 데이터를 얻을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;메모리 효율:&lt;/b&gt; B+Tree의 중간 노드(Internal Node)는 Object[] data 배열을 가지지 않으므로, 같은 메모리 크기(페이지 사이즈) 안에 훨씬 더 많은 keys를 저장할 수 있어 트리의 깊이가 얕아집니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;범위 조회:&lt;/b&gt; B-Tree는 10부터 20까지 찾으려면 트리를 계속 오르락내리락(In-order Traversal) 해야 하지만, B+Tree는 리프 노드에 도달한 뒤 next 포인터만 따라가면 되므로 매우 빠릅니다.&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;MAP과 SET은?&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;1. 주요 차이점 비교&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;4&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Set (셋)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Map (맵)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0,0&quot;&gt;저장 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,1,1,0&quot;&gt;**값(Value)**만 저장&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,1,2,0&quot;&gt;**키(Key)**와 **값(Value)**의 쌍으로 저장&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0,0&quot;&gt;중복 허용&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,2,1,0&quot;&gt;중복 불가 (이미 있으면 무시됨)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,2,2,0&quot;&gt;키(Key)는 중복 불가, 값(Value)은 중복 가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,3,0,0&quot;&gt;인덱스/순서&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,3,1,0&quot;&gt;기본적으로 순서 없음 (구현체에 따라 다름)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,3,2,0&quot;&gt;순서 없음 (구현체에 따라 다름)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,4,0,0&quot;&gt;데이터 접근&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,4,1,0&quot;&gt;Iterator나 향상된 for문을 사용해 탐색&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;4,4,2,0&quot;&gt;**키(Key)**를 사용하여 특정 값을 바로 찾음&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Archive(완료된 내용)/데이터베이스</category>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/92</guid>
      <comments>https://i-source.tistory.com/92#entry92comment</comments>
      <pubDate>Sun, 29 Mar 2026 14:54:25 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] TLS/Dockerfile/Compose/ Entrypoint/ 멀티스테이지/레이어 캐싱 / compose베이스</title>
      <link>https://i-source.tistory.com/91</link>
      <description>&lt;h2 id=&quot;1-원격-docker-접근과-tls&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;1. 원격 Docker 접근과 TLS&lt;/h2&gt;
&lt;h3 id=&quot;외부에서-docker-접속-시-인증서가-없으면-거절되는-이유&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;외부에서 Docker 접속 시 인증서가 없으면 거절되는 이유&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;Docker는 로컬에서는 보통 Unix Socket을 사용하지만, 원격 서버의 Docker 데몬을 제어하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;2375&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;2376&lt;span&gt;&amp;nbsp;&lt;/span&gt;포트를 열어야 한다.&lt;br /&gt;이때 외부 네트워크를 통해 Docker 데몬에 접근하면 보안 문제가 커지기 때문에, 기본적으로 인증과 암호화를 요구한다.&lt;/p&gt;
&lt;h3 id=&quot;docker의-tls-옵션&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;Docker의 TLS 옵션&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;Docker의 TLS는 Docker 클라이언트와 Docker 데몬 사이의 통신을 암호화하고, 서로의 신원을 확인하는 보안 인증 체계다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;필요한 요소:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;14&quot;&gt;CA 인증서&lt;/li&gt;
&lt;li data-source-line=&quot;15&quot;&gt;서버 인증서 / 서버 키&lt;/li&gt;
&lt;li data-source-line=&quot;16&quot;&gt;클라이언트 인증서 / 클라이언트 키&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;insecure-registries&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;Insecure Registries&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 Docker는 레지스트리와 통신할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;TLS(HTTPS)&lt;span&gt;&amp;nbsp;&lt;/span&gt;기반의 암호화 연결을 강제한다.&lt;br /&gt;Insecure Registries&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정은 이 보안 과정을 건너뛰고 HTTP 또는 검증되지 않은 연결을 허용하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;24&quot;&gt;내부 테스트 환경에서는 설정이 간단하다.&lt;/li&gt;
&lt;li data-source-line=&quot;25&quot;&gt;인증서 구성이 번거로운 환경에서 빠르게 붙일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;단점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;29&quot;&gt;MITM(Man-in-the-Middle) 공격에 취약하다.&lt;/li&gt;
&lt;li data-source-line=&quot;30&quot;&gt;이미지 변조 위험이 있다.&lt;/li&gt;
&lt;li data-source-line=&quot;31&quot;&gt;인증 정보가 노출될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-dockerfile-entrypoint-compose의-역할&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;33&quot; data-ke-size=&quot;size26&quot;&gt;2. Dockerfile, Entrypoint, Compose의 역할&lt;/h2&gt;
&lt;h3 id=&quot;dockerfile&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;35&quot; data-ke-size=&quot;size23&quot;&gt;Dockerfile&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;Dockerfile은 이미지를 빌드하기 위한 레시피다.&lt;br /&gt;즉, &quot;서버를 실행할 수 있는 정적인 상태&quot;를 정의하는 문서라고 보면 된다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;39&quot; data-info=&quot;dockerfile {data-source-line=&amp;quot;39&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;# 1. 베이스 이미지 설정
FROM openjdk:17-jdk-slim

# 2. 작업 디렉토리 생성
WORKDIR /app

# 3. 호스트의 빌드된 jar 파일을 컨테이너 내부로 복사
# 실제 경로는 프로젝트 빌드 도구에 따라 달라질 수 있음
COPY build/libs/admin-service.jar app.jar

# 4. 엔트리포인트 스크립트 복사 및 권한 부여
COPY docker_entrypoints/entrypoint-admin.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# 5. 컨테이너 시작 시 실행할 엔트리포인트 지정
ENTRYPOINT [&quot;/bin/bash&quot;, &quot;/entrypoint.sh&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;docker-entrypoint&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;58&quot; data-ke-size=&quot;size23&quot;&gt;Docker Entrypoint&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;59&quot; data-ke-size=&quot;size16&quot;&gt;Entrypoint는 컨테이너가 실제로 시작될 때 실행되는 초기화 스크립트다.&lt;br /&gt;이미지 자체는 정적인 결과물이고, Entrypoint는 &quot;실행 시점 로직&quot;을 담당한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;62&quot; data-info=&quot;bash {data-source-line=&amp;quot;62&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;#!/bin/bash
set -e

echo &quot;Starting Admin Service...&quot;

# 예: DB가 준비될 때까지 대기
# /app/tools/wait-for-it.sh db-server:3306 --timeout=30

# 예: 로그 디렉토리 생성
mkdir -p /app/logs

# 실제 자바 프로세스 실행
# exec를 사용해야 Java 프로세스가 PID 1을 가져가서 시그널 처리 가능
exec java -jar /app/app.jar \
    --spring.profiles.active=${BUILD_STAGE} \
    --server.port=${ADMIN_PORT}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;entrypoint와-cmd-차이&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;81&quot; data-ke-size=&quot;size23&quot;&gt;Entrypoint와 CMD 차이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;83&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;83&quot;&gt;ENTRYPOINT: 이 컨테이너가 &quot;무엇을 실행하는 용도인지&quot;를 고정하는 데 가깝다.&lt;/li&gt;
&lt;li data-source-line=&quot;84&quot;&gt;CMD: 기본 인자를 주거나, 필요 시 쉽게 덮어쓸 수 있는 기본 실행값에 가깝다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;docker-compose&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;86&quot; data-ke-size=&quot;size23&quot;&gt;Docker Compose&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;87&quot; data-ke-size=&quot;size16&quot;&gt;Docker Compose는 앞에서 만든 이미지와 실행 방식을 &quot;어떤 환경에서 띄울지&quot; 정의하는 도구다.&lt;/p&gt;
&lt;pre class=&quot;http&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;89&quot; data-info=&quot;yaml {data-source-line=&amp;quot;89&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;version: '3.8'

services:
  nb-admin:
    image: ${NB_REGISTRY_SERVER}/nb-admin:latest
    container_name: nb-admin-container

    environment:
      - BUILD_STAGE=${BUILD_STAGE}
      - ADMIN_PORT=8080
      - DB_URL=${NB_DB_URL}

    volumes:
      - ../docker_configs/admin/config.properties:/app/config.properties
      - /data/logs/admin:/app/logs

    networks:
      - nb-network

networks:
  nb-network:
    external: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;3-게임-서버와-docker&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;114&quot; data-ke-size=&quot;size26&quot;&gt;3. 게임 서버와 Docker&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;116&quot; data-ke-size=&quot;size16&quot;&gt;게임에서는 클라이언트까지 Docker에 넣는 경우는 드물고, 보통 데디케이티드 서버를 Docker 위에 올리는 경우가 많다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;118&quot; data-ke-size=&quot;size16&quot;&gt;이유:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;120&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;120&quot;&gt;서버는 반복 배포와 자동화가 중요하다.&lt;/li&gt;
&lt;li data-source-line=&quot;121&quot;&gt;동일한 실행 환경을 재현하기 쉽다.&lt;/li&gt;
&lt;li data-source-line=&quot;122&quot;&gt;확장, 재시작, 모니터링 자동화에 유리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-멀티-스테이지-빌드&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;124&quot; data-ke-size=&quot;size26&quot;&gt;4. 멀티 스테이지 빌드&lt;/h2&gt;
&lt;h3 id=&quot;a-일반-빌드single-stage&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;126&quot; data-ke-size=&quot;size23&quot;&gt;A. 일반 빌드(Single Stage)&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;127&quot; data-ke-size=&quot;size16&quot;&gt;이 방식은 빌드 도구와 소스 코드, 결과물을 모두 최종 이미지에 넣는 구조다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;129&quot; data-info=&quot;dockerfile {data-source-line=&amp;quot;129&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;FROM unreal-engine:5.3-build

WORKDIR /src
COPY . .

RUN /Build/BatchFiles/RunUAT.sh BuildCookRun ...

ENTRYPOINT [&quot;./Packaged/LinuxServer/MyProjectServer.sh&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;140&quot; data-ke-size=&quot;size16&quot;&gt;문제점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;142&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;142&quot;&gt;빌드에 사용한 엔진 소스, 컴파일러, 임시 파일이 이미지 안에 그대로 남는다.&lt;/li&gt;
&lt;li data-source-line=&quot;143&quot;&gt;이미지 용량이 매우 커진다.&lt;/li&gt;
&lt;li data-source-line=&quot;144&quot;&gt;소스 코드와 빌드 도구가 남아 있어 보안상 불리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;예시로 생각해볼 수 있는 문제:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;148&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;148&quot;&gt;언리얼 기반이면 수백 GB 수준까지 커질 수 있다.&lt;/li&gt;
&lt;li data-source-line=&quot;149&quot;&gt;Java/Maven 기반도&lt;span&gt;&amp;nbsp;&lt;/span&gt;.m2, 빌드 툴, 소스 코드가 같이 들어가면 수백 MB에서 1GB 이상으로 커질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;b-멀티-스테이지-빌드multi-stage&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;151&quot; data-ke-size=&quot;size23&quot;&gt;B. 멀티 스테이지 빌드(Multi-stage)&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;152&quot; data-ke-size=&quot;size16&quot;&gt;빌드 전용 스테이지와 실행 전용 스테이지를 분리해서, 최종 결과물만 실행 이미지로 가져오는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;154&quot; data-info=&quot;dockerfile {data-source-line=&amp;quot;154&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;# Stage 1: 빌드 전용
FROM unreal-engine:5.3-build AS builder
WORKDIR /src
COPY . .
RUN /Build/BatchFiles/RunUAT.sh BuildCookRun ...

# Stage 2: 실행 전용
FROM ubuntu:22.04
WORKDIR /game

# builder 스테이지에서 결과물만 복사
COPY --from=builder /src/Packaged/LinuxServer/ .

ENTRYPOINT [&quot;./MyProjectServer.sh&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;171&quot; data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;173&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;173&quot;&gt;최종 이미지에는 실행에 필요한 파일만 남는다.&lt;/li&gt;
&lt;li data-source-line=&quot;174&quot;&gt;용량이 크게 줄어든다.&lt;/li&gt;
&lt;li data-source-line=&quot;175&quot;&gt;빌드 도구와 소스 코드가 빠져 보안상 유리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;177&quot; data-ke-size=&quot;size16&quot;&gt;핵심:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;179&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;179&quot;&gt;Stage 1은 빌드용&lt;/li&gt;
&lt;li data-source-line=&quot;180&quot;&gt;Stage 2는 실행용&lt;/li&gt;
&lt;li data-source-line=&quot;181&quot;&gt;최종 배포는 Stage 2만 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-레이어-캐싱-최적화&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;183&quot; data-ke-size=&quot;size26&quot;&gt;5. 레이어 캐싱 최적화&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;185&quot; data-ke-size=&quot;size16&quot;&gt;Docker는 Dockerfile의 각 명령을 실행할 때마다 그 결과를 레이어로 저장한다.&lt;br /&gt;파일이 바뀌지 않으면 이전 레이어를 재사용하므로 빌드 속도가 빨라진다.&lt;/p&gt;
&lt;h3 id=&quot;비효율적인-방식&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;188&quot; data-ke-size=&quot;size23&quot;&gt;비효율적인 방식&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;190&quot; data-info=&quot;dockerfile {data-source-line=&amp;quot;190&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;FROM unreal-engine:5.3-build
WORKDIR /src

COPY . .
RUN /Build/BatchFiles/RunUAT.sh BuildCookRun ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;198&quot; data-ke-size=&quot;size16&quot;&gt;문제:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;200&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;200&quot;&gt;프로젝트 내부 파일 하나만 바뀌어도&lt;span&gt;&amp;nbsp;&lt;/span&gt;COPY . .&lt;span&gt;&amp;nbsp;&lt;/span&gt;레이어가 무효화된다.&lt;/li&gt;
&lt;li data-source-line=&quot;201&quot;&gt;그 아래의 무거운 빌드 단계도 다시 실행된다.&lt;/li&gt;
&lt;li data-source-line=&quot;202&quot;&gt;소스 한 줄 바꾸고도 수십 분을 다시 빌드할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;캐시를-고려한-방식&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;204&quot; data-ke-size=&quot;size23&quot;&gt;캐시를 고려한 방식&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;206&quot; data-info=&quot;dockerfile {data-source-line=&amp;quot;206&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;FROM unreal-engine:5.3-build
WORKDIR /src

# 자주 안 바뀌는 파일 먼저
COPY MyProject.uproject ./
COPY Config/ ./Config/
COPY Plugins/ ./Plugins/

RUN echo &quot;Base environment ready&quot;

# 자주 바뀌는 파일은 뒤에서 복사
COPY Source/ ./Source/
COPY Content/ ./Content/

RUN /Build/BatchFiles/RunUAT.sh BuildCookRun ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;224&quot; data-ke-size=&quot;size16&quot;&gt;핵심 원칙:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;226&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;226&quot;&gt;폴더 단위로 무조건 잘게 쪼개는 것이 아니라&lt;/li&gt;
&lt;li data-source-line=&quot;227&quot;&gt;변경 주기가 비슷한 파일끼리 묶는 것이 더 중요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;왜-무조건-잘게-나누는-게-답은-아닌가&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;229&quot; data-ke-size=&quot;size23&quot;&gt;왜 무조건 잘게 나누는 게 답은 아닌가?&lt;/h3&gt;
&lt;h4 id=&quot;1-언리얼c의-pch-문제&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;231&quot; data-ke-size=&quot;size20&quot;&gt;1. 언리얼/C++의 PCH 문제&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;232&quot; data-ke-size=&quot;size16&quot;&gt;C++은 빌드 속도 향상을 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;PCH(Precompiled Header)를 사용한다.&lt;br /&gt;특정 폴더만 다시 복사해도 공통 헤더 의존성이 있으면, 실제 빌드 시스템은 전체 리빌드를 판단할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;235&quot; data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;237&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;237&quot;&gt;Docker 레이어는 캐시되었더라도&lt;/li&gt;
&lt;li data-source-line=&quot;238&quot;&gt;내부 빌드 시스템이 다시 큰 빌드를 수행할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-레이어-수-증가와-관리-비용&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;240&quot; data-ke-size=&quot;size20&quot;&gt;2. 레이어 수 증가와 관리 비용&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;241&quot; data-ke-size=&quot;size16&quot;&gt;레이어를 지나치게 많이 쪼개면 다음 문제가 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;243&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;243&quot;&gt;변경 체크 비용이 늘어난다.&lt;/li&gt;
&lt;li data-source-line=&quot;244&quot;&gt;Dockerfile 가독성이 떨어진다.&lt;/li&gt;
&lt;li data-source-line=&quot;245&quot;&gt;Jenkins나 API에서 자동화할 때 유지보수가 어려워진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;247&quot; data-ke-size=&quot;size16&quot;&gt;결론:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;249&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;249&quot;&gt;캐시는 중요하지만&lt;/li&gt;
&lt;li data-source-line=&quot;250&quot;&gt;&quot;너무 잘게 쪼개는 것&quot;보다 &quot;변경 패턴에 맞게 나누는 것&quot;이 더 중요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-compose-override&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;252&quot; data-ke-size=&quot;size26&quot;&gt;6. Compose Override&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;254&quot; data-ke-size=&quot;size16&quot;&gt;docker-compose.yml&lt;span&gt;&amp;nbsp;&lt;/span&gt;하나를 공통 설계도로 두고, 개발/테스트/운영 환경마다 필요한 부분만 추가 파일로 덮어쓰는 방식이다.&lt;/p&gt;
&lt;h3 id=&quot;왜-필요한가&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;256&quot; data-ke-size=&quot;size23&quot;&gt;왜 필요한가?&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;257&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 모든 게임 서버가 같은 이미지를 사용한다고 해도, 방마다 설정이 다를 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;259&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;261&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;261&quot;&gt;1번 방 서버:&lt;span&gt;&amp;nbsp;&lt;/span&gt;7777&lt;span&gt;&amp;nbsp;&lt;/span&gt;포트 사용, 데스매치 맵 실행&lt;/li&gt;
&lt;li data-source-line=&quot;262&quot;&gt;2번 방 서버:&lt;span&gt;&amp;nbsp;&lt;/span&gt;7778&lt;span&gt;&amp;nbsp;&lt;/span&gt;포트 사용, 점령전 맵 실행, CPU 제한 강화&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;264&quot; data-ke-size=&quot;size16&quot;&gt;이걸 매번 새 YAML 파일 전체를 복사해서 만들면 관리가 어려워진다.&lt;br /&gt;그래서 기본판 + override 파일 조합 방식을 쓴다.&lt;/p&gt;
&lt;h3 id=&quot;기본-파일&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;267&quot; data-ke-size=&quot;size23&quot;&gt;기본 파일&lt;/h3&gt;
&lt;pre class=&quot;http&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;269&quot; data-info=&quot;yaml {data-source-line=&amp;quot;269&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;version: '3.8'

services:
  game-node:
    image: ${REGISTRY_URL}/unreal-server:latest
    networks:
      - game-network
    restart: always

networks:
  game-network:
    driver: bridge
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;운영용-override-파일&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;284&quot; data-ke-size=&quot;size23&quot;&gt;운영용 override 파일&lt;/h3&gt;
&lt;pre class=&quot;http&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;286&quot; data-info=&quot;yaml {data-source-line=&amp;quot;286&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;version: '3.8'

services:
  game-node:
    ports:
      - &quot;${SERVER_PORT}:7777/udp&quot;
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 2G
    environment:
      - MAP_NAME=${SELECTED_MAP}
      - MAX_PLAYERS=50
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;실행-방법&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;303&quot; data-ke-size=&quot;size23&quot;&gt;실행 방법&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;304&quot; data-ke-size=&quot;size16&quot;&gt;뒤에 오는 파일이 앞 파일 설정을 덮어쓴다.&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;306&quot; data-info=&quot;bash {data-source-line=&amp;quot;306&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;# 기본 + 운영
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 기본 + 개발
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;django-api와의-연계&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;314&quot; data-ke-size=&quot;size23&quot;&gt;Django API와의 연계&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;315&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 Django API에서 아래처럼 환경 변수를 주입해 Docker Compose를 실행할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;317&quot; data-info=&quot;python {data-source-line=&amp;quot;317&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;import subprocess

def deploy_game_server(port, map_name):
    env_vars = {
        &quot;SERVER_PORT&quot;: str(port),
        &quot;SELECTED_MAP&quot;: map_name,
        &quot;REGISTRY_URL&quot;: &quot;my-reg.com&quot;
    }

    cmd = [
        &quot;docker-compose&quot;,
        &quot;-f&quot;, &quot;docker-compose.yml&quot;,
        &quot;-f&quot;, &quot;docker-compose.prod.yml&quot;,
        &quot;up&quot;, &quot;-d&quot;
    ]

    subprocess.run(cmd, env=env_vars)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;7-health-check&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;337&quot; data-ke-size=&quot;size26&quot;&gt;7. Health Check&lt;/h2&gt;
&lt;h3 id=&quot;dockerfile에서-health-check-설정&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;339&quot; data-ke-size=&quot;size23&quot;&gt;Dockerfile에서 Health Check 설정&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;340&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너 내부 서비스가 정상적으로 살아 있는지 Docker가 주기적으로 검사하게 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;342&quot; data-info=&quot;dockerfile {data-source-line=&amp;quot;342&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;# 포트 확인 도구 설치
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y net-tools iproute2

# 30초마다 체크, 5초 제한, 3번 실패 시 unhealthy
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD netstat -anup | grep :7777 || exit 1

ENTRYPOINT [&quot;./MyProjectServer.sh&quot;, &quot;-log&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;django-api에서-health-상태-읽기&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;353&quot; data-ke-size=&quot;size23&quot;&gt;Django API에서 Health 상태 읽기&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: left;&quot; data-source-line=&quot;355&quot; data-info=&quot;python {data-source-line=&amp;quot;355&amp;quot;}&quot; data-role=&quot;codeBlock&quot;&gt;&lt;code&gt;import docker

def get_server_status(container_name):
    client = docker.from_env()
    container = client.containers.get(container_name)

    health_status = container.attrs['State']['Health']['Status']

    if health_status == 'healthy':
        return &quot;접속 가능 (로딩 완료)&quot;
    elif health_status == 'starting':
        return &quot;로딩 중... 잠시만 기다려주세요&quot;
    else:
        return &quot;서버 이상 발생 (Unhealthy)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;실무에서-왜-중요한가&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;372&quot; data-ke-size=&quot;size23&quot;&gt;실무에서 왜 중요한가?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;374&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;374&quot;&gt;unhealthy&lt;span&gt;&amp;nbsp;&lt;/span&gt;상태를 감지해 자동 재시작 전략과 연결할 수 있다.&lt;/li&gt;
&lt;li data-source-line=&quot;375&quot;&gt;매치메이킹에서 아직&lt;span&gt;&amp;nbsp;&lt;/span&gt;starting&lt;span&gt;&amp;nbsp;&lt;/span&gt;상태인 서버를 제외할 수 있다.&lt;/li&gt;
&lt;li data-source-line=&quot;376&quot;&gt;depends_on&lt;span&gt;&amp;nbsp;&lt;/span&gt;및 상태 확인과 조합하면, DB가 완전히 준비된 뒤 게임 서버를 띄우도록 제어할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;8-전체-요약&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-source-line=&quot;378&quot; data-ke-size=&quot;size26&quot;&gt;8. 전체 요약&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;380&quot; data-ke-size=&quot;size16&quot;&gt;이번 내용의 핵심은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-source-line=&quot;382&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-source-line=&quot;382&quot;&gt;원격 Docker 제어에는 TLS 같은 보안 구성이 중요하다.&lt;/li&gt;
&lt;li data-source-line=&quot;383&quot;&gt;Dockerfile은 이미지 정의, Entrypoint는 실행 시점 초기화, Compose는 환경별 실행 구성을 담당한다.&lt;/li&gt;
&lt;li data-source-line=&quot;384&quot;&gt;게임 서버는 Docker에 올리기 적합하지만, 클라이언트는 보통 대상이 아니다.&lt;/li&gt;
&lt;li data-source-line=&quot;385&quot;&gt;멀티 스테이지 빌드는 용량과 보안 측면에서 매우 중요하다.&lt;/li&gt;
&lt;li data-source-line=&quot;386&quot;&gt;레이어 캐싱은 빌드 시간을 줄이지만, 무조건 잘게 나누는 것이 정답은 아니다.&lt;/li&gt;
&lt;li data-source-line=&quot;387&quot;&gt;Compose Override를 쓰면 환경별 설정을 깔끔하게 관리할 수 있다.&lt;/li&gt;
&lt;li data-source-line=&quot;388&quot;&gt;Health Check는 운영 자동화와 장애 대응에 매우 유용하다.&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;span style=&quot;letter-spacing: 0px;&quot;&gt;도커 데몬&lt;/span&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;* 도커 데몬 - 클라로부터 REST API를 듣고 작업 처리 , 이미지 관리, 컨테이너관리, 네트워크 및 볼륨관리&lt;/p&gt;
&lt;p 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;도커 이미지 만드는 과정&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;docker push 명령을 내리면&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;h2 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;3. Dockerfile 빌드와 체크섬&lt;/h2&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;docker build를 실행할 때 도커 엔진은 각 명령어(RUN, COPY, ADD 등)에 대해 체크섬을 검사합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;COPY/ADD 명령어:&lt;/b&gt; 복사하려는 로컬 파일의 내용과 메타데이터를 기반으로 체크섬을 생성합니다. 파일 내용이 1비트라도 다르면 체크섬이 변하고, 캐시는 깨집니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;RUN 명령어:&lt;/b&gt; 실행되는 명령어 텍스트 자체를 체크섬으로 사용합니다. (주의: RUN apt-get update는 명령어 텍스트가 같으면 캐시를 사용하므로, 실제 외부 패키지 업데이트 여부는 감지하지 못할 수 있습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;17&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;4. 레이어 체크섬 확인 방법&lt;/h2&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;터미널에서 특정 이미지의 레이어 체크섬(DiffID) 구조를 직접 확인해 볼 수 있습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwil-ZqnqsSTAxUAAAAAHQAAAAAQPg&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;docker inspect &amp;lt;이미지_이름_또는_ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;출력 결과 중 RootFS.Layers 항목을 보면 해당 이미지를 구성하는 레이어들의 SHA256 체크섬 리스트를 확인할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 빌드 컨텍스트 준비 -&amp;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 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>
      <category>Project(마감 기한이 정해진 목표)/Docker(도커)</category>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/91</guid>
      <comments>https://i-source.tistory.com/91#entry91comment</comments>
      <pubDate>Thu, 26 Mar 2026 21:57:30 +0900</pubDate>
    </item>
    <item>
      <title>[Django]</title>
      <link>https://i-source.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MVT패턴이란 ?&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;역할 : DB의 구조를 정의함&lt;/li&gt;
&lt;li&gt;특징 : SQL을 직접 작성 하지 않고도 파이썬 코드로 DB 테이블을 정의하고 데이터 조작가능한 ORM제공 DB의 CRUD 로직 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할 : 애플리케이션의 비즈니스 로직 처리.&lt;/li&gt;
&lt;li&gt;특징 : 모델에서 필요한 데이터를 가져와 가공하거, 그결과를 템플릿으로 전달함. MVC에서 Controller역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할 : 사용자에게 보여지는 화면을 담장.&lt;/li&gt;
&lt;li&gt;특징 : HTML 파일 내에 DTL을 사용해 뷰에서 전달 받은 데이터를 동적으로 표시함.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;b&gt;Web Server (Nginx/Apache):&lt;/b&gt;&amp;nbsp;사용자 요청을 가장 먼저 받아서 정적 파일(이미지 등)은 직접 처리하고, 동적 요청은 WSGI(Gunicorn 등)를 통해 Django로 넘깁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;URL Conf (&lt;a href=&quot;http://urls.py&quot;&gt;urls.py&lt;/a&gt;):&lt;/b&gt;&amp;nbsp;Django에 도착한 요청의 URL을 보고 &quot;이 주소는 어떤&amp;nbsp;&lt;b&gt;View&lt;/b&gt;가 처리해야 하지?&quot;를 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;View:&lt;/b&gt;&amp;nbsp;담당 View가 호출됩니다. 여기서&amp;nbsp;&lt;b&gt;Model&lt;/b&gt;을 통해 DB에서 데이터를 가져오거나,&amp;nbsp;&lt;b&gt;복잡한 계산 로직을 수행합니다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Template:&lt;/b&gt;&amp;nbsp;View가 처리한 데이터를&amp;nbsp;&lt;b&gt;Template&lt;/b&gt;에 전달합니다. 템플릿은 데이터를 받아 최종적인 HTML 페이지를 완성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Response:&lt;/b&gt;&amp;nbsp;완성된 HTML을 다시 사용자 브라우저로 응답하며 마무리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Middleware&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;스프링의 AOP랑 비슷한듯 가로채는 역할을 수행함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장고는&amp;nbsp;&amp;nbsp;VIEW에 도달하기 전 + 모든 응답이 사용자에게 가기전에 미들웨어를 통과함&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;인증 및 권한 : 특정 세선이 없으면 view까지 가지도 못하게 입구컷 가능&lt;/li&gt;
&lt;li&gt;공통 로깅 : 모든 API의 요청의 소요 시간 , ip같은것을 일괄적으로 수집해 모니터링으로 보내기 좋다.&lt;/li&gt;
&lt;li&gt;보안을 처리하기 좋음 : CSRF공격 방어 , XSS필터링 HTTPS 강제 리다이렉션 등 한곳에 처리.인증 및 권한 : 특정 세선이 없으면 view까지 가지도 못하게 입구컷 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 커스텀 미들 웨어 작성&lt;/p&gt;
&lt;pre id=&quot;code_1774356663790&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # 최초 설정 시 실행됨

    def __call__(self, request):
        # 1. 뷰가 호출되기 전 처리할 로직 (Request 단계)
        print(&quot;뷰 호출 전&quot;)

        response = self.get_response(request)

        # 2. 뷰가 호출된 후 처리할 로직 (Response 단계)
        print(&quot;응답 반환 전&quot;)

        return response&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;2. settings.py에 등록&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774356697662&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ... 기존 미들웨어들 ...
    
    # 본인이 만든 미들웨어 경로 추가 (앱이름.파일이름.클래스이름)
    'myapp.middleware.SimpleMiddleware',
]&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;Django ORM의 장단점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WSHI vs ASGI&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말 숙제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://forms.py&quot;&gt;forms.py&lt;/a&gt; -&amp;gt; 페이지 구성해보고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://models.py&quot;&gt;models.py&lt;/a&gt; -&amp;gt; 페이지 db에 올리고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://urls.py&quot;&gt;urls.py&lt;/a&gt; -&amp;gt;&lt;a href=&quot;http://views.py&quot;&gt;views.py&lt;/a&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;콤보 박스 a가 변화할때 콤보박스 b의 내용을 변화시켜라.&amp;nbsp;&lt;/p&gt;</description>
      <category>Project(마감 기한이 정해진 목표)/부트캠프</category>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/90</guid>
      <comments>https://i-source.tistory.com/90#entry90comment</comments>
      <pubDate>Tue, 24 Mar 2026 22:00:25 +0900</pubDate>
    </item>
    <item>
      <title>백엔드 취업 질문</title>
      <link>https://i-source.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;내가 받아본 질문 목록&lt;br /&gt;&amp;nbsp;&lt;br /&gt;스프링 프록시 객체언제 생성하는지&lt;br /&gt;vue에서 dom이뭐임&lt;br /&gt;RAG란&lt;br /&gt;왜 벡터DB 썻는지&amp;nbsp;&lt;br /&gt;레디스는 몇개의 쓰레드쓰는지 왜 그렇게하는건지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 트랜잭션 격리수준 -&amp;gt; private인건 트랜잭션이 적용 될까요 안될까요&lt;br /&gt;DB 락&amp;nbsp;&lt;br /&gt;DB 인덱스는 뭔지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 트리구조?&lt;br /&gt;B+트리 B트리&lt;br /&gt;인덱스 삭제하면 왜 느려짐?&lt;br /&gt;모든걸 인덱스하면 조회가 빨라질까?&lt;br /&gt;스프링 배치 방법 / 청크나눠 올리는 중에 삭제되면 어떻게 처리될지&lt;br /&gt;스프링 DI/AOP/IOC 최대한 자세히 + 어노테이션&amp;nbsp;&lt;br /&gt;스프링은 예외처리 어떻게 처리할까요?&amp;nbsp;&lt;br /&gt;N+1 해결 방법들&amp;nbsp;&lt;br /&gt;정합성 보장 방법&lt;br /&gt;SSE알림 어떻게 구현했는지&lt;br /&gt;스프링에서 API들어올때 어떤식으로 들어오는지 처음부터 최대한 자세히 과정 설명.&amp;nbsp;&lt;br /&gt;디자인 패턴 아는거&lt;br /&gt;디자인 패턴중 싱글톤에대해서&amp;nbsp;&lt;br /&gt;싱글톤 생성방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리는 리스트가 아님&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리의 재정렬과 리스트의 재정렬을 착각하면 안됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Archive(완료된 내용)/재취업준비</category>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/89</guid>
      <comments>https://i-source.tistory.com/89#entry89comment</comments>
      <pubDate>Tue, 24 Mar 2026 21:18:20 +0900</pubDate>
    </item>
    <item>
      <title>게임 안 NPC와 플레이어가 인스타그램 UI에서 댓글로 상호작용 구현</title>
      <link>https://i-source.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 전체적인 구조 : 언리얼에서는 UVVM을 공부할겸 짜보고 백엔드는 MVC로 구현했음.&amp;nbsp; &lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Mermaid Chart - Create complex, visual diagrams with text.-2026-03-05-024340.png&quot; data-origin-width=&quot;7467&quot; data-origin-height=&quot;5217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4wpXG/dJMcacoH7wM/PKAwSdefZjSWVPVGTG3IH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4wpXG/dJMcacoH7wM/PKAwSdefZjSWVPVGTG3IH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4wpXG/dJMcacoH7wM/PKAwSdefZjSWVPVGTG3IH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4wpXG%2FdJMcacoH7wM%2FPKAwSdefZjSWVPVGTG3IH1%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;7467&quot; height=&quot;5217&quot; data-filename=&quot;Mermaid Chart - Create complex, visual diagrams with text.-2026-03-05-024340.png&quot; data-origin-width=&quot;7467&quot; data-origin-height=&quot;5217&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;-&amp;nbsp;&lt;b&gt;Model&lt;/b&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`UTraningSocialSubsystem` &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;역할:&amp;nbsp;소셜&amp;nbsp;도메인&amp;nbsp;상태&amp;nbsp;보유,&amp;nbsp;서버&amp;nbsp;동기화,&amp;nbsp;댓글&amp;nbsp;전송/검증,&amp;nbsp;이벤트&amp;nbsp;발행 &lt;br /&gt;-&amp;nbsp;&lt;b&gt;ViewModel&lt;/b&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;`UTraningInstagramViewModel` &lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;역할:&amp;nbsp;Model&amp;nbsp;이벤트&amp;nbsp;구독,&amp;nbsp;화면용&amp;nbsp;상태&amp;nbsp;재가공,&amp;nbsp;View&amp;nbsp;액션을&amp;nbsp;Model&amp;nbsp;유스케이스로&amp;nbsp;라우팅 &lt;br /&gt;-&amp;nbsp;&lt;b&gt;View&lt;/b&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;- `UTraningInstagramWidgetBase`&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp;역할:&amp;nbsp;사용자&amp;nbsp;입력&amp;nbsp;수집,&amp;nbsp;ViewModel&amp;nbsp;이벤트&amp;nbsp;반영&amp;nbsp;렌더링,&amp;nbsp;비즈니스&amp;nbsp;로직&amp;nbsp;비보유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. 페르소나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 벡터 DB에 넣을 수 있겠지만 Test용으로 직접 부여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. 알림 기능은 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 미구현 허나 sse나 웹소켓쓰면될듯&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. 요청 개많으면 어떻게 처리함?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 큐에 넣고 순서대로 처리&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;a href=&quot;https://www.youtube.com/watch?v=e4_lrz4bxSs&amp;amp;feature=youtu.be&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=e4_lrz4bxSs&amp;amp;feature=youtu.be&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=e4_lrz4bxSs&quot; data-video-width=&quot;0&quot; data-video-height=&quot;0&quot; data-video-origin-width=&quot;0&quot; data-video-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;인게임 LLM AI  활용&quot; data-video-thumbnail=&quot;&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/e4_lrz4bxSs&quot; width=&quot;0&quot; height=&quot;0&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Archive(완료된 내용)/포트폴리오 강화</category>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/88</guid>
      <comments>https://i-source.tistory.com/88#entry88comment</comments>
      <pubDate>Thu, 5 Mar 2026 13:53:52 +0900</pubDate>
    </item>
    <item>
      <title>MVVM UI</title>
      <link>https://i-source.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모델 - 뷰 - 뷰모델&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼에서 공식 지원하기 시작함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰는 모델을 신결 쓸필요없이 뷰모델한테 달라고하면됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰모델은 뷰의 요청을 모델에서 받아옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; llm api호출 + 비동기 post처리 + 큐에 요청을 담아놓음.&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;llm api호출 -인스타 피드 데이터 저장 -&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;gt; UI에 데이터를 보여줌.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/87</guid>
      <comments>https://i-source.tistory.com/87#entry87comment</comments>
      <pubDate>Fri, 27 Feb 2026 20:50:18 +0900</pubDate>
    </item>
    <item>
      <title>ai에게 일 잘 맡기기</title>
      <link>https://i-source.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 리서치&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 이 폴더를 깊이 읽고 , 어떻게 동작하는지 깊이 이해하고 모든 세부사항을 파악해라. 끝나면 research.md에 상세 보고서를 작성해라.&amp;nbsp; -&amp;gt; 생각해보니 md를 통해 코드 위키를 보고 이해하면 내가 물어보는 토큰을 아낄 수 있다. + 역할 부여하는것도 좋을듯. 어떤 관점에서 기능이 필요한지 ex) 엔진수정이면 엔진 개발자 관점에서 코드이해 같은 또 매우 깊이 상세하게 자세히 가 중요하다고함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 계획&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ~~기능을 작성하려는데&amp;nbsp; 조건1. 조건2. 소스 파일을 읽고 실제 코드 베이스 기반으로 기획을 작성 + Plan.MD도 보고서 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 제일 많이 힘써야하는단계. 내가 놓치는 부분이 없는지 + 고려해야되는데 고려를 못한건 없는지 생각해봐야함. ex) 8진트리로 eqs를 줄이려는데 -&amp;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;그리고 절대 구현 X&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 계획이 잘되어있다면 구현은 기계적으로 하면됨. todo_list를 지우면되기때문에&lt;/p&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>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/86</guid>
      <comments>https://i-source.tistory.com/86#entry86comment</comments>
      <pubDate>Fri, 27 Feb 2026 20:46:35 +0900</pubDate>
    </item>
    <item>
      <title>루트모션 / 모션워핑</title>
      <link>https://i-source.tistory.com/84</link>
      <description>&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;구분 일반 애니메이션 (In-Place) 루트 모션 (Root Motion)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;이동 방식&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;스크립트(가속도/속도)로 캡슐을 이동시킴&lt;/td&gt;
&lt;td&gt;애니메이션의 루트 본 궤적을 따라 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;발 미끄러짐&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;속도와 애니메이션이 안 맞으면 발생함&lt;/td&gt;
&lt;td&gt;발이 땅에 붙어 있는 듯 정교함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;조작성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;즉각적인 반응(정지, 회전)이 유리함&lt;/td&gt;
&lt;td&gt;애니메이션의 길이에 종속되어 반응이 약간 느릴 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;주요 용도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;일반적인 걷기, 뛰기, FPS 게임 등&lt;/td&gt;
&lt;td&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;이를 적용하기 위해서는 DCC툴에서 루트 본에 이동값을 애니메이션 키로 잡아야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;루트 본에 이동값을 키로 잡는다&quot;는 것은 캐릭터가 앞으로 나가는 움직임을 단순히 팔다리만 흔드는 것이 아니라, &lt;b data-index-in-node=&quot;63&quot; data-path-to-node=&quot;1&quot;&gt;최상위 부모인 Root 본 자체가 실제로 좌표 평면 위에서 이동하도록 애니메이션 프레임마다 위치 데이터(Keyframe)를 기록하는 것&lt;/b&gt;을 의미합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트본의 키값은 당연히 로컬 좌표 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size23&quot;&gt;1. 일반 애니메이션 (In-Place)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;Root 본:&lt;/b&gt; 원점 (0, 0, 0)에 고정되어 움직이지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;Pelvis(골반) 본:&lt;/b&gt; 발을 내디디며 몸이 앞으로 나가는 것처럼 보이게 움직이지만, 한 걸음 뒤에는 다시 원점으로 돌아옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0&quot;&gt;결과:&lt;/b&gt; 캐릭터는 트레드밀(런닝머신) 위에서 뛰는 것처럼 제자리걸음을 합니다. 게임 엔진에서는 코드를 통해 캡슐 컴포넌트를 강제로 밀어줘야 이동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;2. 루트 모션 애니메이션 (Root Motion)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;Root 본:&lt;/b&gt; 캐릭터가 발을 내디딜 때마다 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;6,0,0&quot;&gt;Root 본 자체가 앞으로 전진&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;Keyframe:&lt;/b&gt; 0프레임에 (0, 0, 0)이었다면, 30프레임에는 (100, 0, 0) 위치에 가 있도록 위치 값을 입력(Key)합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;결과:&lt;/b&gt; 애니메이션 데이터 안에 &quot;이 캐릭터는 1초 동안 100만큼 전진한다&quot;라는 물리적인 이동 정보가 포함됩니다.&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;/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;/p&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;로컬 to 월드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S-R-T (스케일-&amp;gt; 로테이션-&amp;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;월드 트랜스폼 = 로컬트랜스폼 X 부모 트랜스폼&lt;/p&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;ex) 손의 월드 트랜스폼 = 연결되어있는 Local 핸드 * local 팔꿈치 * &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;local&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 어깨 * &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;local&lt;span&gt; 루트 * &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;local&lt;span&gt; 캡슐 * 월드 아이덴티티&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;언리얼 엔진 내부에서는 이 계산을 매 프레임 수천 번씩 수행해야하므로, 두가지 방식으로 나누어서 관리함.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;1. 컴포넌트 스페이스&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;캐릭터 메쉬의 루트본을(0,0,0)으로 잡고 계산한 방식&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손-&amp;gt;팔꿈치-&amp;gt;어깨-&amp;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;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;2. 월드 스페이스&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;위에서 구한 결과에 마지막으로 캡슐의 월드 트랜스폼을 곱하는 단계&lt;/span&gt;&lt;/span&gt;&lt;/span&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,0,0&quot;&gt;World = Local * Parent&lt;/b&gt; (자식 먼저, 부모 나중)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,1,0&quot;&gt;Local = World * Parent_Inverse&lt;/b&gt; (월드 먼저, 부모 역행렬 나중)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션에서 루트 모션값을 추출해 충돌 판정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExtractRootMotionFromTrackRange를 통해 델타 추출가능함.&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 href=&quot;https://youtu.be/45pgq5Re7hQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/45pgq5Re7hQ&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=45pgq5Re7hQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/Mnzge/dJMb9aKCim4/YhhuEoIjaeWXdP9BYqqzF1/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/NAECg/dJMb8SpE7zc/t1yORlIkmzkTbE3yT7O2TK/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/cJtioq/dJMb8T9WCZI/kQIN51k9KOMC6rwr5EE040/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;루트모션 미리 예측하기&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/45pgq5Re7hQ&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Archive(완료된 내용)/재취업준비</category>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/84</guid>
      <comments>https://i-source.tistory.com/84#entry84comment</comments>
      <pubDate>Thu, 26 Feb 2026 15:34:13 +0900</pubDate>
    </item>
    <item>
      <title>EQS</title>
      <link>https://i-source.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;EQS구성요소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CoverNode 동작 시뮬레이션 구현하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EQS의 3요소 정리&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;Generator (생성기)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 후보지(Items)들을 생성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;Actors:&lt;/b&gt; 주변의 특정 액터들을 후보로 지정.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;Simple Grid:&lt;/b&gt; AI 주변에 일정한 간격으로 점(Point)들을 생성.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;Donut:&lt;/b&gt; 도넛 모양의 범위 안에 점들을 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;Context (맥락)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;기준이 되는 대상을 정의합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;Querier:&lt;/b&gt; 쿼리를 수행하는 AI 자신.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;Player:&lt;/b&gt; 플레이어의 위치.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;Environmental:&lt;/b&gt; 특정 아이템이나 위치.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;Tests (테스트)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;생성된 후보지들을 필터링하고 점수를 매깁니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;Distance:&lt;/b&gt; 기준점으로부터의 거리.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;Trace:&lt;/b&gt; 시야(Line of Sight)가 확보되는지 여부.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;Pathfinding:&lt;/b&gt; 해당 위치까지 실제로 이동 가능한지 여부.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,3,0&quot;&gt;Dot Product:&lt;/b&gt; AI가 특정 방향을 바라보고 있는지 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;1. &quot;메모리 여기저기 흩어져 있으면 왜 느릴까?&quot; (Cache Locality)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;CPU는 생각보다 똑똑하지 않아서, 메모리(RAM)에서 데이터를 직접 하나씩 가져오면 너무 느려집니다. 그래서 **캐시(L1, L2, L3 Cache)**라는 아주 빠른 임시 보관소를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;캐시 라인(Cache Line):&lt;/b&gt; CPU가 메모리에서 데이터를 가져올 때, 딱 필요한 '4바이트'만 가져오는 게 아니라 그 주변 데이터(보통 64바이트)를 &lt;b data-index-in-node=&quot;87&quot; data-path-to-node=&quot;4,0,0&quot;&gt;뭉텅이로&lt;/b&gt; 한꺼번에 가져옵니다. &quot;이거 썼으니까 바로 옆에 있는 것도 쓰겠지?&quot;라고 추측하는 거죠.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;연속된 메모리 (빠름):&lt;/b&gt; 3,000개의 위치 정보가 배열(TArray)에 FVector 형태(단순 구조체)로 쭈욱 붙어 있다면, CPU는 한 번 메모리에 접근해서 수십 개의 위치 정보를 캐시에 담아 광속으로 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0&quot;&gt;흩어진 메모리 (느림, Cache Miss):&lt;/b&gt; 하지만 AActor* 포인터 3,000개는 다릅니다. 각 액터 객체는 월드에 스폰될 때 메모리의 **서로 다른 위치(Heap)**에 생성됩니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;4,2,1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;1번 액터 위치 보러 메모리 갔다 옴 (캐시 채움)&lt;/li&gt;
&lt;li&gt;2번 액터 위치 보러 갔더니 캐시에 없음 &amp;rarr; 다시 메모리 멀리 갔다 옴 (&lt;b data-index-in-node=&quot;41&quot; data-path-to-node=&quot;4,2,1,1,0&quot;&gt;Cache Miss&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;이 과정을 3,000번 반복하면 CPU 연산 장치는 데이터가 오기만을 기다리며 계속 멍 때리게 됩니다(Stall).&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;이게 바로 **&quot;데이터 지향 설계(Data-Oriented Design)&quot;**에서 말하는 핵심 병목 구간입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;0&quot;&gt;그리드(Grid) 해시 맵&lt;/b&gt;은 복잡한 트리 구조(Octree)보다 구현이 훨씬 간단하면서도, 정적인 데이터를 찾을 때는 **상수 시간(&lt;span data-index-in-node=&quot;74&quot; data-math=&quot;O(1)&quot;&gt;$O(1)$&lt;/span&gt;)**에 가까운 성능을 내는 아주 강력한 기법입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 비유하자면, 월드를 아주 작은 **'우편번호 구역'**으로 나누고, 각 구역(Grid Cell)이라는 보관함에 커버 노드들을 미리 담아두는 방식입니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;2&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size23&quot;&gt;1. 핵심 동작 원리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;공간 분할 (Grid Size 결정):&lt;/b&gt; 월드를 가로, 세로, 높이 일정한 크기(예: 500 unit)의 격자로 나눕니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;데이터 등록 (BeginPlay):&lt;/b&gt; 3,000개의 커버 노드를 순회하며 자신의 위치가 어느 격자에 속하는지 계산하여 저장합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GridIndexX = Floor(Location.X / GridSize)&lt;/li&gt;
&lt;li&gt;GridIndexY = Floor(Location.Y / GridSize)&lt;/li&gt;
&lt;li&gt;이 인덱스들을 조합해 하나의 Key로 만듭니다. (예: FIntVector)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0&quot;&gt;데이터 저장:&lt;/b&gt; TMap&amp;lt;FIntVector, TArray&amp;lt;ACoverNode*&amp;gt;&amp;gt; 형태의 구조체에 담습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;2. 캐릭터가 노드를 찾을 때 (Query)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터가 주변 커버 노드를 찾을 때는 더 이상 3,000개를 뒤지지 않습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;즉시 계산:&lt;/b&gt; 캐릭터의 현재 위치를 GridSize로 나누면 캐릭터가 서 있는 칸의 인덱스가 바로 나옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;주변 탐색:&lt;/b&gt; 캐릭터가 속한 칸과 인접한 8칸(혹은 사거리 내의 칸들)의 Key를 생성해 TMap에서 꺼내옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;결과:&lt;/b&gt; 수천 개가 아니라, 단 몇 명의 이웃 칸에 담긴 &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;7,2,0&quot;&gt;수십 개의 노드&lt;/b&gt;만 즉시 손에 쥐게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;8&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;3. 왜 이게 옥트리나 Overlap보다 빠를까?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;연산의 단순함:&lt;/b&gt; 옥트리는 &quot;이 상자 안에 있나?&quot;를 체크하며 여러 단계를 타고 내려가야 합니다(&lt;span data-index-in-node=&quot;53&quot; data-math=&quot;O(\log N)&quot;&gt;$O(\log N)$&lt;/span&gt;). 하지만 그리드는 단순 나눗셈 한 번이면 위치가 나옵니다(&lt;span data-index-in-node=&quot;96&quot; data-math=&quot;O(1)&quot;&gt;$O(1)$&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;메모리 연속성:&lt;/b&gt; TArray에 담긴 노드들만 루프를 돌면 되므로, 아예 상관없는 멀리 있는 노드들의 메모리는 건드리지도 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;예측 가능성:&lt;/b&gt; 물리 엔진의 Overlap은 내부적으로 동적인 물체(캐릭터, 발사체 등)까지 계산하느라 복잡하지만, 이건 &lt;b data-index-in-node=&quot;67&quot; data-path-to-node=&quot;10,2,0&quot;&gt;정적인 커버 노드 전용&lt;/b&gt;이라 군더더기가 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;11&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;4. 구현 예시 (C++ 의사 코드)&lt;/h3&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiIos39ifKSAxUAAAAAHQAAAAAQzAI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;C++&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 1. 그리드 키 생성 함수
FIntVector GetGridKey(FVector Location) {
    return FIntVector(
        FMath::FloorToInt(Location.X / 500.f),
        FMath::FloorToInt(Location.Y / 500.f),
        0 // 2D 그리드라면 Z는 무시 가능
    );
}

// 2. 데이터 등록 (BeginPlay)
TMap&amp;lt;FIntVector, TArray&amp;lt;ACoverNode*&amp;gt;&amp;gt; CoverGridMap;
for (ACoverNode* Node : AllNodes) {
    CoverGridMap.FindOrAdd(GetGridKey(Node-&amp;gt;GetActorLocation())).Add(Node);
}

// 3. 쿼리 (AI가 노드 찾을 때)
FIntVector MyKey = GetGridKey(AIPathLocation);
TArray&amp;lt;ACoverNode*&amp;gt; Candidates;
// 내 칸과 주변 칸들을 순회하며 수집 (예: -1 ~ +1 범위)
for (int x = -1; x &amp;lt;= 1; ++x) {
    for (int y = -1; y &amp;lt;= 1; ++y) {
        FIntVector NeighborKey = MyKey + FIntVector(x, y, 0);
        if (CoverGridMap.Contains(NeighborKey)) {
            Candidates.Append(CoverGridMap[NeighborKey]);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Archive(완료된 내용)/재취업준비</category>
      <author>오늘은치킨이닭</author>
      <guid isPermaLink="true">https://i-source.tistory.com/83</guid>
      <comments>https://i-source.tistory.com/83#entry83comment</comments>
      <pubDate>Wed, 25 Feb 2026 18:15:10 +0900</pubDate>
    </item>
  </channel>
</rss>