<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>soo_vely의 개발로그</title>
    <link>https://soo-vely-dev.tistory.com/</link>
    <description>개발일기 및 알고리즘, 블로그 운영에 대한 글을 포스팅합니다. :)
목표: 뿌리 깊은 개발자 되기</description>
    <language>ko</language>
    <pubDate>Wed, 8 Apr 2026 19:00:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Kim-SooHyeon</managingEditor>
    <image>
      <title>soo_vely의 개발로그</title>
      <url>https://tistory1.daumcdn.net/tistory/3486075/attach/367147d3c9ca49bc9e0577c2f84ccc5a</url>
      <link>https://soo-vely-dev.tistory.com</link>
    </image>
    <item>
      <title>[AI] Claude Code에 Figma MCP 설정</title>
      <link>https://soo-vely-dev.tistory.com/322</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;model-response-message-contentr_6d4272beecda7b6f&quot;&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 최근 AI 개발 생태계의 화두인&lt;b&gt; MCP(Model Context Protocol)&lt;/b&gt;를 활용해,&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;88&quot; data-path-to-node=&quot;3&quot;&gt;Figma와 Claude Code를 연동하는 방법&lt;/b&gt;을 공유하려 합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;디자인 파일을 보며 일일이 CSS 속성을 복사하던 번거로움에서 벗어나,&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 내 디자인 컨텍스트를 직접 이해하게 만드는 생산성 혁명을 경험해 보세요.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;5&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;1. Figma MCP란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MCP(Model Context Protocol)&lt;/b&gt;는 AI 모델이 외부 데이터(GitHub, Figma, DB 등)와&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;직접 상호 작용할 수 있도록 돕는 표준 인터페이스입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;Figma MCP 서버&lt;/b&gt;를 연결하면 다음과 같은 작업이 가능해집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;디자인 컨텍스트 추출:&lt;/b&gt; 변수, 컴포넌트, 레이아웃 데이터를 IDE로 직접 가져오기&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;코드 생성:&lt;/b&gt; 선택한 프레임 기반으로 React, Tailwind 등 즉시 코드 변환&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;실시간 UI 캡처:&lt;/b&gt; 웹 앱의 실시간 UI를 캡처하여 Figma 파일로 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;10&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;2. Claude Code에서 설정하기&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;방법 A: 공식 플러그인 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;가장 표준적인 방법으로, 에이전트 스킬까지 포함된 설치 방식입니다. 터미널에서 아래 명령어를 실행합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwip8-zumduTAxUAAAAAHQAAAAAQdQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div style=&quot;background-color: #f0f4f9; color: #1f1f1f;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;claude plugin install figma@claude-plugins-official
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-path-to-node=&quot;13&quot;&gt;터미널에서 아래 명령어를 입력하여 figma가 정상 설치되었는지 확인합니다.&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot; data-hveid=&quot;0&quot; data-ved=&quot;0CAAQhtANahcKEwip8-zumduTAxUAAAAAHQAAAAAQdQ&quot;&gt;
&lt;div style=&quot;background-color: #f0f4f9; color: #1f1f1f;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;jboss-cli&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;/mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b40q4s/dJMcaibPWtW/PVAc2YDVnNP7MrPLLRkU00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b40q4s/dJMcaibPWtW/PVAc2YDVnNP7MrPLLRkU00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b40q4s/dJMcaibPWtW/PVAc2YDVnNP7MrPLLRkU00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb40q4s%2FdJMcaibPWtW%2FPVAc2YDVnNP7MrPLLRkU00%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;477&quot; height=&quot;390&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h4 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15&quot;&gt;&lt;br /&gt;방법 B: 수동 설정 (Manual Setup)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;만약 플러그인을 설치했는데도 /mcp 목록에 Figma가 나오지 않는다면, 아래 &lt;b data-index-in-node=&quot;45&quot; data-path-to-node=&quot;16&quot;&gt;수동 설정 방식&lt;/b&gt;을 추천합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;(제가 겪었던 문제와 해결책은 아래 트러블슈팅 섹션에 상세히 적어두었습니다.)&lt;/p&gt;
&lt;hr data-path-to-node=&quot;17&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18&quot;&gt;3.   잠깐! 플러그인 설치 후 /mcp 목록에 Figma가 보이지 않는다면?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;저도 처음에 권장 방식인 claude plugin install을 시도했지만, /mcp 명령어를 입력했을 때 목록에 Figma가 노출되지 않는 현상이 있었습니다. 이는 클라이언트 버전 문제나 설정 파일 반영 지연 때문일 수 있습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;이럴 땐 당황하지 말고 아래 명령어를 통해 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;20&quot;&gt;수동 설정&lt;/b&gt;을 진행해 보세요.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwip8-zumduTAxUAAAAAHQAAAAAQdg&quot; data-hveid=&quot;0&quot;&gt;
&lt;div style=&quot;background-color: #f0f4f9; color: #1f1f1f;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;# --scope user 플래그를 통해 모든 프로젝트에서 사용 가능하도록 설정합니다.
claude mcp add --scope user --transport http figma https://mcp.figma.com/mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 직접 서버 주소를 등록하면 Claude Code가 즉시 인식하며,&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;전역 설정을 통해 어떤 프로젝트 폴더에서도 Figma 디자인 컨텍스트를 불러올 수 있게 됩니다!&lt;/p&gt;
&lt;hr data-path-to-node=&quot;23&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24&quot;&gt;4. 인증 및 최종 연결 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;설정 명령어를 입력했다면 이제 실제 Figma 데이터에 접근할 권한을 부여해야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Claude Code를 다시 시작합니다.&lt;/li&gt;
&lt;li&gt;/mcp를 입력하고 목록에서 &lt;b&gt;figma&lt;/b&gt;를 선택합니다.&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b40q4s/dJMcaibPWtW/PVAc2YDVnNP7MrPLLRkU00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b40q4s/dJMcaibPWtW/PVAc2YDVnNP7MrPLLRkU00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b40q4s/dJMcaibPWtW/PVAc2YDVnNP7MrPLLRkU00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb40q4s%2FdJMcaibPWtW%2FPVAc2YDVnNP7MrPLLRkU00%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;477&quot; height=&quot;390&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,2,0&quot;&gt;인증(Authentication)&lt;/b&gt; 메뉴를 선택하면 브라우저가 열립니다.&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLUhly/dJMcabqftcq/NUQ70sZxd8hnnIP0d8IkIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLUhly/dJMcabqftcq/NUQ70sZxd8hnnIP0d8IkIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLUhly/dJMcabqftcq/NUQ70sZxd8hnnIP0d8IkIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLUhly%2FdJMcabqftcq%2FNUQ70sZxd8hnnIP0d8IkIk%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;352&quot; height=&quot;221&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;Figma 로그인을 완료하고 &lt;b&gt;[액세스 허용(Allow access)]&lt;/b&gt;을 클릭합니다.&lt;/li&gt;
&lt;li&gt;터미널에 Authentication successful. Connected to figma 메시지가 뜨면 성공입니다!&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yRnQ2/dJMcafsB7My/TtEXuUgHCDtUjITh0WBwP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yRnQ2/dJMcafsB7My/TtEXuUgHCDtUjITh0WBwP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yRnQ2/dJMcafsB7My/TtEXuUgHCDtUjITh0WBwP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyRnQ2%2FdJMcafsB7My%2FTtEXuUgHCDtUjITh0WBwP1%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;600&quot; height=&quot;60&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-path-to-node=&quot;27&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28&quot;&gt;5. 활용 가이드: 무엇을 물어볼까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;연결이 완료되었다면 이제 Claude Code에게 이렇게 요청해 보세요.&lt;/p&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-path-to-node=&quot;30,0,0,0&quot; data-index-in-node=&quot;0&quot;&gt;&quot;지금 피그마 파일에서 '로그인 버튼' 컴포넌트의 스타일을 React 코드로 짜줘.&quot;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i data-path-to-node=&quot;30,0,1,0&quot; data-index-in-node=&quot;0&quot;&gt;&quot;디자인 시스템에 등록된 폰트 크기 변수들을 알려줘.&quot;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i data-path-to-node=&quot;30,0,2,0&quot; data-index-in-node=&quot;0&quot;&gt;&quot;선택한 프레임의 레이아웃 구조를 분석해서 HTML/CSS로 변환해 줘.&quot;&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;31&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;32&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32&quot;&gt;마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;백엔드 개발자로서 프론트엔드 협업이나 개인 프로젝트를 진행할 때,&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;디자인 수치를 일일이 확인하는 게 가장 번거로운 일 중 하나였는데요&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;Figma MCP를 활용하니 &lt;b&gt;'디자인이 곧 컨텍스트'&lt;/b&gt;가 되어 개발 속도가 비약적으로 상승했습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;저처럼 플러그인 설치 시 목록 노출이 안 되어 고생하셨던 분들께 이 글이 도움이 되길 바랍니다!&lt;/p&gt;
&lt;hr data-path-to-node=&quot;35&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36&quot;&gt;참고 문서:&lt;/b&gt; &lt;a href=&quot;https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Figma Developers - Remote Server Installation&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <category>ai개발도구</category>
      <category>Anthropic</category>
      <category>claudecode</category>
      <category>FigmaMCP</category>
      <category>MCP</category>
      <category>개발생산성</category>
      <category>백엔드개발자</category>
      <category>트러블슈팅</category>
      <category>프롬프트엔지니어링</category>
      <category>피그마연동</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/322</guid>
      <comments>https://soo-vely-dev.tistory.com/322#entry322comment</comments>
      <pubDate>Tue, 7 Apr 2026 16:40:36 +0900</pubDate>
    </item>
    <item>
      <title>[AI] Claude - 커스텀 명령어로 커밋 프롬프트 자동화하기</title>
      <link>https://soo-vely-dev.tistory.com/321</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 개발 과정에서 AI 어시스턴트를 활용하다 보면,&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;특히 깃(Git) 커밋 메시지를 작성하거나 코드 리뷰를 요청할 때, 매번 긴 규칙을 설명하는 것은 상당히 번거로운 일입니다.&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;한 번만 설정해 두면 업무 효율을 비약적으로 끌어올릴 수 있습니다!  &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;커스텀 슬래시 명령어는 자주 사용하는 긴 프롬프트를 하나의 짧은 명령어로 단축하여 사용하는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 길고 복잡한 커밋 생성 프롬프트를 &lt;code&gt;/commit&lt;/code&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;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 명령어 폴더 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 루트 디렉토리에 &lt;code&gt;.claude/commands/&lt;/code&gt; 폴더를 생성합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 마크다운(&lt;code&gt;.md&lt;/code&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;b&gt;폴더 구조 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;.claude/
└── commands/
    └── commit.md     &amp;lt;-- 이 파일이 /commit 명령어가 됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA2pBJ/dJMcadVFBRi/cntd0R5jSQSB64yuk2CLwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA2pBJ/dJMcadVFBRi/cntd0R5jSQSB64yuk2CLwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA2pBJ/dJMcadVFBRi/cntd0R5jSQSB64yuk2CLwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA2pBJ%2FdJMcadVFBRi%2Fcntd0R5jSQSB64yuk2CLwK%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;812&quot; height=&quot;166&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  활용 예시 : 자동 커밋 및 푸시 명령어 (&lt;code&gt;commit.md&lt;/code&gt;)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 주로 사용하는 커밋 메시지 자동화 프롬프트를 예시로 보여드리겠습니다. 아래 내용을 방금 만든 &lt;code&gt;commit.md&lt;/code&gt; 파일에 복사해서 붙여넣어 보세요.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;# Commit and Push

1. 변경된 파일들을 확인해줘 
2. 각 기능들을 나누어서 아래 컨벤션에 맞춰 의미 있는 커밋 메시지를 작성해줘 (한국어)
- feat : 새로운 기능 추가, 기존의 기능을 요구 사항에 맞추어 수정
- fix : 기능에 대한 버그 수정
- build : 빌드 관련 수정
- chore : 패키지 매니저 수정, 그 외 기타 수정 ex) .gitignore
- ci : CI 관련 설정 수정
- docs : 문서 수정 (README, 주석, 문서 파일 변경 시)
- style : 코드 스타일, 포맷팅에 대한 수정
- refactor : 기능의 변화가 아닌 코드 리팩터링 ex) 변수 이름 변경
- test : 테스트 코드 추가/수정
- release : 버전 릴리즈
3. 커밋하고 현재 브랜치에 푸시해줘&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;이제 설정이 모두 끝났습니다! 채팅창에서 다음과 같이 입력해 보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: center;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;/commit&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vNCok/dJMcacJijXS/WyfIkwA0bxkCbJ4mPOkAa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vNCok/dJMcacJijXS/WyfIkwA0bxkCbJ4mPOkAa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vNCok/dJMcacJijXS/WyfIkwA0bxkCbJ4mPOkAa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvNCok%2FdJMcacJijXS%2FWyfIkwA0bxkCbJ4mPOkAa0%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;642&quot; height=&quot;108&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 자동으로 &lt;code&gt;commit.md&lt;/code&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;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;/review&lt;/code&gt; : 사내 코드 리뷰 규칙을 정해두고 검사 목적으로 사용하기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/test&lt;/code&gt; : 특정 프레임워크 기반의 유닛 테스트 코드 템플릿 생성하기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/refactor&lt;/code&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;</description>
      <category>AI</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/321</guid>
      <comments>https://soo-vely-dev.tistory.com/321#entry321comment</comments>
      <pubDate>Wed, 25 Mar 2026 17:56:25 +0900</pubDate>
    </item>
    <item>
      <title>[AI] oh-my-claude code | Claude Code를 팀 오케스트레이션 플랫폼으로</title>
      <link>https://soo-vely-dev.tistory.com/320</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자 쓰던 AI 코딩 도구, 이제 19개 전문 에이전트가 협업하는 팀으로 쓴다&lt;/p&gt;
&lt;/blockquote&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;Claude Code를 사용하다 보면 한 가지 아쉬운 점이 생깁니다.&lt;br /&gt;복잡한 기능을 구현할 때, 코드 작성 &amp;rarr; 리뷰 &amp;rarr; 테스트 &amp;rarr; 보안 검토까지 모든 걸 하나의 프롬프트 흐름에 때려 넣다 보면 컨텍스트가 뒤엉키기 시작하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결해주는 도구가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;oh-my-claudecode(OMC)&lt;/b&gt; &amp;mdash; Claude Code 위에 올라가는 멀티 에이전트 오케스트레이션 레이어입니다.&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;이 글에서는 OMC의 개념부터 설치, 핵심 기능까지 백엔드 개발자 관점에서 정리해보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. oh-my-claudecode란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;oh-my-claudecode(OMC)&lt;/b&gt; 는 Anthropic의 Claude Code CLI를 &lt;b&gt;팀 기반 멀티 에이전트 플랫폼&lt;/b&gt;으로 확장하는 플러그인 시스템입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Teams-first Multi-agent orchestration for Claude Code&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 마디로, 혼자 일하던 Claude를 &lt;b&gt;19개의 전문 에이전트로 구성된 팀&lt;/b&gt;으로 업그레이드하는 것입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Claude Code vs oh-my-claudecode&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;oh-my-claudecode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;인터페이스&lt;/td&gt;
&lt;td&gt;CLI 명령어 직접 입력&lt;/td&gt;
&lt;td&gt;자연어 + 매직 키워드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에이전트&lt;/td&gt;
&lt;td&gt;단일 에이전트&lt;/td&gt;
&lt;td&gt;19개 전문 에이전트 자동 라우팅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행 방식&lt;/td&gt;
&lt;td&gt;순차적&amp;middot;선형&lt;/td&gt;
&lt;td&gt;병렬 팀, 스마트 라우팅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증 루프&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ralph&lt;/code&gt; 모드로 자동 재시도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;멀티 프로바이더&lt;/td&gt;
&lt;td&gt;Claude만&lt;/td&gt;
&lt;td&gt;Claude + Codex + Gemini 동시 오케스트레이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비용 최적화&lt;/td&gt;
&lt;td&gt;수동 모델 선택&lt;/td&gt;
&lt;td&gt;작업 복잡도별 자동 모델 라우팅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에이전트 카탈로그 (19개)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OMC는 역할이 명확히 분리된 전문 에이전트들을 보유하고 있습니다. 각 에이전트는 작업 복잡도에 따라 적절한 모델(haiku / sonnet / opus)로 자동 라우팅됩니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;분류&lt;/th&gt;
&lt;th&gt;에이전트&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;모델&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;계획/설계&lt;/td&gt;
&lt;td&gt;&lt;code&gt;planner&lt;/code&gt;, &lt;code&gt;architect&lt;/code&gt;, &lt;code&gt;analyst&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;요구사항 분석, 시스템 설계&lt;/td&gt;
&lt;td&gt;opus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구현&lt;/td&gt;
&lt;td&gt;&lt;code&gt;executor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;코드 구현 실행&lt;/td&gt;
&lt;td&gt;sonnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증&lt;/td&gt;
&lt;td&gt;&lt;code&gt;verifier&lt;/code&gt;, &lt;code&gt;qa-tester&lt;/code&gt;, &lt;code&gt;test-engineer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;테스트, QA, 검증&lt;/td&gt;
&lt;td&gt;sonnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리뷰&lt;/td&gt;
&lt;td&gt;&lt;code&gt;code-reviewer&lt;/code&gt;, &lt;code&gt;security-reviewer&lt;/code&gt;, &lt;code&gt;critic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;코드 리뷰, 보안 평가&lt;/td&gt;
&lt;td&gt;opus/sonnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분석&lt;/td&gt;
&lt;td&gt;&lt;code&gt;debugger&lt;/code&gt;, &lt;code&gt;tracer&lt;/code&gt;, &lt;code&gt;explore&lt;/code&gt;, &lt;code&gt;scientist&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;디버깅, 코드 추적, 탐색&lt;/td&gt;
&lt;td&gt;sonnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문서/기타&lt;/td&gt;
&lt;td&gt;&lt;code&gt;writer&lt;/code&gt;, &lt;code&gt;document-specialist&lt;/code&gt;, &lt;code&gt;designer&lt;/code&gt;, &lt;code&gt;git-master&lt;/code&gt;, &lt;code&gt;code-simplifier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;문서화, UI/UX, Git&lt;/td&gt;
&lt;td&gt;haiku/sonnet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스킬(Skill)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬은 에이전트들을 조합한 &lt;b&gt;워크플로우 템플릿&lt;/b&gt;입니다. &lt;code&gt;/&lt;/code&gt; 로 시작하는 슬래시 커맨드로 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 스킬:&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;스킬&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;autopilot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;자연어 설명만으로 전체 구현 자율 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ralph&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;목표 달성까지 검증 루프를 포함한 지속 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ultrawork&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;최대 병렬 처리 모드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deep-interview&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;소크라테스식 요구사항 명확화 후 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omc-plan&lt;/code&gt; / &lt;code&gt;ralplan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전략적 플래닝 (반복 합의 포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ccg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claude + Codex + Gemini 삼중 병렬 실행 후 합성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai-slop-cleaner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI가 생성한 코드 슬랍(slop) 정제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;증거 기반 경쟁 가설 추적 디버깅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ultraqa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;테스트-검증-수정 반복 QA 사이클&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;pre class=&quot;coq&quot;&gt;&lt;code&gt;team-plan &amp;rarr; team-prd &amp;rarr; team-exec &amp;rarr; team-verify &amp;rarr; team-fix (루프)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증 실패 시 &lt;code&gt;team-fix&lt;/code&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;h3 data-ke-size=&quot;size23&quot;&gt;사전 요구사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Claude Code CLI 설치 완료&lt;/li&gt;
&lt;li&gt;Claude Max/Pro 구독 또는 Anthropic API 키&lt;/li&gt;
&lt;li&gt;tmux (&lt;code&gt;brew install tmux&lt;/code&gt; / &lt;code&gt;apt install tmux&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;(선택) Codex CLI, Gemini CLI &amp;mdash; 크로스 모델 오케스트레이션용&lt;/li&gt;
&lt;/ul&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;방법 1 &amp;mdash; Claude Code 플러그인 마켓플레이스 (권장)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode
/plugin install oh-my-claudecode
/omc-setup&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nDFjk/dJMcagx0P1k/kxjfXQibUO6O9R0EEzpRek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nDFjk/dJMcagx0P1k/kxjfXQibUO6O9R0EEzpRek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nDFjk/dJMcagx0P1k/kxjfXQibUO6O9R0EEzpRek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnDFjk%2FdJMcagx0P1k%2FkxjfXQibUO6O9R0EEzpRek%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;756&quot; height=&quot;372&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;방법 2 &amp;mdash; npm&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;npm install -g oh-my-claude-sisyphus@latest&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;팀 모드 활성화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 에이전트 팀 기능을 사용하려면 &lt;code&gt;~/.claude/settings.json&lt;/code&gt;에 다음을 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;env&quot;: {
    &quot;CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS&quot;: &quot;1&quot;
  }
}&lt;/code&gt;&lt;/pre&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;pre class=&quot;applescript&quot;&gt;&lt;code&gt;/oh-my-claudecode:omc-setup&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 실제 사용 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 팀 실행&lt;/h3&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;# 3개 executor 에이전트가 병렬로 TypeScript 오류 수정
/team 3:executor &quot;TypeScript 컴파일 오류 모두 수정&quot;

# 보안 리뷰 + 코드 리뷰 병렬 실행
/team 2:security-reviewer &quot;전체 API 엔드포인트 OWASP Top 10 기준 보안 검토&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자율 실행 (Autopilot)&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;autopilot: 도서 대출/반납 REST API 구현, JPA + QueryDSL 사용, 동시성 제어 포함&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요구사항이 모호할 때 (Deep Interview)&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/deep-interview &quot;검색 기능 개선&quot;
# &amp;rarr; OMC가 소크라테스식 질문으로 요구사항을 구체화한 후 실행&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;검증 루프 (Ralph)&lt;/h3&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;ralph: 전체 테스트 통과할 때까지 대출 서비스 구현 및 수정 반복&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;크로스 모델 병렬 실행&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# Claude + Codex + Gemini가 각자 분석 후 결과 합성
/ccg &quot;이 아키텍처의 확장성 문제점 분석&quot;

# Gemini CLI 워커 팀
omc team 2:gemini &quot;API 응답 시간 병목 분석&quot;&lt;/code&gt;&lt;/pre&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;pre class=&quot;glsl&quot;&gt;&lt;code&gt;ulw fix all errors          # ultrawork 모드 실행
ralplan 이 기능 계획 세워줘  # ralplan 스킬 실행
cancelomc                   # 실행 중인 OMC 작업 즉시 중단
deep-analyze 이 코드 분석해줘 # 심층 분석 모드&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 백엔드 개발자가 느낀 실용 포인트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모델 비용 자동 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 코드 검색은 haiku, 일반 구현은 sonnet, 아키텍처 설계나 심층 코드 리뷰는 opus로 자동 라우팅됩니다. 직접 모델을 골라가며 쓰는 것보다 비용이 체감상 30~50% 절감됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨텍스트 오염 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 하나의 대화에서 설계 &amp;rarr; 구현 &amp;rarr; 리뷰를 모두 하다 보면 초반 컨텍스트가 흐려지는 문제가 있었습니다. OMC는 각 단계를 전문 에이전트로 분리해서 실행하므로 이 문제가 해결됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;IDOR, SQL Injection 등 보안 리뷰 자동화&lt;/h3&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;/team 2:security-reviewer &quot;전체 Controller 레이어 IDOR 취약점 점검&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 PR마다 수동으로 하던 보안 체크를 팀 에이전트에 위임할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설계서-코드 정합성 검토&lt;/h3&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;/team 2:critic &quot;api_spec.md와 실제 Controller 코드 간 불일치 항목 모두 찾아라&quot;&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;6. 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;tmux 필수&lt;/b&gt;: CLI 워커 팀(&lt;code&gt;omc team&lt;/code&gt;) 기능은 tmux 없이 동작하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;API 비용 증가 가능성&lt;/b&gt;: 팀 모드는 여러 에이전트가 동시에 실행되므로 단일 실행 대비 토큰 소모가 많습니다. &lt;code&gt;haiku&lt;/code&gt; 모델을 최대한 활용하는 방향으로 조정하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Windows&lt;/b&gt;: &lt;code&gt;psmux&lt;/code&gt; 사용 시 WSL 없이도 동작하나, 완전한 호환성은 macOS/Linux 환경을 권장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검증 루프 무한 방지&lt;/b&gt;: &lt;code&gt;ralph&lt;/code&gt; 모드 사용 시 최대 재시도 횟수를 설정해두지 않으면 의도치 않게 많은 비용이 소모될 수 있습니다.&lt;/li&gt;
&lt;/ul&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;oh-my-claudecode는 &quot;AI 코딩 어시스턴트&quot;를 &quot;AI 개발팀&quot;으로 격상시키는 도구입니다.&lt;br /&gt;특히 복잡한 도메인 로직 구현, 멀티 레이어 보안 검토, 문서-코드 정합성 관리처럼 단일 프롬프트로 처리하기 어려운 작업에서 진가를 발휘합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code를 이미 사용하고 있다면 설치 비용이 거의 없으니 한 번 시도해보시길 권장합니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/Yeachan-Heo/oh-my-claudecode&quot;&gt;https://github.com/Yeachan-Heo/oh-my-claudecode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Claude Code 공식 문서: &lt;a href=&quot;https://docs.anthropic.com/claude-code&quot;&gt;https://docs.anthropic.com/claude-code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/320</guid>
      <comments>https://soo-vely-dev.tistory.com/320#entry320comment</comments>
      <pubDate>Wed, 18 Mar 2026 18:06:03 +0900</pubDate>
    </item>
    <item>
      <title>[AI] Claude Code CLI 설치 가이드</title>
      <link>https://soo-vely-dev.tistory.com/319</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Claude Code CLI 설치 및 실행 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 개발 생산성을 높이기 위해 &lt;b&gt;Claude Code CLI&lt;/b&gt;를 사용하는 분들이 많아지고 있습니다.&lt;br /&gt;이번 글에서는 Windows와 Mac 환경에서 Claude Code를 설치하고, 초기 설정까지 진행하는 방법을 정리해보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ Claude Code 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 터미널(또는 명령 프롬프트)을 실행한 뒤 아래 명령어를 입력합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ Windows&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;npm install -g @anthropic-ai/claude-code
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ Mac&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;sudo npm install -g @anthropic-ai/claude-code
&lt;/code&gt;&lt;/pre&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;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;pre style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;claude
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 인터페이스 스타일(색상 등)을 선택하는 화면이 나타납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는&amp;nbsp;&lt;b&gt;기본값(1번)&lt;/b&gt;을 선택하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;831&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ohEx7/dJMcach4UVV/4473N2W2NZ1G57UjvqSREK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ohEx7/dJMcach4UVV/4473N2W2NZ1G57UjvqSREK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ohEx7/dJMcach4UVV/4473N2W2NZ1G57UjvqSREK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FohEx7%2FdJMcach4UVV%2F4473N2W2NZ1G57UjvqSREK%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;1483&quot; height=&quot;831&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;831&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;1번&lt;/b&gt;: 계정 로그인 (권장)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2번&lt;/b&gt;: 사용량 기반 과금 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로는 &lt;b&gt;1번(계정 로그인)&lt;/b&gt;을 선택하시는 것을 권장드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 사용해보면 과금 방식은 사용량에 따라 비용이 발생하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 코드 생성만으로도 비용이 빠르게 누적될 수 있습니다.&lt;span style=&quot;background-color: #ffffff; color: #121314; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;831&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVLXf6/dJMb99ZYMxu/kOhZ8Gze8KMTvRlDdHkOQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVLXf6/dJMb99ZYMxu/kOhZ8Gze8KMTvRlDdHkOQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVLXf6/dJMb99ZYMxu/kOhZ8Gze8KMTvRlDdHkOQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVLXf6%2FdJMb99ZYMxu%2FkOhZ8Gze8KMTvRlDdHkOQk%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;1483&quot; height=&quot;831&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;831&quot;/&gt;&lt;/span&gt;&lt;/figure&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;로그인 방식을 선택하면 웹 브라우저가 자동으로 열립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에 표시되는 &lt;b&gt;승인&lt;/b&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;이 과정을 통해 CLI와 계정이 연결됩니다.&lt;span style=&quot;background-color: #ffffff; color: #121314;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSH8Lt/dJMcaiCzIs2/GHe75expcPXJ4n0aSWFhq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSH8Lt/dJMcaiCzIs2/GHe75expcPXJ4n0aSWFhq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSH8Lt/dJMcaiCzIs2/GHe75expcPXJ4n0aSWFhq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSH8Lt%2FdJMcaiCzIs2%2FGHe75expcPXJ4n0aSWFhq1%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;622&quot; height=&quot;614&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5️⃣ 설치 완료 및 초기 안내&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 정상적으로 완료되었다는 메시지가 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T7o2e/dJMb99ZZeuu/WwofGZ135ewfA5HzF8yf6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T7o2e/dJMb99ZZeuu/WwofGZ135ewfA5HzF8yf6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T7o2e/dJMb99ZZeuu/WwofGZ135ewfA5HzF8yf6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT7o2e%2FdJMb99ZZeuu%2FWwofGZ135ewfA5HzF8yf6k%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;620&quot; height=&quot;267&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널로 돌아와서 안내에 따라 &lt;b&gt;Enter 키를 눌러 진행&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkaVik/dJMcacoQANG/wymaWhnU99PPKskQk02JsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkaVik/dJMcacoQANG/wymaWhnU99PPKskQk02JsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkaVik/dJMcacoQANG/wymaWhnU99PPKskQk02JsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkaVik%2FdJMcacoQANG%2FwymaWhnU99PPKskQk02JsK%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;628&quot; height=&quot;457&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 간단한 사용 가이드가 출력됩니다. &lt;b&gt;Enter 키를 눌러 진행&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg9hQy/dJMcahXZhUy/1v2MKeTLzzwxXoK3O4Sd9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg9hQy/dJMcahXZhUy/1v2MKeTLzzwxXoK3O4Sd9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg9hQy/dJMcahXZhUy/1v2MKeTLzzwxXoK3O4Sd9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg9hQy%2FdJMcahXZhUy%2F1v2MKeTLzzwxXoK3O4Sd9K%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;628&quot; height=&quot;457&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6️⃣ 폴더 신뢰 여부 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code는 실행 시 현재 디렉토리를 기준으로 동작하기 때문에&lt;br /&gt;폴더를 신뢰하는지 묻는 메시지가 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 개발 환경이라면 &lt;b&gt;Enter를 눌러 진행&lt;/b&gt;하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 해당 메시지는 실행할 때마다 표시될 수 있습니다.&lt;span style=&quot;background-color: #ffffff; color: #121314; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N3J72/dJMcadgZcjU/OgJoNtDbwLdWMOcXXR2jWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N3J72/dJMcadgZcjU/OgJoNtDbwLdWMOcXXR2jWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N3J72/dJMcadgZcjU/OgJoNtDbwLdWMOcXXR2jWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN3J72%2FdJMcadgZcjU%2FOgJoNtDbwLdWMOcXXR2jWK%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;701&quot; height=&quot;373&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7️⃣ 실행 확인 및 종료 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 CLI 화면이 정상적으로 표시된다면&lt;br /&gt;이제 Claude Code를 사용할 준비가 완료된 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BeqAB/dJMcadVxRF4/iMuXlf4vknBQZPZJuhX5b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BeqAB/dJMcadVxRF4/iMuXlf4vknBQZPZJuhX5b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BeqAB/dJMcadVxRF4/iMuXlf4vknBQZPZJuhX5b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBeqAB%2FdJMcadVxRF4%2FiMuXlf4vknBQZPZJuhX5b1%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;628&quot; height=&quot;457&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;457&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;ada&quot;&gt;&lt;code&gt;/exit
&lt;/code&gt;&lt;/pre&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;지금까지 Claude Code CLI 설치부터 실행까지 간단히 정리해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/319</guid>
      <comments>https://soo-vely-dev.tistory.com/319#entry319comment</comments>
      <pubDate>Tue, 17 Mar 2026 11:27:26 +0900</pubDate>
    </item>
    <item>
      <title>[Back end / 보안] 데이터 암호화의 이해: 대칭키, 비대칭키부터 SHA-256 해시까지</title>
      <link>https://soo-vely-dev.tistory.com/318</link>
      <description>&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;비밀번호 보안은 현대 웹 개발에서 가장 중요한 주제 중 하나입니다&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;많은 개발자들이 &quot;어떻게 비밀번호를 암호화할까?&quot;라는 질문부터 시작하지만, &lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;정답은 &quot;암호화가 아니라 &lt;u&gt;&lt;b&gt;해싱&lt;/b&gt;&lt;/u&gt;&quot;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 암호화의 세 가지 기법을 이해하고, 왜 비밀번호에는 해싱을 사용하는지,&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;그리고 비밀번호 분실 시 올바른 처리 로직이 무엇인지 알아보겠습니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;4&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;1. 암호화 알고리즘의 분류&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;암호화 기술은 목적과 방식에 따라 크게 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;6&quot;&gt;대칭키(Symmetric)&lt;/b&gt;, &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;6&quot;&gt;비대칭키(Asymmetric)&lt;/b&gt;, 그리고 &lt;b data-index-in-node=&quot;46&quot; data-path-to-node=&quot;6&quot;&gt;해시(Hash)&lt;/b&gt; 세 가지로 나뉩니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;  대칭키 암호화 (Symmetric Key Encryption)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;암호화할 때와 복호화할 때 &lt;u&gt;&lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;8&quot;&gt;동일한 키&lt;/b&gt;&lt;/u&gt;를 사용하는 방식입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;속도가 매우 빠릅니다.&lt;/li&gt;
&lt;li&gt;처리 효율이 좋아 대용량 데이터 암호화에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;단점&lt;/b&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;N명이 통신할 때 N&amp;times;(N-1)/2개의 키를 관리해야 합니다. (키 관리 부담)&lt;/li&gt;
&lt;li&gt;한 사람의 키가 노출되면 모든 통신이 위험해집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;예시:&lt;/b&gt; AES, DES&lt;/li&gt;
&lt;li&gt;&lt;b&gt;활용 사례&lt;/b&gt; : 파일 암호화, 데이터베이스 저장 데이터 보호&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;  비대칭키 암호화 (Asymmetric Key Encryption)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개키와 개인키라는 &lt;b&gt;서로 다른 두 개의 키&lt;/b&gt;를 사용하는 방식입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;공개키: 누구나 접근 가능 (암호화용)&lt;br /&gt;개인키: 본인만 보유 (복호화용)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키 전달의 보안성이 높습니다. (공개키만 전송)&lt;/li&gt;
&lt;li&gt;전자서명 등 다양한 용도로 활용 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;단점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연산 속도가 대칭키에 비해 상대적으로 느립니다.&lt;/li&gt;
&lt;li&gt;대용량 데이터 암호화에는 부적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;예시:&lt;/b&gt; RSA, ECC (Elliptic Curve Cryptography)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;활용 사례&lt;/b&gt; : HTTPS 통신, 전자서명, API 인증 토큰&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;14&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size26&quot;&gt;2. 왜 비밀번호에는 해싱을 사용할까?&lt;/h2&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;왜 비밀번호는 위 두 방식이 아닌 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;16&quot;&gt;해시&lt;/b&gt;를 사용할까요?&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;정답은 &quot;복호화가 불가능해야 하기 때문&quot;입니다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;해시 알고리즘의 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단방향성 (One-way function)&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;평문 &amp;rarr; SHA-256 &amp;rarr; 해시값
해시값 &amp;rarr; ??? &amp;rarr; 평문 (불가능!)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호를 해시하면 원문을 알아낼 수 없습니다. 이것이 비밀번호 저장의 기본 원칙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;관리자도 사용자의 비밀번호를 모른다&quot;&lt;/b&gt;는 것이 보안의 핵심입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무결성 확인 (Integrity Verification)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값이 조금만 달라져도 결과값이 완전히 변합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;SHA-256(&quot;password&quot;) = 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
SHA-256(&quot;passw0rd&quot;) = e38ad2143029da323b6b0c8f5f5a90b5f5c1b5f0d7d7e7f5f5f5f5f5f5f5f5f&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 특성 덕분에 데이터 변조를 즉시 감지할 수 있습니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;19&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;3. 실무 구현 사례: SHA-256&lt;/h2&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;실제 프로젝트에서 비밀번호를 암호화하는 로직을 살펴보겠습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt; ️ SHA-256 알고리즘&lt;/h3&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;SHA-256은 &quot;Secure Hash Algorithm 256-bit&quot;의 약자로&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;어떤 길이의 문자열을 입력해도 &lt;b&gt;256비트(32바이트)&lt;/b&gt;의 고정된 길이의 해시값을 만들어냅니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;  보안의 핵심: 솔트(Salt)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;동일한 비밀번호(예: 1234)는 동일한 해시값을 가집니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;user1 password: &quot;1234&quot; &amp;rarr; SHA-256 &amp;rarr; abc123...
user2 password: &quot;1234&quot; &amp;rarr; SHA-256 &amp;rarr; abc123...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;수백만 명의 사용자가 있는 서비스라면, 같은 비밀번호를 사용하는 사람들이 있을 가능성이 높습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;해커들은 이 점을 이용해 해시값들을 미리 계산해둔 &lt;b&gt;레인보우 테이블(Rainbow Table)&lt;/b&gt;로 공격합니다.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;25&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;레인보우 테이블 (Rainbow Table)&lt;/b&gt;&lt;br /&gt;해커들은 미리 수백만 개의 단어와 그 SHA-256 해시값을 계산해 테이블로 만들어둡니다.&lt;/blockquote&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 원문에 &lt;b&gt;솔트(Salt)&lt;/b&gt;라는 임의의 문자열을 추가하여 같은 비밀번호이지만 다른 해시값이 생성되도록 보장합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;user1:
  password: &quot;1234&quot;
  salt: &quot;x7k2p9&quot;
  hash = SHA-256(&quot;1234&quot; + &quot;x7k2p9&quot;) = def456...

user2:
  password: &quot;1234&quot; (같은 비밀번호!)
  salt: &quot;m3j1w8&quot; (다른 솔트)
  hash = SHA-256(&quot;1234&quot; + &quot;m3j1w8&quot;) = uvw789...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;27&quot; data-ke-size=&quot;size23&quot;&gt;  최종 데이터 저장: Base64 인코딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해싱된 결과물은 바이너리(바이트) 데이터입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;바이트 데이터: [0xFF, 0xA3, 0x2E, 0x91, ...]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 텍스트 기반 데이터베이스에 저장하려면 Base64로 인코딩합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;Base64: &quot;fw+jLpE...&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;29&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;4. 비밀번호를 분실했다면? &quot;찾기&quot;가 아닌 &quot;재설정&quot;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;해시 알고리즘을 적용하면 관리자도 원문 비밀번호를 알 수 없습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;이는 보안의 장점이면서 동시에 &quot;비밀번호 찾기 기능&quot;을 기술적으로 불가능하게 만듭니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;따라서 현대 보안 가이드라인은&lt;b&gt; '비밀번호 찾기' 대신 &lt;u&gt;'비밀번호 재설정'&lt;/u&gt;&lt;/b&gt;을 권고합니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;34&quot; data-ke-size=&quot;size23&quot;&gt;처리 프로세스&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;35&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,0,0&quot;&gt;본인 인증:&lt;/b&gt; 이메일이나 SMS로 본인 확인을 진행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,1,0&quot;&gt;보안 토큰 생성:&lt;/b&gt; 일회성 재설정 링크(Token 포함)를 사용자에게 발송합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,2,0&quot;&gt;새 비밀번호 입력:&lt;/b&gt; 사용자가 새 비번을 입력하면, 서버는 이를 다시 &lt;b data-index-in-node=&quot;38&quot; data-path-to-node=&quot;35,2,0&quot;&gt;SHA-256 + Salt&lt;/b&gt; 조합으로 해싱하여 DB를 업데이트합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-path-to-node=&quot;36&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;37&quot; data-ke-size=&quot;size26&quot;&gt;  요약 및 결론&lt;/h2&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;비밀번호는 단순히 암호화하는 것을 넘어, &lt;b&gt;&quot;복호화가 필요 없는 구조&quot;&lt;/b&gt;로 설계하는 것이 가장 안전합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 비밀번호를 잊어버렸을 때 기존 값을 알려주는 서비스가 있다면, 그 서비스의 보안을 의심해 보세요!&lt;/p&gt;</description>
      <category>Back end</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/318</guid>
      <comments>https://soo-vely-dev.tistory.com/318#entry318comment</comments>
      <pubDate>Thu, 26 Feb 2026 16:59:26 +0900</pubDate>
    </item>
    <item>
      <title>[Java] JVM Garbage Collection 구조와 Promotion(승격) 조건</title>
      <link>https://soo-vely-dev.tistory.com/317</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. JVM Garbage Collection(GC)이란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Garbage Collection(GC)&lt;/b&gt;은 자바의 메모리 관리 기법 중 하나로, &lt;b&gt;힙(Heap) 영역에서 더 이상 참조되지 않는 객체들을 찾아 메모리에서 해제하는 과정&lt;/b&gt;을 의미합니다. C나 C++ 과 달리 개발자가 직접 메모리를 해제할 필요가 없으며, JVM이 자동으로 관리해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;GC의 장점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;메모리 누수 방지&lt;/b&gt;: 프로그래머의 실수로 인한 메모리 누수를 자동으로 방지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;개발 생산성 향상&lt;/b&gt;: 메모리 관리에 신경 쓰지 않고 비즈니스 로직에만 집중&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;안정성 보장&lt;/b&gt;: 메모리 부족으로 인한 프로그램 정지 위험을 감소&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;GC의 단점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;GC 중단시간(&lt;u&gt;Stop-The-World&lt;/u&gt;)&lt;/b&gt;: GC 실행 중 모든 애플리케이션 스레드가 중단&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;성능 저하&lt;/b&gt;: GC로 인한 CPU 사용률 증가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;예측 불가능성&lt;/b&gt;: GC 발생 시점을 미리 예측하기 어려움&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. JVM 힙 메모리 구조&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JVM의 힙 영역은 객체의 생존 기간에 따라 크게&amp;nbsp;&lt;/span&gt;&lt;b&gt;Young Generation&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;과&amp;nbsp;&lt;/span&gt;&lt;b&gt;Old Generation&lt;/b&gt;으로 나뉩니다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771981664912&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│              JVM Heap 메모리                    │
├─────────────────────────────────────────────────┤
│                                                 │
│  ┌──────────────────────┐  ┌───────────────┐    │
│  │   Young Generation   │  │ Old           │    │
│  │   (Young Gen)        │  │ Generation    │    │
│  │                      │  │ (Old Gen)     │    │
│  │ ┌────────┐ ┌────────┐│  │               │    │
│  │ │ Eden   │ │Survivor││  │               │    │
│  │ │Space   │ │Spaces  ││  │               │    │
│  │ │        │ │S0 / S1 ││  │               │    │
│  │ └────────┘ └────────┘│  │               │    │
│  └──────────────────────┘  └───────────────┘    │
│                                                 │
└─────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Young Generation (Young Gen)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;새로 생성된 객체들이 할당되는 영역&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;전체 힙의 약 10-30% 차지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대부분의 객체가 이 영역에서 사라짐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;세 개의 하위 영역으로 구성:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Eden Space&lt;/b&gt;: 새로운 객체가 생성되는 공간&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Survivor Space 0 (S0)&lt;/b&gt;: 첫 번째 생존자 영역&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Survivor Space 1 (S1)&lt;/b&gt;: 두 번째 생존자 영역&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Old Generation (Old Gen)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Young Gen에서 오래 생존한 객체들이 이동하는 영역&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;전체 힙의 약 60-90% 차지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;GC가 덜 빈번하게 실행됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Minor GC vs Major GC&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWj2kh/dJMcabiZhQF/YWF558gTzgsatWPDkRfXe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWj2kh/dJMcabiZhQF/YWF558gTzgsatWPDkRfXe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWj2kh/dJMcabiZhQF/YWF558gTzgsatWPDkRfXe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWj2kh%2FdJMcabiZhQF%2FYWF558gTzgsatWPDkRfXe0%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;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;GC는 발생하는 영역에 따라 크게 두 가지로 구분됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Minor GC&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;발생 지점:&lt;/b&gt; &lt;b&gt;Young&lt;/b&gt; Generation&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;특징:&lt;/b&gt; &lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체가 처음 생성되는 &lt;b&gt;Eden&lt;/b&gt; 영역이 꽉 차면 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;상대적으로 속도가 매우 빠릅니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;살아남은 객체는 &lt;b&gt;Survivor&lt;/b&gt; 영역으로 이동하며, 이 과정을 반복하며 생존 기간을 기록합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Major GC (Full GC)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;발생 지점:&lt;/b&gt; &lt;b&gt;Old&lt;/b&gt; Generation&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;특징:&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Old 영역의 메모리가 부족해지면 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Minor GC보다 훨씬 오래 걸립니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Stop-the-world&lt;/b&gt; 현상이 발생하여, GC를 실행하는 스레드를 제외한 모든 애플리케이션 스레드가 중단됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. Minor 영역에서 Major 영역으로의 승격(Promotion)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체가 Young Generation에서 Old Generation으로 옮겨가는 과정을&lt;u&gt;&lt;b&gt; Promotion(승격)&lt;/b&gt;&lt;/u&gt;이라고 부릅니다. 이 과정은 어떻게 결정될까요?&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Age bit의 누적:&lt;/b&gt; Young 영역의 Survivor 공간에서 객체가 살아남을 때마다 객체 헤더의 &lt;b&gt;age&lt;/b&gt; 값이 1씩 증가합니다.&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;새로운 객체 생성: &lt;br /&gt;├─ Eden Space (Age: 0)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1차 Minor GC 후: &lt;br /&gt;├─ Survivor S0 (Age: 1) &amp;larr; 살아남은 객체&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2차 Minor GC 후: &lt;br /&gt;├─ Survivor S1 (Age: 2) &amp;larr; S0에서 생존한 객체&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3차 Minor GC 후: &lt;br /&gt;├─&amp;nbsp;Survivor&amp;nbsp;S0&amp;nbsp;(Age:&amp;nbsp;3)&amp;nbsp;&amp;larr;&amp;nbsp;S1에서&amp;nbsp;생존한&amp;nbsp;객체 &lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Age &amp;gt;= Threshold (보통 15)에서: &lt;br /&gt;├─ Old Generation &amp;larr; 승격!&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;MaxTenuringThreshold:&lt;/b&gt; JVM은 이 &lt;u&gt;age 값이 특정 기준(Threshold)에 도달하면 해당 객체가 &quot;오래 살아남을 객체&quot;라고 판단&lt;/u&gt;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;이동:&lt;/b&gt; 기준치를 넘긴 객체는 비로소&lt;u&gt;&lt;b&gt; Old Generation&lt;/b&gt;&lt;/u&gt;으로 &lt;u&gt;&lt;b&gt;복사(Promotion)&lt;/b&gt;&lt;/u&gt;됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: 너무 큰 객체가 생성되어 Eden 영역에 담기지 않을 경우, 바로 Old 영역으로 할당되기도 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 결론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;GC가 자동으로 메모리를 관리해 주지만, &lt;b&gt;Major GC가 빈번하게 발생하거나 길어지면 서비스 응답 속도가 떨어지는 성능 저하&lt;/b&gt;를 초래합니다. 따라서 적절한 힙 크기 설정과 서비스 특성에 맞는 GC 알고리즘(G1, ZGC 등) 선택이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;image&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span style=&quot;background-color: #000000; color: #1f1f1f; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;</description>
      <category>Back end/Java</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/317</guid>
      <comments>https://soo-vely-dev.tistory.com/317#entry317comment</comments>
      <pubDate>Wed, 25 Feb 2026 10:57:01 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 카프카(kafka)란? (feat. 메세지 큐)</title>
      <link>https://soo-vely-dev.tistory.com/316</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;✅&amp;nbsp;카프카(kafka)란?&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache Kafka는 수천 개의 회사에서 고성능 데이터 파이프라인, 스트리밍 분석, 데이터 통합 및 미션 크리티컬 애플리케이션에 사용되는 오픈 소스 분산 이벤트 스트리밍 플랫폼입니다.&lt;br /&gt;- Apache Kafka 공식 홈페이지 -&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, Kafka는 대규모 데이터를 처리할 수 있는 메시지 큐이다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;✅&amp;nbsp;메시지 큐(Message Queue)란?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;u&gt;&lt;b&gt;메시지 큐(Message Queue)&lt;/b&gt;&lt;/u&gt;는 &lt;u&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;큐(Queue) 형태에 데이터를 일시적으로 저장하는 임시 저장소&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;를 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메시지 큐를 활용하면 &lt;u&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;비동기&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;적으로 데이터를 처리할 수 있어서 효율적이다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;✅&amp;nbsp;REST API 방식으로 통신 vs 메시지 큐를 활용한 통신&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;lsquo;REST API 방식으로 통신하는 방식&amp;rsquo;과 &amp;lsquo;메시지 큐를 활용한 통신 방식&amp;rsquo;를 이메일 발송 시나리오를 예시로 비교해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;REST API 방식은 요청을 보낸 뒤에 &lt;u&gt;&lt;b&gt;모든 작업이 다 처리될 때까지 기다렸다가&lt;/b&gt;&lt;/u&gt; 응답을 받는 식으로 통신을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JG5Hj/dJMcagkcOOy/SmN3petesntIVzQstUdBP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JG5Hj/dJMcagkcOOy/SmN3petesntIVzQstUdBP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JG5Hj/dJMcagkcOOy/SmN3petesntIVzQstUdBP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJG5Hj%2FdJMcagkcOOy%2FSmN3petesntIVzQstUdBP1%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;1752&quot; height=&quot;982&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 메시지 큐를 활용한 통신 방식은 비동기적으로 작업을 처리하기 때문에 &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;모든 작업이 다 처리되는 것과 상관없이 응답을 받을 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kACWX/dJMcaaEjRSb/bqcZ9f6D7syr0DoNqJrJ71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kACWX/dJMcaaEjRSb/bqcZ9f6D7syr0DoNqJrJ71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kACWX/dJMcaaEjRSb/bqcZ9f6D7syr0DoNqJrJ71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkACWX%2FdJMcaaEjRSb%2FbqcZ9f6D7syr0DoNqJrJ71%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;2000&quot; height=&quot;485&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용자가 REST API 방식으로 요청을 보낸다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;요청을 받은 서버는 메시지 큐(Message Queue)에 전달할 메시지를 만들어 전달한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메시지 안에는 처리해야 할 요청에 대한 정보들이 담겨있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메시지 큐(Message Queue)는 처리해야 할 메시지들을 보관하는 임시 저장소 역할을 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메시지를 만들어 메시지 큐로 전달하는 서버를 보고 &lt;u&gt;&lt;b&gt;프로듀서(Producer)&lt;/b&gt;&lt;/u&gt;라고 얘기한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;lsquo;메시지를 생산하는 주체&amp;rsquo;를 직관적으로 표현하기 위해 프로듀서(Producer)라는 용어가 만들어졌다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Producer 서버는 메시지 큐에 메시지를 넣자마자 사용자에게 성공 응답을 한다.&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이메일 작업이 끝날 때까지 기다렸다가 응답을 하는 것이 아니라, 메시지 큐에 메시지를 넣자마자 응답을 했다. 이렇게 처리하는 방식을 보고 &lt;u&gt;&amp;lsquo;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;비동기&lt;/span&gt; 방식으로 처리했다&lt;/b&gt;&amp;rsquo;&lt;/u&gt;라고 표현한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;u&gt;&lt;b&gt;비동기 방식&lt;/b&gt;&lt;/u&gt;으로 처리하기 때문에 요청을 효율적으로 처리할 수 있다. 이 때문에 대규모 트래픽을 처리할 때도 유리한 구조이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메시지 큐는 Producer로부터 받은 메시지(요청)를 보관하고 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Consumer 서버가 메시지 큐에 들어있는 메시지(요청)를 꺼내서 실제 작업(ex. 이메일 발송 로직 처리)을 수행한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메시지를 꺼내서 처리하는 서버를 보고 &lt;b&gt;&lt;u&gt;컨슈머(Consumer)&lt;/u&gt;&lt;/b&gt;라고 얘기한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;메시지를 소비하는 주체&amp;rsquo;를 직관적으로 표현하기 위해 컨슈머(Consumer)라는 용어가 만들어졌다. &lt;br /&gt;cf.Consume: 소비하다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Back end/Kafka</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/316</guid>
      <comments>https://soo-vely-dev.tistory.com/316#entry316comment</comments>
      <pubDate>Sat, 21 Feb 2026 15:49:28 +0900</pubDate>
    </item>
    <item>
      <title>[Java] JDBC의 Statement와 PreparedStatement</title>
      <link>https://soo-vely-dev.tistory.com/315</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스와 통신하는 Java 애플리케이션을 개발할 때, JDBC를 통해 SQL을 실행하게 됩니다. 이때 Statement와 PreparedStatement 중 어떤 것을 사용하느냐에 따라 애플리케이션의 성능과 보안이 크게 달라집니다.&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;Statement: 유연하지만 위험한 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Statement는 가장 기본적인 SQL 실행 방식입니다. 쿼리를 작성할 때 문자열 연결(String concatenation)을 통해 동적으로 SQL을 구성합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;String userId = &quot;user123&quot;;
String sql = &quot;SELECT * FROM users WHERE id = '&quot; + userId + &quot;'&quot;;
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단해 보이지만, 이 방식은 사용자 입력이 직접 쿼리 문자열에 포함되기 때문에 SQL 인젝션 공격에 매우 취약합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 userId 변수에 ' OR '1'='1 같은 악의적인 입력이 들어온다면, 쿼리의 의도가 완전히 변조될 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PreparedStatement: 안전하고 효율적인 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PreparedStatement는 이러한 문제를 해결하기 위해 설계된 방식입니다. 쿼리의 구조를 먼저 고정시키고, 나중에 실제 값들을 바인딩합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;String userId = &quot;user123&quot;;
String sql = &quot;SELECT * FROM users WHERE id = ?&quot;;
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, userId);
ResultSet rs = pstmt.executeQuery();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물음표(?)는 플레이스홀더(placeholder) 역할을 하며, setString() 같은 메서드로 실제 값을 나중에 바인딩합니다.&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;1. SQL 인젝션 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PreparedStatement는 쿼리 구조가 이미 확정된 상태이기 때문에, 바인딩되는 데이터는 오직 파라미터로만 취급됩니다. 아무리 악의적인 입력이 들어와도 쿼리의 논리 구조를 변경할 수 없습니다. 반면 Statement는 문자열 연결 방식이므로 사용자 입력이 쿼리 구조 자체를 변경할 여지가 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 데이터베이스 캐싱을 통한 성능 향상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스는 SQL을 실행하기 전에 파싱과 실행 계획 수립이라는 비용이 큰 과정을 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 작업의 결과는 캐시(cache)에 저장되어 재활용됩니다.&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;Statement를 사용하면 파라미터값이 달라질 때마다 전체 쿼리 문자열이 다르게 구성되므로, 매번 파싱과 계획 수립을 새로 진행해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT name FROM tbl WHERE id = 1  -- 서로 다른 SQL로 인식될 가능성
SELECT name FROM tbl WHERE id = 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 PreparedStatement는 SQL 구조가 고정되어 있기 때문에,&lt;br /&gt;데이터베이스나 JDBC 드라이버, 커넥션 풀 설정에 따라&lt;br /&gt;이러한 파싱 결과나 실행 계획을 재활용할 수 있는 여지가 생깁니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT name FROM tbl WHERE id = ?  -- 파싱 후 캐싱
SELECT name FROM tbl WHERE id = ?  -- 동일한 SQL 구조로 재사용 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 같은 쿼리를 여러 번 실행하는 경우, PreparedStatement는 훨씬 빠른 응답 속도를 제공합니다.&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;연결 풀(Connection Pool)과의 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Connection Pool 환경에서는 커넥션이 반복 재사용되므로, PreparedStatement 캐싱을 지원하는 커넥션 풀(HikariCP 등)을 사용할 경우 동일한 SQL에 대한 PreparedStatement를 재사용할 수 있어 성능 이점을 더욱 극대화할 수 있습니다.&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;PreparedStatement가 표준인 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대의 자바 개발에서는 거의 항상 PreparedStatement를 사용해야 합니다. 보안과 성능 양쪽 측면에서 Statement보다 우수하기 때문입니다. 특히 반복적인 쿼리 실행이 이루어지는 환경에서는 그 차이가 더욱 두드러집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Statement는 단순한 학습 목적이나 특수한 상황을 제외하고는 실제 프로덕션 코드에서 사용하지 않는 것이 좋습니다.&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;JdbcTemplate&lt;/b&gt;, &lt;b&gt;MyBatis&lt;/b&gt;, &lt;b&gt;JPA&lt;/b&gt; 같은 추상화 계층을 사용하기 때문에, 개발자가 Statement나 PreparedStatement를 직접 다룰 일은 거의 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이들 내부 구현은 대부분 PreparedStatement를 기반으로 동작합니다. 따라서 두 방식의 차이를 이해하는 것은 성능 튜닝이나 문제 분석에 큰 도움이 됩니다.&lt;/p&gt;</description>
      <category>Back end/Java</category>
      <category>preparedstatement</category>
      <category>statement</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/315</guid>
      <comments>https://soo-vely-dev.tistory.com/315#entry315comment</comments>
      <pubDate>Thu, 22 Jan 2026 11:30:44 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] 테이블 통계정보 손실로 인한 배치 성능 저하 문제 해결 사례</title>
      <link>https://soo-vely-dev.tistory.com/314</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 &lt;b&gt;백업 시간 단축을 위해 데이터를 정제하는 과정에서, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역설적으로 배치 작업 성능이 급격히 저하되는 이슈&lt;/b&gt;를 겪었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 테이블 통계정보의 불일치가 시스템에 어떤 영향을 주는지, 그리고 이를 어떻게 해결했는지 공유합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황: &quot;백업을 최적화했더니 배치가 느려졌다?&quot;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 시스템 배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 시스템은 다음과 같은 데이터 처리 파이프라인으로 구성되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Source&lt;/b&gt;: MSSQL (실시간 CRM 데이터)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Data Warehouse&lt;/b&gt;: Oracle 데이터베이스에 매일 정해진 시간에 스냅샷 적재&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ETL&lt;/b&gt;: MSSQL &amp;rarr; Oracle (스키마 및 형변환 적재)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배치 처리&lt;/b&gt;: 변환된 데이터를 기반으로 다단계 집계 프로세스를 통한 통계 데이터 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 백업&lt;/b&gt;: 데이터 무결성을 위한 정기 Full Backup&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 발생한 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 운영이 4년을 경과하면서 &lt;b&gt;데이터 규모가 급증&lt;/b&gt;했고, &lt;b&gt;Full Backup에 10시간이 소요&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;중간 처리 테이블의 데이터 정제 (배치 완료 후 TRUNCATE 처리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업 시간은 단축되었으나, &lt;b&gt;다음 날부터 평소 1시간이면 끝나던 배치 작업이 5시간 이상 소요&lt;/b&gt;되는 심각한 지연 현상이 발생했습니다.&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;통계정보의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle 옵티마이저는 &lt;b&gt;테이블의 행 개수, 데이터 분포도 등 통계정보&lt;/b&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;중간 처리 테이블을 배치 완료 후 TRUNCATE 하도록 변경했으나, 작업 순서와 Oracle의 자동 통계 수집 스케줄이 엇갈리면서 치명적인 문제가 발생했습니다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px;&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;시각&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px;&quot;&gt;&lt;b&gt; 작업 내용 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px;&quot;&gt;&lt;b&gt; 상태 및 문제점 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;15:00&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;Table TRUNCATE&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;데이터 정제 완료. 테이블은 비어 있고 통계는 '낡은 상태(Stale)'가 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;22:00&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;자동 통계 수집&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;⚠️ &lt;b&gt;[함정]&lt;/b&gt; Oracle이 0건인 상태를 기준으로 통계를 확정함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;06:00&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;Data INSERT&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;대량(약 1억 건)의 데이터가 적재됨. 그러나 통계는 여전히 0건으로 기록됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;07:00&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;배치(SELECT) 실행&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;❌ &lt;b&gt;[이슈]&lt;/b&gt; 옵티마이저는 데이터가 0건인 줄 알고 최악의 경로를 선택함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 옵티마이저가 비효율적인 실행 계획을 수립하여 &lt;b&gt;배치 성능이 급격히 저하&lt;/b&gt;된 것입니다.&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;1. 자동 통계 수집 윈도우 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 시스템은 평일 밤은 짧게, 주말은 길게 통계를 수집하도록 설정되어 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 66px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 22px;&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;WINDOW_NAME&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px;&quot;&gt;&lt;b&gt; 시작 시간 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px;&quot;&gt;&lt;b&gt; &amp;nbsp;유지 시간 (DURATION) &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 22px;&quot;&gt;&lt;b&gt; 상세 설명 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;평일(월~금)&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;밤 22:00&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;4시간&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;밤 10시 시작 ~ 새벽 2시 종료 (시간 제약 있음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;주말(토~일)&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;오전 06:00&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;20시간&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;아침 6시 시작 ~ 다음날 새벽 종료 (넉넉함)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 설정 확인용 쿼리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;요일별 수집 시간대 및 유지 시간 확인&lt;/h4&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 자동 수집 작업이 활성화(ENABLED)되어 있는지 확인
SELECT CLIENT_NAME, STATUS
FROM DBA_AUTOTASK_CLIENT
WHERE CLIENT_NAME = 'auto optimizer stats collection';

-- 요일별 수집 시간대(Window) 확인
SELECT WINDOW_NAME, REPEAT_INTERVAL, DURATION
FROM DBA_SCHEDULER_WINDOWS;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;특정 테이블의 마지막 통계 수집 시점 확인&lt;/h4&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;SELECT OWNER, TABLE_NAME, NUM_ROWS, LAST_ANALYZED 
FROM DBA_TABLES 
WHERE TABLE_NAME = 'TARGET_TABLE_NAME';
&lt;/code&gt;&lt;/pre&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;수동 통계정보 갱신&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 실행 계획 유도를 위해 DBMS_STATS 패키지를 사용하여 통계정보를 수동으로 재계산했습니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname          =&amp;gt; '[SCHEMA_NAME]',
        tabname          =&amp;gt; '[TABLE_NAME]',
        estimate_percent =&amp;gt; DBMS_STATS.AUTO_SAMPLE_SIZE,
        method_opt       =&amp;gt; 'FOR ALL COLUMNS SIZE AUTO',
        cascade          =&amp;gt; TRUE,   -- 인덱스 통계까지 함께 수집
        no_invalidate    =&amp;gt; FALSE   -- 기존 커서 무효화 (즉시 반영)
    );
END;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 파라미터 설명&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ownname: 스키마 이름&lt;/li&gt;
&lt;li&gt;tabname: 대상 테이블 이름&lt;/li&gt;
&lt;li&gt;estimate_percent: 통계 추정 방식 (AUTO_SAMPLE_SIZE는 Oracle이 자동 결정)&lt;/li&gt;
&lt;li&gt;method_opt: 컬럼별 히스토그램 생성 방식 (AUTO는 필요한 컬럼만 자동 선택)&lt;/li&gt;
&lt;li&gt;cascade: TRUE 설정 시 테이블의 인덱스 통계도 함께 수집하여 옵티마이저의 정확성 향상&lt;/li&gt;
&lt;li&gt;no_invalidate: FALSE 설정 시 기존 커서를 무효화하여 새로운 통계정보가 즉시 반영됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;통계정보 갱신 결과, 배치 작업이 정상 속도로 복귀했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;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;이후 통계정보 상태를 모니터링하기 위해 다음 쿼리를 작성했습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;SELECT 
    owner AS &quot;스키마명&quot;, 
    table_name AS &quot;테이블명&quot;, 
    num_rows AS &quot;행(Row)개수&quot;, 
    last_analyzed AS &quot;마지막통계수집일시&quot;
FROM 
    all_tables
WHERE 
    owner NOT IN ('SYS', 'SYSTEM')
ORDER BY 
    owner, num_rows DESC;
&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;각 테이블의 현재 데이터 건수 파악&lt;/li&gt;
&lt;li&gt;통계정보가 언제 마지막으로 갱신되었는지 확인&lt;/li&gt;
&lt;li&gt;오래된 통계정보를 가진 테이블 식별&lt;/li&gt;
&lt;/ul&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;1. 백업 최적화 방안&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 10시간에 소요되는 Full Backup 시간을 단축하기 위해 다음과 같은 개선안을 검토하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단기 개선사항&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미사용 테이블/인덱스 정리 (진행 완료)&lt;/li&gt;
&lt;li&gt;데이터 적재 후 통계정보 자동 갱신: 배치 프로세스의 데이터 적재 완료 단계에서 명시적으로 DBMS_STATS.GATHER_TABLE_STATS()를 호출하여 옵티마이저가 항상 최신 통계정보로 실행 계획을 수립하도록 개선 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장기 개선사항&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테이블 파티셔닝&lt;/b&gt;: 대용량 테이블을 연도별로 파티셔닝하여 백업 대상 축소 및 선택적 백업 지원 검토&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; 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;이번 사건을 통해 &lt;b&gt;&quot;데이터 적재만큼 메타정보 관리도 중요하다&quot;&lt;/b&gt;는 교훈을 얻었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;쿼리 성능이 갑자기 저하된다면, 가장 먼저 last_analyzed 날짜를 확인하세요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;TRUNCATE 등 대량 데이터 작업 이후에는 반드시 통계정보를 갱신해야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업 최적화와 성능 안정화를 동시에 달성하기 위해, 위의 개선사항들을 단계별로 추진해 나갈 계획입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/Oracle</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/314</guid>
      <comments>https://soo-vely-dev.tistory.com/314#entry314comment</comments>
      <pubDate>Tue, 20 Jan 2026 19:17:21 +0900</pubDate>
    </item>
    <item>
      <title>AtomicInteger는 어떻게 동시성을 보장할까? - CAS 알고리즘 깊이 파헤치기</title>
      <link>https://soo-vely-dev.tistory.com/313</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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;일반 int에 synchronized를 걸지 않고 100개 스레드가 각각 +1을 하면 결과가 100이 안 나올 수 있습니다. 하지만 AtomicInteger.addAndGet()을 사용하면 락 없이도 정확히 100이 보장됩니다. 어떻게 이게 가능한가요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시에는 제대로 답변하지 못했지만, 이후 학습을 통해 그 원리를 정리해보았습니다.&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;문제 상황: Race Condition&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 문제를 명확히 해봅시다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 문제가 되는 코드
private int counter = 0;

// 100개의 스레드가 동시에 실행
public void increment() {
    counter++; // 이 한 줄이 사실은 3개의 작업!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;counter++는 원자적(atomic) 연산이 아닙니다. 실제로는:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;READ&lt;/b&gt;: 메모리에서 counter 값을 읽음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MODIFY&lt;/b&gt;: 값을 1 증가&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WRITE&lt;/b&gt;: 증가된 값을 메모리에 씀&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 스레드가 동시에 실행되면:&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;Thread A: READ(0) -&amp;gt; MODIFY(1) -&amp;gt; WRITE(1)
Thread B: READ(0) -&amp;gt; MODIFY(1) -&amp;gt; WRITE(1)
결과: 2가 아니라 1!
&lt;/code&gt;&lt;/pre&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;기존 해결책: synchronized와 Lock&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;synchronized 방식&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;private int counter = 0;

public synchronized void increment() {
    counter++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;: 안전하고 간단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;: 성능 오버헤드가 큽니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 락을 획득하지 못하면 블로킹됨&lt;/li&gt;
&lt;li&gt;컨텍스트 스위칭 비용 발생&lt;/li&gt;
&lt;li&gt;경쟁이 심할수록 성능 저하&lt;/li&gt;
&lt;/ul&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;AtomicInteger의 마법: CAS (Compare-And-Swap)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AtomicInteger는 &lt;b&gt;락을 사용하지 않고도&lt;/b&gt; 동시성을 보장합니다. 그 비밀은 &lt;b&gt;CAS 알고리즘&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CAS란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAS는 하드웨어 레벨에서 지원하는 원자적 연산입니다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;CAS(메모리_주소, 예상값, 새값) {
    if (메모리_주소의_값 == 예상값) {
        메모리_주소의_값 = 새값;
        return true;
    } else {
        return false;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 비교와 교환이 &lt;b&gt;하나의 CPU 명령어&lt;/b&gt;로 수행되므로 중간에 다른 스레드가 끼어들 수 없습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AtomicInteger 내부 구조&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class AtomicInteger extends Number implements java.io.Serializable {
    private static final Unsafe U = Unsafe.getUnsafe();
    private static final long VALUE
        = U.objectFieldOffset(AtomicInteger.class, &quot;value&quot;);

    private volatile int value;
    
    public final int addAndGet(int delta) {
        return U.getAndAddInt(this, VALUE, delta) + delta;
    }
}

// Unsafe 클래스의 실제 구현
public final class Unsafe {
    @IntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 포인트&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getIntVolatile(): 객체 o의 offset 위치에서 volatile 읽기 수행&lt;/li&gt;
&lt;li&gt;weakCompareAndSetInt(): CAS 연산 수행&lt;/li&gt;
&lt;li&gt;@IntrinsicCandidate: JVM이 이 메서드를 네이티브 CPU 명령어로 대체할 수 있음&lt;/li&gt;
&lt;li&gt;메모리 offset을 직접 사용하여 성능 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 과정 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100개 스레드가 동시에 addAndGet(1)을 호출할 때:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Thread A:
1. current = 0을 읽음
2. next = 1 계산
3. CAS(0, 1) 시도 -&amp;gt; 성공! (아직 0이므로)
4. 값이 1로 변경됨

Thread B:
1. current = 0을 읽음 (거의 동시에)
2. next = 1 계산
3. CAS(0, 1) 시도 -&amp;gt; 실패! (이미 1로 변경됨)
4. 루프 재시작
5. current = 1을 읽음
6. next = 2 계산
7. CAS(1, 2) 시도 -&amp;gt; 성공!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getIntVolatile()은 volatile 읽기를 수행하여&lt;br /&gt;&lt;b&gt;CPU 캐시 일관성 프로토콜을 통해 최신 값을 보장받고&lt;/b&gt;,&lt;br /&gt;&lt;b&gt;읽기/쓰기 재정렬을 방지&lt;/b&gt;합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;낙관적 락(Optimistic Lock)과의 유사성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;낙관적 락의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;충돌이 드물 것이라 &lt;b&gt;낙관적으로 가정&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;락을 미리 획득하지 않음&lt;/li&gt;
&lt;li&gt;업데이트 시점에 충돌 검사&lt;/li&gt;
&lt;li&gt;충돌 시 재시도&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CAS도 동일한 철학&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락 없이 작업 진행 (낙관적)&lt;/li&gt;
&lt;li&gt;마지막에 값이 변경되었는지 확인&lt;/li&gt;
&lt;li&gt;변경되었다면 재시도&lt;/li&gt;
&lt;li&gt;충돌이 적을수록 효율적&lt;/li&gt;
&lt;/ul&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;p data-ke-size=&quot;size16&quot;&gt;CAS를 직접 구현할 수는 없지만 (하드웨어 명령어 필요), 개념적으로 어떻게 100을 보장하는지 시뮬레이션해봅시다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;import java.util.concurrent.atomic.AtomicInteger;

public class CustomAtomicCounter {
    private final AtomicInteger value = new AtomicInteger(0);
    
    /**
     * CAS 기반 증가 연산
     */
    public int increment() {
        while (true) {
            int current = value.get();
            int next = current + 1;
            
            // CAS: 현재 값이 여전히 current라면 next로 변경
            if (value.compareAndSet(current, next)) {
                return next; // 성공
            }
            // 실패하면 루프 재시작 (다른 스레드가 값을 변경함)
        }
    }
    
    public int get() {
        return value.get();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 코드&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrencyTest {
    public static void main(String[] args) throws InterruptedException {
        CustomAtomicCounter counter = new CustomAtomicCounter();
        int threadCount = 100;
        
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        // 100개 스레드가 각각 1씩 증가
        for (int i = 0; i &amp;lt; threadCount; i++) {
            executor.submit(() -&amp;gt; {
                counter.increment();
                latch.countDown();
            });
        }
        
        latch.await();
        executor.shutdown();
        
        System.out.println(&quot;최종 값: &quot; + counter.get()); // 항상 100!
    }
}
&lt;/code&gt;&lt;/pre&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;synchronized vs AtomicInteger 성능 비교&lt;/h2&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// 간단한 벤치마크
public class PerformanceTest {
    private static final int ITERATIONS = 1_000_000;
    
    // synchronized 버전
    private int syncCounter = 0;
    public synchronized void syncIncrement() {
        syncCounter++;
    }
    
    // AtomicInteger 버전
    private AtomicInteger atomicCounter = new AtomicInteger(0);
    public void atomicIncrement() {
        atomicCounter.incrementAndGet();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적인 결과&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경쟁이 적을 때: AtomicInteger가 &lt;b&gt;2-5배 빠름&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;경쟁이 심할 때: 성능 차이가 줄어들지만 여전히 AtomicInteger가 유리&lt;/li&gt;
&lt;li&gt;synchronized는 블로킹되지만, CAS는 바쁘게 재시도 (스핀)&lt;/li&gt;
&lt;/ul&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;CAS의 장단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;락 프리(Lock-Free)&lt;/b&gt;: 데드락 불가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;높은 성능&lt;/b&gt;: 경쟁이 적을 때 매우 빠름&lt;/li&gt;
&lt;li&gt;&lt;b&gt;논블로킹&lt;/b&gt;: 스레드가 블로킹되지 않음&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;ABA 문제&lt;/b&gt;: A&amp;rarr;B&amp;rarr;A로 변경될 때 감지 못함 (AtomicStampedReference로 해결)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CPU 사이클 낭비&lt;/b&gt;: 재시도 중 스핀으로 CPU 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경쟁이 심할 때&lt;/b&gt;: 재시도가 많아지면 오히려 비효율적&lt;/li&gt;
&lt;/ol&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;volatile의 역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AtomicInteger 내부의 volatile int value에 주목해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;private volatile int value;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;volatile이 보장하는 것&lt;/b&gt;:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;가시성(Visibility)&lt;/b&gt;: 한 스레드의 변경이 즉시 다른 스레드에게 보임&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재정렬 방지&lt;/b&gt;: 컴파일러/CPU의 최적화로 인한 명령어 순서 변경 금지&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAS만으로는 부족하고, volatile이 함께 있어야 올바른 동작이 보장됩니다.&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;AtomicInteger를 사용하면 좋은 경우&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 카운터, ID 생성기&lt;/li&gt;
&lt;li&gt;경쟁이 낮거나 중간 정도&lt;/li&gt;
&lt;li&gt;락 오버헤드를 피하고 싶을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;synchronized를 사용해야 하는 경우&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 다단계 연산 (단일 원자성 보장 필요)&lt;/li&gt;
&lt;li&gt;경쟁이 매우 심할 때&lt;/li&gt;
&lt;li&gt;블로킹이 문제 없는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Atomic으로는 불가능한 예시
public synchronized void transferMoney(Account from, Account to, int amount) {
    from.balance -= amount;
    to.balance += amount;
    // 두 연산이 함께 원자적이어야 함
}
&lt;/code&gt;&lt;/pre&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;AtomicInteger는 CAS 알고리즘을 사용&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CAS는 하드웨어 레벨의 원자적 연산&lt;/b&gt;으로, 비교와 교환을 한 번에 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실패 시 재시도하는 낙관적 접근&lt;/b&gt;으로 100을 보장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;volatile과 함께 사용&lt;/b&gt;되어 가시성도 보장합니다.&lt;/li&gt;
&lt;li&gt;직접 구현한다면 &lt;b&gt;AtomicInteger를 활용한 CAS 루프&lt;/b&gt;를 사용하면 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java Concurrency in Practice&lt;/li&gt;
&lt;li&gt;Java Memory Model (JMM) Specification&lt;/li&gt;
&lt;li&gt;CPU의 Compare-and-Swap 명령어 (x86의 CMPXCHG)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Back end/Java</category>
      <author>Kim-SooHyeon</author>
      <guid isPermaLink="true">https://soo-vely-dev.tistory.com/313</guid>
      <comments>https://soo-vely-dev.tistory.com/313#entry313comment</comments>
      <pubDate>Wed, 14 Jan 2026 18:57:43 +0900</pubDate>
    </item>
  </channel>
</rss>