<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Arc</title>
    <link>https://seoarc.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 8 May 2026 08:07:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>SeoArc</managingEditor>
    <image>
      <title>Arc</title>
      <url>https://tistory1.daumcdn.net/tistory/5067840/attach/2c59be3ccb154bacae585670cce4984c</url>
      <link>https://seoarc.tistory.com</link>
    </image>
    <item>
      <title>[NCP] Naver Cloud Platform 사용 후기</title>
      <link>https://seoarc.tistory.com/131</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SWYP에 참가하여 프로젝트를 진행하면서, 무료로 제공받은 크레딧으로 NCP(Naver Cloud Platform)를 사용해봤다. NCP는 전반적으로 타 클라우드 플랫폼에 비해 직관적이어서 접근하기 쉬웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 번에도 기회가 있으면 NCP를 사용해 볼 예정이다.&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 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;프로젝트 소개: 냉장고 속 낭비를 줄이는 식비 지킴이&lt;/span&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;우리 집 냉장고와 식료품 창고의 '재고'를 한눈에 파악하고, 유통기한 임박 식료품을 알려주는 알뜰 식비 관리 서비스 '키친로그( Kitchen Log)'를 제작했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;장보고 나서 식재료 사진과 유통기한 정보를 등록하면, 설정한 기준에 맞춰 앱 푸시 알림을 보내준다. 단순히 알림에서 끝나는 것이 아니라, '이 식재료를 어떻게 쓸까?'라는 고민을 해결하기 위해 보유한 식재료 조합으로 할 수 있는 AI 레시피 추천 기능을 포함했다. 새로 장을 보지 않아도 냉장고 속 재고만으로 요리할 수 있는 아이디어를 제공해 식비 절감과 환경 보호를 동시에 돕는 것이 기획의 핵심이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;Github:&lt;/b&gt; &lt;a href=&quot;https://github.com/food-keeper-project/food-keeper-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/food-keeper-project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;Demo:&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;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Ncloud 활용 서비스&lt;/span&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트의 안정적인 운영을 위해 NCP(Naver Cloud Platform)의 다양한 서비스를 활용했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;VPC (Virtual Private Cloud):&lt;/b&gt; 서비스 간 독립적인 네트워크 환경 구축.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;Subnet Management:&lt;/b&gt; Public과 Private 서브넷을 분리하여 보안성 강화.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;Server:&lt;/b&gt; 개발, 운영, 모니터링용으로 용도를 나누어 총 3대의 서버 운영.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,3,0&quot;&gt;Object Storage:&lt;/b&gt; 식재료 이미지 및 정적 데이터 저장소.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,4,0&quot;&gt;Global CDN:&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;h2 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Ncloud 서비스 적용&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;5592&quot; data-origin-height=&quot;3044&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WIwUq/dJMcahXqyG0/JBrhQfxTBvjqtwNDiyvDj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WIwUq/dJMcahXqyG0/JBrhQfxTBvjqtwNDiyvDj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WIwUq/dJMcahXqyG0/JBrhQfxTBvjqtwNDiyvDj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWIwUq%2FdJMcahXqyG0%2FJBrhQfxTBvjqtwNDiyvDj1%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;5592&quot; height=&quot;3044&quot; data-origin-width=&quot;5592&quot; data-origin-height=&quot;3044&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;보안성을 높이기 위해 전체 서비스를 VPC 내부에 배치하고, 서브넷을 분리하여 외부 노출이 필요한 API 서버와 내부 데이터 처리 서버를 격리했다. Server는 여러 OS를 지원하고 생성 과정이 직관적이라 개발 서버부터 모니터링 서버까지 신속하게 구축할 수 있었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;Object Storage를 활용하여, 유저가 업로드한 식재료 사진, 프로필 사진 등의 리소스를 저장하고 이를 Global CDN과 연결했다. 스토리지와의 연동이 매우 쉽고 CDN 정책 설정이 편리해서 고해상도 이미지 전송 시에도 성능 저하 없이 안정적인 서비스를 제공할 수 있었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;만족했던 점과 아쉬운 점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18&quot;&gt;만족했던 점:&lt;/b&gt; NCP의 가장 큰 장점은 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;18&quot;&gt;직관적인 UI&lt;/b&gt;다. 클라우드 인프라를 처음 접하는 개발자라도 별도의 문서를 뒤적거리지 않고 콘솔 내에서 대부분의 설정을 마칠 수 있을 만큼 사용자 친화적이었다. 특히 서브넷 관리나 서버 생성 단계의 가시성이 좋아 인프라 관리 부담을 크게 덜 수 있었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19&quot;&gt;아쉬웠던 점:&lt;/b&gt; Reference 문서가 주로 API 요청에 대한 정보가 많았는데. 국내 개발 생태계에서 점유율이 높은 Spring 기반 환경에서 더 쉽게 연동할 수 있도록, 라이브러리나 SDK가 풍부하게 제공된다면 좋을 것 같다는 생각이 들었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Green Developers 프로그램 참여 소감&lt;/span&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;크레딧 지원 덕분에 나처럼 클라우드 경험이 부족한 예비 개발자가 직접 아키텍처를 설계하고 구축해 보는 경험을 할 수 있었다. 특히 비용 부담 없이 NCP의 다양한 클라우드 서비스를 활용할 수 있는 점이 좋았다. 프로젝트가 실제 서버 위에서 돌아가는 과정을 보며 많은 성취감을 느꼈고, 개발자로서 한 단계 성장할 수 있는 소중한 시간이었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;마치며&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하며 네트워크 설정 등 어려운 순간도 있었지만, 결과적으로 안정적인 서비스를 구축할 수 있어 흥미로운 여정이었다. 향후에는 NCP의 &lt;b&gt;Clova Studio&lt;/b&gt;와 &lt;b&gt;OCR&lt;/b&gt;을 더 적극적으로 활용해 레시피 추천 엔진을 고도화할 계획이다. 개발자들의 성장을 적극적으로 지원해 주는 프로그램에 다시 한번 감사함을 전한다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/131</guid>
      <comments>https://seoarc.tistory.com/131#entry131comment</comments>
      <pubDate>Wed, 7 Jan 2026 14:08:48 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 서버 스펙에 따른 쓰레드 수 조정</title>
      <link>https://seoarc.tistory.com/130</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 프로젝트를 하고 GCP(Google Cloud Platform)에 배포하여 운영 서버를 가동시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용문제로 인해 낮은 스펙을 사용할 수 밖에 없었지만 아직 테스트 사용자만 있는 지금 상황에서는 충분한 스펙이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그럼에도 만족하지 못할 성능이 나오는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 이를 위해 서버 스펙에 맞춰 스레스 수를 조정한 과정을 공유하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 스펙이 낮은 서버를 가동했을 때 Spring에서 고려해봐야 하는 것이 무엇일까?&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;톰캣 스레드&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 우리가 Spring 서버를 배포 할 때는 보통 Tomcat이라는 WAS를 통해 배포하게 된다.&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;Spring은 외부에 Tomcat을 가동한 상태에서 배포할 수도 있고, Spring Boot 내부에 내장되어 있는 Tomcat을 사용해서 배포할 수도 있다. 최근에는 내부에 내장되어있는 Tomcat을 통해 배포하는 방식이 주를 이루고 있다.&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;이렇게 가동한 Tomcat은 &lt;span style=&quot;color: #f89009;&quot;&gt;멀티 스레드&lt;/span&gt; 기반으로 요청을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드의 경우 요청을 병렬적으로 처리할 수 있기 때문에 대량의 다중 요청에 대해서 싱글 스레드보다 빠른 속도로 처리 할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 멀티 스레드 방식을 잘 활용하지 못한다면 싱글 스레드보다 못한 결과를 낳을 수 있다.&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;span style=&quot;color: #409d00;&quot;&gt;멀티 스레드가 병렬적으로 처리되려면&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 알 수 있듯, 멀티 스레드의 장점은 병렬성이다. 그런데, 정말 완벽히 병렬적으로 처리할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터가 연산을 처리하기 위해서는 CPU가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 우리가 보낸 요청을 처리하기 위한 최종 목적지는 CPU다. 멀티 스레드의 요청을 동시에 처리하기 위해서는 CPU도 이 요청을 동시에 처리할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU에게 그럴 능력이 있을까?&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;span style=&quot;color: #409d00;&quot;&gt;CPU 코어&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnL2LW/btsPL0cfwaa/vTPTA9gqvk4F2fSS5y3OK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnL2LW/btsPL0cfwaa/vTPTA9gqvk4F2fSS5y3OK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnL2LW/btsPL0cfwaa/vTPTA9gqvk4F2fSS5y3OK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnL2LW%2FbtsPL0cfwaa%2FvTPTA9gqvk4F2fSS5y3OK0%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;712&quot; height=&quot;146&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 안에는 연산을 처리하기 위한 코어(Core)라는 것이 있는데, 이 코어의 개수에 따라 병렬적으로 처리하는 능력이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 코어가 많을수록 처리 능력이 좋아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진을 보면 8코어 옆에 16스레드라고 적혀있다. 이 스레드는 논리적으로 나눈 코어라고 생각하면 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터의 운영체제는 코어가 16개인 것처럼 여기고 작업을 처리한다. 다시 말해, 코어 1개를 봤을 때 물리적으로 코어가 1개 있더라도 두 개의 스레드를 빠르게 번갈아가며 처리하여 마치 2개의 코어가 일을 처리하는 것처럼 나눈 것이다. (번갈아 실행하여 마치 동시에 실행하는 것처럼 처리하는 것을 동시성(Concurrency)라고 한다)&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;물론 번갈아 처리할 때 Context Switching이 일어나지만 1코어에 1개의 작업만 처리하도록 두는 것보다는 효율적일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;톰캣 스레드 조정&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용을 봤을 때 CPU의 병렬 처리 능력이 서버의 스레드 개수에 어떤 영향을 미칠지 알 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Java의 thread는 kernel thread와 1:1로 mapping 되기 때문에 영향을 미친다)&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;29&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxgn1P/btsPOaY76zT/iBVuOZZD0M6wrWG6IDlhqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxgn1P/btsPOaY76zT/iBVuOZZD0M6wrWG6IDlhqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxgn1P/btsPOaY76zT/iBVuOZZD0M6wrWG6IDlhqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxgn1P%2FbtsPOaY76zT%2FiBVuOZZD0M6wrWG6IDlhqk%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;487&quot; height=&quot;29&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;29&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vCPU 2개, 메모리 2GB로 구성되어 있다.&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;vCPU는 가상 CPU인데 Hyper Threading 기술을 통해 CPU를 가상화하여 코어가 2개인 것처럼 작동하게 된다. 위와 다르게 병렬처리는 가능하겠지만, 실제 물리코어가 2개 있는 것보다는 성능이 약간 떨어질 것이다.&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;그 다음 Spring에서 설정된 기본 Tomcat 설정 값을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1754840447559&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  tomcat:
    connection-timeout: 60s
    max-connections: 8192
    accept-count: 100
    threads:
      min-spare: 10
      max: 200&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;b&gt;connection-timeout&lt;/b&gt;: 클라이언트가 새 연결을 열고, 요청을 보내기까지 기다리는 시간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;max-connections&lt;/b&gt;: 서버에서 accept &amp;amp; process 가능한 최대 connections 수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;accept-count&lt;/b&gt;: max-connections를 넘어서 연결 요청이 추가적으로 들어오는 경우 OS Level의 Queue에서 관리되는데, 이때 Queue의 최대 사이즈&lt;/li&gt;
&lt;li&gt;&lt;b&gt;threads.min-spare&lt;/b&gt;: 항상 Running 상태를 유지하는 스레드의 최소 개수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;threads.max&lt;/b&gt;: 요청을 처리하기 위해서 Connector에서 생성할 수 있는 최대 스레드 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 이번에 테스트 해 볼 것은 &lt;span style=&quot;color: #f89009;&quot;&gt;maxThreads&lt;/span&gt; 이다. 기본값은 &lt;span style=&quot;color: #8a3db6;&quot;&gt;200&lt;/span&gt;으로 설정되어 있는데, 현재 최대 가동 코어가 2개인 서버 스펙에서 최대 200개로 수행한다면 오히려 스레드 간에 경합이 많이 일어나 처리가 늦어질 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 이 개수를 점점 낮춰가며 테스트를 해볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(물론 위에서 톰캣 설정 설명을 보고 알겠지만, max-connections의 개수가 maxThreads보다 수치가 낮다면 유의미한 결과를 얻지 못할 것이다)&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;다음은 ngrinder를 통해 테스트 한 결과이다. vUser 수를 바꿔가며 테스트하여 나온 결과의 평균치를 냈다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 105px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;maxThreads&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;TPS&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;Latency&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;응답 편차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 16px;&quot;&gt;200&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 16px;&quot;&gt;51&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 16px;&quot;&gt;1095ms&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 16px;&quot;&gt;불안정적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;59&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;936ms&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;불안정적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;50&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;50&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1081ms&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;안정적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;30&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;82&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;726ms&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;안정적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;70&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;795ms&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&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;결과로 봤을 때, maxThreads가 30일 때 TPS가 가장 높게 나왔고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;응답도 안정적이었다&lt;/span&gt;. 모든 api를 다 테스트 해본 것이 아니지만 평균적으로 비슷한 결과가 나와 30으로 설정하게 되었다.&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>Backend/Spring</category>
      <category>maxThreads</category>
      <category>Spring</category>
      <category>tomcat</category>
      <category>WAS</category>
      <category>스레드</category>
      <category>톰캣</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/130</guid>
      <comments>https://seoarc.tistory.com/130#entry130comment</comments>
      <pubDate>Mon, 11 Aug 2025 02:06:17 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 좋아요 기능에 대한 동시성 문제</title>
      <link>https://seoarc.tistory.com/129</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재 진행하는 프로젝트에서 뉴스피드의 좋아요 기능에 대한 동시성 문제 해결 과정을 공유하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;현재 프로젝트의 좋아요 기능&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트의 좋아요 기능은 여타의 서비스들의 좋아요 기능(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;유튜브, 인스타그램 등)&lt;/span&gt;과 유사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 유사하다는 의미는, 좋아요 버튼을 토글 방식(한 버튼을 통해 좋아요와 좋아요 취소를 하는 것)으로 구현한 점이 비슷하다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 좋아요를 안한 상태에서는 좋아요 버튼으로 활성화되고 좋아요를 한 상태에서는 좋아요 취소 버튼으로 활성화되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구현하다보니 작은 이슈들이 생겼는데, 이제부터 그 내용을 얘기하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(물론 이 방식이 아니라 다른 방식으로 구현해도 생길 수 있는 이슈들이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;좋아요 중복 클릭 문제(feat. 좋아요 레코드 중복 삽입)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 따닥 이슈라고 많이 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 버튼을 누르면 기대하는 동작 과정은 &lt;span style=&quot;color: #0593d3;&quot;&gt;[좋아요 -&amp;gt; 좋아요 취소]&lt;/span&gt;와 같다. 하지만 버튼을 매우 빠르게 2번 누르면 기대한 동작과 달리 &lt;span style=&quot;color: #0593d3;&quot;&gt;[좋아요 -&amp;gt; 좋아요]&lt;/span&gt;로 동작하는 경우가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 좋아요 이후 &lt;span style=&quot;color: #0593d3;&quot;&gt;'좋아요' 버튼이 '좋아요 취소' 버튼으로 전환되기 전에 2번째 클릭이 발생&lt;/span&gt;해 좋아요 api 호출이 중복으로 일어나는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 서버에서는 한 사람이 2번 좋아요 요청한 것처럼 되어버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이에 대한 적절한 조치를 취하지 않았다면, 좋아요 카운트가 2번 올라가거나 좋아요 리스트에 해당 유저의 레코드가 2번 삽입되었을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 좋아요 개수를 처리하는 것에 대해선 밑에 해결책을 작성할 계획이니 레코드가 2번 삽입되는 문제를 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이에 대한 해결은 어떻게 할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트에서는 다음과 같은 해결책을 생각했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레코드 중복 삽입을 막기 위해 {좋아요_id, 유저_id} 컬럼에 대해 &lt;span style=&quot;color: #f89009;&quot;&gt;Unique Constraint&lt;/span&gt; 걸기&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;Lock&lt;/span&gt;을 통해 중복 요청 막기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 각각의 해결책에 대해 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;Unique Constraint&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Unique Constraint를 거는 것은 간단하다.&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;제약조건을 걸고자 하는 컬럼에 unique를 명시해주면 되는데, 지금 프로젝트에서는 JPA를 사용하고 있어서 Entity에 어노테이션으로 명시해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1754151340369&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Table(
	uniqueConstraints = @UniqueConstraint(
		name = &quot;feedLikeMember&quot;,
		columnNames = {&quot;feed_id&quot;, &quot;member_id&quot;}))
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FeedLike {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(nullable = false, name = &quot;feed_like_id&quot;)
	private Long id;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = &quot;feed_id&quot;)
	private Feed feed;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = &quot;member_id&quot;)
	private Member member;
    
    ...
}&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;이렇게 feed_id와 member_id를 묶어 unique를 걸어주면 해당 값이 중복해서 들어올 때, &lt;span style=&quot;color: #f89009;&quot;&gt;Unique violation 예외&lt;/span&gt;가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 feed_id와 member_id를 복합키로 만들 수 있겠지만, 지금 프로젝트에서는 추후 코드 유지보수 측면에서 불편함이 많을 것 같아 복합키로 설정하지 않았다.&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;span style=&quot;color: #409d00;&quot;&gt;Locking&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로, Lock을 거는 방법에 대해 알아보자.&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;위 문제 같은 경우 Insert 시에 일어나는 문제이기 때문에, DB에서 이미 있는 Record에 Lock을 거는 방식을 사용할 수 없다. 때문에 DB에서 Lock을 걸고자 한다면 Table에 Lock을 걸거나 Named Lock을 거는 방법을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(추가적으로 MySQL에는 Index 사이의 빈 공간에 Lock을 거는 Gap Lock이라는 것도 있지만, Auto Increment PK를 사용하는 상황에서 Lock을 거는 범위를 정확히 짚을 수 없어 사용하지 않았다)&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;하지만, 테이블 Lock은 범위가 테이블이기 때문에 매우 비효율적일 수 있고, Named Lock보다는 Unique가 더 처리하기 편한 방법일 수 있다. 무엇보다, 여태 말한 이 방법들은 모두 DB단에서 처리한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB까지 가지 않고 Application에서 감지하여 처리한다면, DB의 리소스를 불필요하게 낭비하지 않고 유지보수하기 더 수월할 것이다. 때문에 현 프로젝트에서는 Redis Distribution Lock을 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, Redis를 추가로 구축해야한다는 단점이 있지만 다른 서비스에서 캐싱을 처리해야 할 부분에 도입할 예정이었기 때문에 이번에 도입하여 같이 처리하도록 했다.&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;Redis Distribution Lock 구현 부분은 다음 링크의 카카오 기술블로그를 참고하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/troubleshooting-logs-as-a-junior-developer/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tech.kakaopay.com/post/troubleshooting-logs-as-a-junior-developer/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754157569917&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;tech.kakaopay.com&quot; data-og-source-url=&quot;https://tech.kakaopay.com/post/troubleshooting-logs-as-a-junior-developer/&quot; data-og-url=&quot;https://tech.kakaopay.com/post/troubleshooting-logs-as-a-junior-developer/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bG2NSU/hyZuCRLywa/XBZf8z907NRXna18DUPZ40/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/empETo/hyZuAsSNil/A0XfIdFznU2bmFTU3P13z1/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319&quot;&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/troubleshooting-logs-as-a-junior-developer/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakaopay.com/post/troubleshooting-logs-as-a-junior-developer/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bG2NSU/hyZuCRLywa/XBZf8z907NRXna18DUPZ40/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/empETo/hyZuAsSNil/A0XfIdFznU2bmFTU3P13z1/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319');&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;tech.kakaopay.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;pre id=&quot;code_1754157596748&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class LockManager {

	private final RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate;

	public boolean lock(String key) {
		return Boolean.TRUE.equals(
				redisTemplate.opsForValue().setIfAbsent(key, &quot;lock&quot;, Duration.ofSeconds(3)));
	}

	public boolean unlock(String key) {
		return redisTemplate.delete(key);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lock은 현재 Redis 개수 및 서비스 상황에 맞춰 블로그 내용과 같이 Spin Lock으로 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754157716434&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Component
public class RedisLockUtil {

	private static LockManager lockManager;

	public RedisLockUtil(LockManager lockManager) {
		RedisLockUtil.lockManager = lockManager;
	}

	public static &amp;lt;T&amp;gt; T acquireAndRunLock(String key, Supplier&amp;lt;T&amp;gt; block) {
		if (key.isBlank()) {
			log.error(&quot;[Redis] Key가 Blank 상태입니다.&quot;);
			return block.get();
		}

		boolean acquired = acquireLock(key);

		if (acquired) {
			return proceedWithLock(key, block);
		}
		throw new ApplicationException(ErrorCode.FAILED_TO_ACQUIRE_REDIS_LOCK);
	}

	private static boolean acquireLock(String key) {
		try {
			return lockManager.lock(key);
		} catch (Exception e) {
			log.error(&quot;[Redis] Lock 획득에 실패했습니다. key: {} {}&quot;, key, e.getMessage());
			return false;
		}
	}

	private static &amp;lt;T&amp;gt; T proceedWithLock(String key, Supplier&amp;lt;T&amp;gt; block) {
		try {
			return block.get();
		} catch (Exception e) {
			throw e;
		} finally {
			releaseLock(key);
		}
	}

	private static boolean releaseLock(String key) {
		try {
			return lockManager.unlock(key);
		} catch (Exception e) {
			log.error(&quot;[Redis] Lock 해제에 실패했습니다. key: {} {}&quot;, key, e.getMessage());
			return false;
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;획득 실패 시, Lock 획득을 재시도하는 것이 아니라 예외가 발생하도록 하여 2회 이상의 중복 호출이 막아지도록 하였다.&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;이를 다음과 같이 Controller에서 적용하여 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1754158034420&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/{feedId}/like&quot;)
public ResponseEntity&amp;lt;Void&amp;gt; feedLike(@PathVariable Long feedId, @CurrentMember Member member) {
    RedisLockUtil.acquireAndRunLock(
            feedId + &quot;:&quot; + member.getId(),
            () -&amp;gt; {
                feedLikeFacade.feedLikeRetry(feedId, member.getId());
                return true;
            });
    return ResponseEntity.noContent().build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음과 같이 여러 번 동시 시도에도 1번만 좋아요가 늘어나서 테스트가 성공하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yw6Wa/btsPE2OlzzJ/4nBimBaLcv0y6mBZxDkV4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yw6Wa/btsPE2OlzzJ/4nBimBaLcv0y6mBZxDkV4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yw6Wa/btsPE2OlzzJ/4nBimBaLcv0y6mBZxDkV4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyw6Wa%2FbtsPE2OlzzJ%2F4nBimBaLcv0y6mBZxDkV4K%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;464&quot; height=&quot;93&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;좋아요 개수 동시성 문제&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현 프로젝트에서는 좋아요 개수를 직접적으로 보여주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반에는 좋아요 개수를 좋아요 한 사람들의 레코드를 카운트하여 개수를 보여주었다. 하지만 좋아요 개수가 많아질수록 이 방식은 서버에 부담이 많이 가는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 좋아요 개수 컬럼을 뉴스피드 테이블에 따로 빼두었는데, 이렇게 따로 빼두니 좋아요 개수에 대한 증감이 제대로 이뤄지지 않는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #f89009;&quot;&gt;Race Condition&lt;/span&gt;으로 인한 문제인 것이었다. 다음 그림과 같은 것인데, 이런 그림은 인터넷에 검색하면 다양하게 나올 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1773&quot; data-origin-height=&quot;1306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba7xOT/btsPE3fc9p1/bTyYOAPnwehSnhF0H2ZeK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba7xOT/btsPE3fc9p1/bTyYOAPnwehSnhF0H2ZeK0/img.png&quot; data-alt=&quot;race condition&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba7xOT/btsPE3fc9p1/bTyYOAPnwehSnhF0H2ZeK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba7xOT%2FbtsPE3fc9p1%2FbTyYOAPnwehSnhF0H2ZeK0%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;1773&quot; height=&quot;1306&quot; data-origin-width=&quot;1773&quot; data-origin-height=&quot;1306&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;race condition&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 각각의 Transaction이 순서를 지켜 count=0일 때 조회해서 1로 증가시키고, count=1일 때 조회해서 2로 증가시킨 것이 아니라, 조회한 시점에서 서로 0개의 count를 조회하고 서로 각각 1씩 증가시키고 업데이트하여 결국 최종적으로 1개가 되어버린 상황인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 여타 프로그래밍 언어에서 증감연산이 왜 Race Condition이 일어나냐 하면, 위와 같은 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1754111284297&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a = 1;
a++;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a++; 은 단순히 여기서 한 줄이지만 결국 위에서 본 것과 같이 [read -&amp;gt; increase -&amp;gt; write] 과정이 있기 때문에 Race Condition이 발생하는 것이다.&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;때문에 해당 문제를 가지고 있는 상태에서 100명이 동시에 좋아요를 하는 상황을 테스트를 해보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciQOon/btsPGnjAqQm/D4cnlDdeovPIfxlhjJsc6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciQOon/btsPGnjAqQm/D4cnlDdeovPIfxlhjJsc6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciQOon/btsPGnjAqQm/D4cnlDdeovPIfxlhjJsc6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciQOon%2FbtsPGnjAqQm%2FD4cnlDdeovPIfxlhjJsc6K%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;420&quot; height=&quot;167&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 해결하고 싶게 생긴 결과를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 문제를 알았으니 해결책을 생각할 차례다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현 프로젝트에서는 다음과 같은 해결책을 생각했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optimistic Lock&lt;/li&gt;
&lt;li&gt;Pessimistic Lock&lt;/li&gt;
&lt;li&gt;Redis Distribution Lock&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;먼저 Optimistic Lock과 Pessimistic Lock에 대해 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optimistic Lock과 Pessimistic Lock은 이름을 살펴보면(낙관적인 락, 비관적인 락) 서로 상반되는 느낌의 Lock임을 알 수 있다.&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;span style=&quot;color: #409d00;&quot;&gt;Optimistic Lock&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Optimistic Lock은 &lt;span style=&quot;color: #0593d3;&quot;&gt;데이터를 갱신할 때 충돌이 일어나지 않을 것이라고 가정&lt;/span&gt;하고 보는 것이라 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 미리 Record를 선점하여 접근하지 못하게 점유하는 것이 아니라, &lt;span style=&quot;color: #0593d3;&quot;&gt;업데이트를 먼저 시도하고 중간에 변경이 발생했는지 확인&lt;/span&gt;하는 과정을 가진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdSS9m/btsPEpXtroA/P54ZS7lkh9ROKkfqCDt5gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdSS9m/btsPEpXtroA/P54ZS7lkh9ROKkfqCDt5gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdSS9m/btsPEpXtroA/P54ZS7lkh9ROKkfqCDt5gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdSS9m%2FbtsPEpXtroA%2FP54ZS7lkh9ROKkfqCDt5gk%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;1367&quot; height=&quot;663&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, 위와 같이 version을 사용해서 &lt;span style=&quot;color: #0593d3;&quot;&gt;트랜잭션 중간에 version이 바뀌었다면 다른 트랜잭션이 관여했다는 의미이므로 예외를 던진다&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;JPA에서의 구현은 간단한데, 그냥 version 필드만 추가해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1754205877089&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@NoArgsConstructor(access =AccessLevel.PROTECTED)
public class Feed extends BaseTime {

    //...
    
    @Version
    private Long version;
    
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Transaction 수행 중에 충돌이 감지된다면, &lt;span style=&quot;color: #f89009;&quot;&gt;OptimisticLockingFailureException&lt;/span&gt;이 발생할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 서비스의 특성에 따라서 예외를 그대로 던지거나 재시도 하는 방법을 택하여 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 Service Layer에서 수행하는 Transaction 메서드 앞에 Facade Layer를 두어 재시도를 수행하는 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1754206167795&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class FeedLikeFacade {
    private final FeedLikeService feedLikeService;
    
    public void feedLikeRetry(Long feedId, Long memberId, int maxRetry) throws InterruptedException {
        for (int attempt = 0; attempt &amp;lt; maxRetry; attempt++) { // maxRetry까지 재시도
            try {
                feedLikeService.feedLike(feedId, memberId);
                return;
            } catch (ObjectOptimisticLockingFailureException e) {
                Thread.sleep(30); // 부하를 줄이기 위해 충돌 시 Sleep후 재시도
            }
        }
    }
}&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;테스트를 수행해보면 다음과 같이 version을 체크하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s4WRi/btsPEGdEG1b/jfkHEIP9R12Zdv1gzldpPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s4WRi/btsPEGdEG1b/jfkHEIP9R12Zdv1gzldpPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s4WRi/btsPEGdEG1b/jfkHEIP9R12Zdv1gzldpPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs4WRi%2FbtsPEGdEG1b%2FjfkHEIP9R12Zdv1gzldpPk%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;315&quot; height=&quot;505&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;505&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;Pessimistic Lock&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로 Pessimistic Lock은 반대로 &lt;span style=&quot;color: #0593d3;&quot;&gt;데이터를 갱신할 때 충돌이 일어날 것이라고 보고 미리 잠금&lt;/span&gt;을 거는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 Record를 미리 선점하여 Lock을 걸고 다른 트랜잭션이 선점하지(Shared Lock의 경우 Read는 가능할 수 있다) 못하도록 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPVq3O/btsPEuLi5Du/D292XofedERvPo4rwkWO80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPVq3O/btsPEuLi5Du/D292XofedERvPo4rwkWO80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPVq3O/btsPEuLi5Du/D292XofedERvPo4rwkWO80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPVq3O%2FbtsPEuLi5Du%2FD292XofedERvPo4rwkWO80%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;1392&quot; height=&quot;877&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;877&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;Pessimistic Lock의 경우 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JPA에서&lt;span&gt; &lt;/span&gt;&lt;/span&gt;2가지의 구현 방법이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로 &lt;span style=&quot;color: #f89009;&quot;&gt;select ... for update&lt;/span&gt;로 Lock을 건 후 업데이트 하는 방법이다.&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;다음과 같이 Lock 어노테이션에 &lt;span style=&quot;color: #f89009;&quot;&gt;LockModeType&lt;/span&gt;을 &lt;span style=&quot;color: #8a3db6;&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;로 명시해주면 &lt;span style=&quot;color: #f89009;&quot;&gt;select ... for update&lt;/span&gt; 구문으로 쿼리가 생성되어 조회된다. (LockModeType에 PESSIMISTIC_WRITE 이외에도 여러 타입이 있다)&lt;/p&gt;
&lt;pre id=&quot;code_1754206879330&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface FeedRepository extends JpaRepository&amp;lt;Feed, Long&amp;gt; {
    
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional&amp;lt;Feed&amp;gt; findLockedByFeedId(Long feedId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 select ... for update 후에 데이터를 수정해주면 된다.&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;두 번째로, DB update 문을 수행하여 Lock을 거는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;update 쿼리 수행 시, 조건에 해당하는 row에 lock을 걸기 때문에 다른 트랜잭션에 해당 row에 접근할 수 없게 된다. 따라서 좋아요 수 증가를 update 문에서 바로 수행하면 동작을 보장할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1754207417880&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface FeedRepository extends JpaRepository&amp;lt;Feed, Long&amp;gt; {
    @Query(
            value = &quot;update feed set like_count = like_count + 1 where feed_id = :feedId&quot;,
            nativeQuery = true
    )
    @Modifying
    int increase(@Param(&quot;feedId&quot;) Long feedId);
    
    @Query(
            value = &quot;update feed set like_count = like_count - 1 where feed_id = :feedId&quot;,
            nativeQuery = true
    )
    @Modifying
    int decrease(@Param(&quot;feedId&quot;) Long feedId);
}&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;첫 번째 방법의 경우 조회 시점부터 Lock을 걸기 때문에 Lock을 점유하는 시간이 상대적으로 좀 더 길지만, native sql 대신 코드 레벨에서 관리할 수 있다는 장점이 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 방법의 경우 첫 번째 방법보다 Lock을 점유하는 시간이 더 짧지만, native sql을 직접 작성하여 관리해야 한다는 단점이 있다.&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;span style=&quot;color: #409d00;&quot;&gt;Redis Distribution Lock&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Distribution Lock의 경우 위에서 사용한 것과 같이 구현하면 된다. 다만, 우리는 획득 실패 시 재시도를 해야하기 때문에 Optimistic Lock에서 재시도 한 것과 같이 재시도 하는 로직을 넣어 처리해주면 된다.&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;span style=&quot;color: #409d00;&quot;&gt;좋아요 개수 주기적 업데이트&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 좋아요 개수를 특정 시간마다 업데이트 하도록 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사람들이 잘 접속하지 않는 새벽시간마다 좋아요 개수를 카운트하여 좋아요 개수 필드를 업데이트 해주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 좋아요 개수를 정확하게 실시간으로 보여줄 수 없다는 단점이 있지만, 오버헤드는 상당히 적어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트에서는 실시간으로 좋아요 개수를 보여주는 화면이 있기 때문에 이 방법은 적용하지 않았다. 물론 위에서 도입한 Redis를 통해 캐시로 개수를 증가시켜 캐시에서 DB에 업데이트 하는 방법을 사용할 수 있지만, 이때에도 동시성 문제를 해결해 줘야 한다.&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;span style=&quot;color: #409d00;&quot;&gt;어떤 걸 선택할까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 Redis Distribution Lock을 사용했기 때문에, 똑같이 Redis를 사용하면 좋지 않을까 생각할 수 있다. 하지만 현재 프로젝트 구현 상황, 서비스 운영 상황에 따라 결정하는 것이 더 합리적이라 생각했다.&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;먼저 Optimistic Lock의 경우 말 그대로 경합이 일어나지 않을 것이라 가정한다. 때문에 충돌이 많이 생기는 기능의 경우에는 Lock을 얻기 위한 재시도가 많아지고 그러므로써 미리 선점하는 Lock 보다 더 시간이 오래 걸릴 것이다.&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;예를 들어, Optimistic Lock과 Pessimistic Lock을 각각 ngrinder로 테스트를 해보면, 오히려 Optimistic Lock이 더 느린 테스트 시간을 갖는 것을 확인할 수 있다. 물론 내부에서 스프링 부트 테스트를 통해 실행해도 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(100명의 유저가 동시에 좋아요 요청을 보낸다고 가정)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;241&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmI0xQ/btsPDDorVOy/UrN3wZfKGyW4I2BEU7dZh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmI0xQ/btsPDDorVOy/UrN3wZfKGyW4I2BEU7dZh1/img.png&quot; data-alt=&quot;Optimistic Lock Test&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmI0xQ/btsPDDorVOy/UrN3wZfKGyW4I2BEU7dZh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmI0xQ%2FbtsPDDorVOy%2FUrN3wZfKGyW4I2BEU7dZh1%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;241&quot; height=&quot;438&quot; data-origin-width=&quot;241&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Optimistic Lock Test&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaTPi8/btsPDX70DVG/ra2fhwpxiT7iiW5V7S6HGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaTPi8/btsPDX70DVG/ra2fhwpxiT7iiW5V7S6HGK/img.png&quot; data-alt=&quot;Pessimistic Lock&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaTPi8/btsPDX70DVG/ra2fhwpxiT7iiW5V7S6HGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaTPi8%2FbtsPDX70DVG%2Fra2fhwpxiT7iiW5V7S6HGK%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;226&quot; height=&quot;431&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pessimistic Lock&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;하지만 충돌이 많이 일어나지 않는 상황이라면 Optimistic Lock이 더 합리적인 선택이 될 수 있다.&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;위에서 구현한 Redis Distribution Lock도 마찬가지이다. Spin Lock 방식을 채택했기 때문에 락 획득에 실패한다면 계속 반복하여 재시도하기 때문에 성능이 떨어지고 특히 서버 리소스를 많이 소모할 수 있다.&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;일단 현 프로젝트에서는 사용자가 많지 않고 경합이 적을 것이라 예상되어 Optimistic Lock을 적용할 예정이다. 이후 많은 성능 저하가 예상되거나 서버를 분리할 예정이 있다면 다른 방식을 채택하는 것도 좋은 방법일 것 같다.&lt;/p&gt;</description>
      <category>Backend/Spring</category>
      <category>java</category>
      <category>Lock</category>
      <category>Spring</category>
      <category>좋아요</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/129</guid>
      <comments>https://seoarc.tistory.com/129#entry129comment</comments>
      <pubDate>Sun, 3 Aug 2025 22:26:56 +0900</pubDate>
    </item>
    <item>
      <title>[OS] 컴퓨터는 현재시간을 어떻게 알까?</title>
      <link>https://seoarc.tistory.com/127</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터의 시계를 보면 꽤 정확하게 시간을 측정하는 시계를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 컴퓨터는 이 시간을 어떻게 매번 정확하게 측정할 수 있는걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 알기 전에 고등학교 물리 시간으로 돌아가 시간 측정 방법에 대해서 먼저 알아보자.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;협정 시계시(UTC)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;협정 시계시&lt;/span&gt;는 국제적인 표준 시간의 기준으로 쓰이는 시각으로, 원자시계와 윤초 보정을 기반으로 표준화 한 시각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시각은 학교에서 배웠듯이 영국 그리니치 천문대가 기준(UTC+0)이다.&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;대한민국의 시간은 UTC+9로, 영국이 12시라면 한국은 21시가 된다.&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;span style=&quot;color: #409d00;&quot;&gt;원자시계&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협정 시계시는 위에서 말했듯, 원자시계를 통해 측정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원자시계는 세슘 원자를 이용해 정의하며, 세슘 원자가 절대영도 조건에서 방출하는 특정한 파장의 빛이 9,192,631,770번 진동하는 시간을 1초로 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;컴퓨터가 시간을 알아내는 방법&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 컴퓨터가 시간을 어떻게 알아내는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터가 시간을 알아내는 방법은 2가지가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NTP를 통해 시간 동기화&lt;/li&gt;
&lt;li&gt;하드웨어의 시스템 클럭을 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;NTP를 통해 시간 동기화&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터를 키면 자동으로 현재 시간을 보여주는데, 이는 시스템 시간을 &lt;span style=&quot;color: #f89009;&quot;&gt;NTP(Network Time Protocol)&lt;/span&gt;를 통해 동기화하여 나타낸 시간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 컴퓨터는 NTP 서버에 네트워크 요청을 하여 현재 시간을 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;NTP&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;NTP(Network Time Protocol)&lt;/span&gt;는 네트워크 시간 프로토콜로 인터넷에서 &lt;span style=&quot;color: #0593d3;&quot;&gt;라우터 및 기타 하드웨어 디바이스의 클럭을 동기화하는 데 널리 사용되는 프로토콜&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전송 계층 프로토콜로는 빠르고 실시간성을 가진 &lt;span style=&quot;color: #0593d3;&quot;&gt;UDP&lt;/span&gt;를 사용하며, 포트번호는 &lt;span style=&quot;color: #0593d3;&quot;&gt;123&lt;/span&gt;번을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NTP를 통해 시간 정보를 제공해주는 것은 &lt;span style=&quot;color: #f89009;&quot;&gt;NTP 서버&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;NTP 서버&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NTP 서버는 UTC로 나타내는 시간 정보를 전송해준다.&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;아무리 빠른 광회선 인터넷일지라도 패킷을 보내고 응답 받을 때까지 최소 10밀리초 이상의 시간이 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 정확한 시간 정보를 얻으려면 보정이 매우 중요한데, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;NTC 서버는 시간을 전송해 줄 때 통신에 걸리는 시간만큼 정확하게 예상하여 보정해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하지만, 시간 정보를 제공해주는 서버는 소수이기 때문에 이곳에 접속이 집중되면 부하가 너무 커져버릴 수 있다. 그래서 NTP 서버는 계층적인 구조로 이를 대비한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f3c000; text-align: start;&quot;&gt;NTP 서버 계층&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;NTP 서버는 계층으로 이루어져 있으며 그 계층을 Stratum이라고 부르는데 최상위 계층인 Stratum 0을 PRC(Primary Reference Clock)라고 부른다.&lt;/span&gt;&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;&lt;b&gt;PRC?&lt;/b&gt;&lt;br /&gt;PRC는 원자 시계로 시간을 측정하며 아래 계층 요소들이 동기화를 통해 시간을 알 수 있다.&lt;br /&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 style=&quot;color: #333333; text-align: start;&quot;&gt;서버 원천에 직접 연결된 서버들은 Stratum 1이라고 한다. Stratum 1은 Stratum 0에 동기화 시킨 시간 서버이며, 전세계적으로 수백 개 이상의 공식적인 1차 타임서버가 운용중이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이런식으로 Stratum 2부터 Stratum 15까지 계층적 트리 구조를 형성하면서 다수의 컴퓨터에게 시간 정보를 전송해준다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00; text-align: start;&quot;&gt;하드웨어의 시스템 클럭을 이용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그럼 인터넷이 없을 때는 어떻게 시간을 알까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;바로 컴퓨터 내부에 시간을 측정하는 시스템 클럭이 있다.&lt;/span&gt;&lt;/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;&lt;b&gt;클럭?&lt;/b&gt;&lt;br /&gt;순서논리회로에 가해지는 전기적 진동의 속도를 나타내는 단위이며 Hz로 표기한다.&lt;br /&gt;CPU를 비롯한 모든 컴퓨터의 부품들은 특정한 신호에 맞추어 동작을 하는데, 이 특정한 신호를 가리키는 말이 &lt;a href=&quot;https://namu.wiki/w/%ED%81%B4%EB%9F%AD&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;클럭&lt;/a&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 style=&quot;color: #333333; text-align: start;&quot;&gt;이는 &lt;span style=&quot;color: #f89009;&quot;&gt;RTC(Real Time Clock)&lt;/span&gt;라는 모듈을 사용하는데, 아마 데스크탑이 있는 분들은 메인보드를 살펴보면 RTC가 붙어있는 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RTC는 날짜, 시, 분, 초 등의 시간을 카운터하는 디지털 카운터 회로로 구성되어 있으며, 이 카운터 회로를 통해 시간이 측정된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3740&quot; data-origin-height=&quot;2805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwy6X/btsISWTUzKL/KpNIzbKkP5HvAZx8UmmUoK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwy6X/btsISWTUzKL/KpNIzbKkP5HvAZx8UmmUoK/img.jpg&quot; data-alt=&quot;RTC 모듈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwy6X/btsISWTUzKL/KpNIzbKkP5HvAZx8UmmUoK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmwy6X%2FbtsISWTUzKL%2FKpNIzbKkP5HvAZx8UmmUoK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;516&quot; height=&quot;387&quot; data-origin-width=&quot;3740&quot; data-origin-height=&quot;2805&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RTC 모듈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;카운터 회로는 어떻게 정확히 1초를 셀 수 있을까?&lt;/b&gt;&lt;br /&gt;이는 클럭 발생회로에 필요한 &lt;a href=&quot;https://namu.wiki/w/%EC%BF%BC%EC%B8%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;수정 발진기(Crystal Oscillator)&lt;/a&gt;에 대해 찾아보면 알 수 있다. RTC의 오실레이터는 32.768Hz의 수정 발진기를 사용한다.&lt;br /&gt;&lt;a href=&quot;https://noel-embedded.tistory.com/423&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://noel-embedded.tistory.com/423&lt;/a&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;/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;RTC는 자체적으로 배터리를 가지고 있어서 컴퓨터 전원을 끄더라도 계속 시간이 유지될 수 있다. 이 RTC는 컴퓨터뿐만 아니라 시간을 유지해야 하는 거의 모든 전자기기에 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;시스템 클럭과 Unix Time&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 시스템 클럭과 Unix Time의 관계에 대해 알아보자.&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;먼저 시스템 클럭을 통한 컴퓨터의 시간은 특정 시각(Epoch)을 기준으로 &lt;span style=&quot;color: #0593d3;&quot;&gt;시스템 클럭의 틱을 세는 것으로 구현&lt;/span&gt;하는데, 이를 &lt;span style=&quot;color: #f89009;&quot;&gt;시스템 시간&lt;/span&gt;이라고 한다. 여기서 시스템 시간을 값으로 표현한 것을 &lt;span style=&quot;color: #f89009;&quot;&gt;타임스탬프&lt;/span&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 타임스탬프는 운영체제마다 기준 시간과 단위가 다를 수 있는데, Unix 계열 운영체제에서 시간을 표시하는 방법을 &lt;span style=&quot;color: #f89009;&quot;&gt;Unix Time&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;Unix Time은 1970년 1월 1일 0시 0분 0초가 기준 시각이며, 기준 시각은 유닉스 개발자 데니스 리치가 정한 날짜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 기준 시각인 1970년 이전 시간은 음수로 표현되며, 초 단위로 시간이 증가한다.&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;참고&lt;br /&gt;[1] https://ko.wikipedia.org/wiki/%EC%8B%A4%EC%8B%9C%EA%B0%84_%EC%8B%9C%EA%B3%84&lt;br /&gt;[2] https://namu.wiki/w/%ED%81%B4%EB%9F%AD&lt;br /&gt;[3] https://namu.wiki/w/%EC%BF%BC%EC%B8%A0&lt;br /&gt;[4] https://noel-embedded.tistory.com/423&lt;/blockquote&gt;</description>
      <category>Computer Science/OS</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/127</guid>
      <comments>https://seoarc.tistory.com/127#entry127comment</comments>
      <pubDate>Wed, 31 Jul 2024 15:02:22 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] docker의 용량을 차지하는 overlay2는 무엇일까?</title>
      <link>https://seoarc.tistory.com/126</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 overlay2에 용량이 가득 차 배포를 실패하는 상황이 발생하여 해결한 적이 있었다.&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;그런데 이 overlay2라는게 과연 무엇일까?&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;OverlayFS&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;먼저 overlay2를 알기 위해선&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.redhat.com/ko/documentation/red_hat_enterprise_linux/7/html/7.2_release_notes/technology-preview-file_systems#technology-preview-file_systems&quot;&gt;OverlayFS&lt;/a&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;를 알 필요가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;OverlayFS는&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/UnionFS&quot;&gt;유니온 마운트 파일 시스템&lt;/a&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;으로, 이를 통해 사용자는 한 파일 시스템을 다른 파일 시스템 위에 오버레이(overlay)할 수 있다. (이외에도 AUFS, Devicemapper가 있다)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8eFVU/btsIP46nfjY/E3wKRG1C9d9J8Kd2EWyvx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8eFVU/btsIP46nfjY/E3wKRG1C9d9J8Kd2EWyvx0/img.png&quot; data-alt=&quot;union file system&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8eFVU/btsIP46nfjY/E3wKRG1C9d9J8Kd2EWyvx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8eFVU%2FbtsIP46nfjY%2FE3wKRG1C9d9J8Kd2EWyvx0%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;1025&quot; height=&quot;577&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;union file system&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;OverlayFS는&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&quot;&gt;overlay 또는 overlay2&lt;/a&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;중 하나를 사용한다. overlay는 커널 3.18 버전부터 기본적으로 내장되어 있으며, overlay2는 4.0부터 지원된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;Docker를 설치하면 자동으로 사용되도록 설정되며 최신 버전의 Docker는 기본적으로 OverlayFS를 사용한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;overlay2가 docker build와 같은 계층 관련 Docker 명령에 더 나은 성능을 제공하고 백업 파일 시스템에서 더 적은 inode를 사용하여 inode 사용 측면에서 더 효율적이다. 또&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;overlay는 lowerdir라는 단일화된 이미지 레이어를 사용하는 반면 overlay2는 여러 개의 레이어 구조를 지원한다. 가능하다면 overlay2를 사용하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;&lt;a href=&quot;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722174863359&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;5.4.2. Overlay 및 Overlay2 그래프 드라이버 비교 | Red Hat Product Documentation&quot; data-og-description=&quot;페이지 형식 선택 Multi-page Single-page&quot; data-og-host=&quot;docs.redhat.com&quot; data-og-source-url=&quot;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&quot; data-og-url=&quot;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;5.4.2. Overlay 및 Overlay2 그래프 드라이버 비교 | Red Hat Product Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;페이지 형식 선택 Multi-page Single-page&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.redhat.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Copy-on-Write (CoW)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OverlayFS를 포함한 유니온 마운트 파일 시스템은 &lt;span style=&quot;color: #f89009;&quot;&gt;Copy-on-Write(CoW) 전략&lt;/span&gt;을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이미지는 읽기 전용으로 구성되며, 이미지의 데이터를 변경해야 하는 경우 변경된 정보를 따로 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 데이터를 읽을 때는 유니온 파일 시스템을 이용해 원본과 변경된 정보를 조합해서 읽게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로, &lt;span style=&quot;color: #f89009;&quot;&gt;OverlayFS&lt;/span&gt;는 다음과 같이 두 개의 레이어가 있는 구조를 갖고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;lowerdir&lt;/span&gt;은 읽기 전용인 이미지 레이어에 해당하고 변경되지 않는 &lt;span style=&quot;color: #0593d3;&quot;&gt;원본 데이터&lt;/span&gt;를 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;upperdir&lt;/span&gt;는 컨테이너 레이어에 해당하고 읽기/쓰기가 가능하다. 위에서 말한 변경사항에 관련된 정보는 이 레이어에 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 레이어들이 결합되어(merged) &lt;span style=&quot;color: #0593d3;&quot;&gt;컨테이너 마운트(Container mount)에서 통합된 뷰를 생성&lt;/span&gt;한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;OverlayFS.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H8zFb/btsIPSLCvNb/C8F74osI04MSzHRNxe9EWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H8zFb/btsIPSLCvNb/C8F74osI04MSzHRNxe9EWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H8zFb/btsIPSLCvNb/C8F74osI04MSzHRNxe9EWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH8zFb%2FbtsIPSLCvNb%2FC8F74osI04MSzHRNxe9EWK%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;730&quot; height=&quot;277&quot; data-filename=&quot;OverlayFS.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;277&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;추가적으로 &lt;span style=&quot;color: #f89009;&quot;&gt;AUFS&lt;/span&gt;는 &lt;span style=&quot;color: #0593d3;&quot;&gt;여러 개의 이미지 레이어가 있는 구조&lt;/span&gt;를 갖고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/elUfaF/btsIQ1gMUK2/NtGkeIvC2fm6z4B3ueMBYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/elUfaF/btsIQ1gMUK2/NtGkeIvC2fm6z4B3ueMBYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/elUfaF/btsIQ1gMUK2/NtGkeIvC2fm6z4B3ueMBYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FelUfaF%2FbtsIQ1gMUK2%2FNtGkeIvC2fm6z4B3ueMBYK%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;732&quot; height=&quot;403&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 자세히 알고 싶다면 다음 글을 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docker-docs.uclv.cu/storage/storagedriver/aufs-driver/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docker-docs.uclv.cu/storage/storagedriver/aufs-driver/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722172367538&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;Use the AUFS storage driver&quot; data-og-description=&quot;Learn how to optimize your use of AUFS driver.&quot; data-og-host=&quot;docs.docker.com&quot; data-og-source-url=&quot;https://docker-docs.uclv.cu/storage/storagedriver/aufs-driver/&quot; data-og-url=&quot;https://docs.docker.com/storage/storagedriver/aufs-driver/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BaaK6/hyWGVAhPkm/oEDsd6L7Aj8hSke9YoWKgk/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260&quot;&gt;&lt;a href=&quot;https://docker-docs.uclv.cu/storage/storagedriver/aufs-driver/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docker-docs.uclv.cu/storage/storagedriver/aufs-driver/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BaaK6/hyWGVAhPkm/oEDsd6L7Aj8hSke9YoWKgk/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260');&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;Use the AUFS storage driver&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to optimize your use of AUFS driver.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.docker.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;blockquote data-ke-style=&quot;style2&quot;&gt;최근에는 OverlayFS2 방식을 많이 사용한다고 한다. (&lt;a href=&quot;https://tech.kakaoenterprise.com/171&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;카카오 기술블로그 - 컨테이너 파일시스템&lt;/a&gt;)&lt;br /&gt;(유니온 파일 시스템 구현체는 AUFS -&amp;gt; OverlayFS -&amp;gt; OverlayFS2으로 발전되었다.)&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;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;[참고]&lt;br /&gt;[1]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://junstar92.tistory.com/170#1.-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84%EC%9D%98-%EC%9B%90%EB%A6%AC&quot;&gt;https://junstar92.tistory.com/170#1.-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84%EC%9D%98-%EC%9B%90%EB%A6%AC&lt;/a&gt;&lt;br /&gt;[2]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&quot;&gt;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&lt;/a&gt;&lt;br /&gt;[3]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.redhat.com/ko/documentation/red_hat_enterprise_linux/7/html/7.2_release_notes/technology-preview-file_systems#technology-preview-file_systems&quot;&gt;https://docs.redhat.com/ko/documentation/red_hat_enterprise_linux/7/html/7.2_release_notes/technology-preview-file_systems#technology-preview-file_systems&lt;/a&gt;&lt;br /&gt;[4]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.docker.com/storage/storagedriver/&quot;&gt;https://docs.docker.com/storage/storagedriver/&lt;/a&gt;&lt;br /&gt;[5] &lt;a href=&quot;https://docker-docs.uclv.cu/storage/storagedriver/aufs-driver/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docker-docs.uclv.cu/storage/storagedriver/aufs-driver/&lt;/a&gt;&lt;br /&gt;[6] &lt;a href=&quot;https://www.joinc.co.kr/w/man/12/docker/storage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.joinc.co.kr/w/man/12/docker/storage&lt;/a&gt;&lt;br /&gt;[7] &lt;a href=&quot;https://tech.kakaoenterprise.com/154&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tech.kakaoenterprise.com/154&lt;/a&gt;&lt;br /&gt;[8] &lt;a href=&quot;https://tech.kakaoenterprise.com/171&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tech.kakaoenterprise.com/171&lt;/a&gt;&lt;br /&gt;[9]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://blog.naver.com/alice_k106/221530340759&quot;&gt;https://blog.naver.com/alice_k106/221530340759&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/Docker</category>
      <category>Docker</category>
      <category>overlay2</category>
      <category>overlayfs</category>
      <category>유니온 파일 시스템</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/126</guid>
      <comments>https://seoarc.tistory.com/126#entry126comment</comments>
      <pubDate>Sun, 28 Jul 2024 22:54:58 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] 디스크 용량으로 인한 배포 실패 상황</title>
      <link>https://seoarc.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CodeDeploy를 통해 개발 서버에 배포 하던 중 배포가 실패된 상황이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722068215904&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;No space left on device&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞다. 서버에 남은 공간이 없다는 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용적인 문제로 인해 개발서버를 t2.micro에 띄워 배포하고 있었는데, 프리티어의 경우 storage volumn이 최대 30GB까지만 허용되기 때문에 30GB를 다 사용했다는 말이었다.&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;그런데, 여태 코드 몇 줄 추가 했다고 30GB가 전부 채워질리는 없었다.&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;pre id=&quot;code_1722069087139&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        4.0M     0  4.0M   0% /dev
tmpfs           475M     0  475M   0% /dev/shm
tmpfs           190M  3.0M  187M   2% /run
/dev/xvda1       30G   27G    3G  90% /
tmpfs           475M     0  475M   0% /tmp
/dev/xvda128     10M  1.3M  8.7M  13% /boot/efi
tmpfs            95M     0   95M   0% /run/user/1000
overlay          30G   27G    3G  90% /var/lib/docker/overlay2/8ac2a574daa56a8bfa9de4a4e5f06ffddb866&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해 보면 docker에서 27GB의 용량을 차지하고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;overlay2?&lt;br /&gt;overlay2에 관련해서는 다음 글을 참고하자.&lt;br /&gt;&lt;a href=&quot;https://seoarc.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://seoarc.tistory.com/126&lt;/a&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;원인&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;/var/lib/docker/overlay2&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;Docker를 통해 여러 번 새로운 배포를 수행하면 이 overlay2에 여러 임시파일이나 미사용 이미지, 컨테이너 파일 등이 남아있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 먼저 중지된 컨테이너, 사용하지 않은 이미지, 삭제하지 않고 남긴 로그 데이터가 있진 않은지 다시 한번 확인해 보는 것이 좋다. 또 추가적으로 고정된 volume이 있다면 이 부분도 확인이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 서버를 계속 실행하다 보면 로그가 쌓이기 마련인데, Docker는 자체적으로 컨테이너 로그를 json으로 캐싱하는 &lt;a href=&quot;https://docs.docker.com/config/containers/logging/json-file/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;json-file logging driver&lt;/a&gt;를 사용한다. 이로 인해 로그가 쌓여  디스크가 고갈되는 경우가 생긴다. (&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.docker.com/config/containers/logging/configure/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.docker.com/config/containers/logging/configure/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722182974182&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;Configure logging drivers&quot; data-og-description=&quot;Learn how to configure logging driver for the Docker daemon&quot; data-og-host=&quot;docs.docker.com&quot; data-og-source-url=&quot;https://docs.docker.com/config/containers/logging/configure/&quot; data-og-url=&quot;https://docs.docker.com/config/containers/logging/configure/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/68xWK/hyWGRxXYC6/tPS4KnhzvKgAl7DPj1wpFk/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260&quot;&gt;&lt;a href=&quot;https://docs.docker.com/config/containers/logging/configure/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.docker.com/config/containers/logging/configure/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/68xWK/hyWGRxXYC6/tPS4KnhzvKgAl7DPj1wpFk/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260');&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 logging drivers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to configure logging driver for the Docker daemon&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문서에서는 디스크 고갈 방지를 위해 local logging driver를 구성하여 사용할 것을 권장하고 있다.&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;두번째로, 이미지에 대한 변경사항들이 과도하게 쌓이지 않았는지 확인해보는 것이 좋다. 이는 Docker가 유니온 파일 시스템 구조를 사용하고 있기 때문인데, 유니온 파일 시스템은 Copy-on-Write(CoW) 전략을 사용하기 때문에 변경사항을 원본에 반영하지 않고 따로 저장하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니온 파일 시스템에 관련하여 추가적으로 알고 싶다면 다음 링크를 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seoarc.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seoarc.tistory.com/126&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722174931622&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;[Docker] docker의 용량을 차지하는 overlay2는 무엇일까?&quot; data-og-description=&quot;이전에 overlay2에 용량이 가득 차 배포를 실패하는 상황이 발생하여 해결한 적이 있었다.&amp;nbsp;그런데 이 overlay2라는게 과연 무엇일까?&amp;nbsp;&amp;nbsp;OverlayFS먼저 overlay2를 알기 위해선&amp;nbsp;OverlayFS를 알 필요가 있다.Ov&quot; data-og-host=&quot;seoarc.tistory.com&quot; data-og-source-url=&quot;https://seoarc.tistory.com/126&quot; data-og-url=&quot;https://seoarc.tistory.com/126&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/letCA/hyWGN99yES/qum8AhW4VCQ1z7QYytOoUk/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/daQjGt/hyWG2TMytG/tMwlQQkkfNdxzMNU1Nv7gK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/iwAwp/hyWGUBqhu5/VAwOMhXh9pM7SFzKuDwask/img.png?width=1025&amp;amp;height=577&amp;amp;face=0_0_1025_577&quot;&gt;&lt;a href=&quot;https://seoarc.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seoarc.tistory.com/126&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/letCA/hyWGN99yES/qum8AhW4VCQ1z7QYytOoUk/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/daQjGt/hyWG2TMytG/tMwlQQkkfNdxzMNU1Nv7gK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/iwAwp/hyWGUBqhu5/VAwOMhXh9pM7SFzKuDwask/img.png?width=1025&amp;amp;height=577&amp;amp;face=0_0_1025_577');&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;[Docker] docker의 용량을 차지하는 overlay2는 무엇일까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전에 overlay2에 용량이 가득 차 배포를 실패하는 상황이 발생하여 해결한 적이 있었다.&amp;nbsp;그런데 이 overlay2라는게 과연 무엇일까?&amp;nbsp;&amp;nbsp;OverlayFS먼저 overlay2를 알기 위해선&amp;nbsp;OverlayFS를 알 필요가 있다.Ov&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seoarc.tistory.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;만약 남긴 데이터가 없다면, 남은 것은 캐시 데이터일 확률이 높다. Docker는 이미지 레이어를 재사용하기 위해 캐싱하는데, 이미지를 삭제해도 캐시가 남아있을 수 있기 때문에 이를 삭제해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://depot.dev/blog/docker-clear-cache&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://depot.dev/blog/docker-clear-cache&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722173579074&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;How to clear Docker cache and free up space on your system&quot; data-og-description=&quot;A breakdown of different Docker artifacts and build cache items that take up disk space and how to prune them individually or clear them all locally and in CI.&quot; data-og-host=&quot;depot.dev&quot; data-og-source-url=&quot;https://depot.dev/blog/docker-clear-cache&quot; data-og-url=&quot;https://depot.dev/blog/docker-clear-cache&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cXVQgW/hyWGPUpTF0/sW0XpkMe3lEjo3MIs4ZB90/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/54fEC/hyWG20yjQa/FhHHzdsKSCZqqZeKkqHEQ0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/csbUNG/hyWGUuFSlM/CxJgkim9eCZ4SbKN6dXbT0/img.png?width=1600&amp;amp;height=817&amp;amp;face=0_0_1600_817&quot;&gt;&lt;a href=&quot;https://depot.dev/blog/docker-clear-cache&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://depot.dev/blog/docker-clear-cache&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cXVQgW/hyWGPUpTF0/sW0XpkMe3lEjo3MIs4ZB90/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/54fEC/hyWG20yjQa/FhHHzdsKSCZqqZeKkqHEQ0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/csbUNG/hyWGUuFSlM/CxJgkim9eCZ4SbKN6dXbT0/img.png?width=1600&amp;amp;height=817&amp;amp;face=0_0_1600_817');&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;How to clear Docker cache and free up space on your system&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A breakdown of different Docker artifacts and build cache items that take up disk space and how to prune them individually or clear them all locally and in CI.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;depot.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;해결&lt;/span&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;span style=&quot;color: #409d00;&quot;&gt;미사용 리소스 삭제&lt;/span&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 디스크 공간을 지우기 위한 방법을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 docker에는 prune이라는 명령이 있는데, 이 명령을 통해 사용하지 않는 리소스를 삭제할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker 캐시에서 컨테이너 제거: docker container prune&lt;/li&gt;
&lt;li&gt;이미지 제거: docker image prune&lt;/li&gt;
&lt;li&gt;볼륨 제거: docker volume prune&lt;/li&gt;
&lt;li&gt;빌드 캐시 제거: docker buildx prune&lt;/li&gt;
&lt;li&gt;네트워크 제거: docker network prune&lt;/li&gt;
&lt;li&gt;모든 것을 제거: docker system prune&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prune 명령 시 삭제할 거냐는 경고문이 발생한다. 경고문을 띄우고 싶지 않다면 &lt;span style=&quot;color: #8a3db6;&quot;&gt;-f&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;blockquote data-ke-style=&quot;style2&quot;&gt;[참고]&lt;br /&gt;[1] &lt;a href=&quot;https://junstar92.tistory.com/170#1.-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84%EC%9D%98-%EC%9B%90%EB%A6%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://junstar92.tistory.com/170#1.-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84%EC%9D%98-%EC%9B%90%EB%A6%AC&lt;/a&gt;&lt;br /&gt;[2] &lt;a href=&quot;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.redhat.com/ko/documentation/openshift_container_platform/3.11/html/scaling_and_performance_guide/comparing-overlay-graph-drivers&lt;/a&gt;&lt;br /&gt;[3] &lt;a href=&quot;https://docs.redhat.com/ko/documentation/red_hat_enterprise_linux/7/html/7.2_release_notes/technology-preview-file_systems#technology-preview-file_systems&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.redhat.com/ko/documentation/red_hat_enterprise_linux/7/html/7.2_release_notes/technology-preview-file_systems#technology-preview-file_systems&lt;/a&gt;&lt;br /&gt;[4] &lt;a href=&quot;https://docs.docker.com/storage/storagedriver/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.docker.com/storage/storagedriver/&lt;/a&gt;&lt;br /&gt;[5] &lt;a href=&quot;https://blog.naver.com/alice_k106/221530340759&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.naver.com/alice_k106/221530340759&lt;/a&gt;&lt;br /&gt;[6] &lt;a href=&quot;https://uiandwe.tistory.com/1313&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://uiandwe.tistory.com/1313&lt;/a&gt;&lt;br /&gt;[7] &lt;a href=&quot;https://ngela.tistory.com/90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ngela.tistory.com/90&lt;/a&gt;&lt;br /&gt;[8] &lt;a href=&quot;https://velog.io/@lom/Docker-overlay2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@lom/Docker-overlay2&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/Docker</category>
      <category>Docker</category>
      <category>overlay2</category>
      <category>prune</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/125</guid>
      <comments>https://seoarc.tistory.com/125#entry125comment</comments>
      <pubDate>Sat, 27 Jul 2024 18:20:06 +0900</pubDate>
    </item>
    <item>
      <title>[Java] System.out.println의 사용을 지양하자</title>
      <link>https://seoarc.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 프로젝트를 진행하면서 요청을 기록 및 관찰하기 위해 logging을 한다.&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;아마 초반에 토이 프로젝트를 하다보면 요청이 잘 갔는지 확인해보기 위해 System.out.println을 사용하는 경우가 많을 것이다.&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;이번에 말할 주제는 제목과 같이 System.out.println의 사용을 지양해야 한다는 것이다. 왜 그래야하는지 한 번 살펴보자.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;logging framework&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 자바/스프링부트 기반의 프로젝트를 진행해봤다면, 스프링부트 프로젝트 생성 시 로깅 프레임워크들(log4j2, logback 등)이 추가되는 것을 볼 수 있다.&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;이 로깅 프레임워크들을 이용해서 다음과 같이 logging을 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1718902699000&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class App {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(App.class);
        logger.info(&quot;start&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;기본적으로 프로젝트할 때 이런 로깅 프레임워크를 통해 로깅할 것을 권장하고 있다. 이는 위에서 말한 &quot;System.out.println 사용을 지양하자&quot;와 이어지는 말이다. 그 이유가 무엇일까?&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;System.out.println의 문제점&lt;/span&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;span style=&quot;color: #409d00;&quot;&gt;성능 문제&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 성능의 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System.out.println은 Stream을 통해 콘솔에 출력을 해주는 역할을 한다. 여기서 알 수 있듯이 당연하게 I/O 작업이 동반된다. 실행하고 있는 해당 스레드에서 blocking I/O 처리로 인해 프로그램이 느려지는 결과가 초래된다.&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;또 println의 내부 코드를 살펴보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;1801&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvVldr/btsH628SLqa/A0RHESp3YPKnF8lCzBJ8ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvVldr/btsH628SLqa/A0RHESp3YPKnF8lCzBJ8ZK/img.png&quot; data-alt=&quot;java.io.PrintStream&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvVldr/btsH628SLqa/A0RHESp3YPKnF8lCzBJ8ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvVldr%2FbtsH628SLqa%2FA0RHESp3YPKnF8lCzBJ8ZK%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;958&quot; height=&quot;1801&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;1801&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;java.io.PrintStream&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;이렇게 synchronized block으로 감싸 동기화 처리를 해준 것을 볼 수 있는데, 이 때문에 성능 저하가 올 수 있다. 물론 적은 요청에서는 눈에 띌만 한 차이는 발생하지 않는다. 더군다나 console에 출력할 경우 로그의 경우도 동기화처리는 필수적이기 때문에 큰 성능 차이를 기대하기 힘들다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/546WN/btsH9r9alZt/HQhMW6SDzPGz6DXEJUrbz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/546WN/btsH9r9alZt/HQhMW6SDzPGz6DXEJUrbz1/img.png&quot; data-alt=&quot;1,000,000번의 요청 실행시간(ms)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/546WN/btsH9r9alZt/HQhMW6SDzPGz6DXEJUrbz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F546WN%2FbtsH9r9alZt%2FHQhMW6SDzPGz6DXEJUrbz1%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;224&quot; height=&quot;102&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1,000,000번의 요청 실행시간(ms)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1,000,000의 요청을 가정하고 실행 시간을 테스트 해봤을 때 오히려 log가 더 오래 걸린 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;로깅 프레임워크들은 thread safe 하지 않은건가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 기본적으로 추가되는 로깅 프레임워크들(log4j2, logback)은 thread safe하게 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 synchronized block을 사용하여 동기화 처리를 하기 보다는, 좀 더 효율적으로 처리한다. 또한 비동기 로깅 방식도 지원한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGIFsa/btsH7YFmVZH/JDIbl7R04tKl4pugTcUOh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGIFsa/btsH7YFmVZH/JDIbl7R04tKl4pugTcUOh0/img.png&quot; data-alt=&quot;log4j document&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGIFsa/btsH7YFmVZH/JDIbl7R04tKl4pugTcUOh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGIFsa%2FbtsH7YFmVZH%2FJDIbl7R04tKl4pugTcUOh0%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;1556&quot; height=&quot;125&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;log4j document&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyxO4B/btsH6t0Rjrb/Gdk2oX7QJkogSFjy0mCtbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyxO4B/btsH6t0Rjrb/Gdk2oX7QJkogSFjy0mCtbk/img.png&quot; data-alt=&quot;logback.core.OutputStreamAppender&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyxO4B/btsH6t0Rjrb/Gdk2oX7QJkogSFjy0mCtbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyxO4B%2FbtsH6t0Rjrb%2FGdk2oX7QJkogSFjy0mCtbk%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;911&quot; height=&quot;451&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;logback.core.OutputStreamAppender&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;1030&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chfkRW/btsH7Dg9QR0/OK2eLFCyDg2T4lLBkKpkKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chfkRW/btsH7Dg9QR0/OK2eLFCyDg2T4lLBkKpkKK/img.png&quot; data-alt=&quot;logback.core.OutputStreamAppender&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chfkRW/btsH7Dg9QR0/OK2eLFCyDg2T4lLBkKpkKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchfkRW%2FbtsH7Dg9QR0%2FOK2eLFCyDg2T4lLBkKpkKK%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;898&quot; height=&quot;1030&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;1030&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;logback.core.OutputStreamAppender&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 logback에서는 &lt;a href=&quot;https://www.baeldung.com/java-concurrent-locks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ReentrantLock&lt;/a&gt;을 통해 처리하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;로그 레벨 관리 문제&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅 프레임워크는 여러 로그 레벨에 따라 구분하여 로깅할 수 있다. 예를 들어, 디버깅 목적일 경우 debug level, 정보 목적일 경우 info level, 경고 목적일 경우 warn level 등으로 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1718947025924&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logger.trace(&quot;Trace&quot;);
logger.debug(&quot;Debug&quot;);
logger.info(&quot;Info&quot;);
logger.error(&quot;Error&quot;);
logger.warn(&quot;Warn&quot;);
logger.fatal(&quot;Fatal&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;하지만 println()의 경우 System.out.println()과 System.err.println() 두 가지로만 사용할 수 있다. 여러 상황이 많은 운영 환경에서는 이러한 레벨별 로그 관리가 중요하므로 로깅 프레임워크 사용을 권장한다.&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;span style=&quot;color: #409d00;&quot;&gt;로그 추적 문제&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅 프레임워크를 통해 로그를 남길 경우 다음과 같이 기본적인 시간, 로그 레벨 등의 정보를 남길 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qkrs1/btsH7ZZiYd1/vaIWt1gHQg0VZH4YSe5n20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qkrs1/btsH7ZZiYd1/vaIWt1gHQg0VZH4YSe5n20/img.png&quot; data-alt=&quot;logback.classic.layout.TTLLLayout&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qkrs1/btsH7ZZiYd1/vaIWt1gHQg0VZH4YSe5n20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqkrs1%2FbtsH7ZZiYd1%2FvaIWt1gHQg0VZH4YSe5n20%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;846&quot; height=&quot;862&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;logback.classic.layout.TTLLLayout&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1973&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1WeQJ/btsH8ET4UWF/Ve13RaxztKyuBq5SkYhBg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1WeQJ/btsH8ET4UWF/Ve13RaxztKyuBq5SkYhBg0/img.png&quot; data-alt=&quot;스프링 실행 로그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1WeQJ/btsH8ET4UWF/Ve13RaxztKyuBq5SkYhBg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1WeQJ%2FbtsH8ET4UWF%2FVe13RaxztKyuBq5SkYhBg0%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;1973&quot; height=&quot;216&quot; data-origin-width=&quot;1973&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스프링 실행 로그&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 System.out.println() 의 경우 이러한 기능을 지원해주지 않아 하나씩 직접 남겨야하며, 특히 파일에 로그를 기록해야 할 경우 추가적인 파일 입출력 구현이 필요하다.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;로깅 프레임워크는 안심하고 사용해도 되나?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 System.out.println() 대신 로깅 프레임워크를 쓴다면 안심하고 써도 될까?&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;로깅 프레임워크를 통해 로깅하는 것도 결국 I/O 작업을 수반하며, 그에 따른 동기화 처리도 불가피하게 작동되어야 한다. 때문에 로그를 남발하게 되면 애플리케이션에 성능 저하가 일어날 수 있으므로 조심해서 써야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;로그는 꼭 필요한 곳에 사용하고 레벨을 적절히 사용하는 것이 중요하다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;또 추가적으로 배치 처리나 버퍼링을 통해 I/O 작업을 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅 프레임워크들은 비동기 처리를 지원하므로, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;만약 대규모 애플리케이션이라면&lt;span&gt; &lt;/span&gt;&lt;/span&gt;이를 통해 성능 개선을 기대할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://logging.apache.org/log4j/2.x/manual/async.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://logging.apache.org/log4j/2.x/manual/async.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718947999897&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;Log4j &amp;ndash; Log4j 2 Lock-free Asynchronous Loggers for Low-Latency Logging&quot; data-og-description=&quot;Asynchronous Loggers for Low-Latency Logging Asynchronous logging can improve your application's performance by executing the I/O operations in a separate thread. Log4j 2 makes a number of improvements in this area. Asynchronous Loggers are a new addition &quot; data-og-host=&quot;logging.apache.org&quot; data-og-source-url=&quot;https://logging.apache.org/log4j/2.x/manual/async.html&quot; data-og-url=&quot;https://logging.apache.org/log4j/2.x/manual/async.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nyk2B/hyWoJ1IkEL/4k4f48cD2IOibAhxGq8Ut1/img.png?width=800&amp;amp;height=457&amp;amp;face=0_0_800_457,https://scrap.kakaocdn.net/dn/B5Yix/hyWoCuMC6N/2w7IG1H798KYzHKPTkWouK/img.png?width=800&amp;amp;height=457&amp;amp;face=0_0_800_457,https://scrap.kakaocdn.net/dn/dZv70B/hyWoFkH5fL/fvkaYlW1swgT9ESrXbKku1/img.png?width=758&amp;amp;height=399&amp;amp;face=0_0_758_399&quot;&gt;&lt;a href=&quot;https://logging.apache.org/log4j/2.x/manual/async.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://logging.apache.org/log4j/2.x/manual/async.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nyk2B/hyWoJ1IkEL/4k4f48cD2IOibAhxGq8Ut1/img.png?width=800&amp;amp;height=457&amp;amp;face=0_0_800_457,https://scrap.kakaocdn.net/dn/B5Yix/hyWoCuMC6N/2w7IG1H798KYzHKPTkWouK/img.png?width=800&amp;amp;height=457&amp;amp;face=0_0_800_457,https://scrap.kakaocdn.net/dn/dZv70B/hyWoFkH5fL/fvkaYlW1swgT9ESrXbKku1/img.png?width=758&amp;amp;height=399&amp;amp;face=0_0_758_399');&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;Log4j &amp;ndash; Log4j 2 Lock-free Asynchronous Loggers for Low-Latency Logging&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Asynchronous Loggers for Low-Latency Logging Asynchronous logging can improve your application's performance by executing the I/O operations in a separate thread. Log4j 2 makes a number of improvements in this area. Asynchronous Loggers are a new addition&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;logging.apache.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;마무리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅 프레임워크를 사용하는 습관을 들이는 것이 중요해보인다. 또 로깅도 성능적으로 무시할 수 없는 부분이 많다. 이를 위해선 로깅 프레임워크의 구조와 활용 전략에 대해 더 알아보는 것이 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고 자료&lt;br /&gt;https://logging.apache.org/log4j/1.x/faq.html#a1.7&lt;br /&gt;https://logging.apache.org/log4j/2.x/manual/async.html&lt;br /&gt;https://docs.oracle.com/javase/7/docs/api/java/util/logging/Logger.html&lt;br /&gt;https://www.quora.com/What-is-Log4j-and-how-is-it-thread-safe&lt;br /&gt;https://www.quora.com/Is-Log4J-thread-safe-Is-Log4j-only-for-Java&lt;br /&gt;https://liltdevs.tistory.com/180&lt;/blockquote&gt;</description>
      <category>Language/Java</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/124</guid>
      <comments>https://seoarc.tistory.com/124#entry124comment</comments>
      <pubDate>Fri, 21 Jun 2024 14:45:59 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 위도/경도 값에 BigDecimal or double?</title>
      <link>https://seoarc.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;위/경도 값을 저장할 때 BigDecimal을 사용하는 것을 본 적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 BigDecimal을 사용하는 것이 좋은걸까? double을 사용하는 건 좋지 않은 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal과 double에 어떤 특징이 있는지, 위경도에 어느 자료형이 적합할지 한 번 살펴보자.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;double의 부동소수점 문제&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘날&amp;nbsp;컴퓨터는&amp;nbsp;대부분&amp;nbsp;&lt;a title=&quot;IEEE 754 부동 소수점 방식&quot; href=&quot;https://ko.wikipedia.org/wiki/IEEE_754&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IEEE&amp;nbsp;754&amp;nbsp;부동&amp;nbsp;소수점&amp;nbsp;방식&lt;/a&gt;을 사용하기 때문에 Java에서 double을 사용해 소수를 표현한다면 소수점 약 15자리부터 오차가 발생할 수 있다.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;오차를 해결하려면?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에는 BigDecimal이라는 자료형으로 부동소수점 방식으로 인해 생기는 오차를 막을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714408477351&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;BigDecimal (Java Platform SE 8 )&quot; data-og-description=&quot;Returns a string representation of this BigDecimal, using engineering notation if an exponent is needed. Returns a string that represents the BigDecimal as described in the toString() method, except that if exponential notation is used, the power of ten is&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;BigDecimal (Java Platform SE 8 )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Returns a string representation of this BigDecimal, using engineering notation if an exponent is needed. Returns a string that represents the BigDecimal as described in the toString() method, except that if exponential notation is used, the power of ten is&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;BigDecimal은 어떻게 정확하게 소수를 표시할 수 있을까?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal의 내부를 살펴보면 여러 상태 값들이 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;995&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OihER/btsG0TMH8OB/5LapQXX77xtYPkSJb6U0h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OihER/btsG0TMH8OB/5LapQXX77xtYPkSJb6U0h0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OihER/btsG0TMH8OB/5LapQXX77xtYPkSJb6U0h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOihER%2FbtsG0TMH8OB%2F5LapQXX77xtYPkSJb6U0h0%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;1036&quot; height=&quot;995&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;995&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal은 이러한 상태 값들을 활용하여 정수로 소수를 다루기 때문에 정확한 표현을 할 수 있는 것이다.&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;BigDecimal이 소수를 표현하기 위한 상태 값들은 다음과 같은 것들이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1714408779528&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BigInteger intVal;   // 정수부 값
int precision;       // 정수부, 소수부의 길이를 합한 값
int scale;           // 소수부의 길이
String stringCache;  // 숫자를 String으로 변환한 값
long intCompact;     // 소수점을 제외한 전체 수&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;&lt;span style=&quot;color: #409d00;&quot;&gt; BigDecimal은 valueOf를 통해 생성하자 &lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 BigDecimal을 사용하게 된다면 생성자를 통한 객체 생성은 피하는 것이 좋다.&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;pre id=&quot;code_1714408948297&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;double number = 1.0;
BigDeciaml bigDecimal = new BigDecimal(number); // 0.100000000000000005551115123...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 경우 double에서 이미 오차가 발생한 상황이다. 그렇기 때문에 BigDecimal에 저장된 값도 오차가 있는 값이 들어가게 된다.&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;다음과&amp;nbsp;같이&amp;nbsp;valueOf를&amp;nbsp;통해&amp;nbsp;생성하면&amp;nbsp;해결할&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;valueOf는&amp;nbsp;내부에서&amp;nbsp;문자열로&amp;nbsp;변환시키는&amp;nbsp;로직을&amp;nbsp;수행하기&amp;nbsp;때문에&amp;nbsp;문제가&amp;nbsp;되지&amp;nbsp;않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1714408986009&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;double number = 1.0;
BigDecimal bigDecimal = BigDecimal.valueOf(number);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1714408999298&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static BigDecimal valueOf(double val) {  
    return new BigDecimal(Double.toString(val));  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위&amp;nbsp;코드에서&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있듯,&amp;nbsp;직접&amp;nbsp;문자열로&amp;nbsp;변환하면&amp;nbsp;생성자를&amp;nbsp;사용해서&amp;nbsp;생성해도&amp;nbsp;위&amp;nbsp;문제를&amp;nbsp;해결할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;pre id=&quot;code_1714409021124&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;double number = 1.0;
BigDecimal bigDecimal = new BigDecimal(Double.toString(number));&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;&lt;span style=&quot;color: #409d00;&quot;&gt;BigDecimal의 단점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal을 사용했을 때 단점은 무엇이 있을까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 먼저 사용하기 까다롭다는 점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수를 생성할 때 항상 객체 생성을 해야하며, 연산을 할 때도 연산자가 아닌 메서드를 통해 수행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 다음과 같은 로직은 수행할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1714409201417&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BigDeciaml a = BigDecimal.valueOf(1.1);
BigDeciaml b = BigDecimal.valueOf(2.2);
BigDecimal result = a * b; // error&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 로직을 수행하려면 다음과 같이 변경해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1714409241059&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BigDeciaml a = BigDecimal.valueOf(1.1);
BigDeciaml b = BigDecimal.valueOf(2.2);
BigDecimal result = a.multiply(b);&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;또, db에서의 연산도 꺼려진다. BigDecimal과의 mapping을 지원해주지 않는 db가 많아 형변환을 거쳐야 하기에 double보다 다루기 힘들 것이다.&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;두번째로, 메모리 오버헤드다. BigDecimal은 위에서 말했듯이 수를 표현하기 위해 항상 객체 생성이 필요하다. 또 BigDecimal은 정밀한 숫자를 표현하기 위해 객체가 차지하는 메모리 공간이 적지 않다.&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;위 단점이 있더라도 만약 정밀함이 더 중요한 서비스라면(ex 돈과 관련된 기능) BigDecimal의 사용은 필수적일 수 있다.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;위/경도에는 어떤 자료형을 쓸까&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약&amp;nbsp;개발&amp;nbsp;시&amp;nbsp;지도&amp;nbsp;api를&amp;nbsp;활용한&amp;nbsp;서비스를&amp;nbsp;준비한다면&amp;nbsp;위경도&amp;nbsp;값을&amp;nbsp;api를&amp;nbsp;통해&amp;nbsp;받을&amp;nbsp;것이다.&lt;br /&gt;대부분의&amp;nbsp;지도&amp;nbsp;API(Google&amp;nbsp;Map,&amp;nbsp;네이버&amp;nbsp;지도,&amp;nbsp;카카오맵&amp;nbsp;등)은&amp;nbsp;&lt;a title=&quot;WGS84 위도/경도 좌표계&quot; href=&quot;https://docs.qgis.org/3.16/ko/docs/gentle_gis_introduction/coordinate_reference_systems.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WGS84&amp;nbsp;위도/경도&amp;nbsp;좌표계&lt;/a&gt;를&amp;nbsp;사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그럼&amp;nbsp;api를&amp;nbsp;받을&amp;nbsp;때&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;형식으로&amp;nbsp;받을&amp;nbsp;것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1714409682675&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ latitude: 37.51243121154322, longitude: 127.03763421212431 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;여기서&amp;nbsp;소수점&amp;nbsp;오차를&amp;nbsp;얼마나&amp;nbsp;허용할&amp;nbsp;수&amp;nbsp;있을까?&lt;br /&gt;&lt;br /&gt;위/경도에서 5자리까지는 약 1m, 6자리는 약 10cm, 7자리는 약 1cm의 차이가 있다.&lt;br /&gt;만약 매우 정밀한 위치 표시를 요구하는게 아니라면 double을 사용해서 좌표를 표시해도 무리가 없어보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;마무리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약&amp;nbsp;위치,&amp;nbsp;지도를&amp;nbsp;사용하는&amp;nbsp;서비스를&amp;nbsp;개발&amp;nbsp;중이라면&amp;nbsp;위/경도의&amp;nbsp;자료형을&amp;nbsp;한&amp;nbsp;번&amp;nbsp;고민을&amp;nbsp;해보는&amp;nbsp;것이&amp;nbsp;좋을&amp;nbsp;것&amp;nbsp;같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고&lt;br /&gt;https://ko.wikipedia.org/wiki/IEEE_754&lt;br /&gt;https://f-lab.kr/insight/understanding-and-utilizing-bigdecimal&lt;br /&gt;https://devs0n.tistory.com/102&lt;br /&gt;https://programtip.tistory.com/2456&lt;/blockquote&gt;</description>
      <category>Language/Java</category>
      <category>BigDecimal</category>
      <category>Double</category>
      <category>java</category>
      <category>경도</category>
      <category>위도</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/123</guid>
      <comments>https://seoarc.tistory.com/123#entry123comment</comments>
      <pubDate>Tue, 30 Apr 2024 01:57:59 +0900</pubDate>
    </item>
    <item>
      <title>[Java] utility class는 무엇으로 구현하는 것이 좋을까?</title>
      <link>https://seoarc.tistory.com/122</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Utility class?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;utility class(유틸리티 클래스)&lt;/b&gt;는 애플리케이션 전체에서 활용할 수 있는 클래스로 정적 메소드(static method)를 통해 구현한다.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Interface의 static method 사용?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 이후로, interface에 static method를 사용할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 static method를 사용하여 유틸리티 클래스를 구현한다면 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713108844252&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface CalculatorUtils {
    static int getSumResult(int a, int b) {
        return a + b;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스로 구현하니 간결하고 실용적이게 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인터페이스로 유틸리티 클래스를 구현하는 데에는 몇 가지 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;1. 상속 및 객체 생성 차단 불가&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스는 특성 상 상속을 막을 수 없다. 이 말은 즉, 다음과 같이 할 수 있다는 얘기다.&lt;/p&gt;
&lt;pre id=&quot;code_1713109676465&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Client {
    public static void main(String[] args) {
        CalUtils calUtils = new CalUtils() {};
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익명 클래스를 구현하여 인스턴스를 생성할 수 있게 된다. 이렇게 되면 유틸리티 클래스의 의도와 멀어지게 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유틸리티 클래스는 기본적으로 정적 메서드만 구현하여 제공하기 때문에 인스턴스화할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스화한다면 메모리만 낭비할 뿐이며 다른 개발자들이 잘못 보고 인스턴스화해서 사용할 가능성도 생기게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/java-helper-vs-utility-classes&quot;&gt;https://www.baeldung.com/java-helper-vs-utility-classes&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;2. 인터페이스 의도와 멀어진 설계&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 인터페이스는 클래스들이 구현해야 하는 동작을 정의하는데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스는 클래스의 메서드 구현을 위한 기본 틀이 되기도 하며, 다른 클래스가 동작을 호출하기 위한 추상 자료형이 되기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 활용하여 같은 틀에 여러 방식으로 구현 할 수 있으며, 필요한 구현이 사용되도록 할 수 있다. 이 특징을 다형성이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 이후에는 인터페이스에 default, static method를 구현할 수 있게 되었는데, 이는 &lt;b&gt;하위 호환성을 위한 업데이트&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 다음 인터페이스가 있다고 가정할 때&lt;/p&gt;
&lt;pre id=&quot;code_1713110795548&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface Calculator {
    int getSumResult(int a, int b);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 곱셈을 위한 메서드가 추가되었다고 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1713111038255&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Calculator {
    int getSumResult(int a, int b);
    int getMultipleResult(int a, int b);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이 인터페이스를 구현한 클래스는 모두 &lt;span style=&quot;color: #8a3db6;&quot;&gt;getMultipleResult&lt;/span&gt;를 구현해야 될 것이다. 만약 이 인터페이스를 구현한 라이브러리가 수백 개라면 큰 참사가 일어날 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, default 메서드를 활용하여 구현하면 이를 방지할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713111162876&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface Calculator {
    int getSumResult(int a, int b);
    default int getMultipleResult(int a, int b) {
        return a * b;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 기본 설계를 제공해주어 강제로 구현해야 되는 것을 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static method는 default 메서드를 보조하는 helper method로 사용될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;span style=&quot;color: #8a3db6;&quot;&gt;getZoneId&lt;/span&gt; 정적 메서드가 &lt;span style=&quot;color: #8a3db6;&quot;&gt;getZonedDateTime&lt;/span&gt; default 메서드의 &lt;a href=&quot;https://www.baeldung.com/java-helper-vs-utility-classes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;helper method&lt;/a&gt;로 사용된 예시이다.&lt;/p&gt;
&lt;pre id=&quot;code_1713111397207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface TimeClient {
    // ...
    static ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println(&quot;Invalid time zone: &quot; + zoneString +
                &quot;; using default time zone instead.&quot;);
            return ZoneId.systemDefault();
        }
    }

    default ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 인터페이스는 클래스의 설계, 구현, 호환성을 위한 자료형임을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 유틸리티 클래스로 사용하게 되면 인터페이스가 클래스의 기능을 제공하는 것이 아니라 정적 메서드의 모음을 제공한다는 혼란을 가져다줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 추상 자료형이 아닌 여러 구현이 포함된다는 점에서 인터페이스의 기능이 이미 상실된 것 처럼 느껴진다.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;그럼 어떻게 구현해야 할까?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 유틸리티 클래스는 위 링크에서도 나와있듯이 상속 및 인스턴스화 하지 못하게 구현하는 것이 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1713112323658&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public final class CalculatorUtils {

    private CalculatorUtils() {}

    public static int getSumResult(int a, int b) {
        return a + b;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;final class&lt;/b&gt;로 구현한 후 생성자를 &lt;b&gt;private&lt;/b&gt;으로 선언하면 상속되거나 인스턴스화 되는 것을 막을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 유틸리티 클래스는 인스턴스화 하지 못하게 구현하므로 상태를 가지지 않도록하며, 메소드는 상태를 처리하는 것이 아닌 Input과 Output에 대한 연산을 처리하도록 구성한다.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Utility Class가 최선일까?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 유틸리티 클래스를 구성하는 것 자체가 좋지 않은 구현이 될 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유틸리티 클래스는 상태를 가지지 않으므로 객체의 자율성이 없다. 즉, 객체지향적인 설계를 하기 어렵게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 유틸리티 클래스를 사용하는 순간 다른 클래스와의 결합도가 높아질 수 밖에 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1713114706211&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Client {
    public void printSumResult() {
        System.out.println(CalculatorUtils.getSumResult()); // CalculatorUtils와 강결합이 된다.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강결합 되어 있기 때문에 테스트도 까다로워 진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 적은 곳에서 사용된다면 클래스들의 책임이 적절하게 부여되어 있는지 고민해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 곳에서 사용된다면 자율적인 객체로 만들어 의존성을 주입하여 해결할 수 있는지 고민해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;추가&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface를 통한 유틸리티 클래스 구현을 아예 안 좋다고&amp;nbsp;생각하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 조직 내에서 실용적인 방식이라 판단되고 이를 구분할 수 있는 여러 규칙을 규정하여 사용한다면 인터페이스를 활용할 수 있을 것이라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java API 중에서도 &lt;span style=&quot;color: #8a3db6;&quot;&gt;org.springframework.data.util.StreamUtils&lt;/span&gt;는 정적 메서드만 포함된 유틸리티 클래스가 인터페이스를 통해 구현이 되어있으며, &lt;span style=&quot;color: #8a3db6;&quot;&gt;java.util.Comparator&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;또한 oracle에서는 interface의 static method는 interface에 관련한 helper method로 명시하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StreamUtils, Comparator와 같이 인터페이스와 관련한 기능을 별도의 클래스가 아닌 한 인터페이스에 구현할 수 있다는 장점을 드러내고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713113066179&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;Default Methods (The Java&amp;trade; Tutorials &amp;gt;        
            Learning the Java Language &amp;gt; Interfaces and Inheritance)&quot; data-og-description=&quot;The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated&quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Default Methods (The Java&amp;trade; Tutorials &amp;gt; Learning the Java Language &amp;gt; Interfaces and Inheritance)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.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;하지만 interface를 유틸리티 클래스로 사용하는 것에는 많은 주의가 필요해 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고 자료&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;https://www.baeldung.com/java-interface-default-method-vs-abstract-class&lt;br /&gt;https://www.baeldung.com/java-helper-vs-utility-classes&lt;br /&gt;https://www.baeldung.com/java-static-default-methods&lt;br /&gt;https://stackoverflow.com/questions/30905236/are-interfaces-a-valid-substitute-for-utility-classes-in-java-8&lt;br /&gt;https://en.m.wikipedia.org/wiki/Constant_interface&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Language/Java</category>
      <category>java</category>
      <category>유틸리티 클래스</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/122</guid>
      <comments>https://seoarc.tistory.com/122#entry122comment</comments>
      <pubDate>Mon, 15 Apr 2024 01:52:40 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 제네릭(Generic)</title>
      <link>https://seoarc.tistory.com/106</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;제네릭(Generic)?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객채 타입을 컴파일 타임에 체크하기 때문에 잘못된 형변환으로 인해 오류가 발생하는 상황을 막아주고, 형변환의 번거로움이 줄어들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;제네릭 클래스&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭은 클래스와 메서드에 사용할 수 있는데, 먼저 제네릭 클래스는 클래스 명옆에 &amp;lt;T&amp;gt;와 같이 타입 변수를 붙여 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1681711006863&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Box&amp;lt;T&amp;gt; {
    private T item;
    
    public void setItem(T item) {
        this.item = item;
    }
    
    public T getItem() {
        return this.item;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 T는 임의로 지정한 변수명으로 T가 아닌 다른 명칭으로 지정할 수 있다.&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;pre id=&quot;code_1681711363851&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Box&amp;lt;Apple&amp;gt; appleBox = new Box&amp;lt;Apple&amp;gt;();
Box&amp;lt;Apple&amp;gt; appleBox = new Box&amp;lt;&amp;gt;(); // JDK 1.7 부터 추정 가능한 경우 타입 생략가능 -&amp;gt; 다이아몬드 연산자&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 제네릭 타입을 Apple로 지정했다면 Apple 이외의 타입은 지정할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1681712002367&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;appleBox.setItem(new Object()); // 지정불가
appleBox.setItem(new Apple());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호환성을 위해 지네릭 타입을 지정하지 않고 객체를 생성하는 것이 허용되지만, 권장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/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;span style=&quot;color: #409d00;&quot;&gt;제한점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 변수에 대해 다음과 같이 객체 별로 다른 타입을 지정하는 것은 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1681715521204&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Box&amp;lt;Apple&amp;gt; appleBox = new Box&amp;lt;Apple&amp;gt;();
Box&amp;lt;Grape&amp;gt; grapeBox = new Box&amp;lt;Grape&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 타입 변수는 인스턴스 변수로 간주되기 때문에 모든 객체에 대해 동일하게 동작해야하는 static 멤버에 타입 변수를 사용할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1681715572617&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Box&amp;lt;T&amp;gt; {
    private static T item; // 지정할 수 없다.
    public static int compare(T t1, T t2) { } // 지정할 수 없다
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 제네릭 타입의 배열을 생성하는 것도 허용되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1681716176471&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Box&amp;lt;T&amp;gt; {
    private T[] itemArr; // 선언 가능 -&amp;gt; T타입의 배열을 위한 참조변수
    
    public T[] toArray() {
        T[] tmpArr = new T[itemArr.length]; // 선언 불가 -&amp;gt; 제네릭 타입의 배열 생성 불가
        return tmpArr;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 new 연산자로 인해 생성될 수 없는 것이다. 왜냐하면 컴파일 시점에 타입 T가 정확히 뭔지 알아야하는데 Box&amp;lt;T&amp;gt; 클래스를 컴파일 하는 시점에서는 T가 어떤 타입이 되는지 알 수 없기 때문이다. 이와 같은 이유로 instanceof 연산자도 T를 피연산자로 사용할 수 없다.&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;꼭 제네릭 배열을 생성해야 한다면, 'Reflection API'의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열을 생성하거나, Object 배열을 생성해서 복사한 다음 'T[]'로 형변환하는 방법 등을 사용할 수 있다.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;제한된 제네릭 클래스&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 타입은 다음과 같이 extends를 통해 특정 타입의 자손들만 대입할 수 있도록 제한할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1681716560027&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class FruitBox&amp;lt;T extends Fruit&amp;gt; {
    private List&amp;lt;T&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1681716636426&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Fruit { }

class Apple extends Fruit { }

class Grape extends Fruit { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 위와 같은 상속 구조를 가지고 있다면&amp;nbsp;다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1681716766227&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FruitBox&amp;lt;Fruit&amp;gt; fruitBox = new FruitBox&amp;lt;&amp;gt;();
// FruitBox&amp;lt;Toy&amp;gt; toyBox = new FruitBox&amp;lt;&amp;gt;(); // 에러 -&amp;gt; Toy는 Fruit의 자손이 아니다
fruitBox.add(new Apple());
fruitBox.add(new Grape());&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;만약 클래스가 아닌 인터페이스를 구현해야하는 제약을 추가하고 싶을 때도 extends를 사용한다. 또 여러 개를 추가할 때는 &amp;amp; 기호로 연결한다.&lt;/p&gt;
&lt;pre id=&quot;code_1681716863992&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class FruitBox&amp;lt;T extends Fruit &amp;amp; Eatable&amp;gt; { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;재귀적 타입 한정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정할 수 있는데, 이를 재귀적 타입 한정(recursive type bound)라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀적 타입 한정은 주로 Comparable 인터페이스와 함께 쓰인다.&lt;/p&gt;
&lt;pre id=&quot;code_1710228480430&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static &amp;lt;E extends Comparable&amp;lt;E&amp;gt;&amp;gt; E max(Collection&amp;lt;E&amp;gt; c);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 E extends Comparable&amp;lt;E&amp;gt;형식으로 사용할 수 있는데, 이는 타입 매개변수 E는 Comparable&amp;lt;E&amp;gt;를 구현한 타입이 비교할 수 있는 원소의 타입을 정의한다. 즉, 자기 자신을 서브 타입으로 구현한 Comparable 구현체로 한정한다는 의미이다.&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;따라서 String은 Comparable&amp;lt;String&amp;gt;을 구현하고, Integer는 Comparable&amp;lt;Integer&amp;gt;를 구현하는 식이다. Comparable를 구현한 객체이면서 오로지 같은 E인 Integer 타입만 받는다는 의미이다.&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;와일드 카드&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 클래스가 있다고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1681717700355&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Juicer {
    public static Juice makeJuice(FruitBox&amp;lt;Fruit&amp;gt; box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) {
            tmp.append(f).append(&quot; &quot;);
        }
        return new Juice(tmp.toString());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메서드를 보면 제네릭이 아닌 일반 클래스이고 static 메서드이기 때문에 매개변수에 타입 변수를 적용할 수 없다.&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;pre id=&quot;code_1681720749765&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FruitBox&amp;lt;Fruit&amp;gt; fruitBox = new FruitBox&amp;lt;&amp;gt;();
FruitBox&amp;lt;Apple&amp;gt; appleBox = new FruitBox&amp;lt;&amp;gt;(); // 에러&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위 코드를 수행시키려면 어떻게 해야될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 할 수 있을까?&lt;/p&gt;
&lt;pre id=&quot;code_1681720798222&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Juicer {
    public static Juice makeJuice(FruitBox&amp;lt;Fruit&amp;gt; box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) {
            tmp.append(f).append(&quot; &quot;);
        }
        return new Juice(tmp.toString());
    }
    
    public static Juice makeJuice(FruitBox&amp;lt;Apple&amp;gt; box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) {
            tmp.append(f).append(&quot; &quot;);
        }
        return new Juice(tmp.toString());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게도 제네릭 타입만 다른 것으로 오버로딩을 할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이를 해결하기 위해 제네릭의 와일드 카드 기능을 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와일드 카드는 '?' 기호로 표시하며, 어떤 타입도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;? extends T&amp;gt;: 와일드 카드의 상한 제한. T와 그 자손들만 가능&lt;/li&gt;
&lt;li&gt;&amp;lt;? super T&amp;gt;: 와일드 카드의 하한 제한. T와 그 조상들만 가능&lt;/li&gt;
&lt;li&gt;&amp;lt;?&amp;gt; 제한 없음. 모든 타입이 가능 -&amp;gt; &amp;lt;? extends Object&amp;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;/p&gt;
&lt;pre id=&quot;code_1681721115881&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Juicer {
    public static Juice makeJuice(FruitBox&amp;lt;? extends Fruit&amp;gt; box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) {
            tmp.append(f).append(&quot; &quot;);
        }
        return new Juice(tmp.toString());
    }
}&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;&lt;span style=&quot;color: #409d00;&quot;&gt;주의점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 매개변수 타입을 &amp;lt;? extends Object&amp;gt;로 하면 모든 종류의 FruitBox가 가능해지겠지만 위와 달리 Fruit의 자손이라는 보장이 없기 때문에 for문에서 Fruit 타입의 참조변수로 받을 수 없게된다.&lt;/p&gt;
&lt;pre id=&quot;code_1681784757709&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Juicer {
    public static Juice makeJuice(FruitBox&amp;lt;? extends Object&amp;gt; box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) { // 에러
            tmp.append(f).append(&quot; &quot;);
        }
        return new Juice(tmp.toString());
    }
}&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;제네릭 메서드&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 메서드에 타입 변수를 선언할 수 있는데, 이를 제네릭 메서드라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 메서드에서 제네릭 타입은 반환 타입 바로 앞에 선언한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1681784855212&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static &amp;lt;T&amp;gt; void sort(List&amp;lt;T&amp;gt; list, Comparator&amp;lt;? super T&amp;gt; c);&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;&lt;span style=&quot;color: #409d00;&quot;&gt;제네릭 클래스 vs 제네릭 메서드&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 클래스에 정의된 타입 변수와 제네릭 메서드에 정의된 타입 변수는 다른 것이다. 즉, 다음 코드에서 FruitBox에 선언된 T와 sort() 에 선언된 T는 다른 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1681785271518&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class FruitBox&amp;lt;T&amp;gt; {
    public static &amp;lt;T&amp;gt; void sort(List&amp;lt;T&amp;gt; list, Comparator&amp;lt;? super T&amp;gt; c) { }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 위 메서드를 살펴보면 static으로 선언된 것을 볼 수 있는데, staticc 멤버에는 타입 매개변수를 사용할 수 없지만 위처럼 제네릭 메서드로 만든 후 사용하는 것은 가능하다.&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;span style=&quot;color: #409d00;&quot;&gt;사용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 메서드는 다음과 같이 호출할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1681785587144&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FruitBox&amp;lt;Fruit&amp;gt; fruitBox = new FruitBox&amp;lt;&amp;gt;();
Juicer.&amp;lt;Fruit&amp;gt;makeJuice(fruitBox);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 대부분 컴파일러가 선언부를 통해 대입된 타입을 추정할 수 있기 때문에 다음과 같이 생략할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1681785660558&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Juicer.makeJuice(fruitBox);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 주의할 점은 만약 대입된 타입을 생략할 수 없다면, 참조변수나 클래스 이름을 생략할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1681785728454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Fruit&amp;gt;makeJuice(fruitBox); // 에러
this.&amp;lt;Fruit&amp;gt;makeJuice(fruitBox);
Juicer.&amp;lt;Fruit&amp;gt;makeJuice(fruitBox);&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 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;형변환&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 제네릭 타입과 Non제네릭 타입 간의 형변환은 경고가 발생하지만 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1681785837028&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Box box = null;
Box&amp;lt;Object&amp;gt; objBox = null;

box = (Box) objBox;
objBox = (Box&amp;lt;Object&amp;gt;) box;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 다른 제네릭 타입 간의 형변환은 불가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1681785944737&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Box&amp;lt;String&amp;gt; strBox = null;
Box&amp;lt;Object&amp;gt; objBox = null;

strBox = (Box&amp;lt;String&amp;gt;) objBox; // 에러
objBox = (Box&amp;lt;Object&amp;gt;) strBox; // 에러&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다른 타입으로 어떻게 형변환 할 수 있을까?? 다음 코드를 한 번 작성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1681786028483&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Box&amp;lt;? extends Object&amp;gt; wBox = new Box&amp;lt;String&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형변환이 된다는 것을 확인했는가? 이렇게 형변환이 가능하기 때문에 다음과 같이 다형성이 적용될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1681786140434&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FruitBox&amp;lt;? extends Fruit&amp;gt; box = new FruitBox&amp;lt;Fruit&amp;gt;();
FruitBox&amp;lt;? extends Fruit&amp;gt; box = new FruitBox&amp;lt;Apple&amp;gt;();
FruitBox&amp;lt;? extends Fruit&amp;gt; box = new FruitBox&amp;lt;Grape&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 반대로 형변환 하는 것도 가능하지만, 경고가 발생한다.&lt;/p&gt;</description>
      <category>Language/Java</category>
      <category>? extends</category>
      <category>Generic</category>
      <category>java</category>
      <category>java 7</category>
      <category>T</category>
      <category>WildCard</category>
      <category>다이아몬드 연산자</category>
      <category>와일드카드</category>
      <category>자바</category>
      <category>제네릭</category>
      <author>SeoArc</author>
      <guid isPermaLink="true">https://seoarc.tistory.com/106</guid>
      <comments>https://seoarc.tistory.com/106#entry106comment</comments>
      <pubDate>Tue, 12 Mar 2024 16:32:49 +0900</pubDate>
    </item>
  </channel>
</rss>