<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>truthgarnet</title>
    <link>https://truthgarnet.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 5 Aug 2026 07:49:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>truthgarnet</managingEditor>
    <image>
      <title>truthgarnet</title>
      <url>https://tistory1.daumcdn.net/tistory/5164551/attach/baea1301c7a74c49b35b7a436013cada</url>
      <link>https://truthgarnet.tistory.com</link>
    </image>
    <item>
      <title>CHIP-8 에뮬레이터 만들기(1)</title>
      <link>https://truthgarnet.tistory.com/114</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;article id=&quot;39641da7-9fda-8025-ae59-ee8cd7a61195&quot; class=&quot;page sans&quot; data-notion-page-icon=&quot; &quot;&gt;&lt;header&gt;&lt;/header&gt;
&lt;div class=&quot;page-body&quot;&gt;
&lt;h2 id=&quot;39641da7-9fda-80ef-a642-fbef933a3f99&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size26&quot;&gt;CHIP-8에 대해 공부하게 된 계기&lt;/h2&gt;
&lt;p id=&quot;39641da7-9fda-800f-81fc-c064a459eea7&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8은 가상 머신에서 실행되는 저수준 명령어 체계입니다. 운영체제를 공부하면서 CPU, 메모리, 레지스터 같은 컴퓨터 구조의 개념을 접하게 되었고, 이를 직접 구현해보며 더 깊게 이해하고 싶어 CHIP-8을 공부하게 되었습니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8023-a661-c1278ca2d86a&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8022-af84-dec8aa3d576c&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8 에뮬레이터라고 부르지만, 실제 하드웨어를 에뮬레이션하는 것이 아니라 CHIP-8 명령어를 해석하고 실행하는 &lt;b&gt;인터프리터를 작성하는 작업&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;h2 id=&quot;39641da7-9fda-803b-ba52-c226c998318a&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size26&quot;&gt;역사&lt;/h2&gt;
&lt;p id=&quot;39641da7-9fda-801f-8aa5-e63b997b01ac&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;1977년 당시 게임을 개발하는 과정은 매우 어려웠습니다. 게임을 만들기 위해서는 CPU가 직접 이해할 수 있는 복잡한 기계어를 사용해야 했기 때문입니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-80b0-9ce7-c6c7d4d98c9e&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;이를 더 쉽게 만들기 위해 RCA의 엔지니어인 조 웨이스베커(Joseph Weisbecker)는 CHIP-8이라는 새로운 실행 환경을 만들었습니다.&lt;/p&gt;
&lt;blockquote id=&quot;39641da7-9fda-809a-ac96-ce7cf322f6e4&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-style=&quot;style1&quot;&gt;여기까지만 들으면 CHIP-8이 프로그래밍 언어라고 생각할 수도 있습니다. 하지만 CHIP-8은 단순한 언어가 아니라 &lt;b&gt;가상의 작은 컴퓨터 시스템&lt;/b&gt;입니다.&lt;/blockquote&gt;
&lt;p id=&quot;39641da7-9fda-80ab-9e02-eef28159a6a3&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-80b7-a959-dd25d76e5d14&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;┌──────────────────┐&lt;br /&gt;│ CHIP-8 │&lt;br /&gt;│ │&lt;br /&gt;│ CPU │&lt;br /&gt;│ 메모리 │&lt;br /&gt;│ 화면 │&lt;br /&gt;│ 키보드 │&lt;br /&gt;└──────────────────┘&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8036-98bd-c82d369c5de2&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;실제 컴퓨터에서는 다음과 같은 구조로 동작합니다.&lt;/p&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js&quot;&gt;&lt;/script&gt;
&lt;pre id=&quot;39641da7-9fda-80d3-99c8-cc84e1a8fd96&quot; class=&quot;code code-wrap angelscript&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;실제 컴퓨터

&amp;darr;

CHIP-8 인터프리터 실행

&amp;darr;

그 안에서 CHIP-8 게임 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-8026-aaeb-d103e25e16d4&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;39641da7-9fda-8056-995f-dbcadb696a7a&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size26&quot;&gt;구성 요소&lt;/h2&gt;
&lt;h3 id=&quot;39641da7-9fda-8021-a986-f86940cc4a89&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size23&quot;&gt;메모리(RAM)&lt;/h3&gt;
&lt;p id=&quot;39641da7-9fda-802a-8d2c-f801e86ef812&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8은 총 4096개의 메모리 공간을 가지고 있습니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-80f4-b42e-dcf393b47d6b&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;이 메모리에는 다음과 같은 데이터가 저장됩니다.&lt;/p&gt;
&lt;ul id=&quot;39641da7-9fda-80b2-9563-d9ddad51b344&quot; class=&quot;bulleted-list&quot; dir=&quot;auto&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;게임&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;39641da7-9fda-805b-96a8-fbdfbaf9c458&quot; class=&quot;bulleted-list&quot; dir=&quot;auto&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;그림&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;39641da7-9fda-8028-818e-ec345cf40c5b&quot; class=&quot;bulleted-list&quot; dir=&quot;auto&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;숫자&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;39641da7-9fda-8068-8c4d-ca0891cc188b&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;즉, 게임 실행에 필요한 모든 정보가 메모리에 저장됩니다.&lt;/p&gt;
&lt;h3 id=&quot;39641da7-9fda-80a5-8717-d487efe7f53b&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size23&quot;&gt;레지스터&lt;/h3&gt;
&lt;p id=&quot;39641da7-9fda-800c-9e82-f373bd6f395d&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8에는 16개의 8비트 범용 레지스터가 존재합니다.&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-8011-b76d-ee0b2253a4d4&quot; class=&quot;code code-wrap mipsasm&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;V0

V1

V2

...

VF&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-80cf-96e9-f94cec45f7da&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;각 레지스터는 개발자가 원하는 값을 저장하는 용도로 사용할 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-80ad-9e0f-f0da870f115e&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-803a-bf4c-cc9e156e4e50&quot; class=&quot;code code-wrap ini&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;V0 = 플레이어 X좌표

V1 = 플레이어 Y좌표

V2 = 점수&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-8065-90a8-c1749fa316d5&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;처럼 사용할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;39641da7-9fda-8059-8c19-f7f7d7791cb7&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size23&quot;&gt;프로그램 카운터(PC)&lt;/h3&gt;
&lt;p id=&quot;39641da7-9fda-80fd-adb3-d431e3d4ae9d&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;프로그램 카운터(Program Counter)는 현재 실행해야 하는 명령어의 위치를 저장합니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-80f3-92b4-e03e0c65ed53&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-80ef-af85-f408f33d7392&quot; class=&quot;code code-wrap ini&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;PC = 200 &lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-804c-aa66-cf63eda33fa9&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;이라면 메모리 주소 200에 있는 명령어를 실행한다는 의미입니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8084-beed-f42264597374&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8의 명령어는 항상 2바이트 크기이기 때문에 명령어를 실행한 후 PC는 다음과 같이 증가합니다.&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-8033-b906-dcb811a4b183&quot; class=&quot;code code-wrap angelscript&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;PC = 200 &amp;rarr; PC = 202&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;39641da7-9fda-803e-ad6d-feeadadeaf02&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size23&quot;&gt;인덱스 레지스터(I)&lt;/h3&gt;
&lt;p id=&quot;39641da7-9fda-80ac-8af5-d797d44b701c&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;인덱스 레지스터 I는 특정 데이터가 저장된 메모리 주소를 가리킬 때 사용합니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8024-9d13-fdf64303d4ba&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 메모리 0x300부터 그림 데이터가 저장되어 있다고 가정해봅시다.&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-80ca-aef5-cf2d8e63752e&quot; class=&quot;code code-wrap angelscript&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;주소

0x300   11110000

0x301   10010000

0x302   10010000

0x303   11110000&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-80e7-bdc7-ef7e0c3a658f&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;컴퓨터는 그림이 어디에 저장되어 있는지 알아야 화면에 출력할 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8093-b894-de874cdd65be&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;따라서,&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-80d3-9e70-e59eb1d89c5e&quot; class=&quot;code code-wrap ini&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;I = 0x300&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-8016-8656-f59a49add475&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;으로 저장하면 I는 그림 데이터가 시작되는 위치를 기억하게 됩니다.&lt;/p&gt;
&lt;h3 id=&quot;39641da7-9fda-80e2-bcd9-ed79e0deded9&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size23&quot;&gt;스택&lt;/h3&gt;
&lt;p id=&quot;39641da7-9fda-80b1-b43d-ddd56b7cdcd2&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 CHIP-8에 스택이 왜 필요한지 의문이 들었습니다. 하지만 함수 호출 과정을 이해하면서 스택이 중요한 역할을 한다는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8005-adf9-f9c5f497358f&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 슈팅 게임의 실행 흐름이 다음과 같다고 가정해봅시다.&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-8076-b2d3-e787fd93145f&quot; class=&quot;code code-wrap&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;게임 시작

플레이어 이동

적 이동

총알 쏘기

점수 계산

다음 프레임&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-807d-8ea5-c1d66758e931&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;총알을 발사하는 과정에서는 여러 작업이 필요합니다.&lt;/p&gt;
&lt;ul id=&quot;39641da7-9fda-8016-8447-cccf4bdc0fd7&quot; class=&quot;bulleted-list&quot; dir=&quot;auto&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;총알 위치 정하기&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;39641da7-9fda-80c0-b617-cd6ba0787cec&quot; class=&quot;bulleted-list&quot; dir=&quot;auto&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;총알 방향 정하기&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;39641da7-9fda-8019-a569-c077126a7dbf&quot; class=&quot;bulleted-list&quot; dir=&quot;auto&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;총알 효과음 재생하기&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;39641da7-9fda-80d8-a3ea-edb64329e878&quot; class=&quot;bulleted-list&quot; dir=&quot;auto&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;총알 애니메이션 재생하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;39641da7-9fda-80db-9ce3-ec82d5454d6b&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8001-a541-c6014b0b1205&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;이 작업들을 하나의 함수로 만들어 관리할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-80cf-8393-dc95b1d66bda&quot; class=&quot;code code-wrap http&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;0x500: ShootBullet()

- 총알 생성
- 위치 설정
- 방향 설정
- 효과음 재생
- 종료&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-806d-ac8d-c94c1500145d&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;게임을 실행중:&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-8099-a8c6-c3465fc7c29a&quot; class=&quot;code code-wrap angelscript&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;0x200 게임 시작

0x202 플레이어 이동

0x204 적 이동

0x206 ShootBullet() 호출 &amp;larr; 현재 실행 중

0x208 점수 계산

0x20A 다음 프레임&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-80f0-a489-c8e57897d3fa&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;현재 명령어가 0x206이라면 ShootBullet() 함수가 있는 0x500으로 이동합니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-801e-9a59-c19508b97862&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;하지만 함수 실행이 끝난 후 어디로 돌아가야 하는지 알아야 합니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8027-bcfb-fc2c86020426&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;그래서 함수 호출 전에 돌아갈 주소를 스택에 저장합니다.&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-8063-bc8b-f1b589575661&quot; class=&quot;code code-wrap angelscript&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;스택

┌────────┐
│0x208   │
└────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-8029-b7d9-e45622832c8e&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;함수가 종료되면 스택에서 0x208을 꺼내 다시 실행을 이어갑니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-80fe-a340-e3fe657d4a46&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;39641da7-9fda-80f9-b5fe-d3e77335f13d&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size23&quot;&gt;화면&lt;/h3&gt;
&lt;p id=&quot;39641da7-9fda-80c9-a9ec-fe4422261fe0&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8의 화면 크기는 64x32 픽셀입니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-801d-b825-f74d64cb13e2&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;각 픽셀은 0과 1로 표현되며, 흑백 화면으로 출력됩니다.&lt;/p&gt;
&lt;h3 id=&quot;39641da7-9fda-80d5-9c53-cfccabbeca29&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size23&quot;&gt;키패드&lt;/h3&gt;
&lt;p id=&quot;39641da7-9fda-808e-9c96-c1521ab424a9&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8은 16개의 버튼을 가지고 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfEI6g/dJMcadiuxzc/Od5QRgDpzkzIpIDUM1O7WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfEI6g/dJMcadiuxzc/Od5QRgDpzkzIpIDUM1O7WK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfEI6g/dJMcadiuxzc/Od5QRgDpzkzIpIDUM1O7WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfEI6g%2FdJMcadiuxzc%2FOd5QRgDpzkzIpIDUM1O7WK%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre id=&quot;39641da7-9fda-806c-91ac-d589c4e3da9c&quot; class=&quot;code code-wrap angelscript&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;// COSMAC VIP 키패드
1 2 3 C

4 5 6 D

7 8 9 E

A 0 B F

// 최신 PC 키패드
1 2	3	4
Q	W	E	R
A	S	D F
Z	X	C	V&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-80da-b062-e3a199e757de&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;게임에서는 다음과 같이 입력을 확인합니다.&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-80b6-8441-d6c997cfd0b7&quot; class=&quot;code code-wrap 1c&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;&quot;5번 버튼 눌렸나?&quot;

&quot;A 버튼 눌렸나?&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-80a0-a26f-cbf2607ea8ea&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;39641da7-9fda-8088-b46a-fd3713469f0c&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size26&quot;&gt;게임 실행&lt;/h2&gt;
&lt;p id=&quot;39641da7-9fda-8027-bc77-c4cfc4555cfd&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8 인터프리터는 아래 과정을 초당 수백 번 반복합니다.&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-8086-ae57-ef1ed23f35e5&quot; class=&quot;code code-wrap&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;명령 읽기

&amp;darr;

무슨 뜻인지 해석

&amp;darr;

실행

&amp;darr;

다음 명령&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-807e-93bb-c59b92378503&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 반복하면서 게임이 실행됩니다.&lt;/p&gt;
&lt;h3 id=&quot;39641da7-9fda-80b7-879d-cc9269ac91a7&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size23&quot;&gt;화면에 그림&lt;/h3&gt;
&lt;p id=&quot;39641da7-9fda-8097-bd38-f68fc3665394&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;CHIP-8에서는 그림을 스프라이트(Sprite)라는 작은 데이터 형태로 저장합니다.&lt;/p&gt;
&lt;p id=&quot;39641da7-9fda-8016-a9e4-f7719af1361f&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre id=&quot;39641da7-9fda-80d0-b1bc-cfa9b88d8e95&quot; class=&quot;code code-wrap angelscript&quot; data-notion-code-syntax=&quot;powershell&quot;&gt;&lt;code&gt;11110000

10010000

10010000

11110000

이라는 데이터가 있다면 화면에는 다음과 같이 출력됩니다.

■■■■

■□□■

■□□■

■■■■&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;39641da7-9fda-8020-afcd-cfa9ca13badb&quot; class=&quot;&quot; dir=&quot;auto&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 작은 픽셀 데이터 조합을 이용해 캐릭터와 게임 요소를 표현합니다.&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/article&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;</description>
      <category>Project/토이프로젝트</category>
      <category>chip-8</category>
      <category>Emulator</category>
      <category>Java</category>
      <category>OS</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/114</guid>
      <comments>https://truthgarnet.tistory.com/114#entry114comment</comments>
      <pubDate>Wed, 15 Jul 2026 10:35:35 +0900</pubDate>
    </item>
    <item>
      <title>Trust anchor for certification path not found, 원인은 인증서 순서였습니다</title>
      <link>https://truthgarnet.tistory.com/113</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서 로그인이 실패한다는 문의를 받았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 로그를 확인해 보니 다음과 같은 오류가 발생하고 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;javax.net.ssl.SSLHandshakeException:
java.security.cert.CertPathValidatorException:
Trust anchor for certification path not found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSL Handshake 과정에서 인증서 검증에 실패한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 아래의 로그를 살펴보면 다음과 같은 원인도 함께 확인할 수 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Flattened final assist data: 4868 bytes, containing 1 windows, 16 views
2026-07-08 17:33:46.768 25909-26119 ==========...==========          E  ================HttpConnect=====================
2026-07-08 17:33:50.033 25909-26114 nkiosk.app.tes                   W  Accessing hidden method Ljava/lang/invoke/MethodHandles$Lookup;-&amp;gt;&amp;lt;init&amp;gt;(Ljava/lang/Class;I)V (light greylist, reflection)
2026-07-08 17:33:50.125 25909-26114 System.err                       W  **javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.**

2026-07-08 17:33:50.129 25909-26114 System.err                       W  **Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.**
2026-07-08 17:33:50.129 25909-26114 System.err                       W     at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:646)
2026-07-08 17:33:50.129 25909-26114 System.err                       W     at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:495)
2026-07-08 17:33:50.129 25909-26114 System.err                       W     at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:418)
2026-07-08 17:33:50.129 25909-26114 System.err                       W     at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:339)
2026-07-08 17:33:50.129 25909-26114 System.err                       W     at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)
2026-07-08 17:33:50.130 25909-26114 System.err                       W     at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)
2026-07-08 17:33:50.130 25909-26114 System.err                       W     at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:208)
2026-07-08 17:33:50.130 25909-26114 System.err                       W     at com.android.org.conscrypt.ConscryptFileDescriptorSocket.verifyCertificateChain(ConscryptFileDescriptorSocket.java:404)
2026-07-08 17:33:50.130 25909-26114 System.err                       W     at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
2026-07-08 17:33:50.130 25909-26114 System.err                       W     at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:375)
2026-07-08 17:33:50.130 25909-26114 System.err                       W     at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:224)
2026-07-08 17:33:50.130 25909-26114 System.err                       W     ... 30 more
2026-07-08 17:33:50.131 25909-26114 System.err                       W  **Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.**
2026-07-08 17:33:50.131 25909-26114 System.err                       W     ... 41 more
2026-07-08 17:33:50.194 25909-25909 kr.co.sol....SOL_Logger          E  [[LoginActivity]] 로그인 페이지 - 로그인 실패 , result = null
2026-07-08 17:33:50.202 25909-25909 System.err                       W  java.lang.Exception: 사용자 네트워크 연결이 불안정 하거나
2026-07-08 17:33:50.202 25909-25909 System.err                       W  인터넷 서비스 문제 일 수 있습니다.
2026-07-08 17:33:50.202 25909-25909 System.err                       W  인터넷 연결 확인 후 다시 시도해주세요.
2026-07-08 17:33:50.203 25909-25909 System.err                       W     at $4$1.success(LoginActivity.java:189)
2026-07-08 17:33:50.203 25909-25909 System.err                       W     at $8.completedConnection(ApiRequest.java:425)
2026-07-08 17:33:50.203 25909-25909 System.err                       W     at com.w.base.lib.socket.ConnectionManager$ConnectionProcess.onPostExecute(ConnectionManager.java:284)
2026-07-08 17:33:50.203 25909-25909 System.err                       W     at com.w.base.lib.socket.ConnectionManager$ConnectionProcess.onPostExecute(ConnectionManager.java:91)
2026-07-08 17:33:50.204 25909-25909 System.err                       W     at android.os.AsyncTask.finish(AsyncTask.java:695)
2026-07-08 17:33:50.204 25909-25909 System.err                       W     at android.os.AsyncTask.access$600(AsyncTask.java:180)
2026-07-08 17:33:50.204 25909-25909 System.err                       W     at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:712)
2026-07-08 17:33:50.204 25909-25909 System.err                       W     at android.os.Handler.dispatchMessage(Handler.java:106)
2026-07-08 17:33:50.204 25909-25909 System.err                       W     at android.os.Looper.loop(Looper.java:193)
2026-07-08 17:33:50.204 25909-25909 System.err                       W     at android.app.ActivityThread.main(ActivityThread.java:6718)
2026-07-08 17:33:50.204 25909-25909 System.err                       W     at java.lang.reflect.Method.invoke(Native Method)
2026-07-08 17:33:50.205 25909-25909 System.err                       W     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
2026-07-08 17:33:50.205 25909-25909 System.err                       W     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2026-07-08 17:33:50.213 25909-25909 kr.co.sol....SOL_Logger          E  [[LoginActivity]] 사용자 네트워크 연결이 불안정 하거나&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**Trust anchor for certification path not found.**는 인증서 체인을 따라 최종적으로 신뢰할 수 있는 루트 인증기관(Root CA)까지 도달하지 못했다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 클라이언트 입장에서는 &lt;b&gt;이 서버가 정말 신뢰할 수 있는 기관에서 발급받은 인증서인지 끝까지 증명하지 못했다&lt;/b&gt; 라고 판단한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생하기 전, SSL 인증서를 새롭게 발급하여 서버에 적용했던 것이 떠올랐습니다. 혹시 인증서 교체 과정에서 문제가 발생했던 것은 아닌지 웹 호스팅 업체에 문의를 진행했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 등록된 SSL 인증서 체인의 순서가 올바르지 않은 것 같다&lt;/p&gt;
&lt;/blockquote&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;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서 순서가 정말 이렇게까지 중요한 걸까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 인증서를 적용했을 때는 인증서 순서와 상관없이 적용이 되었던 것을 기억했습니다. 그렇다면 무슨 차이가 있길래 또 인증서 순서가 왜 중요한지에 대해 알기 위해 다시 공부해 보기로 했습니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSL 인증서는 어떻게 신뢰를 증명할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSL 인증서의 구조를 이해하기 위해서는 인증기관(CA)이 왜 여러 단계로 나누어져 있는지부터 알아야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희가 해외여행을 간다고 생각 해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입국 심사관들은 &amp;ldquo;대한민국은 입국 허가할 정도로 믿을 만한 나라다.&amp;rdquo;는 사실을 미리 알고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 &lt;b&gt;Root CA&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;ldquo;이 관청은 대한민국이라는 이름으로 여권을 발급할 권한이 있다&amp;rdquo;라는 위임장을 각 구청과 영사관에 줍니다. 이 **위임장이 중간 인증서(Intermediate CA)**입니다.&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;b&gt;서버 인증서(Server Certificate)&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;만약 어느 구청의 위임장이 도난 당하더라도 그 구청의 위임장만 취소하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정부 본청의 도장(Root CA)은 안전하게 남습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 SSL 인증서도 같은 이유로 Root CA가 Intermediate CA를 발급하고, Intermediate CA가 다시 Server Certificate를 발급하는 계층 구조를 사용합니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인증서는 어떻게 검증될까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여권에는 이런 내용이 적혀 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 여권은 강남구청에서 발급했습니다.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 입국 심사관은 강남구청을 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심사관이 신뢰하는 건 오직 대한민국 정부뿐입니다.&lt;/p&gt;
&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;pre class=&quot;gcode&quot;&gt;&lt;code&gt;여권 확인
&amp;darr;
강남구청이 발급했네
&amp;darr;
강남구청 위임장을 확인
&amp;darr;
대한민국 정부가 서명했네
&amp;darr;
대한민국 정부는 내가 이미 가지고 있는 신뢰 목록(Trust Store)에 있어
&amp;darr;
통과
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 인증서 체인(Certificate Chain) 검증이라고 합니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;중간 인증서가 없다면?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여권만 제출하고 위임장을 빠뜨리면 어떤 일이 발생할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심사관은 이렇게 생각할 것입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강남구청? 진짜 권한이 있는 기관인지 내가 어떻게 알아?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결고리(chain)가 끊어져 입국 거부가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 불완전한 인증서 체인(Incomplete Certificate Chain)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 다음과 같은 오류를 자주 볼 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;smalltalk&quot;&gt;&lt;code&gt;&quot;Certificate chain incomplete&quot;
&quot;Unable to verify the first certificate&quot;
&quot;Certificate not trusted&quot;
&quot;SSL certificate problem: unable to get local issuer certificate&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 서버 인증서만 전달하고 중간 인증서를 전달하지 않으면 클라이언트는 루트 인증기관까지 연결되는 체인을 만들 수 없기 때문에 인증서를 신뢰하지 못하게 됩니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그렇다면 인증서 순서는 왜 중요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심사관은 다음 순서로 확인 합니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;여권
&amp;darr;
위임장
&amp;darr;
정부
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데,&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;위임장
&amp;darr;
여권
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순으로 제출하게 되면 어떻게 될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유능한 심사관(관대한 클라이언트)이라면, 서류가 뒤섞여 있어도 알아서 정리해서 읽어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 최신 브라우저나 OpenSSL, 일부 Android 환경은 인증서를 재조합하여 올바른 체인을 찾아주는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 원칙대로 검증하는 심사관(엄격한 클라이언트)은 순서가 올바르지 않으면 반려할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 오래된 Android나 Java환경은 서버가 전달한 인증서 순서를 그대로 검증하기 때문에 체인 순서가 올바르지 않으면 인증서를 신뢰하지 못하는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 대부분의 서버는 다음 순서대로 인증서를 구성하는 것이 권장됩니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Server Certificate
&amp;darr;
Intermediate Certificate
&amp;darr;
(Root CA는 일반적으로 포함하지 않음)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오래된 기기에서는 왜 더 자주 발생할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 오래된 공항은 최신 정부 목록을 가지고 있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새롭게 신뢰받기 시작한 정부가 있다면 그 정부를 아직 모를 수도 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 구청은&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 정부의 위임장뿐만 아니라, 오래전부터 모두가 신뢰하던 정부의 보증도 함께 받아 둡니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 공항은 새로운 정부를 보고 통과시키고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래된 공항은 예전 정부를 보고 통과시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSL에서는 이러한 방식을 교차 서명(Cross Signing)이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 오래된 운영체제나 브라우저에서도 동일한 인증서를 계속 사용할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이번 장애를 통해 배운 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 일을 겪으면서 인증서는 단순히 &amp;ldquo;발급만 받으면 끝&amp;rdquo;인 것이 아니라는 사실을 다시 한번 느끼게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서 자체가 정상이라 하더라도 인증서 체인이 올바르게 구성되어 있는지, 중간 인증서가 함께 전달되는지, 체인의 순서가 적절한지에 따라 일부 환경에서는 SSLHandshake가 실패할 수 있습니다.&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>코딩/☠ Error</category>
      <category>certificate</category>
      <category>https</category>
      <category>SSL</category>
      <category>sslhandshakeexception</category>
      <category>Troubleshooting</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/113</guid>
      <comments>https://truthgarnet.tistory.com/113#entry113comment</comments>
      <pubDate>Sat, 11 Jul 2026 10:59:12 +0900</pubDate>
    </item>
    <item>
      <title>Windows 환경에서 클로드 작업 알림 설정(Hook연동)</title>
      <link>https://truthgarnet.tistory.com/112</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code로 긴 작업을 실행해 두고 다른 일을 하다 보면 작업이 끝난 것을 놓치는 경우가 종종 있습니다. 특히 AI가 사용자의 입력을 기다리거나 작업을 완료했는데도 한참 뒤에 확인하게 되는 경우가 생기곤 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Windows의 &lt;b&gt;Toast Notification&lt;/b&gt;을 이용해 Claude Code가 작업을 완료하거나 제 응답을 기다리는 순간 알림을 받을 수 있도록 설정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;BurntToast&lt;/b&gt; 모듈을 이용해 Windows 알림을 생성하는 방법과, Claude Code의 &lt;b&gt;Hook 기능&lt;/b&gt;과 연동하여 작업 완료 및 사용자 입력 요청 시 알림을 표시하는 방법을 정리해 보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BurnToast 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BurntToast는 PowerShell에서 Windows Toast Notification을 쉽게 생성할 수 있도록 만들어진 모듈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Windows 10&lt;/b&gt;과 &lt;b&gt;Windows Server 2019&lt;/b&gt; 이상에서 사용할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://github.com/Windos/BurntToast&quot;&gt;https://github.com/Windos/BurntToast&lt;/a&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1783405750307&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Windos/BurntToast: Module for creating and displaying Toast Notifications on Microsoft Windows 10.&quot; data-og-description=&quot;Module for creating and displaying Toast Notifications on Microsoft Windows 10. - Windos/BurntToast&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Windos/BurntToast&quot; data-og-url=&quot;https://github.com/Windos/BurntToast&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/diOs21/dJMb85v0Abz/kKftmekJlo3PT31UtT7sOk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bmcOoL/dJMb9c9Jj8k/gS70stJTmO6M1J4YkkRFk0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/Windos/BurntToast&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Windos/BurntToast&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/diOs21/dJMb85v0Abz/kKftmekJlo3PT31UtT7sOk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bmcOoL/dJMb9c9Jj8k/gS70stJTmO6M1J4YkkRFk0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Windos/BurntToast: Module for creating and displaying Toast Notifications on Microsoft Windows 10.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Module for creating and displaying Toast Notifications on Microsoft Windows 10. - Windos/BurntToast&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PowerShell 사용해서 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Install-Module -Name BurntToast -Force&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;Chocolatey 사용해서 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;choco install burnttoast-psmodule
&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BurntToast의 모든 알림은 New-BurntToastNotification 명령으로 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 예시는 다음과 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;버튼 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Toast Notification에는 버튼도 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 블로그로 이동하는 버튼을 만들어 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$MyBlog = New-BTButton -Content &quot;Go to MyBlog&quot; -Arguments &quot;&amp;lt;https://truthgarnet.tistory.com/&amp;gt;&quot;

New-BurntToastNotification `
    -Text &quot;제 블로그에 환영합니다.&quot; `
    -Button $MyBlog&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S3Sg7/dJMcaf8e4bi/MTSyj5NUloLw9Y4kckEhU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S3Sg7/dJMcaf8e4bi/MTSyj5NUloLw9Y4kckEhU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S3Sg7/dJMcaf8e4bi/MTSyj5NUloLw9Y4kckEhU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS3Sg7%2FdJMcaf8e4bi%2FMTSyj5NUloLw9Y4kckEhU0%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;390&quot; height=&quot;198&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로고 변경하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 아이콘 대신 원하는 이미지를 사용할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-AppLogo 옵션에 이미지 경로를 지정하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Import-Module BurntToast

New-BurntToastNotification `
    -Text &quot;ClaudeCode&quot;, &quot;정신차려 이 각박한 세상 속에서&quot; `
    -Sound Default `
    -AppLogo &quot;C:\Desktop\HybridSamySosaRioLaby.png&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drwiVf/dJMcaf8e4bW/W7aRjaODKKErPd5ElIDG0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drwiVf/dJMcaf8e4bW/W7aRjaODKKErPd5ElIDG0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drwiVf/dJMcaf8e4bW/W7aRjaODKKErPd5ElIDG0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrwiVf%2FdJMcaf8e4bW%2FW7aRjaODKKErPd5ElIDG0K%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;390&quot; height=&quot;153&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사운드 추가&lt;/h3&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;New-BurntToastNotification -Text 'Cookies are done' -SnoozeAndDismiss -Sound alarm
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어 말고도 지원하는 다양한 기능은 공식 문서를 참고하면 됩니다.&lt;br /&gt;&lt;a href=&quot;https://github.com/Windos/BurntToast/blob/main/Help/New-BurntToastNotification.md&quot;&gt;https://github.com/Windos/BurntToast/blob/main/Help/New-BurntToastNotification.md&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1783405863939&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;BurntToast/Help/New-BurntToastNotification.md at main &amp;middot; Windos/BurntToast&quot; data-og-description=&quot;Module for creating and displaying Toast Notifications on Microsoft Windows 10. - Windos/BurntToast&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Windos/BurntToast/blob/main/Help/New-BurntToastNotification.md&quot; data-og-url=&quot;https://github.com/Windos/BurntToast/blob/main/Help/New-BurntToastNotification.md&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ctFVqe/dJMb84YahgY/YMZAA70fnaP8naNmNLb9m0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cIJRSC/dJMb8XShrqB/neotCk6vbFy2OWUQGOgTV0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/Windos/BurntToast/blob/main/Help/New-BurntToastNotification.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Windos/BurntToast/blob/main/Help/New-BurntToastNotification.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ctFVqe/dJMb84YahgY/YMZAA70fnaP8naNmNLb9m0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cIJRSC/dJMb8XShrqB/neotCk6vbFy2OWUQGOgTV0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;BurntToast/Help/New-BurntToastNotification.md at main &amp;middot; Windos/BurntToast&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Module for creating and displaying Toast Notifications on Microsoft Windows 10. - Windos/BurntToast&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;799&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh2ukd/dJMcaaFZwVo/0QrqqIWmkqtd0KAaPSbZk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh2ukd/dJMcaaFZwVo/0QrqqIWmkqtd0KAaPSbZk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh2ukd/dJMcaaFZwVo/0QrqqIWmkqtd0KAaPSbZk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh2ukd%2FdJMcaaFZwVo%2F0QrqqIWmkqtd0KAaPSbZk0%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;1103&quot; height=&quot;799&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;799&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자주 발생하는 오류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BurntToast를 처음 설치하면 다음과 같은 오류를 만날 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Import-Module : 이 시스템에서 스크립트를 실행할 수 없으므로 ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;New-BurntToastNotification :
명령을 찾을 수 없습니다.

New-BurntToastNotification : 'New-BurntToastNotification' 명령이 'BurntToast' 모듈에 있지만 모듈을 로드할 수 없습니다. 
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 &lt;b&gt;PowerShell Execution Policy&lt;/b&gt; 때문에 발생하는 문제입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재 PowerShell 세션에서만 허용&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재 사용자만 변경&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 변경한 뒤 다시 Import-Module BurntToast를 실행하면 정상적으로 동작합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Claude Code Hook에 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BurntToast의 기본적인 사용법을 확인했으니, Claude Code의 Hook 기능과 함께 사용해볼 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 다음 두 가지 이벤트에 알림을 연결했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Stop&lt;/b&gt; : Claude가 작업을 완료했을 때&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PermissionRequest&lt;/b&gt; : Claude가 사용자 입력을 기다릴 때&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;Cluade Code에서 &lt;b&gt;/hooks &lt;/b&gt;을 선택해서 Hooks Stop Hook 메뉴를 열면 다음과 같은 화면이 등장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehMEOf/dJMcahLN9YB/CpGSADSGTLiPU4w88kj3zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehMEOf/dJMcahLN9YB/CpGSADSGTLiPU4w88kj3zK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehMEOf/dJMcahLN9YB/CpGSADSGTLiPU4w88kj3zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FehMEOf%2FdJMcahLN9YB%2FCpGSADSGTLiPU4w88kj3zK%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;1106&quot; height=&quot;154&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1106&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FkxmA/dJMcahE0wrE/QDmfsGKaJuf9iIJoUUlNKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FkxmA/dJMcahE0wrE/QDmfsGKaJuf9iIJoUUlNKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FkxmA/dJMcahE0wrE/QDmfsGKaJuf9iIJoUUlNKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFkxmA%2FdJMcahE0wrE%2FQDmfsGKaJuf9iIJoUUlNKK%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;1061&quot; height=&quot;214&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X9mPZ/dJMcahE0wrP/WUXcuIadGVIvhDjVyhX2Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X9mPZ/dJMcahE0wrP/WUXcuIadGVIvhDjVyhX2Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X9mPZ/dJMcahE0wrP/WUXcuIadGVIvhDjVyhX2Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX9mPZ%2FdJMcahE0wrP%2FWUXcuIadGVIvhDjVyhX2Kk%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;1063&quot; height=&quot;268&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 &lt;b&gt;Stop Hook&lt;/b&gt;을 찾아서 선택하면, 다음과 같은 안내가 표시됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To modify or remove this hook, edit settings.json directly...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude에게 수정하도록 요청할 수도 있지만, 저는 직접 &lt;b&gt;.vscode/settings.json&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;image.png&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmkMIc/dJMcagMYGKs/xdIWvtkTJctkXBci0pk7lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmkMIc/dJMcagMYGKs/xdIWvtkTJctkXBci0pk7lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmkMIc/dJMcagMYGKs/xdIWvtkTJctkXBci0pk7lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmkMIc%2FdJMcagMYGKs%2FxdIWvtkTJctkXBci0pk7lk%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;1667&quot; height=&quot;239&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Command&lt;/b&gt;를 아래와 같이 변경하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;&quot;command&quot;: &quot;powershell -ExecutionPolicy RemoteSigned -Command \&quot;Import-Module BurntToast; New-BurntToastNotification -Text '{Title}', '{Contnet}' -Sound Default -Sound Default -AppLogo '{이미지절대경로}'\&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아래와 같이 Claude Code의 작업이 끝날 때마다 Windows 알림이 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;388&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLakBj/dJMcai4WZ0l/kQRK5zStRwiuTXqxLQK78k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLakBj/dJMcai4WZ0l/kQRK5zStRwiuTXqxLQK78k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLakBj/dJMcai4WZ0l/kQRK5zStRwiuTXqxLQK78k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLakBj%2FdJMcai4WZ0l%2FkQRK5zStRwiuTXqxLQK78k%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;388&quot; height=&quot;158&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;388&quot; data-origin-height=&quot;158&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;PermissionRequest도 Stop과 같은 방식으로 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PermissionRequest에서 사용할 수 있는 주요 필드는 다음과 같습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 139px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;필드&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;behavior&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&quot;allow&quot;는 권한을 부여,&amp;nbsp;&quot;deny&quot;는 거부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;updatedInput&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&quot;allow&quot;에만 해당: 실행 전에 도구의 입력 매개변수를 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;updatedPermissions&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&quot;allow&quot;에만 해당: 권한 규칙 업데이트를 적용, 사용자가 &quot;항상 허용&quot; 옵션을 선택하는 것과 동일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;message&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&quot;deny&quot;에만 해당: Claude에게 권한이 거부된 이유를 알려줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;interrupt&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&quot;deny&quot;에만 해당:&amp;nbsp;true이면 Claude를 중지&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;Stop과 설정하준 것과 같이 /hooks를 통해 들어가서 PermissionRequest를 찾아줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l7Ddm/dJMcacqfPtu/xYChfAsl1akBc7a5wcnYX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l7Ddm/dJMcacqfPtu/xYChfAsl1akBc7a5wcnYX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l7Ddm/dJMcacqfPtu/xYChfAsl1akBc7a5wcnYX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl7Ddm%2FdJMcacqfPtu%2FxYChfAsl1akBc7a5wcnYX1%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;914&quot; height=&quot;192&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHPHoz/dJMb99UxeSV/s71tMxMe6lVCjH5jaQmmNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHPHoz/dJMb99UxeSV/s71tMxMe6lVCjH5jaQmmNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHPHoz/dJMb99UxeSV/s71tMxMe6lVCjH5jaQmmNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHPHoz%2FdJMb99UxeSV%2Fs71tMxMe6lVCjH5jaQmmNk%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;630&quot; height=&quot;175&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 Command를 &lt;b&gt;.vscode/settings.json&lt;/b&gt;에 입력하면 제가 좋아하는 캐릭터가 등장해 &quot;Choose your life&quot;라는 메세지를 띄어주게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clwObO/dJMcabrjgii/KU9wMtRkzHR1H1mcCjkNG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clwObO/dJMcabrjgii/KU9wMtRkzHR1H1mcCjkNG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clwObO/dJMcabrjgii/KU9wMtRkzHR1H1mcCjkNG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclwObO%2FdJMcabrjgii%2FKU9wMtRkzHR1H1mcCjkNG0%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;384&quot; height=&quot;144&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BurntToast는 단순히 Windows 알림을 띄우는 모듈이지만, Claude Code의 Hook 기능과 함께 사용하면 활용도가 훨씬 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 AI에게 긴 작업을 맡겨두는 경우라면 작업 완료나 사용자 입력 요청을 즉시 확인할 수 있어 생산성이 크게 좋아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows 환경에서 Claude Code를 사용하고 있다면 한 번쯤 설정해 보는 것을 추천합니다.&lt;/p&gt;</description>
      <category>코딩/AI</category>
      <category>claude code</category>
      <category>Hook</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/112</guid>
      <comments>https://truthgarnet.tistory.com/112#entry112comment</comments>
      <pubDate>Tue, 7 Jul 2026 21:49:34 +0900</pubDate>
    </item>
    <item>
      <title>서버 오류(Too many connections)</title>
      <link>https://truthgarnet.tistory.com/111</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;MYSQL Too many connections 문제 발생&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시 대처로 Sleep을 삭제하는 스케쥴러 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;b&gt;Too many connections&lt;/b&gt; 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TLjik/dJMcahxTCJU/EC2d7o7tbIkDKoSKtAwX2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TLjik/dJMcahxTCJU/EC2d7o7tbIkDKoSKtAwX2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TLjik/dJMcahxTCJU/EC2d7o7tbIkDKoSKtAwX2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTLjik%2FdJMcahxTCJU%2FEC2d7o7tbIkDKoSKtAwX2k%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;1337&quot; height=&quot;789&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 connections의 수가 570 ~590으로 윗돌고 있음&lt;/li&gt;
&lt;li&gt;SLEEP되는 connections가 540개 정도 존재many connections의 문제로 그렇다면 DB connection이지 않을 까 생각해서 총 연결할 수 있는 수를 검색, 현재 connections되고 있는 것을 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image2.png&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AiSVX/dJMcacDnKJM/QbKweVvJq4UXN7QqpZxejK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AiSVX/dJMcacDnKJM/QbKweVvJq4UXN7QqpZxejK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AiSVX/dJMcacDnKJM/QbKweVvJq4UXN7QqpZxejK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAiSVX%2FdJMcacDnKJM%2FQbKweVvJq4UXN7QqpZxejK%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;354&quot; height=&quot;117&quot; data-filename=&quot;image2.png&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;임시로 DB에서 직접 명령어를 통해 SLEEP를 삭제&lt;/li&gt;
&lt;li&gt;-- 현재 DB 프로세스 전체 조회 SELECT count(*) FROM information_schema.processlist -- KILL이 붙여진 프로세스 조회 SELECT CONCAT('KILL ', id, ';') FROM information_schema.processlist WHERE command='Sleep';&lt;/li&gt;
&lt;li&gt;root 계정이 없는 관계로 cron을 통해서 cron -e 1시간 마다 10분이상이 된 SLEEP들을 삭제하는 방법으로 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;0 0 * * * mysql -u user -pPW -N -e &quot;SELECT CONCAT('KILL ',id,';') FROM information_schema.processlist WHERE command='Sleep' AND time &amp;gt; 600 AND user='appuser';&quot; | mysql -u user -pPW
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩/☠ Error</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/111</guid>
      <comments>https://truthgarnet.tistory.com/111#entry111comment</comments>
      <pubDate>Thu, 4 Jun 2026 12:55:22 +0900</pubDate>
    </item>
    <item>
      <title>MySQL Too Many Connections 문제와 DBCP(Database Connection Pool)</title>
      <link>https://truthgarnet.tistory.com/110</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 MySQL의 Too many connections 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해 보니 MySQL 내부에 Sleep 상태의 connection이 과도하게 쌓여 있었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 DB가 허용 가능한 최대 connection 수를 초과하면서 장애가 발생한 것으로 보였다.&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;b&gt;DB Connection이 무엇인지&lt;/b&gt;, 그리고 &lt;b&gt;DBCP(Database Connection Pool)&lt;/b&gt; 이 왜 필요한지를 이해해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;DB Connection이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션과 DB 서버는 네트워크를 통해 통신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;애플리케이션이 SQL 요청 전송&lt;/li&gt;
&lt;li&gt;DB 서버가 SQL 실행&lt;/li&gt;
&lt;li&gt;결과 응답 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 반복하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 MySQL은 TCP 기반으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 애플리케이션과 DB 서버는 단순히 데이터를 보내는 것이 아니라 &lt;b&gt;TCP Connection&lt;/b&gt; 을 맺고 통신한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;DB Connection 생성 비용&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP는 연결 지향 프로토콜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 통신 전에 반드시 연결 과정이 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;연결 생성: 3-Way Handshake&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 연결 시:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SYN&lt;/li&gt;
&lt;li&gt;SYN + ACK&lt;/li&gt;
&lt;li&gt;ACK&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정을 거쳐 연결이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 3-Way Handshake라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;연결 종료: 4-Way Handshake&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 종료 시에는:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FIN&lt;/li&gt;
&lt;li&gt;ACK&lt;/li&gt;
&lt;li&gt;FIN&lt;/li&gt;
&lt;li&gt;ACK&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정을 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 4-Way Handshake라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 SQL 요청마다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP 연결 생성&lt;/li&gt;
&lt;li&gt;SQL 실행&lt;/li&gt;
&lt;li&gt;TCP 연결 종료&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;매 요청마다 Handshake 비용이 발생하게 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽이 많아질수록 성능 저하가 커지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, DB 작업 자체보다도 connection 생성/종료 비용이 무시할 수 없게 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;DBCP(Database Connection Pool)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 등장한 것이 DBCP(Database Connection Pool)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBCP는 애플리케이션이 시작될 때 미리 여러 개의 DB Connection을 생성해 두고, 이를 Pool에 보관하여 재사용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;요청 발생
&amp;rarr; Pool에서 Connection 가져오기
&amp;rarr; SQL 실행
&amp;rarr; Connection 반환
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 매번 새로운 TCP 연결을 만드는 것이 아니라, 기존 connection을 재사용한다는 점이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Connection close()의 진짜 의미&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBCP 환경에서 connection.close()는 실제 TCP 연결 종료가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Pool에 Connection 반환(return)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 의미를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;close() 호출 &amp;rarr; Pool로 반환&lt;/li&gt;
&lt;li&gt;실제 TCP 연결 유지 &amp;rarr; 다음 요청에서 재사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;반환하지 않으면 발생하는 문제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 application에서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;connection 반환 누락&lt;/li&gt;
&lt;li&gt;예외 발생 후 close() 미호출&lt;/li&gt;
&lt;li&gt;비정상 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등이 발생하면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pool의 connection이 계속 점유된 상태로 남게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;새로운 요청
&amp;rarr; Pool에 사용 가능한 Connection 없음
&amp;rarr; 새로운 Connection 생성 시도
&amp;rarr; MySQL max_connections 초과
&amp;rarr; Too many connections 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Sleep 상태가 많으면 무조건 문제일까?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 그렇지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서 Sleep 상태는 일반적으로:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;현재 쿼리를 수행하지 않고 대기 중인 idle connection
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBCP는 재사용을 위해 idle connection을 유지하기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정 수준의 Sleep connection은 정상이다.&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;connection 반환 누락&lt;/li&gt;
&lt;li&gt;pool 크기 과다 설정&lt;/li&gt;
&lt;li&gt;트래픽 급증&lt;/li&gt;
&lt;li&gt;장시간 idle connection 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황이 겹치면 Sleep connection이 비정상적으로 증가하게 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;MySQL 주요 설정&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;max_connections&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL이 동시에 허용할 수 있는 최대 connection 수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;max_connections = 600
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라면 최대 100개의 client 연결만 허용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 운영 상황 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;A 서버만 있을 때&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;A 서버
&amp;rarr; Connection 100개 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 없음.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B 서버 증설&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽 증가로 인해 서버를 추가했다고 가정하자.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;A 서버 &amp;rarr; 100개 사용
B 서버 &amp;rarr; 추가 요청 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이미 MySQL의 최대 connection 수가 가득 찬 상태라면:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Too many connections
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 서버를 늘려도 DB의 connection 제한은 그대로이기 때문에 병목이 발생할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;wait_timeout&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wait_timeout은 일정 시간 동안 아무 요청이 없는 idle connection(Sleep 상태)을 MySQL이 언제 끊을지를 결정하는 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;wait_timeout=60
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라면 60초 동안 사용되지 않은 connection은 MySQL이 종료한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;주의할 점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 DBCP가 connection을 계속 재사용하려고 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL이 먼저 connection을 끊어버리면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;59초 동안 idle
&amp;rarr; 갑자기 요청 발생
&amp;rarr; DB는 이미 60초 시점에 connection 종료
&amp;rarr; application 사용 시 exception 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;DBCP 주요 설정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(HikariCP 기준)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;minimumIdle&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pool에서 유지할 최소 idle connection 수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;항상 최소 몇 개는 대기 상태로 유지할 것인가
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 의미한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;maximumPoolSize&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pool이 가질 수 있는 최대 connection 수&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;idle&lt;/li&gt;
&lt;li&gt;active&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;fix&quot;&gt;&lt;code&gt;idle + active &amp;lt;= maximumPoolSize
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;maximumPoolSize를 크게 하면 좋은 걸까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 그렇지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connection 수가 너무 많아지면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB Thread 증가&lt;/li&gt;
&lt;li&gt;Context Switching 증가&lt;/li&gt;
&lt;li&gt;Lock 경합 증가&lt;/li&gt;
&lt;li&gt;메모리 사용량 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 무조건 크게 설정하는 것이 아니라 적절한 값을 찾는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;minimumIdle과 maximumPoolSize&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;minimumIdle = maximumPoolSize
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태를 많이 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽 변화에 따라 connection을 생성/삭제하는 비용을 줄일 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 시스템 특성에 따라 다르게 설정하기도 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;maxLifetime&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pool에서 connection을 재사용할 수 있는 최대 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;DB가 connection을 끊기 전에
Pool이 먼저 안전하게 교체
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하도록 설정하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적으로:&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;maxLifetime &amp;lt; wait_timeout
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 설정한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;connectionTimeout&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pool에서 connection을 얻기 위해 기다리는 최대 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;connectionTimeout = 3000ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3초 동안 connection 획득 대기&lt;/li&gt;
&lt;li&gt;실패 시 exception 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;적절한 Connection 수 찾기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 시스템 부하 테스트와 모니터링을 통해 찾는 수밖에 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;일반적인 튜닝 방법&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 모니터링 환경 구축&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해야 할 것:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB CPU&lt;/li&gt;
&lt;li&gt;DB Memory&lt;/li&gt;
&lt;li&gt;서버 Thread 수&lt;/li&gt;
&lt;li&gt;DBCP 상태&lt;/li&gt;
&lt;li&gt;Active / Idle Connection 수&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 부하 테스트 진행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nGrinder&lt;/li&gt;
&lt;li&gt;JMeter&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등을 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 핵심 지표 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TPS&lt;/li&gt;
&lt;li&gt;RPS(Request Per Second)&lt;/li&gt;
&lt;li&gt;평균 응답 시간(avg response time)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등을 확인한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 병목 지점 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Connection 수를 늘렸을 때:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB CPU 급증&lt;/li&gt;
&lt;li&gt;응답 시간 증가&lt;/li&gt;
&lt;li&gt;lock 증가&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-way handshake&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;SYN 전송: Client -&amp;gt; Server 연갈하고 싶어요
SYN + ACK 전송: Server -&amp;gt; Client 요청 잘 받았고, 나도 연결 가능해요
ACK 전송: 좋아요, 이제 연결합시다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-way handshake&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;FIN 전송: Clinet -&amp;gt; Server 난 이제 보낼 거 없습니다.
ACK 전송: Server -&amp;gt; Client 알겠어요
FIN 전송: SERVER -&amp;gt; CLIEN 나도 이제 끝났습니다.
ACK 전송: Clinet -&amp;gt; Server 확인했습니다 연결 종료
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩/DataBase</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/110</guid>
      <comments>https://truthgarnet.tistory.com/110#entry110comment</comments>
      <pubDate>Thu, 4 Jun 2026 12:51:14 +0900</pubDate>
    </item>
    <item>
      <title>Android AGP 버전 업그레이드</title>
      <link>https://truthgarnet.tistory.com/109</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;전임자가 개발해둔 앱을 인수인계받았는데, 처음부터 쉽지 않았다.&lt;/span&gt;&lt;br /&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;&lt;span&gt;자바와 Gradle 버전을 하나씩 맞춰가며 겨우 앱을 실행시키는 데 성공했다.&lt;/span&gt;&lt;br /&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&gt;바로 &amp;lsquo;16KB 페이지 크기&amp;rsquo; 제한 때문이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q77TH/dJMcagSrPiD/BhKCOREiqrIxukN8E8xfK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q77TH/dJMcagSrPiD/BhKCOREiqrIxukN8E8xfK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q77TH/dJMcagSrPiD/BhKCOREiqrIxukN8E8xfK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq77TH%2FdJMcagSrPiD%2FBhKCOREiqrIxukN8E8xfK1%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;568&quot; height=&quot;439&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;버전 업그레이드 단계 요약&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;600&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #1f4e79;&quot; width=&quot;107&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;단계&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #1f4e79;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;AGP &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #1f4e79;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Gradle &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #1f4e79;&quot; width=&quot;227&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;핵심 변경사항&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;107&quot;&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;단계&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;span&gt;4.x &amp;rarr; 7.x&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;span&gt;6.5 &amp;rarr; 7.5&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;227&quot;&gt;&lt;span&gt;&lt;span&gt;jcenter &lt;/span&gt;&lt;span&gt;제거&lt;span&gt;, support &amp;rarr; androidx &lt;/span&gt;전환&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;107&quot;&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;단계&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;span&gt;7.x &amp;rarr; 8.0&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;span&gt;7.5 &amp;rarr; 8.0&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;227&quot;&gt;&lt;span&gt;&lt;span&gt;namespace &lt;/span&gt;&lt;span&gt;필수화&lt;span&gt;, buildFeatures &lt;/span&gt;추가&lt;span&gt;, flavorDimensions &lt;/span&gt;문법 변경&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;107&quot;&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;단계&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;span&gt;8.0 &amp;rarr; 8.5&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;span&gt;8.0 &amp;rarr; 8.7&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;227&quot;&gt;&lt;span&gt;&lt;span&gt;16KB &lt;/span&gt;&lt;span&gt;정렬 패키징 설정&lt;span&gt; (useLegacyPackaging = false)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;107&quot;&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;단계&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;span&gt;라이브러리&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;133&quot;&gt;&lt;span&gt;&lt;span&gt;CameraX 1.3.4 / ML Kit 16.0.1&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;227&quot;&gt;&lt;span&gt;&lt;span&gt;16KB &lt;/span&gt;&lt;span&gt;지원&lt;span&gt;, API 23 &lt;/span&gt;이상 요구&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;1. &lt;/span&gt;업그레이드 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;OCR(&lt;/span&gt;인쇄물&lt;span&gt;, &lt;/span&gt;필기체&lt;span&gt;, &lt;/span&gt;이미지 내 텍스트를 디지털 텍스트로 변환하는 기술&lt;span&gt;) &lt;/span&gt;기능을 도입하기 위해&lt;span&gt; Google ML Kit&lt;/span&gt;를 사용하려 했으나&lt;span&gt;, &lt;/span&gt;당시 사용 중이던&lt;span&gt; AGP(Android Gradle Plugin) &lt;/span&gt;버전에서는&lt;span&gt; 16KB &lt;/span&gt;패키지를 지원하는&lt;span&gt; ML Kit &lt;/span&gt;최신 버전을 적용할 수 없었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국&lt;span&gt; OCR &lt;/span&gt;기능을 제대로 사용하기 위해서는&lt;span&gt; AGP&lt;/span&gt;를 전반적으로 업데이트해야 했고&lt;span&gt;, &lt;/span&gt;이에 맞춰&lt;span&gt; ML Kit &lt;/span&gt;또한 최신 버전으로 함께 업그레이드하는 작업이 필요했다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;2. &lt;/span&gt;최종 목표&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;ML Kit &lt;/span&gt;최신 버전 도입을 통해&lt;span&gt; Google &lt;/span&gt;콘솔에 업로드 가능한 상태로 전환했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;ML Kit&amp;nbsp;최신 버전&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;Android API 23&amp;nbsp;이상 필요Android API 23&amp;nbsp;이상&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;Java 17&amp;nbsp;이상 필요Java 17&amp;nbsp;이상&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;AGP 8.x&amp;nbsp;이상 필요&amp;nbsp;&amp;there4; AGP 4.x &amp;rarr; 8.5&amp;nbsp;업그레이드가 선행 조건&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단계별 업그레이드 경로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;AGP 4.x &amp;rarr; 7.x : jcenter &lt;/span&gt;종료&lt;span&gt;, support &amp;rarr; androidx &lt;/span&gt;전환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;AGP 7.x &amp;rarr; 8.0 : compileSdkVersion &lt;/span&gt;문법 변경&lt;span&gt;, flavorDimensions &lt;/span&gt;문법 변경&lt;span&gt;, namespace &lt;/span&gt;필수화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;AGP 8.0 &amp;rarr; 8.5 : packaging.jniLibs.useLegacyPackaging = false &lt;/span&gt;설정&lt;span&gt; (16KB &lt;/span&gt;정렬 패키징&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;3. &lt;/span&gt;현재 환경&lt;span&gt; (&lt;/span&gt;업그레이드 전&lt;span&gt;)&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;AGP &lt;/span&gt;버전&lt;span&gt;: 4.x&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Gradle &lt;/span&gt;버전&lt;span&gt;: 6.5&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Java &lt;/span&gt;버전&lt;span&gt;: 8 (VERSION_1_8)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;compileSdkVersion: 30&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;minSdkVersion: 22&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;4. &lt;/span&gt;환경설정&lt;span&gt; &amp;mdash; Android Studio Koala &lt;/span&gt;설치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Android Studio Koala&lt;/span&gt;는 구버전으로 공식 사이트 다운로드 페이지에서는 직접 받을 수 없어&lt;span&gt;&amp;nbsp;&lt;/span&gt;아래 방법으로 다운로드 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이전 버전 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;JetBrains IntelliJ Platform Plugin SDK &lt;/span&gt;문서에서 버전 목록 확인&lt;span&gt;: &lt;/span&gt;&lt;span style=&quot;color: #2e75b6;&quot;&gt;&lt;a href=&quot;https://plugins.jetbrains.com/docs/intellij/android-studio-releases-list.html&quot;&gt;https://plugins.jetbrains.com/docs/intellij/android-studio-releases-list.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;직접 다운로드&lt;span&gt; (Google CDN)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래&lt;span&gt; URL &lt;/span&gt;패턴으로 특정 버전을 직접 다운로드할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1782574108791&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;https://redirector.gvt1.com/edgedl/android/studio/install/{버전}/android-studio-{버전}-windows.exe
 
// 예시
https://redirector.gvt1.com/edgedl/android/studio/install/2024.1.2.13/android-studio-2024.1.2.13-windows.exe&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;5. &lt;/span&gt;작업 과정&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Step 1. AGP 4.x &amp;rarr; AGP 7.x&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 변경사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;a title=&quot; jcenter() 저장소 제거 &amp;rarr; mavenCentral() 사용&quot; href=&quot;https://jfrog.com/blog/jcenter-sunset/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;jcenter() &lt;/span&gt;저장소 제거&lt;span&gt; &amp;rarr; mavenCentral() &lt;/span&gt;사용&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1vdox/dJMcaivUThD/yRoLjhZ4mk9q3n81ur6L5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1vdox/dJMcaivUThD/yRoLjhZ4mk9q3n81ur6L5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1vdox/dJMcaivUThD/yRoLjhZ4mk9q3n81ur6L5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1vdox%2FdJMcaivUThD%2FyRoLjhZ4mk9q3n81ur6L5k%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;904&quot; height=&quot;549&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;com.android.support.* &amp;rarr; androidx.* &lt;/span&gt;라이브러리 전환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;compileSdkVersion 30 &amp;rarr; 35 &lt;/span&gt;상향&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;targetSdkVersion 30 &amp;rarr; 35 &lt;/span&gt;상향&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;build.gradle (&lt;/span&gt;루트&lt;span&gt;)&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782573761686&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;buildscript {
    repositories {
        google()
        mavenCentral()
        // jcenter() 제거 완료 (2021년 서비스 종료)
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.4.2'
    }
}
 
allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
        maven { url 'https://dl.bintray.com/raphaelbussa/maven' }

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;gradle-wrapper.properties&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782573788880&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;lib-build.gradle (&lt;/span&gt;라이브러리 모듈&lt;span&gt;)&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782573804228&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
    compileSdkVersion 35  // 30 &amp;rarr; 35 상향
 
    defaultConfig {
        minSdkVersion 23  //  22 &amp;rarr; 23 (ML Kit 최신 버전 요구사항)
        targetSdkVersion 35
    }
 
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
        // ※ Java 버전은 Step 2에서 17로 상향
    }
}
 
dependencies {
    //  com.android.support.* &amp;rarr; androidx.* 전환 완료
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'androidx.multidex:multidex:2.0.1'
 
    implementation 'com.squareup.okhttp3:okhttp:3.2.0'
    implementation 'com.google.code.gson:gson:2.8.7'
    implementation 'com.auth0.android:jwtdecode:2.0.0'
    implementation 'com.github.bumptech.glide:glide:4.9.0'
    implementation 'org.apache.commons:commons-lang3:3.12.0'
 
    implementation project(path: ':lib_converter')
    implementation project(path: ':lib_http')
 
    constraints.implementation 'androidx.fragment:fragment:1.3.6'
 
    implementation files('libs\\nfcmodule.jar')
    implementation files('libs\\spots-dialog-1.1.aar')
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Step 2. AGP 7.x &amp;rarr; AGP 8.0&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 변경사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;namespace &lt;/span&gt;각 모듈에 명시적 선언 필수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;buildFeatures &lt;/span&gt;블록으로&lt;span&gt; BuildConfig &lt;/span&gt;등 명시적 활성화 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;flavorDimensions &lt;/span&gt;문법 변경&lt;span&gt;: &lt;/span&gt;&lt;span style=&quot;background-color: #f2f2f2; color: #c00000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;flavorDimensions &quot;default&quot; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; &lt;/span&gt;&lt;span style=&quot;background-color: #f2f2f2; color: #c00000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;flavorDimensions = [&quot;default&quot;] &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Java 17&lt;/span&gt;로 버전 상향&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;build.gradle (&lt;/span&gt;루트&lt;span&gt;)&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782573815848&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath &quot;com.android.tools.build:gradle:8.0.0&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;gradle-wrapper.properties&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782573837833&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;각 모듈&lt;span&gt; build.gradle &amp;mdash; namespace &lt;/span&gt;및&lt;span&gt; buildFeatures &lt;/span&gt;추가&lt;/h3&gt;
&lt;pre id=&quot;code_1782573852986&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
    //  AGP 8.0 필수: namespace 명시적 선언
    namespace &quot;com.example.yourmodule&quot;  // 실제 패키지명으로 교체
 
    compileSdk 35
 
    defaultConfig {
        minSdk 23
        targetSdk 35
    }
 
    //  Java 17로 상향 (API 23 이상 요구사항)
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
 
    //  AGP 8.0 필수: BuildConfig 등 부가 기능 명시적 활성화
    buildFeatures {
        buildConfig true    // BuildConfig 클래스 생성 여부
        // viewBinding true  // ViewBinding 사용 시 추가
        // dataBinding true  // DataBinding 사용 시 추가
    }
 
    //  AGP 8.0 문법 변경: 배열 형식으로 선언
    flavorDimensions = [&quot;default&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;namespace&amp;nbsp;적용 대상 모듈&amp;nbsp;&amp;nbsp;&amp;nbsp;:app&amp;nbsp;모듈&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;rarr; BuildConfig&amp;nbsp;생성&amp;nbsp;&amp;nbsp;:lib_base&amp;nbsp;모듈&amp;nbsp;&lt;br /&gt;&amp;rarr; BuildConfig&amp;nbsp;생성&amp;nbsp;&amp;nbsp;:lib_http&amp;nbsp;모듈&amp;nbsp;&lt;br /&gt;&amp;rarr; BuildConfig&amp;nbsp;생성&amp;nbsp;모든 모듈에 개별&amp;nbsp;namespace를 반드시 선언해야 합니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Step 3. AGP 8.0 &amp;rarr; AGP 8.5&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 변경사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;16KB &lt;/span&gt;페이지 정렬 지원을 위한&lt;span&gt; JNI &lt;/span&gt;라이브러리 패키징 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;build.gradle (&lt;/span&gt;루트&lt;span&gt;)&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782573919450&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath &quot;com.android.tools.build:gradle:8.5.1&quot;
    }
}
 
allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
        maven { url 'https://dl.bintray.com/raphaelbussa/maven' }

    }
 
    // Kotlin 1.8+에서 stdlib-jdk7/jdk8이 stdlib에 통합됨
    //    &amp;rarr; 중복 클래스 충돌 방지를 위해 버전 강제 통일
    configurations.all {
        resolutionStrategy.eachDependency { details -&amp;gt;
            if (details.requested.group == 'org.jetbrains.kotlin') {
                details.useVersion '1.8.22'
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;gradle-wrapper.properties&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782573932027&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앱 모듈&lt;span&gt; build.gradle &amp;mdash; 16KB &lt;/span&gt;패키징 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1782573950446&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
    namespace &quot;com.example.app&quot;
    compileSdk 35
 
    defaultConfig {
        minSdk 23
        targetSdk 35
    }
 
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
 
    //  AGP 8.5: 16KB 페이지 정렬 패키징 설정
    //    압축하지 않은 상태로 APK에 포함하되, 16KB 정렬을 유지하는 방식
    packaging {
        jniLibs {
            useLegacyPackaging = false
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Step 4. CameraX + ML Kit &lt;/span&gt;업그레이드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AGP 8.5 &lt;/span&gt;환경 준비 완료 후&lt;span&gt; CameraX&lt;/span&gt;와&lt;span&gt; ML Kit&lt;/span&gt;를&lt;span&gt; 16KB &lt;/span&gt;지원 버전으로 교체합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782573964399&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CameraX dependencies (1.3.4 &amp;mdash; stable, 16KB 지원)
implementation 'androidx.camera:camera-core:1.3.4'
implementation 'androidx.camera:camera-camera2:1.3.4'
implementation 'androidx.camera:camera-lifecycle:1.3.4'
implementation 'androidx.camera:camera-view:1.3.4'
implementation 'androidx.camera:camera-extensions:1.3.4'
 
// ML Kit Text Recognition (16.0.1 &amp;mdash; 16KB 지원, API 23 이상 요구)
implementation 'com.google.mlkit:text-recognition:16.0.1'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;6. &lt;/span&gt;마주한 오류들&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오류&lt;span&gt; 1. android.jar &lt;/span&gt;손상 문제&lt;/h2&gt;
&lt;pre id=&quot;code_1782573974004&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Android resource linking failed
aapt2.exe E  LoadedArsc.cpp:94] RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.
aapt2.exe E  ApkAssets.cpp:149] Failed to load resources table in APK
  'C:\Users\KONG\AppData\Local\Android\Sdk\platforms\android-35\android.jar'.
error: failed to load include path ...\android-35\android.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;android-35&lt;/span&gt;의&lt;span&gt; android.jar&lt;/span&gt;가 손상된 상태로 설치됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;원인&lt;span&gt;: &lt;/span&gt;사내&lt;span&gt; Proxy&lt;/span&gt;의 최대 파일 크기 제한으로 인해 대용량 파일 다운로드 중 손상 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;AGP &lt;/span&gt;버전과&lt;span&gt; android-35 SDK &lt;/span&gt;간의 호환성 충돌도 복합적으로 작용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;단기 해결책&lt;span&gt;: compileSdk&lt;/span&gt;를&lt;span&gt; android-35 &amp;rarr; android-34&lt;/span&gt;로 변경하여&lt;span&gt; Proxy &lt;/span&gt;크기 제한 회피&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;근본 해결책&lt;span&gt;: &lt;/span&gt;사내&lt;span&gt; Proxy &lt;/span&gt;외부 환경&lt;span&gt;(&lt;/span&gt;모바일 핫스팟 등&lt;span&gt;)&lt;/span&gt;에서&lt;span&gt; SDK&lt;/span&gt;를 재다운로드하거나&lt;span&gt;, SDK &lt;/span&gt;관리자에서&lt;span&gt; android-35 &lt;/span&gt;삭제 후 재설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오류&lt;span&gt; 2. Kotlin stdlib &lt;/span&gt;중복 클래스 충돌&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류 메시지&lt;/h3&gt;
&lt;pre id=&quot;code_1782573983514&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; Task :app:checkPlusonepayDebugDuplicateClasses FAILED
  Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found in modules
    kotlin-stdlib-1.8.22 (org.jetbrains.kotlin:kotlin-stdlib:1.8.22) and
    kotlin-stdlib-jdk8-1.6.0 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0)
 
  Duplicate class kotlin.internal.jdk7.JDK7PlatformImplementations found in modules
    kotlin-stdlib-1.8.22 and kotlin-stdlib-jdk7-1.6.0
  ... (이하 동일 패턴 반복)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;span&gt; (&lt;/span&gt;라면 비유&lt;span&gt;)&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1782573992631&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;냄비에 A 라면 세트(건더기 스프 대용량 포함)와 B 라면 세트(건더기 스프 소용량 포함)를
동시에 넣으면 어떤 건더기 스프를 써야 할지 모르는 상황과 같습니다.
 
Kotlin 1.8+부터는 stdlib-jdk7 / stdlib-jdk8의 내용이 kotlin-stdlib에 통합되었는데,
다른 의존성 라이브러리가 구버전 stdlib-jdk7:1.6.0 / stdlib-jdk8:1.6.0을 함께 끌어오면서
같은 클래스가 두 jar에 중복으로 존재해 충돌이 발생합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;span&gt; &amp;mdash; resolutionStrategy &lt;/span&gt;강제 통일&lt;/h3&gt;
&lt;pre id=&quot;code_1782574002654&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle (루트) &amp;mdash; allprojects 블록에 추가
allprojects {
    configurations.all {
        resolutionStrategy.eachDependency { details -&amp;gt;
            if (details.requested.group == 'org.jetbrains.kotlin') {
                details.useVersion '1.8.22'
                // 규칙: kotlin 관련 의존성은 무조건 1.8.22(대용량 통합본)만 사용
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&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;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f2f2f2; color: #c00000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;configurations.all &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= &lt;/span&gt;냄비에 들어오는 모든 재료에 대해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f2f2f2; color: #c00000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;if (group == 'org.jetbrains.kotlin') &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= kotlin &lt;/span&gt;계열 재료라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f2f2f2; color: #c00000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;useVersion '1.8.22' &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= &lt;/span&gt;무조건 통합 대용량&lt;span&gt;(1.8.22)&lt;/span&gt;만 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;7. &lt;/span&gt;보안 체크리스트&lt;/h1&gt;
&lt;pre id=&quot;code_1782574017362&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;아래 항목을 빌드 전 반드시 확인하세요.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;HTTP &amp;rarr; HTTPS &lt;/span&gt;저장소&lt;span&gt; URL&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;maven &lt;/span&gt;저장소&lt;span&gt; URL&lt;/span&gt;은 반드시&lt;span&gt; HTTPS&lt;/span&gt;를 사용해야 합니다&lt;span&gt;. HTTP&lt;/span&gt;는 중간자 공격&lt;span&gt;(MITM)&lt;/span&gt;에 취약하며&lt;span&gt;, &lt;/span&gt;악성 의존성이 빌드에 포함될 수 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782574026349&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 위험 (HTTP)
maven { url 'http://dl.bintray.com/raphaelbussa/maven' }
 
// 안전 (HTTPS)
maven { url 'https://dl.bintray.com/raphaelbussa/maven' }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;jcenter() &lt;/span&gt;저장소 제거 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;jcenter&lt;/span&gt;는&lt;span&gt; 2021&lt;/span&gt;년 서비스 종료 후 더 이상 유지보수되지 않으며&lt;span&gt;, &lt;/span&gt;보안 패치가 제공되지 않습니다&lt;span&gt;. &lt;/span&gt;모든 모듈의&lt;span&gt; repositories &lt;/span&gt;블록에서&lt;span&gt; jcenter()&lt;/span&gt;가 완전히 제거되었는지 확인하세요&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782574044366&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;repositories {
    google()
    mavenCentral()
    // jcenter() 없음 &amp;mdash; 확인 완료
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;minSdk 23 &lt;/span&gt;이상 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;ML Kit &lt;/span&gt;최신 버전은&lt;span&gt; API 23 &lt;/span&gt;미만 디바이스를 지원하지 않습니다&lt;span&gt;. minSdkVersion&lt;/span&gt;이&lt;span&gt; 22 &lt;/span&gt;이하로 남아있으면 빌드는 성공하더라도 런타임에 크래시가 발생할 수 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782574055205&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;defaultConfig {
    minSdk 23  //  22 &amp;rarr; 23으로 변경 필수
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;8. &lt;/span&gt;참고&lt;span&gt;: AGP &lt;/span&gt;버전별 요구사항 대응표&lt;/h1&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;600&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #1f4e79;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;AGP &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #1f4e79;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Gradle &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #1f4e79;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Java &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;버전&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #1f4e79;&quot; width=&quot;240&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;비고&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;4.x&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;6.5&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;Java 8&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;240&quot;&gt;&lt;span&gt;&lt;span&gt;레거시&lt;span&gt;. jcenter &lt;/span&gt;사용&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;7.x&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;7.5&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;Java 11+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;240&quot;&gt;&lt;span&gt;&lt;span&gt;androidx &lt;/span&gt;&lt;span&gt;전환 완료 권장&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;8.0&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;8.0&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;Java 17+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ebf3fb;&quot; width=&quot;240&quot;&gt;&lt;span&gt;&lt;span&gt;namespace &lt;/span&gt;&lt;span&gt;필수&lt;span&gt;, buildFeatures &lt;/span&gt;추가&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;8.5&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;8.7&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;Java 17+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #ffffff;&quot; width=&quot;240&quot;&gt;&lt;span&gt;&lt;span&gt;16KB &lt;/span&gt;&lt;span&gt;정렬 패키징 지원&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&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;</description>
      <category>Project/회사프로젝트</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/109</guid>
      <comments>https://truthgarnet.tistory.com/109#entry109comment</comments>
      <pubDate>Wed, 25 Mar 2026 16:08:51 +0900</pubDate>
    </item>
    <item>
      <title>[0주차] F-Lab 서포터즈: 온라인 킥오프</title>
      <link>https://truthgarnet.tistory.com/107</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서포터즈의 첫 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학생&amp;nbsp;때도&amp;nbsp;안 한&amp;nbsp;서포터즈를&amp;nbsp;지금&amp;nbsp;와서&amp;nbsp;시작하게&amp;nbsp;되었습니다.&amp;nbsp; &lt;br /&gt;3년간 회사 생활을 마치고, 회사와 집만 오가는 반복된 생활 속에서 이번&amp;nbsp;기회에&amp;nbsp;다양한&amp;nbsp;경험과&amp;nbsp;다양한&amp;nbsp;분을&amp;nbsp;만나보고&amp;nbsp;싶다고&amp;nbsp;생각이&amp;nbsp;들었습니다. &lt;br /&gt;처음으로 서포터즈 활동을 하게 되어 '잘할 수 있을까? 어색한 분위기는 어떡하지?'라는 생각에 겁도 났습니다. 하지만, 지금 서포터즈를 하지 않으면 후회할 것 같은 느낌을 받아서 바로 당장 시작하기로 마음먹고 신청하게 되었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘의&amp;nbsp;기분&amp;nbsp;점수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행이도 기분점수를 통해 서포터즈 매니저님인 `Soi`님께서 시작 전에 분위기를 풀어주셨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어색한 분위기를 풀 겸 오늘 서로 각자 기분 점수를 이야기하게 되었고, 퇴근을 못 하신 분들은 2~3점에 머무르셔서 씁쓸한 웃음을 지었고, 저도 전에 야근을 하던 일이 생각이 나 마음속으로 공감하면서 눈물을 흘렸습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIPMc8/btsMOt2hnem/SZZdW7egTcZUT8JB2mvcu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIPMc8/btsMOt2hnem/SZZdW7egTcZUT8JB2mvcu1/img.png&quot; data-alt=&quot;마음 속 저의 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIPMc8/btsMOt2hnem/SZZdW7egTcZUT8JB2mvcu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIPMc8%2FbtsMOt2hnem%2FSZZdW7egTcZUT8JB2mvcu1%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;378&quot; height=&quot;279&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마음 속 저의 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서포터즈 2기 활동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 서포터즈는 다양한 활동을 하는 데요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오프라인 프로그램 참여 부분에서 무박 2일 해커톤에 참여자나 스테프 참여가 가장 관심이 갔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마도 스테프 참여를 하게 되지 않을까 싶습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학습 공간 활동&lt;/li&gt;
&lt;li&gt;개인 블로그 &amp;amp; SNS 공유&lt;/li&gt;
&lt;li&gt;오프라인 프로그램 참여
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오프라인 토요 모각코&lt;/li&gt;
&lt;li&gt;무박 2일 해커톤&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;온라인 프로그램 참여
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미니컨퍼런스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;온라인 정기 미팅&lt;/li&gt;
&lt;li&gt;오프라인 모임&lt;/li&gt;
&lt;/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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 이런 활동이 낯설어 삐걱거리지만, 오랜만에 새로운 도전을 하며 설렘도 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로의 활동도 너무 기대가 되고, 서포터즈 활동을 통해 좋은 추억을 만들고 싶습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 미팅까지 준비할 내용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서포터즈 2기 네이밍&lt;/h3&gt;
&lt;p data-end=&quot;171&quot; data-start=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;F-Lab이 연구실이라는 점에서 떠올린 단어가 &lt;b&gt;Flask&lt;/b&gt;였습니다. 마침 'F'로 시작하기도 하고, 플라스크는 연구실과도 잘 어울리는 이미지라서 &lt;b&gt;F-Lask&lt;/b&gt;라는 이름을 생각해봤습니다. &amp;lsquo;플라스크&amp;rsquo;로 읽고, F-Lask로 기재하면 어떨까 생각했습니다.&lt;/p&gt;
&lt;p data-end=&quot;320&quot; data-start=&quot;173&quot; data-ke-size=&quot;size16&quot;&gt;다른 후보도 있었는데, 연구소니까 &amp;lsquo;연구원&amp;rsquo;을 의미하는 이름을 떠올려 보기도 했습니다. 하지만 &lt;b&gt;Researcher&lt;/b&gt;나 &amp;lsquo;리서쳐&amp;rsquo; 같은 단어는 뭔가 임팩트가 약하기도 하고 귀에 착 붙는 느낌도 부족했습니다. 그래서 결국 &lt;b&gt;F-Lask&lt;/b&gt;가 가장 끌렸습니다.&lt;/p&gt;</description>
      <category>서포터즈</category>
      <category>F-LAB</category>
      <category>서포터즈</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/107</guid>
      <comments>https://truthgarnet.tistory.com/107#entry107comment</comments>
      <pubDate>Wed, 19 Mar 2025 00:18:51 +0900</pubDate>
    </item>
    <item>
      <title>Flyway 적용 과정</title>
      <link>https://truthgarnet.tistory.com/106</link>
      <description>&lt;h2 id=&quot;block-15041da79fda8017a7aedfe701054612&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Flyway에서 MySQL 사용&lt;/h2&gt;
&lt;p id=&quot;block-14641da79fda806ab1cad183ac2c408e&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;flyway에서 MySQL를 연결하기까지 고난을 정리하고자 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;flyway란 오픈소스 데이터베이스 마이그레이션 도구입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실서버에서 데이터베이스의 구조가 변경하면, 위험요소가 너무 큽니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구조가 바뀜으로 서버에 오류가 발생할 수도 있습니다. 그렇기 때문에, Git처럼 형상관리를 하고자 사용하는 툴입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 id=&quot;block-15041da79fda80bd9566cde5f90f79c3&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 라이브러리 문제&lt;/h2&gt;
&lt;h3 id=&quot;block-15141da79fda80118039dccdc4355ded&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;문제 발생&lt;/h3&gt;
&lt;p id=&quot;block-15141da79fda806fb49ce43e2a341ae6&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Caused by: org.flywaydb.core.api.FlywayException: Unsupported Database: MySQL 8.0&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-15141da79fda80cf9d1fde58d7959fa0&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;발생 이유&lt;/h3&gt;
&lt;p id=&quot;block-15141da79fda800a9084fa8409a4139c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Flyway는 특정 버전에서 MySQL의 일부 버전만 지원해서 Flyway와 MySQL의 버전을 맞춰야 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-15141da79fda80b5ae0cc89b4ba82b1f&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;h4 id=&quot;block-15141da79fda80c591f7f8a8e69febed&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. Flyway 버전 변경(❌)&lt;/h4&gt;
&lt;p id=&quot;block-15141da79fda80428c61e010f41280f3&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL 8.0을 사용하고 있는 데,&lt;/p&gt;
&lt;pre id=&quot;code_1733202987511&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flyway -v // flyway 버전 확인&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;block-15141da79fda808d8f68f6fbb1772a05&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ULdTM/btsK4bXxjs9/jj4TCGCeFRzxWvKnANFoc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ULdTM/btsK4bXxjs9/jj4TCGCeFRzxWvKnANFoc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ULdTM/btsK4bXxjs9/jj4TCGCeFRzxWvKnANFoc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FULdTM%2FbtsK4bXxjs9%2Fjj4TCGCeFRzxWvKnANFoc1%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;707.9844&quot; height=&quot;304.8266&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p id=&quot;block-15041da79fda80af8a7aec693e95b28c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공식 사이트에 들어가면, flyway가 제공해 주는 버전이 나옵니다.&lt;/p&gt;
&lt;div id=&quot;block-15141da79fda80a19f8fcbff1e36bfc7&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLZCd/btsK6jF1V1z/ZaYJDopqksi50yPHvYvjH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLZCd/btsK6jF1V1z/ZaYJDopqksi50yPHvYvjH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLZCd/btsK6jF1V1z/ZaYJDopqksi50yPHvYvjH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLZCd%2FbtsK6jF1V1z%2FZaYJDopqksi50yPHvYvjH1%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;707.9219&quot; height=&quot;83.7327&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div id=&quot;block-15141da79fda8076ba47d595442fa3a8&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;917&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJiYtD/btsK4bb6xJ9/XIm0bAstTUnBvWjtxhzslK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJiYtD/btsK4bb6xJ9/XIm0bAstTUnBvWjtxhzslK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJiYtD/btsK4bb6xJ9/XIm0bAstTUnBvWjtxhzslK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJiYtD%2FbtsK4bb6xJ9%2FXIm0bAstTUnBvWjtxhzslK%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;707.9844&quot; height=&quot;793.66956&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;917&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p id=&quot;block-15141da79fda8073bd8bd0a5478bf4e6&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MySQL은 8.0으로 제한된 테스트 기능을 사용할 수 있다고 나옵니다.&lt;/p&gt;
&lt;p id=&quot;block-15141da79fda80fcbb2dc991bf6de3f6&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저는 기존에 8.0으로 사용하고 있어서 문제가 8.0에 있는 줄 알고 업그레이드를 진행했습니다.&lt;/p&gt;
&lt;p id=&quot;block-15141da79fda8036bf4ff342eee36720&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같은 MySQL 버전 변경하는 방법을 거쳐서 변경해 줍니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;span style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;MySQL 버전 변경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div id=&quot;block-15141da79fda80ceb6e5c03c92723539&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;div style=&quot;list-style-type: none;&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;list-style-type: none; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li id=&quot;block-15141da79fda80a49ca4ce661e323163&quot;&gt;기존에 돌리고 있는 MySQL 종료&lt;/li&gt;
&lt;li id=&quot;block-15141da79fda8032af4ed5b03a32a572&quot;&gt;MySQL의 Data와 my.ini 백업 C:\ProgramData\MySQL\MySQL Server 8.0\Data 에 존재&lt;/li&gt;
&lt;li id=&quot;block-15141da79fda807fb420d487913160fc&quot;&gt;다른 버전 MySQL 설치&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://downloads.mysql.com/archives/community/&quot;&gt;https://downloads.mysql.com/archives/community/&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;block-15141da79fda80a9aa1eda5d301fc7e5&quot;&gt;설치 후, 환경 변수 설정&lt;/li&gt;
&lt;li id=&quot;block-15141da79fda8022b0c2da5d6f7b0ef9&quot;&gt;기존 데이터 복구&lt;/li&gt;
&lt;li id=&quot;block-15141da79fda80a082cecae39403db8a&quot;&gt;MySQL 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p id=&quot;block-15141da79fda80158053c00cdfb090f4&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기대를 품고 다시 실행한 결과&lt;/p&gt;
&lt;p id=&quot;block-15141da79fda80f896bfd1ca455c6f42&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Caused by: org.flywaydb.core.api.FlywayException: Unsupported Database: MySQL 8.1&lt;/b&gt;&lt;/p&gt;
&lt;p id=&quot;block-15141da79fda80eeb105f9370c1c2b82&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 8.1에서 문제 생긴 것을 알 수 있습니다.&lt;b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;block-15141da79fda80028fd3c67280cfedf0&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. flyway-mysql 라이브러리 추가(⭕)&lt;/h4&gt;
&lt;p id=&quot;block-15141da79fda803cb86ec983e418220b&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공식 사이트에 검색해 본 결과, flyway-mysql 의존성을 추가해야 한다고 합니다.&lt;/p&gt;
&lt;blockquote id=&quot;block-15141da79fda80219842cd07513bf4cf&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span&gt;친절하게 Flyway 10.7.0부터는 MariaDB 드라이버는 더 이상 기본적으로 MySQL URL을 허용하지 않는다고 합니다. MySQL를 사용하려면 따로 flyway-mysql 라이브러리를 추가 해야 한다고 나옵니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;div id=&quot;block-15141da79fda803ea4f2d0199d613195&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqjdi/btsK5B8rTb5/V9hPalEK3lL4c5tpS4n8KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqjdi/btsK5B8rTb5/V9hPalEK3lL4c5tpS4n8KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqjdi/btsK5B8rTb5/V9hPalEK3lL4c5tpS4n8KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmqjdi%2FbtsK5B8rTb5%2FV9hPalEK3lL4c5tpS4n8KK%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;707.96875&quot; height=&quot;540.333&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p id=&quot;block-15141da79fda806bb94ec0516f8dc50a&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Gradle 설정&lt;/p&gt;
&lt;div id=&quot;block-15141da79fda80a9acfbeb241f8b7cba&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;pre id=&quot;code_1733203004007&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* flyway */
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;h2 id=&quot;block-15141da79fda80d8a249e78cfd7c20c9&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. Flyway 마이그레이션 실패&lt;/h2&gt;
&lt;h3 id=&quot;block-15141da79fda80059238c7f7008ad804&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;문제 발생&lt;/h3&gt;
&lt;p id=&quot;block-15141da79fda800b869df22552e4ccf7&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Caused by: org.flywaydb.core.api.exception.FlywayValidateException: Validate failed: Migrations have failed validation&lt;/b&gt;&lt;/p&gt;
&lt;h3 id=&quot;block-15141da79fda80e8a6d1e47fddd59eb4&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;발생 이유&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li id=&quot;block-15141da79fda804f9124d40dbde9bf90&quot;&gt;쿼리문 잘못 작성&lt;/li&gt;
&lt;li id=&quot;block-15141da79fda8059ad5cd529718c6f65&quot;&gt;flyway_schema_history 실패로 인한 실행 불가&lt;/li&gt;
&lt;li id=&quot;block-15141da79fda80b480d0fb87c1aad649&quot;&gt;데이터베이스 불일치&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;block-15141da79fda808caefde0b4cceb4814&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p id=&quot;block-15141da79fda80f3901cd00b7ae2e153&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저의 경우에는 이미 success = 0 이 발생하여서 발생한 문제였습니다.&lt;/p&gt;
&lt;p id=&quot;block-15141da79fda80699b9cc50dfcd6b7cf&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SELECT * FROM flyway_schema_history WHERE success = 0;&lt;/p&gt;
&lt;div id=&quot;block-15141da79fda80e195cac9637c8318ab&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RO7Hu/btsK5cHT6hu/wRLsPNm15YJenXAqlwv9Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RO7Hu/btsK5cHT6hu/wRLsPNm15YJenXAqlwv9Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RO7Hu/btsK5cHT6hu/wRLsPNm15YJenXAqlwv9Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRO7Hu%2FbtsK5cHT6hu%2FwRLsPNm15YJenXAqlwv9Rk%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;707.90625&quot; height=&quot;55.999462&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p id=&quot;block-15141da79fda80c99c75d2fff13d08fa&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;필드명을 바꾸는 와중에 필드명이 맞지 않았는 데, 수정하고 돌리니 이미 데이터베이스에 flyway가 적용되어서 실행된 flyway를 DELETE 하고 새로운 쿼리를 작성해야 합니다.&lt;/p&gt;
&lt;p id=&quot;block-15141da79fda80379199e5a54b1a0e25&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;드디어 성공한 모습입니다.&lt;/p&gt;
&lt;div id=&quot;block-15141da79fda80fdae41d7515bef8e5c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLfjD/btsK5BtQuQh/aMWpIvdJTGwsRfOpSHxvf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLfjD/btsK5BtQuQh/aMWpIvdJTGwsRfOpSHxvf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLfjD/btsK5BtQuQh/aMWpIvdJTGwsRfOpSHxvf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLfjD%2FbtsK5BtQuQh%2FaMWpIvdJTGwsRfOpSHxvf0%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;707.8281&quot; height=&quot;62.420784&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p id=&quot;block-15141da79fda80b08837f21cc64b2130&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Flyway를 적용하는 데 시간이 많이 발생했습니다. 긴 여정이었지만, 해결하고 정상적으로 돌아가는 모습을 보니 너무 뿌듯하다는 생각이 들었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-15141da79fda80f3b18cd15bcf52ab15&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 사이트&lt;/b&gt;&lt;/p&gt;
&lt;blockquote id=&quot;block-15141da79fda807ba11dfae18c1200bf&quot; data-ke-style=&quot;style2&quot;&gt;Flyway 공식 사이트: &lt;a href=&quot;https://documentation.red-gate.com/flyway/getting-started-with-flyway/system-requirements/supported-databases-and-versions&quot;&gt;https://documentation.red-gate.com/flyway/getting-started-with-flyway/system-requirements/supported-databases-and-versions&lt;/a&gt;&lt;br /&gt;MySQL 공식 사이트: &lt;a href=&quot;https://downloads.mysql.com/archives/community/&quot;&gt;https://downloads.mysql.com/archives/community/&lt;/a&gt; &lt;br /&gt;stackOverflow: &lt;a href=&quot;https://stackoverflow.com/questions/72711770/flyway-unsupported-database-mysql-8-0&quot;&gt;https://stackoverflow.com/questions/72711770/flyway-unsupported-database-mysql-8-0&lt;/a&gt;&lt;/blockquote&gt;</description>
      <category>서버</category>
      <category>flyway</category>
      <category>데이터베이스</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/106</guid>
      <comments>https://truthgarnet.tistory.com/106#entry106comment</comments>
      <pubDate>Tue, 3 Dec 2024 14:26:20 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 리팩토링: Swagger 보안</title>
      <link>https://truthgarnet.tistory.com/105</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Swagger IP 접근 제한 처리&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swagger의 접근을 서버의 IP와 swagger url만 알게 된다면, 모두가 접근할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 보안에 대해서 생각을 잘 못하고 있다가 한번 패스워드가 누군가에 의해 변경되는 사고를 겪고 나서 보안에 대해서 더욱 공부를 많이 하고 할 수 있는 보안 처리는 모두 하자라는 생각이 들었습니다. 그래서 스웨거의 접근에 대해서 고민을 하게 되었고, Swagger를 접근할 수 있는 IP를 설정하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방안&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Spring Security를 사용 시, hasIpAddress(&amp;rsquo;&amp;rsquo;)으로 허용할 IP를 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpEL은 Spring Expression Language의 약자로, Spring Framework에서 제공하는 표현식 언어&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;Spring Security, Spring Data JPA, Spring ELK 등 다양한 Spring 모듈에서 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers(&quot;/swagger-ui/**&quot;, &quot;/v3/api-docs/**&quot;)
                .access(&quot;hasIpAddress('192.0.0.1')&quot;) // SpEL (Spring Expression Language)
                .anyRequests().permitAll()
                .and()
                .csrf().disable();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Filter를 통해서 IP 설정&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String clientIp = httpServletRequest.getRemoteAddr();

        // Swagger URL에 대한 IP 제한
        String requestUri = httpServletRequest.getRequestURI();
        if ((requestUri.startsWith(&quot;/swagger-ui&quot;) || requestUri.startsWith(&quot;/v3/api-docs&quot;)) &amp;amp;&amp;amp; !ALLOWED_IP.equals(clientIp)) {
            log.error(&quot;허용되지 않는 IP입니다.&quot;);
            throw new SecurityException(&quot;허용되지 않는 IP입니다.&quot;);
        }

        chain.doFilter(request, response);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. interceptor으로 ip 허용&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WiSNL/btsKZ8qVzWy/InEmQB1plzSbWUlYJnFsck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WiSNL/btsKZ8qVzWy/InEmQB1plzSbWUlYJnFsck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WiSNL/btsKZ8qVzWy/InEmQB1plzSbWUlYJnFsck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWiSNL%2FbtsKZ8qVzWy%2FInEmQB1plzSbWUlYJnFsck%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;1280&quot; height=&quot;478&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 디스패치 서블릿이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조 또는 가공&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;```
@Component
public class SwaggerInterceptor implements HandlerInterceptor {

    private static final String ALLOWED_IP = &quot;192.168.1.100&quot;; // 허용할 IP

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String clientIp = getClientIp(request);

        if (ALLOWED_IP.equals(clientIp)) {
            return true; // 허용된 IP면 요청 진행
        }

        response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 Forbidden
        response.getWriter().write(&quot;Access Denied&quot;);
        return false; // 요청 차단
    }

    private String getClientIp(HttpServletRequest request) {
        String clientIp = request.getHeader(&quot;X-Forwarded-For&quot;);
        if (clientIp == null || clientIp.isEmpty()) {
            clientIp = request.getRemoteAddr();
        }
        return clientIp;
    }
}
```&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. proxy 서버에서의 설정(ex. nginx)&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;location /swagger-ui {
    allow 192.168.1.100; # 허용할 IP
    deny all; # 다른 IP는 차단
}

location /v3/api-docs {
    allow 192.168.1.100; # 허용할 IP
    deny all; # 다른 IP는 차단
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Swagger Config 파일에서 설정&lt;/h3&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.OAS_30)
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.any())
            .build()
            .enable(isSwaggerEnabled());
    }

    private boolean isSwaggerEnabled() {
        String allowedIp = &quot;192.168.1.100&quot;;
        String clientIp = getClientIp();
        return allowedIp.equals(clientIp);
    }

    private String getClientIp() {
        // IP 가져오는 로직 구현 필요
        return &quot;192.168.1.100&quot;; // 예시
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변경 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 다양한 방법 중에서 2번을 선택하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링하면서 중요하게 본 것이 &lt;b&gt;실제 서버에게 영향을 최대한 주지 않으면서 할 수 있는 방법&lt;/b&gt;은 무언인가 생각했습니다.&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;Spring Expression Language 을 사용하는 것은 너무 생소하기도 하고, 설정이 잘 안 되어서 포기한 부분이었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3번 제외 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interceptort은 DispatcherServlet 이후에 동작하기 때문에 Spring 내부 로직에 진입한 이후에 실행되어서 IP 접근 제한은 Filter로 작성하는 것이 더 적절하다고 생각이 들었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4번 제외 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실 서버에서 nginx을 사용하여 proxy가 작동되고 있지만, nginx 설정 파일을 건들여야 하고 재시작을 해야 한다는 점이 리스크가 발생할 수 있다고 생각이 들어 4번은 제외 대상이 되었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5번 제외 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Config 파일은 서버가 실행될 때 한 번 실행되기 때문에, 로그를 추적할 수 없습니다. 저는 여기에 접속한 IP가 무엇인지 추적하고 싶었습니다. 그래서 제외 대상이 되었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fail2ban&lt;/code&gt;을 설정 했습니다.&lt;br /&gt;&lt;code&gt;fail2ban&lt;/code&gt;이란 로그 파일을 스캔하여, 로그인 시도를 많이 실패하는 IP 주소를 차단하는 프로그램입니다.&lt;br /&gt;/var/log/auth.log을 필터링하여 ssh 접속을 감시합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEfPwu/btsK4gv4pXQ/cQHwxpVQGuEL10FxkHIrUK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEfPwu/btsK4gv4pXQ/cQHwxpVQGuEL10FxkHIrUK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEfPwu/btsK4gv4pXQ/cQHwxpVQGuEL10FxkHIrUK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEfPwu%2FbtsK4gv4pXQ%2FcQHwxpVQGuEL10FxkHIrUK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;353&quot; height=&quot;138&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사진을 보면 무차별적인 접속이 오는 것을 알 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 백엔드 개발자임에 불구하고, 보안 쪽에 너무 모르고 있었지 않나 생각이 듭니다. 개발할 때, 보안 측면에서 생각하는 노력을 키워야겠다는 생각이 듭니다. 물론, 보안 프로그램이 존재한다면 좋겠지만 없는 데로 제가 할 수 있는 것을 해야만 한다고 생각합니다. 다음에는 부족한 부분을 다른 방식으로 어떻게 해결할 수 있는가에 대해서 더욱 고민해 보는 노력을 해야겠습니다.&lt;/p&gt;</description>
      <category>Project/회사프로젝트</category>
      <category>nginx</category>
      <category>리팩토링</category>
      <category>스프링</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/105</guid>
      <comments>https://truthgarnet.tistory.com/105#entry105comment</comments>
      <pubDate>Mon, 2 Dec 2024 11:59:33 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 리팩토링: 아키텍처</title>
      <link>https://truthgarnet.tistory.com/104</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;탑 다운 방식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에 뭣도 모르고 개발한 코드가 레거시 하다고 생각하고 리팩토링을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더 구조를 탑 다운 방식으로 생기는 문제가 발생했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbf5iH/btsKY95cJzl/vjLYw1kuKH2A1mbVbdkbjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbf5iH/btsKY95cJzl/vjLYw1kuKH2A1mbVbdkbjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbf5iH/btsKY95cJzl/vjLYw1kuKH2A1mbVbdkbjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbf5iH%2FbtsKY95cJzl%2FvjLYw1kuKH2A1mbVbdkbjK%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;382&quot; height=&quot;766&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사진을 봤을 때, ArIcon ArIcon이 cms, user로 구분되어서 &lt;b&gt;중복&lt;/b&gt;되어서 사용되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 진행할 때, 구조를 cms와 user로 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;변경점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;아키텍처 변경 (탑 다운 &amp;rarr; 클린 + 레이어드)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링 한 폴더 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;presentation 은 &lt;b&gt;Client와 가까운 코드들&lt;/b&gt;로 Controller, Facade가 위치하게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application 은 &lt;b&gt;실제 로직에서 사용하는 코드들&lt;/b&gt;로 Service, Repository 인터페이스가 위치할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;infrastructure 은 &lt;b&gt;외부에서 주입되는 코드들&lt;/b&gt;로 데이터베이스 주입(JPA)과 Redis 주입 등이 여기에 들어갈 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Repository와 JPA Repository로 구분하며 구성한 이유는 그 후에 들어오실 분이 Mybatis 사용에 익숙하시면, Mybatis로 JPA가 익숙하면 JPA로 로직으로 실제 로직(Service)에 영향이 없게 하기 위해서 구성했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mxdwu/btsKZdT65lA/15zmDrD3fNnk6c2Nt29Rak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mxdwu/btsKZdT65lA/15zmDrD3fNnk6c2Nt29Rak/img.png&quot; data-alt=&quot;변경전 / 변경후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mxdwu/btsKZdT65lA/15zmDrD3fNnk6c2Nt29Rak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmxdwu%2FbtsKZdT65lA%2F15zmDrD3fNnk6c2Nt29Rak%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;644&quot; height=&quot;388&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;변경전 / 변경후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;느낀 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렴풋이 개발 설계가 중요하다는 것을 느꼈지만, 이렇게까지 뼈저리게 느끼게 될 줄은 몰랐습니다. 역시나 사람은 경험해 보고 나서야 안다고 한다더니 제 이야기였습니다... 개발하기 전에 공부를 더 많이 했더라면 이런 실수는 범하지 않았을 것이라고 느꼈습니다. &lt;b&gt;다음에는 더욱 많은 아키텍처를 경험해 보고 상황에 따라 어떤 아키텍처가 적절한지&amp;nbsp;고민해 보고 개발을 진행하려고 합니다.&lt;/b&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project/회사프로젝트</category>
      <category>리팩토링</category>
      <category>아키텍쳐</category>
      <author>truthgarnet</author>
      <guid isPermaLink="true">https://truthgarnet.tistory.com/104</guid>
      <comments>https://truthgarnet.tistory.com/104#entry104comment</comments>
      <pubDate>Wed, 27 Nov 2024 18:23:00 +0900</pubDate>
    </item>
  </channel>
</rss>